Skip to main content

Missing Re-entrancy Protection

Description

Re-entrancy is a vulnerability that occurs when a contract makes an external call to an untrusted contract, which then calls back into the original contract before the first call has completed. This can lead to unexpected behavior, including re-entrancy attacks where the attacker repeatedly re-enters the original contract, exploiting it to extract more funds or perform unauthorized actions.

One common way to prevent re-entrancy attacks is to use a mutex lock. A mutex lock is a simple mechanism that allows a contract to ensure that only one execution path can access critical code at any given time. By using a mutex lock, a contract can prevent any external call from interrupting its execution and ensure that all state changes are committed before any new external call can be made.

Example Code

Consider the following code, which allows users to withdraw funds from their account:

pragma solidity ^0.8.0;

contract MyContract {
mapping (address => uint256) balances;

function withdraw(uint256 amount) public {
require(amount <= balances[msg.sender], "Insufficient balance");

// send funds to user
(bool success,) = msg.sender.call{value: amount}("");
require(success, "Failed to send funds");

// update balance
balances[msg.sender] -= amount;
}
}

This code is vulnerable to re-entrancy attacks because the withdraw function sends funds to an external contract before updating the account balance. An attacker could exploit this vulnerability by creating a malicious contract that repeatedly calls the withdraw function, extracting more funds than the user is entitled to.

Recommendation

To prevent re-entrancy attacks, it is recommended to use a mutex lock to ensure that only one execution path can access critical code at any given time. One common pattern is to use the ReentrancyGuard contract from OpenZeppelin, which provides a nonReentrant modifier that can be applied to any function that requires protection.

pragma solidity ^0.8.0;

import "@openzeppelin/contracts/security/ReentrancyGuard.sol";

contract MyContract is ReentrancyGuard {
mapping (address => uint256) balances;

function withdraw(uint256 amount) public nonReentrant {
require(amount <= balances[msg.sender], "Insufficient balance");

// send funds to user
(bool success,) = msg.sender.call{value: amount}("");
require(success, "Failed to send funds");

// update balance
balances[msg.sender] -= amount;
}
}

By using the nonReentrant modifier, this code ensures that the withdraw function can only be called once at any given time, preventing any re-entrancy attacks.