Smart contract security: 4. Integer overflow

1.Vulnerabilities

For example, the uint8 type has a total of 8 bits, which can represent the value of 00000000~11111111, converted into decimal, which is the value range of 0~255. At this time, once the result is 256, since there are only 8 digits in total, the 9th digit 1 cannot be displayed, leaving only 00000000, which is the desired 256, but actually 0 is obtained.

For example, the following TimeLock contract:

pragma solidity ^0.4.18;

contract TimeLock {
    mapping(address => uint) public balances;
    mapping(address => uint) public lockTime;

    function deposit() public payable {
        balances[msg.sender] += msg.value;
        lockTime[msg.sender] = now + 1 weeks;
    }

    function increaseLockTime(uint _secondsToIncrease) public {
        lockTime[msg.sender] += _secondsToIncrease;
    }

    function withdraw() public {
        require(balances[msg.sender] > 0);
        require(now > lockTime[msg.sender]);
        uint transferValue = balances[msg.sender];
        balances[msg.sender] = 0;
        msg.sender.transfer(transferValue);
    }
}

In the increaseLockTime function, since a free timestamp increment can be input by itself, there is a risk of integer overflow. Just imagine, if the input _secondsToIncrease is added to the original lockTime[msg.sender] ,due to overflow, the value of lockTime[msg.sender] will finally become a very small value, so that in the withdraw function, you can smoothly pass

require(now > lockTime[msg.sender]);

这一行,使得deposit进去的ETH可以提前被取出。

2.Preventive measures

First of all, in version 0.8.0, this problem has been solved at the language level: once an integer overflow occurs, the transaction will be directly reverted. Before version 0.8.0, a SafeMath library of openzeppelin was required.

3.Real cases

On April 22, 2018, hackers launched an attack on the BEC smart contract and took out out of thin air:

57,896,044,618,658,100,000,000,000,000,000,000,000,000,000,000,000,000,000,000.792003956564819968 BEC tokens were sold in the market. If it is 0, the market collapses instantly.

The contract version is ^0.4.16, which is less than version 0.8, and the SafeMath library is not used, so there is an integer overflow problem.

function batchTransfer(address[] _receivers, uint256 _value) public whenNotPaused returns (bool) {
    uint cnt = _receivers.length;
    uint256 amount = uint256(cnt) * _value; //溢出点,这里存在整数溢出
    require(cnt > 0 && cnt <= 20);
    require(_value > 0 && balances[msg.sender] >= amount);
 
    balances[msg.sender] = balances[msg.sender].sub(amount);
    for (uint i = 0; i < cnt; i++) {
        balances[_receivers[i]] = balances[_receivers[i]].add(_value);
        Transfer(msg.sender, _receivers[i], _value);
    }
    return true;
  }

The hacker passed in a very large value (here it is 2**255), and overflowed through multiplication, so that the amount (the total number of coins to be transferred) overflowed and became a small number or 0 (here became 0) , so as to bypass the check code of balances[msg.sender] >= amount, so that the malicious transfer of a huge amount of _value can be successful.

Malicious transfer records of actual attacks:

post image