For further reading on arithmetic over/underflows, see , Ethereum Smart Contract Best Practices, and .

    The Vulnerability

    An over/underflow occurs when an operation is performed that requires a fixed-size variable to store a number (or piece of data) that is outside the range of the variable’s data type.

    For example, subtracting 1 from a uint8 (unsigned integer of 8 bits; i.e., nonnegative) variable whose value is 0 will result in the number 255. This is an underflow. We have assigned a number below the range of the uint8, so the result wraps around and gives the largest number a uint8 can store. Similarly, adding 2^8=256 to a uint8 will leave the variable unchanged, as we have wrapped around the entire length of the uint. Two simple analogies of this behavior are odometers in cars, which measure distance traveled (they reset to 000000, after the largest number, i.e., 999999, is surpassed) and periodic mathematical functions (adding 2π to the argument of sin leaves the value unchanged).

    Adding numbers larger than the data type’s range is called an overflow. For clarity, adding 257 to a that currently has a value of 0 will result in the number 1. It is sometimes instructive to think of fixed-size variables as being cyclic, where we start again from zero if we add numbers above the largest possible stored number, and start counting down from the largest number if we subtract from zero. In the case of signed int types, which can represent negative numbers, we start again once we reach the largest negative value; for example, if we try to subtract 1 from a int8 whose value is -128, we will get 127.

    These kinds of numerical gotchas allow attackers to misuse code and create unexpected logic flows. For example, consider the TimeLock contract in .

    Example 3. TimeLock.sol

    In the event that a user is forced to hand over their private key, a contract such as this might be handy to ensure their ether is unobtainable for a short period of time. But if a user had locked in 100 ether in this contract and handed their keys over to an attacker, the attacker could use an overflow to receive the ether, regardless of the lockTime.

    The attacker could determine the current lockTime for the address they now hold the key for (it’s a public variable). Let’s call this userLockTime. They could then call the increaseLockTime function and pass as an argument the number 2^256 - userLockTime. This number would be added to the current and cause an overflow, resetting lockTime[msg.sender] to 0. The attacker could then simply call the withdraw function to obtain their reward.

    Let’s look at another example (Underflow vulnerability example from Ethernaut challenge), this one from the .

    SPOILER ALERT: If you have not yet done the Ethernaut challenges, this gives a solution to one of the levels.

    Example 4. Underflow vulnerability example from Ethernaut challenge

    This is a simple token contract that employs a transfer function, allowing participants to move their tokens around. Can you see the error in this contract?

    Preventative Techniques

    The current conventional technique to guard against under/overflow vulnerabilities is to use or build mathematical libraries that replace the standard math operators addition, subtraction, and multiplication (division is excluded as it does not cause over/underflows and the EVM reverts on division by 0).

    has done a great job of building and auditing secure libraries for the Ethereum community. In particular, its SafeMath library can be used to avoid under/overflow vulnerabilities.

    To demonstrate how these libraries are used in Solidity, let’s correct the TimeLock contract using the SafeMath library. The overflow-free version of the contract is:

    Notice that all standard math operations have been replaced by those defined in the SafeMath library. The contract no longer performs any operation that is capable of under/overflow.

    Real-World Examples: PoWHC and Batch Transfer Overflow (CVE-2018–10299)

    Proof of Weak Hands Coin (PoWHC), originally devised as a joke of sorts, was a Ponzi scheme written by an internet collective. Unfortunately it seems that the author(s) of the contract had not seen over/underflows before, and consequently 866 ether were liberated from its contract. Eric Banisadr gives a good overview of how the underflow occurred (which is not too dissimilar to the Ethernaut challenge described earlier) in his blog post on the event.

    comes from the implementation of a batchTransfer() function into a group of ERC20 token contracts. The implementation contained an overflow vulnerability; you can read about the details in PeckShield’s account.