2020年9月17日,发现 Solidity 代码生成器中存在一个漏洞。该漏洞已在0.7.3 版本(于 2020年10月7日发布)中修复。**所有早期版本的 Solidity 都存在此漏洞。**
我们为该漏洞分配了“中等”严重级别。
漏洞的技术细节
**摘要**:对于大小最多为 16 字节的动态大小存储数组,需要删除槽的赋值操作无法正确清零已删除的槽。
考虑一个存储中的动态大小数组,其基本类型足够小,以至于多个值可以打包到一个槽中,例如uint128[]。假设其长度为 l。当此数组从另一个长度较小的数组(例如 m)赋值时,元素 m 和 l 之间的槽必须通过清零来清理。但是,此清理操作未正确执行。具体来说,在对应于 m 的槽之后,仅清理了第一个打包的值。如果此数组调整大小到大于 m 的长度,则对应于槽未清理部分的索引将包含原始值,而不是 0。此处的调整大小是通过将数组的 length 赋值、通过 push() 或通过内联汇编来执行的。
以下是一个漏洞示例
// SPDX-License-Identifier: GPL-3.0 // The bug is present in all versions of solidity prior to 0.7.3 pragma solidity >=0.6.0 <0.7.3; contract C { uint128[] x; function f() public { x.push(42); x.push(42); x.push(42); x.push(42); uint128[] memory y = new uint128[](1); y[0] = 23; // This will shrink the array x to one element. x = y; // Resizing the array to length 4. x.push(); x.push(); x.push(); // After resizing the array, its contents are [23, 0, 0, 42], // instead of [23, 0, 0, 0]. Resizing can be also be done by // assigning to `.length` or by assigning to the `slot` member // inside inline assembly. } }
如何检查合约是否存在漏洞
如果您的合约满足以下所有条件,请尝试运行测试以查看您的合约是否依赖于调整大小部分中的元素为零。
- 合约在存储中包含一个动态大小数组,其基本类型的大小最多为 16 字节。
- 您通过另一个长度较小的数组(来自内存、存储或 calldata)对上述数组进行赋值。
- 您增加了上述数组的长度,但未为新元素赋值。
- 您在为新元素赋值之前读取了其中一个新元素。
如果您仅使用 .push(<arg>) 或在增加数组长度后为新元素赋值(即使为零),则您的合约不受影响。