Contents

Solidity Security By Example #11: Denial of Service With Induction Variable Overflow


Originally published in Valix Consulting’s Medium.

Smart contract security is one of the biggest impediments to 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.

Induction variable overflow can happen when a smart contract developer declares some variable with a too-small data type. Subsequently, the vulnerable contract cannot operate expectedly or even be exploited.

This article will explain how a smart contract vulnerable to the induction variable overflow can incur an unexpected denial-of-service (DoS) issue and how to prevent it. Enjoy reading. 😊

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

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 Vulnerability


The code below presents the VulnerableSimpleAirdrop contract that facilitates an airdrop launcher to quickly transfer an Ether airdrop to multiple receivers in only one transaction through the transferAirdrops function (lines 17–30).

To transfer an airdrop, the contract must be deployed with at least 1 Ether (line 8), and only a registered launcher can successfully execute the transferAirdrops function (lines 9, 12–15, and 17).

Unsurprisingly, the VulnerableSimpleAirdrop contract is vulnerable. Can you detect any issues? 👀

 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
pragma solidity 0.6.12;

contract VulnerableSimpleAirdrop {
    address public immutable launcher;

    constructor(address _launcher) public payable {
        require(_launcher != address(0), "Launcher cannot be a zero address");
        require(msg.value >= 1 ether, "Require at least 1 Ether");
        launcher = _launcher;
    }

    modifier onlyLauncher() {
        require(launcher == msg.sender, "You are not the launcher");
        _;
    }

    function transferAirdrops(uint256 _amount, address[] calldata _receivers) external onlyLauncher {
        require(_amount > 0, "Amount must be more than 0");
        require(_receivers.length > 0, "Require at least 1 receiver");
        require(
            getBalance() >= _amount *  _receivers.length,  // Integer overflow issue (not the scope of this example)
            "Insufficient Ether balance to transfer"
        );

        // Transfer airdrops to all receivers
        for (uint8 i = 0; i < _receivers.length; i++) {
            (bool success, ) = _receivers[i].call{value: _amount}("");  // Denial-of-service issue (not the scope of this example)
            require(success, "Failed to send Ether");
        }
    }

    function getBalance() public view returns (uint256) {
        return address(this).balance;
    }
}
VulnerableSimpleAirdrop.sol


We would like to note that the VulnerableSimpleAirdrop contract got two vulnerabilities that are considered out of scope in this article.

First is an integer overflow vulnerability that can bypass the require check in lines 20–23. In case you might be interested, please refer to our past article. 🕵️‍♂️

Second is a denial-of-service with revert vulnerability in line 27. We also described its vulnerability details in our past article. 🕵️‍♂️


The induction variable overflow occurs in line 26 in the transferAirdrops function.

Consider the following Figure to understand the vulnerability in more detail.

Figure 1. How the induction variable overflow occurs

Figure 1. How the induction variable overflow occurs


The transferAirdrops function loops over the _receivers array (lines 26–29) to transfer an airdrop to each receiver in line 27 (Steps 1–4). But if the length of the _receivers array is greater than 255, an overflow will occur since the variable i is declared as uint8 in line 26. 🤔

As a result, the loop will be restarted at index 0 repeatedly (Step 5). Consequently, the transaction will consume all gas and be reverted eventually, leading to an unexpected denial-of-service (DoS) issue, even if an airdrop launcher would set the transaction’s gas limit to the block gas limit. Oh My!! 👻

More specifically, the array indexes 256 and beyond would never be reached due to the out-of-range of the uint8 data type. 🎃


.    .    .

The Simulation


Below is the IssueSimulation contract that can simulate the VulnerableSimpleAirdrop contract’s induction variable overflow vulnerability.

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

interface ISimpleAirdrop {
    function transferAirdrops(uint256 _amount, address[] calldata _receivers) external;
}

