Arithmetic Overflow and Underflow

In Solidity versions before 0.8, integers could overflow or underflow without any errors. This could lead to serious vulnerabilities in your smart contracts. However, starting from Solidity 0.8, the default behavior for overflow and underflow is to throw an error, which helps prevent such vulnerabilities.

Arithmetic Overflow

Arithmetic overflow is a condition that occurs when a calculation produces a result that is greater in magnitude than that which a given register or storage location can store or represent.

For instance, consider a uint8 , which can only have 8 bits. That means the largest number it can store is binary 11111111 (in decimal 2^8−1=255). If you try to store a value greater than this, it will cause an overflow.

Here’s an example in Solidity:

uint8 balance = 255;
balance++; // Overflow!

If you execute the code above the “balance” will be 0. This is a simple example of overflow. If you add 1 to the binary 11111111, it resets back to 00000000.

Arithmetic Underflow

Arithmetic underflow is the opposite of an overflow. It 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.

For instance, consider a uint8 in Solidity. The smallest number it can hold is 0. If you try to subtract 1 from a uint8 that is 0, it will cause an underflow.

Here’s an example in Solidity:

uint8 balance = 0;
balance--; // Underflow!

If you execute the code above the “balance” will be 255. This is a simple example of underflow. If you subtract 1 from 0, it wraps around to 255.

The TimeLock Contract

Consider a contract designed to act as a time vault. Users can deposit into this contract but cannot withdraw for at least a week. They can also extend the wait time beyond the 1-week waiting period.

The TimeLock contract is designed to act as a time vault. Users can deposit Ether into this contract but cannot withdraw for at least a week. They can also extend the wait time beyond the 1-week waiting period.

The deposit function allows a user to deposit Ether and sets a lockTime for their address to the current block.timestamp plus 1 week.

The increaseLockTime function allows a user to increase their lockTime by a specified number of seconds.

The withdraw function allows a user to withdraw their Ether after their lockTime has passed

An attacker could exploit the overflow vulnerability in the increaseLockTime function to immediately withdraw their ether, bypassing the 1-week waiting period.

The Attack

Here’s how the attack works:

The Attack contract is designed to exploit an overflow vulnerability in the TimeLock contract.

The attack function works as follows:

  1. It calls the deposit function of the TimeLock contract to deposit some Ether and set a lockTime.

  2. It then calls the increaseLockTime function with a carefully chosen value that causes the lockTime to overflow.

  3. Finally, it calls the withdraw function to withdraw the Ether immediately, bypassing the intended 1-week waiting period.

let’s break down the calculation:

Here’s what’s happening:

  1. type(uint).max is the maximum value that a uint (unsigned integer) can hold in Solidity. For a uint256 (which is the default uint in Solidity), this value is 2^256−1.

  2. timeLock.lockTime(address(this)) is the current lockTime of the Attack contract in the TimeLock contract.

  3. type(uint).max + 1 - timeLock.lockTime(address(this)) calculates the value that, when added to the current lockTime, will cause it to overflow.

Let’s say the current lockTime is t. The calculation is finding an x such that x + t = 2**256, which would cause an overflow. Rearranging the equation gives x = 2**256 - t. Since 2**256 is equivalent to type(uint).max + 1, the calculation becomes x = type(uint).max + 1 - t.

So, the increaseLockTime function is called with a value that will cause the lockTime to overflow and wrap around to a small number, allowing the attacker to withdraw their Ether immediately.

Preventative Techniques

To prevent arithmetic overflow and underflow, you can use the SafeMath library, which provides functions for arithmetic operations that throw an error on overflow and underflow. This can help ensure the safety of your smart contracts.

Starting from Solidity 0.8, the language defaults to throwing an error for overflow and underflow. This significant improvement helps secure your contracts against these types of vulnerabilities.

Conclusion

Understanding how arithmetic overflow and underflow work and how they can be exploited is crucial for writing secure smart contracts. You can protect your contracts from these vulnerabilities by using preventative techniques such as SafeMath or upgrading to Solidity 0.8 or later. Always remember, that security should be a top priority when developing smart contracts.

Reference

Last updated