2023 年 10 月 24 日,Ori Pomerantz报告了一个错误,该错误影响了在 Yul 代码中使用verbatim 内置函数。经过调查,团队确认了问题并找到了问题根源。该错误存在于块去重优化器步骤中,该步骤会识别并合并等效的汇编块。被相同操作码包围的 verbatim 汇编项目被错误地视为相同并被合并。
该错误从 0.8.5 版本开始存在,该版本引入了 verbatim,并且只影响启用了优化的纯 Yul 编译。Solidity 代码或内联汇编块中使用的 Yul 不会触发该错误。
使用 verbatim 不常见,触发该错误的条件(两个或多个被相同操作码包围的 verbatim 项目)非常特殊。此外,根据团队的调查,没有证据表明这可以被用作漏洞或攻击向量。虽然该错误如果存在,其影响将非常严重,但其可能性非常低。考虑到这一点,团队为该错误分配了 低 的整体评分。
哪些合约受到影响?
可能触发该错误的条件如下
- 编译纯 Yul 代码。
- 对具有不同数据的 verbatim 内置函数进行多次调用。
- 块去重优化器步骤正在使用中。
请注意,当启用了优化器时,默认情况下会启用块去重器。
如果您的项目不包含用纯 Yul 编写的合约,或者不使用 verbatim,那么它就不存在被影响的风险。
技术细节
使用 verbatim 的 Yul 代码,例如以下示例,容易受到该错误的影响
verbatim.yul
{ let special := 0xFFFFFFFFFFFF let input := sload(0) let output switch input case 0x00 { output := verbatim_1i_1o(hex"506000", special) } case 0x01 { output := 1 } case 0x02 { output := verbatim_1i_1o(hex"506002", special) } case 0x03 { output := 3 } sstore(0, output) }
在这种情况下,块去重器错误地认为 0x00 和 0x02 的案例块相同,因此可以合并为一个块。
去重步骤是 基于操作码的优化器 的一部分,它对汇编代码进行操作。它将指令序列拆分为块,通常用 JUMPS 和 JUMPDEST 分隔。对这些块进行分析,并执行一系列优化步骤。在去重步骤中,优化器会映射哪些标签可以互相替换,因为它们引用的是相同操作码序列的块。如果两个块具有相同顺序的相同操作码,则它们被认为是相同的。
块去重器会识别出顺序执行的汇编操作码块,直到遇到能够改变控制流的操作码,例如 JUMP、RETURN 或 REVERT。然后,优化器会检查是否存在两个由相同汇编操作码序列组成的块。如果存在这样的块,优化器会用另一个块的标签替换引用一个块的标签,这意味着后面一个块将不再使用,并且可以随后被删除。
当检查两个块是否相等时,该错误出现了。对 verbatim 汇编项目的比较错误地认为所有此类项目都是相同的,无论它们的数据如何。因此,如果具有不同数据的 verbatim 指令出现在其他方面完全相同的块中,即使 verbatim 数据不同,它们也会被去重。
例如,当编译之前显示的 Yul 代码片段 **没有** 优化时,它将包含下面显示的汇编代码片段
solc --strict-assembly verbatim.yul --asm
tag_2: dup4 verbatimbytecode_506000 swap2 pop jump(tag_1) tag_3: 0x01 swap2 pop jump(tag_1) tag_4: dup4 verbatimbytecode_506002 swap2 pop jump(tag_1)
请注意,由 tag_2 和 tag_4 引用的块几乎相同。由于该错误,优化器的去重步骤会认为 verbatim 项目相等,尽管它们的内容不同。然后,优化器会 错误地 将 tag_4 映射为 tag_2 的替换项,这将进一步导致由 tag_2 引用的块在后面被删除。
该错误的结果可以在下面显示的优化代码的输出中看到
solc --strict-assembly verbatim.yul --asm --optimize
0x00 dup1 sload dup1 iszero tag_5 jumpi dup1 0x01 eq tag_3 jumpi dup1 0x02 eq tag_5 jumpi 0x03 eq tag_7 jumpi 0x00 sstore stop tag_7: pop 0x03 0x00 sstore stop tag_5: pop pop 0xaabbccddeeff verbatimbytecode_506002 0x00 sstore stop tag_3: pop pop 0x01 0x00 sstore stop
请注意,这两个跳转都指向从 tag_5 开始的块。该块替换了 verbatimbytecode 项目的两个实例,尽管它们的内容不同。