Compiler Bugs

EVM是运行Solidity编译出来的Bytecode,如果编译器出问题,造成的结果是毁灭性的(比如之前的Vyper编译器安全事件)。Solidity编译器也在不断更新迭代,我们这里来回顾几个以往编译器版本存在的问题。



ABIEncoderV2 Array



Ethereum Release tag v0.5.10

0.4.7~0.5.9之间,使用ABI encoder会造成一些未知错误。比如下面的代码:

pragma solidity 0.5.1;pragma experimental ABIEncoderV2;contract A {    uint[2][3] bad_arr = [[1, 2], [3, 4], [5, 6]];
/* Array of arrays passed to abi.encode is vulnerable */ function bad() public view returns(bytes memory){ bytes memory b = abi.encode(bad_arr); return b; }}

看似bad_arr的结果是[[1, 2], [3, 4], [5, 6]],实际上ABI encoder解析出来的结果是如下:[[1, 2], [2, 3], [3, 4]]

000000000000000000000000000000000000000000000000000000000000000100000000000000000000000000000000000000000000000000000000000000020000000000000000000000000000000000000000000000000000000000000002000000000000000000000000000000000000000000000000000000000000000300000000000000000000000000000000000000000000000000000000000000030000000000000000000000000000000000000000000000000000000000000004

为了避免这种潜在的问题,推荐使用^0.5.10的编译器版本。




consturctor


Ethereum Release tag v0.4.23

0.4.22版本,一个合约可以包含两种构造器:constructor()和function <Contract Name>(),编译器选择的方式:谁先写,就用谁的。

举个例子:A合约constructor()会被使用,x的结果是0。B合约function B()会被使用,x的结果是1

pragma solidity 0.4.22;contract A {    uint256 public x; // 0    constructor() public {        x = 0;    }    function A() public {        x = 1;    }}
contract B { uint256 public x; // 1 function B() public { x = 1; } constructor() public { x = 0; }
}

建议统一采用constructor()和高版本的编译器。



nested structs


Ethereum issues 5520

0.5.0版本之前,如果mapping中使用了嵌套struct的结构体,那么会返回错误的值。

下面的例子中,hello[any]的值都是 128:

pragma solidity 0.4.26;
contract test {
struct A { uint x; }
struct B { A y; }
mapping(uint256 => B) public hello;
constructor() public { // 试试获取hello[0], [1], [2]的值 A memory a; a.x = 10; B memory b; b.y = a; hello[0] = b; hello[1] = b; hello[2] = b; }
}

建议使用更高版本的编译器就没有问题了:

pragma solidity 0.8.17;
contract test { struct A { uint x; uint256 get; }
struct B { uint256 num; A y; }
mapping(uint256 => B) public hello;
constructor() public { A memory a; a.x = 10; a.get = 2; B memory b; b.y = a; b.num = 11; hello[1] = b; }// this works: hello[1]:// 0:uint256: num 11// 1:tuple(uint256,uint256): y 10,2}



immutable&view bug


Ethereum issues 14049

此bug存在于目前任何版本的编译器(0~0.8.22),immutable的变量会被内联汇编隐式地修改。

来看这个例子:假设我们用0x5B38Da6a701c568545dCfcB03FcB875f56beddC4作为msg.sender来调用这个合约

pragma solidity 0.8.21;contract C {    address public immutable a;    constructor() public {        a = 0x4B20993Bc481177ec7E8f571ceCaE8A9e22C02db;        F(msg.sender);    }    function F(address witnessAddress) public pure returns(address){        assembly{            mstore(0x80,witnessAddress)        }    }}

运行结果是:a的值为0x5B38Da6a701c568545dCfcB03FcB875f56beddC4。很奇怪吧,一个pure的函数,居然会对合约产生影响。

原因:immutable类型的变量会从Memory 0x80位置开始依次存储(每bytes32一个变量),然后编译器会用SLOAD从这个位置取值到Stack中。这就意味着,如果在此期间,Memory的0x80位置之后的某些位置内容被修改了,就会影响这个immutable变量,即使是pure, view修饰的。这些操作是在内存中的,虽然不写到链上,但是如果有些操作需要用到Memory的数据,那么这些操作也会被影响。可能造成这种情况是使用内联汇编相关的操作,比如mstore,codecopy等。

其实严格意义上来说不算bug,这是EVM取immutable类型的值的规则,任何操作都是在堆栈上进行的,在使用内联汇编时需要格外注意。



总结


本文举了4个例子,其中三个是以往编译器存在的问题,一个是长期存在的,那么对于开发者来说,使用最新版本的编译器是最好的选择,在使用内联汇编的时候需要格外的小心。对于从事安全的审计师来说,熟悉编译器的潜在问题和EVM的运行规则更是至关重要的。

目前由于编译器造成的重大安全问题并不多,如果想探索更多,可以关注Ethereum issues和Ethereum Release发行页,有条件的可以研究其编译器。


作者:陈钦

编辑:舒婷





原文始发于微信公众号(ChainSecLabs):Compiler Bugs

版权声明:admin 发表于 2024年4月21日 下午10:07。
转载请注明:Compiler Bugs | CTF导航

相关文章