Open In App

Overflow and Underflow Attacks on Smart Contracts

Last Updated : 27 Mar, 2023
Improve
Improve
Like Article
Like
Save
Share
Report

Smart contracts are programs running on top of blockchain platforms. They interact with each other through well-defined interfaces to perform financial transactions in a distributed system with no trusted third parties. But these interfaces also provide a favorable setting for attackers, who can exploit security vulnerabilities in smart contracts to achieve financial gain.

  • A smart contract is basically a piece of code where the written code is publicly visible in the blockchain, and transparent to anyone who is connected to the network. 
  • Upon fulfilling the conditions by the desired time, the contract gets triggered to execute the digital transaction. 
  • Since the conditions are encrypted cryptographically, no party is able to alter the contents of a contract. 
  • The immutable nature of blockchain also ensures that every single device connected to the network contains a copy of the contract, thus securing a backup version of the contract.

The article focuses on discussing the overflow and underflow attacks on smart contracts. The overflow and underflow attacks are classified as Integer overflows.

What is Integer Overflow?

In Solidity, there are 2 types of integers:

  • unsigned integers (uint): These are the positive numbers ranging from 0 to (2256 – 1).
  • signed integers (int): This includes both positive and negative numbers ranging from -2255 to (2255 – 1).

An overflow/ underflow occurs when an operation is performed that requires a fixed-size variable to store the result of the operation.

Overflow Situation:

Consider an unsigned 8-bit integer variable uint8 a. This variable has a range from 0 to 255:

uint8 a = 255;

a++;

This results in an overflow error as the variable a can take values in the interval 0-255 then incrementing the value of a by 1 would result in an overflow situation.

Underflow Situation:

Consider an unsigned 8-bit integer variable uint8 a. This variable has a range from 0 to 255:

uint8 a = 0;

a–;

This results in an underflow error as the variable a can take a value in the range of 0-255 and decrementing the value by 1 would result in code collapse.

Process and Challenges of Detecting Integer Overflow in Ethereum

  1. No indication: In other programming languages there is an indication of integer overflow but this is not the case with EVM. It is possible to deduce that overflow has occurred from the values stored after the transaction. The most common way is to rerun the transaction to identify if there is an overflow condition.
  2. Arithmetic Operations: Addition or subtraction of 2 numbers can also result in Integer overflow/ underflow. The multiplication operation is based on addition and exponent operation is based on multiplication, so both of these operations are also prone to overflow attack. 
  3. False positives: In some cases, it is possible to identify what operation has caused integer overflow and whether the operands are signed or unsigned integers. Some compilers intentionally create overflow conditions to run some functionality so when there is an overflow condition it is hard to guess whether it is an actual error or an intentionally raised situation.
  4. No types on byte code level: The types of signed and unsigned integers are declared in high-level programming language only but not on the byte code level. So in cases where there is no Solidity source code for the smart contract then it is hard to guess the type of integers.

Overflow Error Attacks on Smart Contract

An overflow error attack on a smart contract occurs when more value is provided than the maximum value. When this happens, it circles back to zero, and this feature can be exploited by repeatedly invoking the feature that increases the value. Since Solidity does not provide native overflow detection, it is important for developers to be aware of this issue and to ensure that their code takes these possible errors into account. This can be done by using safe math libraries or using appropriate types such as uint256 for operations with integers.

Example 1:

