Solidity Security By Example #04: Cross-Function Reentrancy
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.
Cross-function reentrancy is another level of reentrancy in terms of complexity. Typically, the root cause of this issue is that there are multiple functions mutually sharing the same state variable, and some of them update that variable insecurely.
In this article, you will learn how the cross-function reentrancy attack happens 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/04_cross_function_reentrancy.
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 the FixedEtherVault
contracts.
|
|
The ReentrancyGuard
contains the noReentrant
modifier that is used to prevent reentrancy attacks. The noReentrant
(lines 6–11) is a simple lock that allows only a single entrance to the function applying it. If there is an attempt to do the reentrant, the transaction would be reverted by the require
statement in line 7.
The Vulnerability
The below code presents the InsecureEtherVault
contract, providing a simple vault allowing users to deposit their Ethers, transfer deposited Ethers to other users, withdraw all deposited Ethers, and check their balances.
As you may know, the InsecureEtherVault
contract is vulnerable to a reentrancy attack. But, can you find the issue? 👀
|
|
You may notice that the withdrawAll
function is attached with the noReentrant
modifier (line 19). Hence, the withdrawAll
function is safe from the reentrancy attack 🕵. Please refer to our previous article on the single-function reentrancy attack to understand how the insecure withdrawAll
function can be exploited.
However, the InsecureEtherVault
contract in this article got another level of reentrancy in terms of complexity; we would call it: cross-function reentrancy. 🤖
The cross-function reentrancy begins in line 23 in the withdrawAll
function. Figure 1 below pictures how the cross-function reentrancy attack occurs.
The root cause of cross-function reentrancy attack is typically due to there are multiple functions mutually sharing the same state variable, and some of them update that variable insecurely.
Since the withdrawAll
function applies the noReentrant
modifier, an attacker cannot do the reentrancy on this function anymore.
Nevertheless, the withdrawAll
function does not update the withdrawer’s balance (userBalances[msg.sender] = 0
) before sending Ethers back to the withdrawer (Step 4). Therefore, the attacker can perform the cross-function reentrancy attack by manipulating the control flow in the Attack #1
contract’s receive
function to transfer its balance (Step 5) to another contract, Attack #2
(i.e., contract instance #2 in Figure 1).
This way, the attacker can execute another transaction by calling the attackNext
function of the Attack #2
contract (Step 6) to gradually withdraw Ethers from the InsecureEtherVault
contract and then transfer the Attack #2
contract’s balance to the Attack #1
contract.
To steal all Ethers locked in the InsecureEtherVault
, the attacker executes the attackNext
function of the Attack #1
and Attack #2
contracts alternately. WOW! 🎃
In fact, the attacker can integrate the attack step 6 into a single transaction call to automate the attack. Though, the step 6 was intentionally isolated for the understanding sake.
The Attack
The code below shows the Attack
contract that can exploit the InsecureEtherVault
contract.
|
|
To exploit the InsecureEtherVault
, an attacker has to deploy two Attack
contracts (i.e., attack1
and attack2
) and then perform the following actions:
-
Call:
attack1.attackInit() and supplies 1 Ether
-
Call:
attack2.attackNext()
-
Alternately Call:
attack1.attackNext()
andattack2.attackNext()
to drain all locked Ethers
To understand how the Attack
contract works in more detail, please refer to the attack steps illustrated in Figure 1 above.
As mentioned earlier, the actions #2 and #3 can be integrated into the action #1 for an atomic attack transaction call. Nonetheless, we intentionally isolated them for the sake of understanding.
The result of the attack is shown in Figure 2. As you can see, the attacker drained all locked Ethers by making separate transactions to the two Attack
contracts alternately. 🤑
The Solution
The FixedEtherVault
contract below is the fixed version of the InsecureEtherVault
. 👨🔧
|
|
To fix the associated issue, the withdrawAll
function was improved to follow the checks-effects-interactions pattern. In other words, we moved the so-called effect part (userBalances[msg.sender] = 0
) to line 24 to execute it before the interaction part in line 26 (msg.sender.call{value: balance}("")
). This coding pattern guarantees that the withdrawer’s balance would be updated before sending Ethers back to the withdrawer, precluding the cross-function reentrancy attack.
Summary
In this article, you have learned the cross-function reentrancy vulnerability in a smart contract, how an attacker exploits the attack, and the preventive solution to fix the issue. We hope you find this article useful. See you again in the following article.
Again, you can find all related source code at 👉 https://github.com/serial-coder/solidity-security-by-example/tree/main/04_cross_function_reentrancy.
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.