2021 年 7 月 1 日,通过差异模糊测试发现了 Solidity 代码生成器中的一个错误。该错误导致旧版代码生成管道生成代码,该代码在复制时可能会将脏值写入存储bytes 数组来自 calldata 或 memory。
最初,假设存储中的脏值仅使用内联汇编可见。但是,使用空 .push() 调整 bytes 数组的大小,而无需实际向其写入值,可以在不使用任何内联汇编的情况下暴露脏字节。
该错误在编译器早期版本中 bytes 数组的初始实现中就已经存在。尽管如此,它从未被外部利用或报告,因此我们将其严重程度评定为“低”。
哪些合约受到影响?
从 memory 或 calldata 复制 bytes 数组到 storage 的大多数情况都可能受到影响,因为它们可能会写入脏值。只有在 bytes 数组中存在空 .push()(在旧版本中还包括对其 .length 字段的赋值)并且结果元素被假设为零而没有实际写入它们时,存储中的这些脏值才会变得可见。
例如,以下操作以前会导致 h() 中新 .push() 的数组元素不为零
contract C { event ev(uint[], uint); bytes s; constructor() { // The following event emission involves writing to temporary memory at the current location // of the free memory pointer. Several other operations (e.g. certain keccak256 calls) will // use temporary memory in a similar manner. // In this particular case, the length of the passed array will be written to temporary memory // exactly such that the byte after the 63 bytes allocated below will be 0x02. This dirty byte // will then be written to storage during the assignment and become visible with the push in ``h``. emit ev(new uint[](2), 0); bytes memory m = new bytes(63); s = m; } function h() external returns (bytes memory) { s.push(); return s; } }
类似地,来自 calldata 的脏值最终可能会被复制到存储中
contract C { bytes public s; function f(bytes calldata c) external returns (bytes memory) { s = c; s.push(); return s; } }
此处的函数 f 可以使用 c 长度后的脏字节在 calldata 中调用,这些字节将被复制到 s 并在 s.push() 之后变得可见。
但是,这个错误直到现在才被发现的事实表明,实际项目似乎并不依赖于空 .push() 到 bytes 数组应该添加一个零值新元素这一事实。