solidity智能合约[49]-安全-溢出***

***回顾

1
2
3
4
5
6
7
2016年6月,以太坊最大众筹项目The DAO被***,***获得超过350万个以太币,最终导致以太坊分叉为ETH和ETC。
2016年拒绝服务***:GovernMental's 1100 ETH
2016年KotET(“纷争时代”)合约遭受***。
2017年Parity钱包,遭受delecate call注入,销毁了合约。损失513,774.16 Ether
2017年ANT Token遭受重入漏洞。
2017年Simoleon合约被***。***通过部署***合约获得了超过700万的token,从57万账户中脱颖而出,一举成为该合约token的第四大持有者。
2018年BEC代币遭到袭击,***手法被披露的24小时内,就有30多个合约遭受***

溢出

孔子曾经说过 过犹不及。做事情都有限度,一旦超过了限度就会适得其反。理解溢出问题最好的是在千禧之年爆发的千年虫事件。过去,由于计算机程序中使用两个数字来表示年份,如1998年被表示为“98”、1999年被表示为“99”;而2000年被表示为“00”,这样将会导致某些程序在计算时得到不正确的结果,如把“00”误解为1900年。在嵌入式系统中可能存在同样的问题,这有可能导致设备停止运转或者发生更加灾难性的后果。

solidity中的溢出问题

下面是一个简单的函数,其功能是将桉树加1.例如传递4,返回5。传递200,返回201。但是里面暗藏着陷阱。例如当传递255的时候,会返回0…这就是溢出。这是由于uint8的最大值为255,在内存中:为1111 1111。一旦加1之后,变为了
1 0000 0000,但是最大的位数为8位。截断之后,变为了0000 0000 因此返回的结果为0。

1
2
3
function add(uint8 a) public pure returns(uint8){
   return a+1;
}

safeMath避免溢出问题

因此我们可以看到,对于4则运算,很容易的发生溢出问题。OpenZeppelin 建立了一个叫做 SafeMath 的 库。这就规避掉溢出问题。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
library SafeMath {
   function add(uint a, uint b) internal pure returns (uint c) {
       c = a + b;
       require(c >= a);
   }
   function sub(uint a, uint b) internal pure returns (uint c) {
       require(b <= a);
       c = a - b;
   }
   function mul(uint a, uint b) internal pure returns (uint c) {
       c = a * b;
       require(a == 0 || c / a == b);
   }
   function div(uint a, uint b) internal pure returns (uint c) {
       require(b > 0);
       c = a / b;
   }
}

BEC代币***全纪实

如下为BEC代币的源代码抽离出来的部分。2018年4月份BEC代币遭到***的溢出袭击。***为自己的两个账号转移了2^255次方的代币。导致市场的恐慌,币价一度一文不值。***手法被披露的24小时内,就有类似30多个合约遭受***

在TokenExample合约中,有一个batchTransfer函数。此函数的功能为对账户进行转账操作。第一个参数为动态长度地址,明确要转账的账户。第二个参数为转账的金额。 要转账成功,必须要保证在balance资金表中,发送者必须有超过总金额(账户数量转账金额)。但是 uint256 amount = uint256(cnt) value;这段代码并没有做安全的乘法,导致可能会发生溢出***。当***调用合约的时候,在remix中:
输入地址数组以及:[“0xb4D30Cac5124b46C2Df0CF3e3e1Be05f42119033”,“0x0e823fFE018727585EaF5Bc769Fa80472F76C3d7”],
以及
value"0x8000000000000000000000000000000000000000000000000000000000000000"即2*255,
使得amount=2^255 * 2,超出uint256类型的范围[0,2**256-1],溢出为0,发送者账户余额不减少,并且,本例中,发送者的代币可以为零,实现"无中生有"。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39

library SafeMath{
   function sub(uint256 a,uint256 b) internal pure returns(uint256){
       assert(b<=a);
       return a-b;
   }

   function add(uint256 a,uint256 b) internal pure returns(uint256 c){
       c=a+b;
       assert(c>=a);
       return c;
   }
}

contract TokenExample{
   //使用safemath库
   using SafeMath for uint256;

   //资金表
   mapping(address=>uint256) public balance;
   function batchTransfer(address[] _receivers,uint256 _value) public returns(bool){
       //要转移的地址的数量  
       uint cnt = _receivers.length;
       //转账总金额
       uint256 amount = uint256(cnt)*_value;
       //判断转账地址必须大于0
       require(cnt >0 && cnt <=20);
       //判断发送者拥有的金额必须大于转账的总金额
       require(_value >0 && balance[msg.sender]>=amount);
       //发送者账户金额减少 20
       balance[msg.sender] = balance[msg.sender].sub(amount);
       //接受者金额增加   10
       for(uint i =0 ;i<cnt;i++){
           balance[_receivers[i]] = balance[_receivers[i]].add(_value);
       }

       return true;
   }
}


猜你喜欢

转载自blog.51cto.com/13784902/2322329