{ 跳至内容 }

Solidity 内存数组创建溢出错误

由 Solidity 团队于 2020 年 4 月 6 日发布

安全警报

3 月 28 日,Solidity 代码生成器中发现了一个错误,该错误通过以太坊基金会赏金计划,由 Certora 的 John Toman 报告。此错误已在 2020 年 4 月 6 日发布的 0.6.5 版本中修复。此错误存在于所有之前的 Solidity 版本中。

我们将其严重程度级别指定为“低”,因为我们发现此错误并不常见,同时也很难利用。

谁应该关注

如果您部署了一个合约,该合约分配了用户提供的长度的内存数组,但没有复制或遍历它,则可能导致内存损坏。特别是,在分配内存数组时,如果长度足够大,会导致后续的内存分配与数组的内存区域重叠。但是,数组的长度被正确存储,因此在这些情况下,复制或遍历数组(我们筛选的所有合约在分配后立即执行的操作)将终止并以超出 gas 限制的方式回滚交易。

如何检查合约是否易受攻击

如果您使用new T[](length) 分配动态内存数组,其中长度取决于用户输入,您可能容易受到此错误的影响。如果可以提供足够大的长度,则后续的内存分配将具有重叠的内存区域,并且使用内存暂存空间的操作可能会使数组的内容失效。但是,如果您在创建数组后遍历或复制数组,则交易将以超出 gas 限制的方式回滚,因为数组长度已正确存储。

安全示例

    contract C {
        function f(uint length) public {
            uint[] memory x = new uint[](length);
            // This is safe because the loop will run out of gas.
            for (uint i = 0; i < length; ++i) {
                x[i] = i**2;
            }
            ...
        }
    }

另一个安全示例

    contract C {
        uint[] x;
        function f(uint length) public {
            // This is safe because the copy from memory to storage will
            // iterate over the whole array and go out-of-gas.
            x = new uint[](length);
            ...
        }
    }

可能存在漏洞的示例

    contract C {
        function f(uint length, AnotherContract c, uint index) public {
            uint[] memory x = new uint[](length);
            uint[] memory y = new uint[](4);
            y[0] = c.g(x[index]);
            y[1] = c.g(x[index + 1]);
            y[2] = c.g(x[index + 2]);
            y[3] = c.g(x[index + 3]);
            c.h(y);
        }
    }

技术细节

Solidity 在运行时没有对动态数组分配施加上限长度。虽然数组的长度直接存储,但要分配的内存量(即“空闲内存指针”的增量)被计算为数组长度的 32 倍。由于此乘法没有防溢出保护,因此足够大的长度会导致溢出,导致实际分配的内存量很小(即空闲内存指针只会根据溢出大小增加)。因此,后续的内存分配将使用与最初分配的数组重叠的内存区域。从 Solidity 版本 0.6.5 开始,动态内存数组的最大分配大小为 2**64-1。尝试分配更大的数组现在将直接回滚。这有效地防止了此类溢出发生。

上一篇

下一篇

参与进来

GitHub

推特

Mastodon

矩阵

了解更多

博客文档用例贡献关于论坛

2024Solidity 团队

安全策略

行为准则