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:
It calls the
depositfunction of theTimeLockcontract to deposit some Ether and set alockTime.It then calls the
increaseLockTimefunction with a carefully chosen value that causes thelockTimeto overflow.Finally, it calls the
withdrawfunction to withdraw the Ether immediately, bypassing the intended 1-week waiting period.
let’s break down the calculation:
Here’s what’s happening:
type(uint).maxis the maximum value that auint(unsigned integer) can hold in Solidity. For auint256(which is the defaultuintin Solidity), this value is 2^256−1.timeLock.lockTime(address(this))is the currentlockTimeof theAttackcontract in theTimeLockcontract.type(uint).max + 1 - timeLock.lockTime(address(this))calculates the value that, when added to the currentlockTime, 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