Certora团队的John Toman发现了Solidity 0.7.3中的一个bug,该bug导致编译器在一些写入操作中错误地将垃圾数据写入持久存储。这一问题可能导致合同执行成本增加,但目前尚未确认其安全影响。Solidity编译器团队已在0.7.4版本中修复了该bug。
Certora 开发团队的 John Toman 发现了 Solidity 0.7.3 代码生成中的一个之前未知的漏洞,该漏洞导致 Solidity 编译器在某些写入操作中将垃圾数据写入持久存储。虽然我们尚未确认这个漏洞的任何安全隐患,但它可能导致存储被不正确地设置为非零值,从而显著增加合约执行成本。此外,在某些情况下,这个漏洞还可能导致新分配的字节数组元素是非零值。Solidity 编译器团队 在 0.7.4 版本中修复了这个漏洞 。
正如 我们之前的文章 中提到的,Solidity 使用增量分配器,将新分配的对象(如结构体或数组)放置在连续的内存块中。与拥有专用堆的语言(例如 Java 或 Python)不同,和具有不安全内存的语言(例如 C、C++)类似,一个对象的数据可能会通过不同对象的指针被意外读取或写入(这就是之前披露的溢出漏洞的来源)。
为了确保内存的完整性,Solidity 编译器将数组的长度存储在包含数组元素的内存段之前的一个 32 字节字中。例如,一个长度为三的 uint256 数组实际上占用四个连续的 32 字节字:第一个字存储长度(在这种情况下为三),随后是三个字用于每个元素。
一个 零长度 数组只占用一个由长度字段组成的字,长度字段的值为零。在这种情况下,长度字段后面没有元素数据;内存中的下一个字将包含不确定的数据(零、哈希留下的垃圾数据或其他分配对象中的数据)。
存储中的数组 大体上 遵循与内存中数组相同的模式:元素在存储中逐个布局,长度存储在一个单独的槽中。然而,对于长度为31或更低的字节或字符串存在特殊情况。在这种情况下,Solidity 编译器会将数组的元素数据打包存储在长度存储槽的上层(高位字节),将最低有效字节留给数组的长度。实际上,Solidity 编译器试图通过仅使用一个存储槽来为短字节数组节省空间(从而节省 gas)。对于更长的数组(长度为 32 或更大),长度和数据会分别存储。
鉴于字节数组在内存中的存储布局是可变的,不足为奇的是,内存到存储的复制代码相当复杂。Solidity 编译器生成多个条件以确定数组的长度,以选择正确的复制算法。
特别地,Solidity 编译器生成检查以查看数组的长度是否小于 32。如果不是,生成的代码则回退到未打包的表示。不幸的是,在 Solidity 检查长度是否大于 32 之后,才会检查长度是否为零,此时长零意味着不可能。如果长度小于等于 31,生成的代码将进入使用打包表示的分支。
回想一下,EVM 中的内存是以 32 字节块读取的;因此,在数组元素段的开头进行一次读取足以读取整个数组的内容。因此,Solidity 编译器生成的代码将数组指针增量 32 字节(跳过长度字段),然后从该位置生成无条件读取。读取到的数据随后与长度打包在一起并存储到内存中。
此漏洞发生在要复制的数组长度为 0 的情况下。Solidity 编译器从未检查在长度字段之后是否有数据供其读取;如上所述,关于长度为零的检查只在长度已经确定为 32 或更大时进行。换句话说,Solidity 编译器(错误地)假设至少必须有一个字节的数据。然而,如上所述,如果数组长度为零,长度字段后面的字节是完全任意的。因此,读取和随后的“数组数据”的存储存储了一些毫无意义的 31 个字节,后跟最低有效字节中的长度(即 0)。
因此,清空字节数组或存储空字符串的合约将不会必然获得将存储插槽设置为零而节省 gas 的好处,可能会因此高额的 gas 用于将插槽设置为非零值。
除了以上描述的 gas 影响外,如果使用无参数版本的 push
扩展存储数组,还可以观察到被损坏的数组内容。回想一下,无参数版本会将“默认”值推送到数组中,对于字节数组来说,这是 0。Solidity 编译器生成的代码假设字节槽的上 31 字节中的数据为零,因此仅简单地增加打包表示的长度部分。然而,由于上述损坏,新“添加”的元素可能是非零的,这可能进一步损坏未来的计算。
我们已发布一个可执行的 概念证明,展示了这个漏洞。
上述漏洞说明了 Certora 的字节码级别验证的重要性;我们验证你实际运行的内容,这可能与你的意图有惊人的差异。
感谢 Solidity 编译器团队对此问题的有益讨论,并迅速 发布了修复。我们还要感谢 Christian Reitwiessner 提醒我们 push
的影响。
- 原文链接: medium.com/certora/the-s...
- 登链社区 AI 助手,为大家转译优秀英文文章,如有翻译不通的地方,还请包涵~
如果觉得我的文章对您有用,请随意打赏。你的支持将鼓励我继续创作!