{ 跳至内容 }

带符号不可变变量错误

发布于 2021年9月29日 由 Solidity 团队

安全警报

2021年9月28日,Solidity 团队发现,对于短于 256 位的带符号整数类型的不可变变量,其值的符号扩展(清理)并不总是正确执行。

据我们所知,仅在使用内联汇编时才能以未清理状态访问该值。此错误从 Solidity 0.6.5 引入immutable 功能起就存在,并在 0.8.9 中修复。

我们为该错误分配了“极低”的严重性。

技术细节

在构造阶段,当 Solidity 中分配不可变变量时,该值会存储在内存中,直到创建完成。对于短于 32 字节的类型,这些值会在 32 字节字中左对齐存储,并且当再次读取这些值时,它们会相应地向右移位,但不会对值进行清理。特别是,不会对带符号值执行 signextend 操作。

例如,如果您将 -2 存储在 int8 中,则它将以二进制补码编码存储为 0xfe 的单字节值。由于 EVM 中的字使用 32 字节,因此在执行诸如带符号除法之类的操作之前,需要正确地对该值进行符号扩展,否则该值将被解释为正值。

符号扩展是指将高位设置为与符号位相同的值,符号位是实际类型的最高有效位。

以 32 位二进制补码表示的 -2int8)为 0xfe,但是,在 EVM 中,它被符号扩展为 0xfffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffe,当解释为 int256 时,它也表示值 -2。请注意,当解释为 int256 时,0xfe+254

之前没有发现缺少的符号扩展,因为 Solidity 始终在需要具有干净的高位(例如复制到内存、比较或除值)的操作之前执行额外的清理(如果执行两次,则优化器可以将其删除)。

因此,获取不可变变量的“未清理”值的唯一方法(据我们所知)是首先将其分配给局部变量,然后在内联汇编中从该局部变量读取。

受影响的示例

在下面的示例中,对 f() 的调用返回 0x000000...0000fe 而不是 0xffffff...fffffe

contract C {
    int8 immutable x = -2;
    function f() public view returns (bytes32 r) {
        int8 y = x;
        assembly { r := y }
    }
}

如果您使用类似 r = bytes32(int(y)); 的方法在没有内联汇编的情况下返回值,它将返回正确的值。使用 getter 时也是如此 - int8 public immutable x = -2

需要内联汇编的理由

我们认为,此错误的出现需要内联汇编,这不仅是因为我们相信 Solidity 编译器在需要时始终正确地清理值,还因为另一个原因

此错误仅存在于我们称为“传统”代码生成器中。在新的基于 Yul-IR 的代码生成器(仍被视为实验性)中,已正确实现了读取不可变值时的清理。

为了在新实现中查找错误,我们使用生成式(基于语言语法生成的 Solidity 程序)和变异式(现有 Solidity 程序的变异变体)测试创建引擎定期对两种实现进行模糊测试。

当然,这种机制不仅可以查找新实现中的错误,还可以查找旧实现中的错误。

生成式引擎目前未使用内联汇编,因为我们知道两种实现的汇编级别存在差异,这会导致过多的误报。

由于模糊测试器在它成为编译器的一部分的近 18 个月内没有发现该错误,因此我们非常确定除了内联汇编之外,没有其他方法可以访问未清理的值。

上一篇

下一篇

参与进来

GitHub

推特

Mastodon

矩阵

了解更多

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

2024Solidity 团队

安全策略

行为准则