contract IssueSimulation {
    uint256 public constant MAX_RECEIVERS = 300;

    function simulateIssue(ISimpleAirdrop _simpleAirdrop, uint256 _amount) external {
        require(_amount > 0, "Amount must be more than 0");

        // Generate a mock-up set of 300 receivers
        address[] memory receivers = new address[](MAX_RECEIVERS);
        for (uint256 i = 0; i < MAX_RECEIVERS; i++) {
            receivers[i] = address(i + 1000);  // receiver addresses: [1000 - 1299]
        }

        _simpleAirdrop.transferAirdrops(_amount, receivers);
    }
}
IssueSimulation.sol


To simulate the vulnerability, for instance, we can execute the issueSimulation.simulateIssue(address(vulnerableSimpleAirdrop), 1 wei) function (lines 10–20 above).

The issueSimulation.simulateIssue() function will generate a mock-up set of 300 airdrop receivers (lines 8 and 14–17). Then, the function will invoke the vulnerableSimpleAirdrop.transferAirdrops(_amount, receivers) function (line 19).

Please have a look at Figure 2 below for the vulnerability simulation result.

Figure 2. The vulnerability simulation result

Figure 2. The vulnerability simulation result


As you can see, the generated array of 300 dummy airdrop receivers caused the induction variable overflow issue. Ultimately, the airdrop transaction was reverted due to exceeding the block gas limit error. 👾

If you are interested in more about the denial-of-service vulnerability regarding the block gas limit, please check out our past article. 🕵️‍♂️


.    .    .

The Solution


The below FixedSimpleAirdrop contract is the improved version of the VulnerableSimpleAirdrop contract. 👨‍🔧

 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
pragma solidity 0.6.12;

// This contract still has integer overflow and denial-of-service issues, but they are not 
// the scope of this example though. Therefore, please do not use this contract code in production
contract FixedSimpleAirdrop {
    address public immutable launcher;

    constructor(address _launcher) public payable {
        require(_launcher != address(0), "Launcher cannot be a zero address");
        require(msg.value >= 1 ether, "Require at least 1 Ether");
        launcher = _launcher;
    }

    modifier onlyLauncher() {
        require(launcher == msg.sender, "You are not the launcher");
        _;
    }
    
    function transferAirdrops(uint256 _amount, address[] calldata _receivers) external onlyLauncher {
        require(_amount > 0, "Amount must be more than 0");
        require(_receivers.length > 0, "Require at least 1 receiver");
        require(
            getBalance() >= _amount *  _receivers.length,  // Integer overflow issue (not the scope of this example)
            "Insufficient Ether balance to transfer"
        );

        // Transfer airdrops to all receivers
        for (uint256 i = 0; i < _receivers.length; i++) {  // FIX: Apply uint256 for the variable "i"
            (bool success, ) = _receivers[i].call{value: _amount}("");  // Denial-of-service issue (not the scope of this example)
            require(success, "Failed to send Ether");
        }
    }

    function getBalance() public view returns (uint256) {
        return address(this).balance;
    }
}
FixedSimpleAirdrop.sol


To remediate the induction variable overflow vulnerability on the transferAirdrops function, the associated variable i was changed its data type from uint8 to the larger data type uint256 (line 28 above).

With the uint256 data type, the variable i will support any length of the _receivers array as long as the transaction’s gas used is neither reached the gas limit set by an airdrop launcher nor reached the block gas limit of the blockchain network.

Note that:

Please do not use the FixedSimpleAirdrop contract in your production. The contract is just a proof of concept of how to fix the induction variable overflow vulnerability only.

Nevertheless, the contract still implants other security vulnerabilities as mentioned in the Vulnerability section above.


.    .    .

Summary


In this article, you have learned how the induction variable overflow vulnerability can cause an unexpected denial-of-service (DoS) issue in the smart contract.

We demonstrated how the DoS issue could occur and how to fix the issue. We hope you enjoy our article and hope to see you again in the forthcoming article.

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


.    .    .

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, industry knowledge, and support staff, strive to deliver consistently superior quality services.

For any business inquiries, please contact us via Twitter, Facebook, or info@valix.io.


.    .    .

Originally published in Valix Consulting’s Medium.