Reentrancy Attack explained

A reentrancy attack is a security vulnerability in smart contracts. It occurs when a function makes an external call to another untrusted contract before updating its state. If the untrusted contract makes a recursive call back to the original function before the state change is completed(the state change occurs after the external call.), it can potentially manipulate the state, drain funds, or cause other unintended behaviors.

How the Reentrancy Attack Works

A reentrancy attack exploits the sequential execution of operations in systems like Ethereum, where multiple operations can occur within a single transaction before the state changes are finalized.

This attack targets functions in smart contracts that change the contract’s state and perform critical actions, such as transferring assets. An attacker prepares a malicious contract designed for repeated calls and initiates the attack by calling the vulnerable function to transfer tokens. During the processing of this initial call, the attacker’s contract makes a reentrant call via an external contract call back into the same function before the critical state change is finalized. Since the critical state change (e.g., balance update) hasn't been updated yet, the attacker can re-enter and exploit the incomplete state, effectively double-spending the assets.

This process can be repeated multiple times within a single transaction, significantly amplifying the attack’s impact and causing severe damage to the contract’s integrity and funds.

In the example below, the withdraw function sends Ether to the caller before updating the `balances` mapping.

The vulnerability arises because the transfer of Ether ({value: balances[msg.sender]}("")) is performed before updating the balance (balances[msg.sender] = 0). This sequence allows a reentrancy attack, where an attacker can re-enter the withdraw function before the balance is updated, allowing them to withdraw multiple times.

Below is an `Attacker` contract that repeatedly calls the withdraw function within its fallback function. When{value: balances[msg.sender]}("") is executed, the attacker's fallback function is triggered. This fallback function makes another call to withdraw before the original withdraw function finishes execution and updates the balance.

Reentrancy Attack Steps

How to Prevent Reentrancy Attack?

Use Checks-Effects-Interactions Pattern

One effective way to prevent reentrancy attacks is to employ the "checks-effects-interactions" pattern in smart contract development. This pattern involves organizing the function logic into three distinct phases:

Checks: Validate all necessary conditions before proceeding with the function logic. This ensures that all prerequisites are met, preventing unintended behavior.

Effects: Update the contract’s state variables before making any external calls. This guarantees that the contract’s state reflects the intended changes before any external interactions occur.

Interactions: Interact with other contracts or perform external calls. Since the state has already been updated, any reentrant call will see the correct state, mitigating the risk of reentrancy.

With this pattern, developers can ensure that the contract is in the expected state before continuing with any operations that involve external interactions.

Use Function Modifiers that Prevent Reentrancy

Another method to protect against reentrancy attacks is to use function modifiers that prevent reentrancy. A reentrancy guard can be implemented using a simple boolean lock mechanism. This involves creating a modifier that sets a lock before the function execution begins and releases it after the function has completed.