Halborn Logo

// Blog

Blockchain Security

Arithmetic Underflow and Overflow Vulnerabilities In Solidity


profile

Rob Behnke

March 13th, 2023


In a previous article on delegatecall vulnerabilities in Solidity, we explained how the Solidity language has certain quirks that can potentially result in insecure execution of smart contracts. In this article, we’ll discuss vulnerabilities arising from improper handling of mathematical operations in Solidity: arithmetic underflow and arithmetic overflow. 

We'll briefly describe the concept of arithmetic underflow and arithmetic overflow in Solidity and explain how they can lead to real-world bugs and exploits. We’ll also highlight some tips for mitigating underflow and overflow issues in smart contracts. 

What is arithmetic overflow? 

Computer programs—including smart contracts—store data using binary format where binary numbers are a collection of “bits” and each bit is equal to 0 or 1. If, for example, a smart contract operates on 256-bit unsigned integers (uint256), the maximum value it can store is 2256 - 1. This is a fairly large value, but it is possible to generate a value outside this range—and this is where overflow comes into the picture. 

Arithmetic overflow occurs when the result of a mathematical operation exceeds the maximum value that the program can store. Going back to the previous example, this would mean executing the code such that the resulting value is larger than 2256 - 1. 

In earlier versions of Solidity (< Solidity 8.0), executions where a generated number exceeded the range specified in the function's data type (eg. uint64 or uint256) would “wrap around” instead of throwing exceptions. We say a calculation wraps around if increasing the largest possible integer value causes it to continue from the smallest possible integer value (and vice-versa). 

Here’s an example for context:

pragma solidity 0.7.0;

contract ChangeBalance {
    uint8 public balance;
    function decrease() public {
        balance--;
    }
    function increase() public {
        balance++;
    }
}

This is a simple contract that stores a balance of using uint8 values (where the maximum value it can store is  28 - 1 = 255). If a user executes the function with an input that increases the balance to 256, the calculation wraps around and reverts the next lowest value possible (0) in Solidity (pre-version 8.0).

The 2018 Beauty Chain hack is an example of how attackers can exploit arithmetic overflows in Solidity contracts. Here, the attacker passed an arbitrarily large number (2256) into a function that calculated the amount to be withdrawn from the contract. This triggered an integer overflow and allowed the attacker to bypass checks that would have prevented the caller from a token amount greater than their balance. 

What is arithmetic underflow? 

An arithmetic underflow is the opposite of an arithmetic overflow, although both follow similar patterns. Arithmetic underflow occurs when a calculation produces a value too low to be stored in the associated data type. This would cause the calculation to wrap around and start from the next largest possible value. 

We can illustrate using the changeBalance contract mentioned earlier. In this case, calling decrease() after the balance reaches 0 would cause the contract function to produce 255 (the max value) as the result. 

The Proof of Weak Hands hack (which also occurred in 2018 and cost users 866 ETH) is an example of what happens if a contract fails to guard against underflowing arithmetic operations. With the PoWH hack, the attacker caused an account’s token balance to underflow during a transferTokens operation. This left the account with the maximum amount of tokens (2256 - 1) in its balance—allowing the attacker to drain a large amount from the contract. 

Preventing arithmetic underflow and overflow in Solidity 

You can prevent arithmetic underflow and overflow vulnerabilities by adding modifiers to functions performing arithmetic operations that check for integer overflow/underflow. 

Since Solidity 8.0, any calculations that underflow or underflow will automatically revert and throw an error. As such, it is advisable to compile your contracts using a version from 0.80 upwards. This automatically makes your contracts safe from overflowing and underflowing integers without needing to use a library or write custom logic.