Reentrancy Vulnerability: How to Identify, Exploit, and Prevent

In the world of smart contracts, reentrancy is considered one of the most dangerous vulnerabilities. This article will help you not only understand what a reentrancy attack is but also how to effectively prevent it. From basic techniques to advanced solutions, we will explore ways to protect your entire project.

How Reentrancy Works: Basic Attack Mechanism

To understand reentrancy, first, we need to grasp the basic concept: a smart contract can call another contract, and at that moment, the second contract can call back into the first while it is still executing.

Imagine you have two contracts: ContractA holds 10 Ether, and ContractB has sent 1 Ether into it. When ContractB calls the withdraw function, it checks if there is enough balance. If so, Ether is sent back to ContractB. At this point, if there are no proper protections, this is the vulnerability that an attacker can exploit.

In a typical reentrancy attack, the attacker needs two functions: attack() to initiate the attack, and fallback() to perform the re-call. The fallback function is a special function in Solidity — it has no name, no parameters, and is automatically called whenever Ether is sent to the contract without any data.

Step-by-Step Reentrancy Attack Process

Follow the attack process step by step. The attacker calls attack() from their contract. Inside this function, it calls withdraw() from ContractA.

When ContractA receives this call, it checks if ContractB’s balance is greater than 0. Since it has 1 Ether, the check passes. Then, ContractA sends 1 Ether back to ContractB, triggering its fallback function. At this moment, ContractA still has 9 Ether, but more importantly — the balance of ContractB in ContractA’s ledger has not yet been updated to zero.

This is the flaw: the fallback function calls withdraw() from ContractA again. ContractA checks the balance of ContractB — it’s still 1 Ether! Why? Because the line balance[msg.sender] = 0 has not yet been executed, as it comes after the Ether transfer.

This process repeats: call withdraw → check balance (still > 0) → send Ether → trigger fallback → call withdraw again… until all Ether in ContractA is drained.

Code Analysis: When Reentrancy Becomes Reality

EtherStore is a typical example of a vulnerable contract. It has deposit() to store balances and withdrawAll() to withdraw funds. The problem lies in how withdrawAll() is implemented: it checks conditions, sends Ether, then updates the balance.

The Attack contract exploits this vulnerability. In its constructor, the attacker passes the EtherStore’s address, allowing it to call its functions. The fallback() of the Attack contract is triggered whenever Ether is sent from EtherStore, and inside it, it continues calling withdrawAll() as long as there is Ether left. The attack() function starts the process by sending 1 Ether initially to EtherStore to pass the initial check.

As a result, the entire funds of EtherStore are drained in a single transaction.

Three Strategies to Protect Contracts from Reentrancy

To safeguard smart contracts, there are three levels of protection, from basic to comprehensive.

NoReentrant Pattern: Basic Protection Solution

The simplest method is to use the noReentrant() modifier. A modifier is a special type of function in Solidity that allows you to alter the behavior of other functions without rewriting them entirely.

The idea is straightforward: when a function is protected by noReentrant(), it locks the contract during execution. Any call attempting to re-enter this function will fail because the lock variable prevents it. Only after the function completes and unlocks can other calls succeed.

This solution is very effective for protecting a single function but does not handle more complex scenarios.

Check-Effect-Interaction Pattern: Preventing Multi-Function Reentrancy

The second, more powerful technique is applying the Check-Effect-Interaction pattern. Instead of just protecting one function, this pattern changes how you write your logic.

The core principle is: check conditions first (Check), update state immediately after (Effect), then interact with external contracts (Interaction). This prevents attackers from exploiting because, when they call back, the balance has already been updated to zero.

Instead of updating balance[msg.sender] = 0 after sending Ether, move this line before the transfer. Then, no matter how many times fallback() is called, the check will always fail because the balance is already zero.

This method protects the contract against reentrancy throughout, even with multiple withdrawal functions.

GlobalReentrancyGuard: Comprehensive Protection Across the Entire Project

For complex projects with multiple interacting contracts, a more comprehensive solution is needed: GlobalReentrancyGuard.

Instead of locking at the function level, this approach locks at the entire project level. You create a separate contract that stores a global lock variable, and all other contracts in the project reference it.

Imagine this scenario: an attacker calls a function in ScheduledTransfer. After passing checks, it sends Ether to AttackTransfer. The fallback() of AttackTransfer is triggered and tries to call ScheduledTransfer again. But because GlobalReentrancyGuard has locked the global state, this call is blocked immediately.

This method is especially useful for large projects with many contracts, where reentrancy could occur across different contracts.

Choosing the Right Technique for Your Project

The choice of strategy depends on your project’s complexity. If your contract has few interacting functions, noReentrant() is sufficient. If there are multiple withdrawal functions, the Check-Effect-Interaction pattern is a good choice. For large-scale projects with many contracts, GlobalReentrancyGuard provides comprehensive protection.

Whichever you choose, the key point is to understand how reentrancy works so you can recognize and proactively prevent it.

To stay updated daily on smart contract security, source code reviews, and the latest trends in Web3, follow specialized resources on Solidity safety.

View Original
This page may contain third-party content, which is provided for information purposes only (not representations/warranties) and should not be considered as an endorsement of its views by Gate, nor as financial or professional advice. See Disclaimer for details.
  • Reward
  • Comment
  • Repost
  • Share
Comment
Add a comment
Add a comment
No comments
  • Pin