{ 跳至内容 }

Solidity 优化器 Keccak 缓存漏洞

发布于 2021 年 3 月 23 日,作者:Solidity 团队

安全警报

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) 在某些情况下被认为相等,如果 length1length2(向上取整到最接近的 32 的倍数)相同,并且可以推断出 mpos1mpos2 处的内存内容相等。

如果您在内联汇编中计算相同内容但长度不同的多个 Keccak-256 哈希值,并且启用了优化器,则可能会受到影响

不受影响的情况

如果您的代码

  • 使用 keccak256 且长度在编译时未知,
  • 仅使用 keccak256 且长度始终为 32 的倍数。特别地,对于存储槽计算,Solidity 编译器始终计算长度和偏移量为 32 的倍数的内存区域的 Keccak-256 哈希值,
  • 包含一对 keccak256 哈希值,它们被某些指令隔开,例如
    • 破坏控制流的指令,例如 jumpijump 等。在高级代码中,这将是函数调用、if 语句、循环等,
    • 写入内存的指令,而不是非常简单的 mstore,例如 callreturndatacopy 等。

请注意,除了内置函数 keccak256 之外,编译器还会在内部生成使用 Keccak-256 哈希的代码。例如,

  • 在某些索引事件参数中,
  • 在函数 abi.encodeWithSignature 中,
  • 在映射中使用 stringbytes 作为键时,
  • 在访问 mappingarray 类型的内存位置时。

严重程度

大多数 keccak256 的用法都属于上一节中提到的不受影响的情况之一。特别是,这只会影响没有中间跳转或外部调用的 keccak256 操作码对,这使得漏洞出现在现有代码中的可能性非常低。这也是漏洞在优化器中存在这么久而没有被发现的事实验证。因此,我们为该漏洞分配了“中等”严重程度级别。

模糊测试

该漏洞是通过对当前代码生成和即将推出的基于 Yul 的代码生成进行差分测试,使用模糊测试器生成的输入发现的。本质上,solidity#11131 中的示例在使用不同的代码生成路径编译时产生了不同的结果;这是因为字节码优化器(漏洞所在)仅在旧的代码生成中激活,而基于 Yul 的代码生成当前仅使用不包含此漏洞的 Yul 优化器。

此漏洞发现是持续进行的模糊测试相关工作的一部分。您可以在本介绍性博客文章中详细了解 Solidity 对模糊测试的方法博客文章。

上一篇

下一篇

参与进来

GitHub

Twitter

Mastodon

Matrix

了解更多

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

2024Solidity 团队

安全策略

行为准则