Single-Function Reentrancy

Single-Function Reentrancy Vulnerabilities

Reentrancy vulnerabilities all come down to how Smart Contracts are structured and manipulate state. They can be easily mitigated with proper testing, best practices, or the use of OpenZeppelins reentrancy guard. These vulnerabilities and attacks are incredibly simple yet have led to massive loss of funds in the Web3 ecosystem. As of late, reentrancy attacks have become less common, or perhaps have changed shape, as smart contract engineers and auditors have become more keen to the topic. There are multiple types of reentrancy attacks or  vulnerabilities that include:

  • Single-Function Reentrancy

  • Cross-Function Reentrancy

  • Cross-Contract Reentrancy

  • Read-Only Reentrancy

For this post, we will just cover single-function reentrancy.


Single Function Reentrancy

A function is considered re-entrant when its execution can begin and then is interrupted, but called again, and both iterations of the function call will execute without any errors or reverts. The most infamous reentrancy attack might be The DAO attack in 2016 in which a hacker stole $150m from The DAO’s smart contract. 

The diagram above visually shows how the attack happened. It’s like an infinity loop that continuously was allowed to call the withdraw function. To do this the hacker created a malicious smart contract that took advantage of Solidity's fallback() function which can execute custom logic when a contract receives Ether. In this case, the hacker “legally” called the DAO withdrawBalance() function from the malicious smart contract. When this contract received Ether it set off the fallback() function, which included a second “illegal” call to the DAO’s withdrawBalance() function. At the point when this second call is made the state of The DAO’s attackers' balance has not been updated, it will still read as if no transactions have happened. Because of this the second “illegal” withdrawal call from the fallback() function was allowed to proceed and the hacker leaves with more Ether than they should have been allowed to. 

Mitigation & Testing

The DAO’s smart contract vulnerability arises because the state of the balance was updated after the withdrawBalance() was called and the Ether transferred. This violates a solidity best practice called the Checks - Effects - Interactions pattern (CEI). Following this pattern in solidity development requires you to write functions so that they first check that all preconditions are properly met, make the changes to the state of the contract (effects), and then proceed with any external interactions. If the malicious contract would have tried calling the withdraw function that had followed the CEI pattern by the time the call to transfer the ETH would have happened their balance would already be set to 0 and the transfer would revert or fail. 

Along with following the CEI pattern when writing smart contracts, OpenZeppelin has a ReentrancyGuard contract that can be imported and used in any smart contract which severely mitigates the potential for reentrancy attacks. When testing, always be aware of any external calls your smart contracts might be making. It is always a good idea to run a static analyzer like Slither to quickly check for any well-known vulnerabilities, although these tools are not foolproof and do produce false positives or negatives. 

Conclusion

In conclusion, the single-function reentrancy vulnerability is one of the most common vulnerabilities in smart contracts, yet easily preventable. The DAO attack serves as a reminder that it’s important for smart contract developers and auditors to remain vigilant when assessing code for potential security flaws. By implementing CEI flow patterns, OpenZeppelins Reentrancy Guard, and through testing developers can prevent reentrancy attacks and ensure their applications are secure against malicious actors.

Subscribe to Dumb Code
Receive the latest updates directly to your inbox.
Mint this entry as an NFT to add it to your collection.
Verification
This entry has been permanently stored onchain and signed by its creator.