function transfer(address _to, uint256 _amount) {

   // Check if sender has sufficient balance 

   require(balanceOf[msg.sender] >= _amount);

   // Add and subtract new balances 

   balanceOf[msg.sender] -= _amount;

   balanceOf[_to] += _amount;

Explanation:

  • In this example, the malicious user is able to send a large amount of ether to the contract that exceeds the maximum value of a unit. 
  • If the value of the amount variable reaches the maximum value of uint (2^256) then it is going to circle back and make it 0 which can be very problematic to handle. 
  • This can lead to an unexpected result, such as the total balance becoming negative, and can enable the attacker to steal funds from the contract. 

Solution:

function transfer(address _to, uint256 _amount) {

   // Check if sender has balance and for overflows 

   require(balanceOf[msg.sender] >= _amount && 

               balanceOf[_to] + _amount >= balanceOf[_to]);

   /* Add and subtract new balances */

   balanceOf[msg.sender] -= _amount;

   balanceOf[_to] += _amount;

Explanation: Here, the overflow condition is checked by checking if the sum of balance in the recipient’s account and the amount to be transferred is greater than or equal to the balance in the recipient’s account or not. This ensures that even if the user is allowed to increment the value by 1 at a time, even then there is no feasible way to reach this limit.

To prevent this type of attack, developers should make sure to use safe math libraries or appropriate types that provide overflow detection.

Example 2: 

An attacker is able to call this function with parameters to exploit the vulnerability. For instance, the code below shifts the check to balances[msg.sender] > = total. An attacker can input 2 addresses in the receivers’ function in order for the token smart contract to transmit ether to both addresses.

function batchTransfer(address[] _acceptors,

                                    uint256 _value) public whenNotPaused returns (bool) {

        uint cnt = _acceptors.length;

        uint256 total = uint256(cnt) * _value;

        require(cnt > 0 && cnt <=20);

        require(_value > 0 && balances[msg.sender] >= total);

        balances[msg.sender] = balances[msg.sender].sub(total);

        for(uint i = 0; i < cnt; i++) {

               balances[_acceptos[i]] = balances[_acceptors[i]].add(_value);

               Transfer(msg.sender, _acceptors[i], _value);

        }

        return true;

}

There is a bug in the line 

uint256 total = uint256(cnt) * _value;

Explanation:

  • The attacker initiates the attack by sending 1 Wei to the target contract.
  • The contract credits the sender for funds sent.
  • A subsequent withdrawal of the same 1 Wei is called.
  • The contract subtracts 1 Wei from the sender’s credit, now the balance is zero again.
  • Because the target contract sends ether to the attacker, the attacker’s fallback function is also triggered and a withdrawal is called again.
  • The withdrawal of 1 Wei is recorded.
  • The balance of the attacker’s contract has been updated twice the first time to zero and the second time to -1.
  • The attacker’s balance is reset to 2²⁵⁶.
  • The attacker completes the attack by withdrawing all of the funds from the targeted contract.

Underflow Error Attacks on Smart Contract

This error attack operates in the exact opposite of the overflow error. Instead of exceeding the maximum value, an underflow error occurs when you go below the minimum amount. This triggers the system to bring you right back up to maximum value instead of reverting to zero. However, the underflow attacks are simpler to perform as achieving the required token to cause overflow is often challenging for attackers. These vulnerabilities are relatively easy to initiate and occur in transactions that accept unauthorized input data or value. 

Example: Below example demonstrates that the function does not check for integer underflows and thus allows a large number of tokens to be withdrawn.

function withdraw(uint _amount) {

      require(balances[msg.sender] – _amount > 0);

      msg.sender.transfer(_amount);

      balances[msg.sender] -= _amount;

}

Explanation: Here, there is no check for integer underflow, the attacker can attack the code and withdraw large amount of tokens. To avoid falling victim to an underflow attack, the best practice is to check if the updated integer stays within its byte range. 

Impact of Overflow and Underflow Attacks

Underflow errors are more likely to occur as opposed to overflows, and the outcome can be disastrous. 

  • If a program lacks the feature that checks for underflow and overflow, an attacker can get more tokens than they own. They can also get a maxed-out balance, which is essentially stealing.
  • These errors can cause the whole system to break because the number of tokens being maxed out is not the same as the tokens in the system.

Mitigation of Overflow and Underflow Attack

Below are some of the ways to mitigate overflow and underflow attacks:

  • SafeMath library: To prevent an integer overflow or underflow attack, developers should make sure to use safe math libraries or appropriate types that provide overflow detection. This library provides the basic arithmetic operations but it can also check the preconditions and postconditions to determine whether an overflow has occurred or not. In case of an error, the library fails the transaction and updates the status of the transaction to be ‘Reverted’.
  • Compiler Version: The attack can be prevented by using solidity >= 0.8. Compile smart contracts with a newer version of the compiler. Thus, the preventive code of external libraries like SafeMath is embedded in the compiled code. 
  • Use modifier ‘onlyOwner’: Additionally, they can also incorporate checks in their code to ensure the values never exceed their expected range or use modifiers such as ‘onlyOwner’ for extra security. 
  • Regularly update code: Finally, it is also important to keep the code regularly updated to avoid any potential vulnerabilities.

Real-life Examples of Overflow and Underflow Attacks

  • The mintToken function of the smart contract implementation of Coinstar (CSTR), an Ethereum token has an integer overflow that allows the owner of the smart contract to set the balance of any random user to any random value.
  • Proof of Weak Hands Coin(PoWHC) is a ponzi scheme on Ethereum written in Solidity by 4chan group.. The authors of the smart contract had not observed the underflow or overflow condition and thus liberated 866 ethers from the contract.
  • BeautyChain contract is one example where the attacker used the behavior of integer overflow to overcome some security checks and stole a huge amount of BEC tokens.


Like Article
Suggest improvement
Share your thoughts in the comments

Similar Reads