2022年4月7日,Certora 开发团队的 John Toman 报告了 Solidity 代码生成器中的一个错误。Certora 的错误披露帖子可以在这里找到这里.
此错误已在2022年5月17日发布的 Solidity 版本 0.8.14中修复。此错误最早在 Solidity 版本 0.5.8 中引入。
我们为该错误分配了“极低”的严重级别。
哪些合约受到影响?
如果您将嵌套数组直接传递给另一个外部函数调用或使用abi.encode 对其进行编码,则可能会受到影响。
如果 calldata 以某种方式格式错误,则调用可能不会像预期的那样回退。相反,在末尾附加额外零的数据将传递给被调用的合约或 abi.encode 函数。
技术细节
嵌套动态类型的 calldata 验证被推迟到第一次访问嵌套值时。例如,此类访问可能是复制到内存或对外部类型的索引或成员访问。虽然在大多数此类访问中,calldata 验证会正确检查嵌套数组的数据区域(请参阅ABI 编码规范) 完全包含在传递的 calldata 中(即在范围 [0, calldatasize()] 内),但是当再次直接从 calldata 对此类嵌套类型进行 ABI 编码时,此检查可能不会执行。
例如,如果 calldata 中具有嵌套动态数组的值被传递给外部调用、在 abi.encode 中使用或作为事件发出,则可能会发生这种情况。在这种情况下,如果嵌套数组的数据区域超出 calldatasize(),则对其进行 ABI 编码不会回退,而是继续读取超出 calldatasize() 的值(即零值)。
例如,在此合约中
contract C { event e(uint[][]); function g(uint[][] calldata) external { ... } function f(uint[][] calldata a) external pure { bytes memory data = abi.encode(a); this.g(a); emit e(a); } }
对 f 的调用,其 calldata 已损坏,其中 a 的一个元素具有超出 calldatasize 的数据区域,将不会回退。
但是,以下每个案例都会针对 calldatasize 进行正确验证,并且在 calldata 损坏时会回退。这是因为 calldata 数组仅被解码,而不是在单个操作中解码并重新编码。编译器中的另一条代码路径处理了这种情况,并且不受此错误的影响
contract C { function f1(uint[][] calldata a) external pure { a[0]; // Where a[0] is the element that extends beyond ``calldatasize``. } uint[][] s; function f2(uint[][] calldata a) external pure { s = a; } function f2(uint[][] calldata a) external pure { uint[][] memory x = a; } }