2021 年 3 月 20 日,通过差分模糊测试发现 Solidity 字节码优化器存在漏洞。该漏洞已在2021 年 3 月 23 日发布的 v0.8.3 版本中修复。所有早于该版本的 Solidity 都存在此漏洞。
我们为该漏洞分配了“中等”严重程度级别。
技术细节
摘要:字节码优化器错误地重复使用先前评估的 Keccak-256 哈希值。如果您不在内联汇编中计算 Keccak-256 哈希值,则不太可能受到影响。
Solidity 的字节码优化器包含一个步骤,如果在编译时已知调用 Keccak-256 内置函数的内存内容,则该步骤可以计算 Keccak-256 哈希值。此步骤还具有确定两个 Keccak-256 哈希值是否相等(即使在编译时无法知道内存中的值)的机制。此步骤存在一个漏洞,即相同内存内容但大小不同的 Keccak-256 哈希值被认为相等。以下是一个简单的示例说明了此漏洞
contract C { function bug() public returns (uint a, uint b) { assembly { mstore(0, 0) // The optimizer computes the value at compile time: // 0x290decd9548b62a8d60345a988386fc84ba6bc95484008f6362f93160ef3e563 a := keccak256(0, 32) // The optimizer incorrectly uses the cached value // and transforms the next line to // b := 0x290decd9548b62a8d60345a988386fc84ba6bc95484008f6362f93160ef3e563 // instead of // b := 0xe2b9f9f9430b05bfa9a3abd3bac9a181434d23a707ef1cde8bd25d30203538d8 b := keccak256(0, 23) } } }
在上面的示例中,这两个 Keccak-256 哈希值都可以在编译时计算。以下是另外两种情况,在这些情况下,哈希值无法在编译时计算,但优化器认为它们相等,因此修改了语义
contract C { function bug(string memory s) public returns (bool ret) { assembly { let a := keccak256(s, 32) let b := keccak256(s, 8) // Here `a` and `b` were considered equal, // leading to `ret` being incorrectly set to true. ret := eq(a, b) } } }
contract C { function bug() public view returns (bool ret) { assembly { let x := calldataload(0) mstore(0, x) mstore(0x20, x) let a := keccak256(0, 4) // even though the memory location is different, // the 32-byte content is the same. let b := keccak256(0x20, 8) // Here `a` and `b` were considered equal, // leading to `ret` being incorrectly set to true. ret := eq(a, b) } } }
具体来说,keccak256(mpos1, length1) 和 keccak256(mpos2, length2) 在某些情况下被认为相等,如果 length1 和 length2(向上取整到最接近的 32 的倍数)相同,并且可以推断出 mpos1 和 mpos2 处的内存内容相等。
如果您在内联汇编中计算相同内容但长度不同的多个 Keccak-256 哈希值,并且启用了优化器,则可能会受到影响。
不受影响的情况
如果您的代码
- 使用 keccak256 且长度在编译时未知,
- 仅使用 keccak256 且长度始终为 32 的倍数。特别地,对于存储槽计算,Solidity 编译器始终计算长度和偏移量为 32 的倍数的内存区域的 Keccak-256 哈希值,
- 包含一对 keccak256 哈希值,它们被某些指令隔开,例如
- 破坏控制流的指令,例如 jumpi、jump 等。在高级代码中,这将是函数调用、if 语句、循环等,
- 写入内存的指令,而不是非常简单的 mstore,例如 call、returndatacopy 等。
请注意,除了内置函数 keccak256 之外,编译器还会在内部生成使用 Keccak-256 哈希的代码。例如,
- 在某些索引事件参数中,
- 在函数 abi.encodeWithSignature 中,
- 在映射中使用 string 或 bytes 作为键时,
- 在访问 mapping 和 array 类型的内存位置时。
严重程度
大多数 keccak256 的用法都属于上一节中提到的不受影响的情况之一。特别是,这只会影响没有中间跳转或外部调用的 keccak256 操作码对,这使得漏洞出现在现有代码中的可能性非常低。这也是漏洞在优化器中存在这么久而没有被发现的事实验证。因此,我们为该漏洞分配了“中等”严重程度级别。
模糊测试
该漏洞是通过对当前代码生成和即将推出的基于 Yul 的代码生成进行差分测试,使用模糊测试器生成的输入发现的。本质上,solidity#11131 中的示例在使用不同的代码生成路径编译时产生了不同的结果;这是因为字节码优化器(漏洞所在)仅在旧的代码生成中激活,而基于 Yul 的代码生成当前仅使用不包含此漏洞的 Yul 优化器。
此漏洞发现是持续进行的模糊测试相关工作的一部分。您可以在本介绍性博客文章中详细了解 Solidity 对模糊测试的方法博客文章。