Overflow attack in Ethereum smart contracts

Description

This attack took place in April 22, 2018 due to a well known and common issue in many programming languages called Integer overflow. It termed as batchOverflow or proxyOverflow and some exchanges (like OKEx, Poloniex, HitBTC and Huobi Pro) suspended deposits and withdrawals of ALL ERC20 tokens, especially for Beauty Ecosystem Coin (BEC) that was targeted by this exploit. In this attack, someone was able to run a transaction and transfer two extremely large amount of BEC token to two addresses. Although BEC developers had considered most of the security measurements, only one line of the code (line 257) was vulnerable against this classic integer overflow issue [4]:

_images/batch_overflow_04.png

Figure 1: Vulnerable code in BEC token, batchTransfer() function

The attacker was able to pass a combination of input values that generate large results than the maximum value of uint256 data type can hold. It caused integer overflow and only the least significant bits have been retained. In other words, the uint256 variable reached to the maximum value that can be held and it wraps around by starting from 0. For example, an uint8 (8-bit unsigned integer) can represent maximum value of \(2^8-1=255\) (0xff). Multiplying 0x02 by 0x80 causes integer overflow and produces 0x00 as the result (0x02 * 0x80 = 0x100 => 0x00). We can achieve the same result by adding 0x01 to 0xff (0x01 + 0xff = 0x100 => 0x00). So, In BEC case, the attacker passed two addresses ( cnt = _receivers.lengh = 0x02 ) and a large value ( _value = 0x8000000000000000000000000000000000000000000000000000000000000000 (63 0's) ) to batchTransfer() function. Because of wrap around, the result of amount variable (line 257) was calculated as 0x00 and this result bypassed sanity checks in line 259. Hence, line 264 transferred the specified _value to those two addresses. This transfer was even more than the initial supply of the token which was 7000000000000000000000000000 (27 0's) tokens. It potentially allows the attacker to take control of token finance and manipulate its price.

Smart Mesh (SMT) token was the next victime of this exploit on April 24, 2018 by transferring 0x8fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff (63 f’s) tokens to one address and 0x7000000000000000000000000000000000000000000000000000000000000001 (62 0's) as huge fee to the transaction initiator. An attacker called proxyOverflow() function which was designed for transferring tokens on behalf on someone else by taking a fee. Line 206 of this smart contract was vulnerable and sum of _feeSmt and _value produced zero and bypassed the sanity check in line 206:

_images/batch_overflow_05.png

Figure 2: Vulnerable code in SMT token, proxyTransfer() function

In addition to BEC and SMT, the following tokens have been identified as overflow-affected [5]:

  1. MESH

  2. UGToken

  3. SMART

  4. MTC

  5. First

  6. GG Token

  7. CNY Token

  8. CNYTokenPLus

Reproducing the issue

To check feasibility of this attack in the current version of solidity programming language (0.5.2 as writing this), the below smart contract is created and used to test overflow attack on uint256 data type:

_images/batch_overflow_01.png

Figure 3: Integer overflow demonstration in solidity

We initially set c=0x3 to check its result before and after multiplication operation performed by a_multiply_b() function. On the left, we can see initial value of c=3 before execution of the function and on the right, after that. Value of c has been set to zero after execution of the function due to wrap around.

_images/batch_overflow_02.png

Figure 4: Result of multiplication operation in case of integer overflow

Ethereum executed a_multiply_b() function in unchecked context and showed successful status by returning true as output of the function:

_images/batch_overflow_03.png

Figure 5: By default, integer overflow does not throw a runtime exception in Ethereum

The same overflow result can be r in the sum of two uint256 numbers:

_images/batch_overflow_06.png

Figure 6: Integer overflow demonstration in solidity

_images/batch_overflow_07.png

Figure 7: Result of addition operation in case of integer overflow

As shown above, the arithmetic result of numeric values outside of the representable range will lead to wrap around and sets the result to 0. Although this is expected behavior in Ethereum, it causes security problems as explained in CVE-2018–10299 and CVE-2018-10376. To address this issue, there are best practices to follow as explained in the next section.

Mitigation

To prevent overflow attack, it is recommended to use SafeMath library when performing any arithmetic calculations. This library offered by OpenZeppelin and becomes industry standard for catching overflows. Moreover, auditing before launching the code could prevent such human errors and help to be in compliance with best practices. We used SafeMath library and re-implemented vulnerable functions in the previous section:

_images/batch_overflow_08.png

Figure 8: Re-implemented multiply function by using SafeMath library

This time, execution of a_multiply_b() function raised an exception and stopped execution of the code:

_images/batch_overflow_09.png

Figure 9: Raised exception in case of overflow issue

Full code using SafeMath:

  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
 40
 41
 42
 43
 44
 45
 46
 47
 48
 49
 50
 51
 52
 53
 54
 55
 56
 57
 58
 59
 60
 61
 62
 63
 64
 65
 66
 67
 68
 69
 70
 71
 72
 73
 74
 75
 76
 77
 78
 79
 80
 81
 82
 83
 84
 85
 86
 87
 88
 89
 90
 91
 92
 93
 94
 95
 96
 97
 98
 99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
pragma solidity ^0.5.2;

/**
 * @title SafeMath
 * @dev Math operations with safety checks that revert on error
 */
library SafeMath {
    int256 constant private INT256_MIN = -2**255;

    /**
    * @dev Multiplies two unsigned integers, reverts on overflow.
    */
    function mul(uint256 a, uint256 b) internal pure returns (uint256) {
        // Gas optimization: this is cheaper than requiring 'a' not being zero, but the
        // benefit is lost if 'b' is also tested.
        // See: https://github.com/OpenZeppelin/openzeppelin-solidity/pull/522
        if (a == 0) {
            return 0;
        }

        uint256 c = a * b;
        require(c / a == b);

        return c;
    }

    /**
    * @dev Multiplies two signed integers, reverts on overflow.
    */
    function mul(int256 a, int256 b) internal pure returns (int256) {
        // Gas optimization: this is cheaper than requiring 'a' not being zero, but the
        // benefit is lost if 'b' is also tested.
        // See: https://github.com/OpenZeppelin/openzeppelin-solidity/pull/522
        if (a == 0) {
            return 0;
        }

        require(!(a == -1 && b == INT256_MIN)); // This is the only case of overflow not detected by the check below

        int256 c = a * b;
        require(c / a == b);

        return c;
    }

    /**
    * @dev Integer division of two unsigned integers truncating the quotient, reverts on division by zero.
    */
    function div(uint256 a, uint256 b) internal pure returns (uint256) {
        // Solidity only automatically asserts when dividing by 0
        require(b > 0);
        uint256 c = a / b;
        // assert(a == b * c + a % b); // There is no case in which this doesn't hold

        return c;
    }

    /**
    * @dev Integer division of two signed integers truncating the quotient, reverts on division by zero.
    */
    function div(int256 a, int256 b) internal pure returns (int256) {
        require(b != 0); // Solidity only automatically asserts when dividing by 0
        require(!(b == -1 && a == INT256_MIN)); // This is the only case of overflow

        int256 c = a / b;

        return c;
    }

    /**
    * @dev Subtracts two unsigned integers, reverts on overflow (i.e. if subtrahend is greater than minuend).
    */
    function sub(uint256 a, uint256 b) internal pure returns (uint256) {
        require(b <= a);
        uint256 c = a - b;

        return c;
    }

    /**
    * @dev Subtracts two signed integers, reverts on overflow.
    */
    function sub(int256 a, int256 b) internal pure returns (int256) {
        int256 c = a - b;
        require((b >= 0 && c <= a) || (b < 0 && c > a));

        return c;
    }

    /**
    * @dev Adds two unsigned integers, reverts on overflow.
    */
    function add(uint256 a, uint256 b) internal pure returns (uint256) {
        uint256 c = a + b;
        require(c >= a);

        return c;
    }

    /**
    * @dev Adds two signed integers, reverts on overflow.
    */
    function add(int256 a, int256 b) internal pure returns (int256) {
        int256 c = a + b;
        require((b >= 0 && c >= a) || (b < 0 && c < a));

        return c;
    }

    /**
    * @dev Divides two unsigned integers and returns the remainder (unsigned integer modulo),
    * reverts when dividing by zero.
    */
    function mod(uint256 a, uint256 b) internal pure returns (uint256) {
        require(b != 0);
        return a % b;
    }
}


/**
 * @title multiplyDemoSafe
 * @dev Use SafeMath to prevent overflow attack
 */
contract multiplyDemoSafe {
    using SafeMath for uint256;

    uint256 public a = 0x8000000000000000000000000000000000000000000000000000000000000000;
    uint256 public b = 0x2;
    uint256 public c;

    constructor() public {
        c = 0x3;
    }

    function a_multiply_b() public returns (bool){
        c = a.mul(b);
        return (c == 0) ? true : false;
    }
}

contract additionDemoSafe {
    using SafeMath for uint256;

    uint256 public a = 0xffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff;
    uint256 public b = 0x0000000000000000000000000000000000000000000000000000000000000001;
    uint256 public c;

    constructor() public {
        c = 0x3;
    }

    function a_plus_b() public returns (bool){
        c = a.add(b);
        return (c == 0) ? true : false;
    }
}

Recommendation

In order to have a secure solidity code and mitigate against overflow attack, it is recommended to use SafeMath library in any arithmetic operation.





References

1

V. Buterin F. Vogelsteller. ERC-20 Token Standard. https://github.com/ethereum/EIPs/blob/master/EIPS/eip-20.md, November 2015. [Online; accessed 2-Dec-2018].

2

Tom Hale. Resolution on the EIP20 API Approve / TransferFrom multiple withdrawal attack #738. https://github.com/ethereum/EIPs/issues/738, October 2017. [Online; accessed 5-Dec-2018].

2

C. Ho. The Rise of Stablecoins & What Are The Different Types Out There. https://hackernoon.com/the-rise-of-stablecoins-what-are-the-different-types-out-there-9b8b7f31dadc, November 2018. [Online; accessed 21-Jan-2019].

4

PeckShield. ALERT: New batchOverflow Bug in Multiple ERC20 Smart Contracts (CVE-2018-10299). https://blog.peckshield.com/2018/04/22/batchOverflow/, April 2018. [Online; accessed 26-Dec-2018].

5

PeckShield. New proxyOverflow Bug in Multiple ERC20 Smart Contracts (CVE-2018-10376). https://blog.peckshield.com/2018/04/25/proxyOverflow/, April 2018. [Online; accessed 30-Dec-2018].

4

Ethereum Project. Create your own crypto-currency). https://www.ethereum.org/token, December 2017. [Online; accessed 01-Dec-2018].

5

M. Vladimirov. ERC: Token standard #20). https://github.com/ethereum/EIPs/issues/20#issuecomment-263563087, November 2016. [Online; accessed 25-Nov-2018].

6

M. Vladimirov and D. Khovratovich. ERC20 API: An Attack Vector on Approve/TransferFrom Methods. https://docs.google.com/document/d/1YLPtQxZu1UAvO9cZ1O2RPXBbT0mooh4DYKjA_jp-RLM/edit#heading=h.m9fhqynw2xvt, November 2016. [Online; accessed 25-Nov-2018].

7

Wikipedia. Compare-and-swap. https://en.wikipedia.org/wiki/Compare-and-swap, July 2018. [Online; accessed 10-Dec-2018].





Date

Dec 24, 2018

Updated

Jan 19, 2020

Authors

About