Solidity Security By Example #02: 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.
Reentrancy is one of the most famous attacks in the smart contract security field. The most famous example of reentrancy might be The DAO hack in 2016, causing the stealing of 3.6 million ETH. The incident resulted in the infamous hard fork of Ethereum to restore the seized assets.
In this article, you will learn how the 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/02_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 Vulnerability
The below presents the InsecureEtherVault
contract, a simple vault allowing users to deposit their Ethers, withdraw all their Ethers, and check their balances.
Of course, this contract is vulnerable to a reentrancy attack. But, can you discover the issue? 👀
|
|
A reentrancy is a programmatic approach in which an attacker performs recursive withdrawals to steal all Ethers locked in a contract.
In the case of InsecureEtherVault
contract, the reentrancy begins in line 14 in the withdrawAll
function. Figure 1 below illustrates how the reentrancy attack occurs.
As soon as the low-level function call
is executed, a number of Ethers indicated by the balance
variable would be sent to the user wallet or external contract (Step 4). If an attacker’s Attack
contract is the recipient, the contract can do the reentrancy by recursively calling the withdrawAll
function (Step 5) to drain out all Ethers locked in the InsecureEtherVault
contract. 😿
The attack is effective here because the call
function is executed before updating the withdrawer’s balance to 0 (i.e., userBalances[msg.sender] = 0
). Consequently, the Attack
contract can interrupt the control flow in the middle and execute the loop calls to the withdrawAll
function. Since the withdrawAll
function would still retain the balance before the update, the Attack
contract can steal all Ethers. OMG! 🙀
The Attack
The Attack
contract below can be used to exploit the InsecureEtherVault
contract.
|
|
To exploit the InsecureEtherVault
, an attacker invokes Attack.attack()
function and supplies 1 Ether to it. To understand how the Attack
contract works in more detail, please refer to the attack steps described in Figure 1 above.
The result of the attack is shown in Figure 2. To steal all 5 Ethers locked in the InsecureEtherVault
, the attacker deposited 1 Ether, withdrew their initial Ether, and performed five reentrants. 🤑
The Solutions
There are three preventive solutions to tackle the reentrancy attack. 👨🔧
-
Applying the checks-effects-interactions pattern
-
Applying the mutex lock
-
Using both solutions #1 and #2
|
|
The FixedEtherVault
contract above is the fixed version of the InsecureEtherVault
. We apply both solutions #1 (checks-effects-interactions pattern) and #2 (mutex lock) here.
For solution #1 (checks-effects-interactions pattern), the withdrawAll
function was improved to follow the checks-effects-interactions pattern by relocating the so-called effect part (userBalances[msg.sender] = 0
) to line 26 to execute it before the interaction part in line 28 (msg.sender.call{value: balance}("")
). This coding pattern guarantees that the withdrawer’s balance would be updated before sending Ethers back to the withdrawer.
For solution #2 (mutex lock), we attached the noReentrant
modifier to the withdrawAll
function in line 21. 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.
Summary
In this article, you have learned the reentrancy vulnerability in the smart contract, how an attacker exploits the vulnerable contract, and the preventive solutions to fix the issue. We hope you enjoy reading our article. See you in the next article.
Again, you can find all related source code at 👉 https://github.com/serial-coder/solidity-security-by-example/tree/main/02_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.