SCWE-046: Reentrancy Attacks
Stable Version v1.0
This content is in the version-(v1.0) and still under active development, so it is subject to change any time (e.g. structure, IDs, content, URLs, etc.).
Relationships¶
- CWE-367: Time-of-check Time-of-use (TOCTOU) Race Condition
https://cwe.mitre.org/data/definitions/367.html
Description¶
Reentrancy attacks occur when a contract allows untrusted external calls during execution without properly updating state variables or implementing protections. This enables attackers to repeatedly call functions and manipulate the contract’s state before execution completes. Common issues include:
- Making external calls before updating state variables.
- Lack of mechanisms to prevent repeated or recursive calls.
- Improper handling of external interactions.
Remediation¶
- Update state first: Modify critical state variables before any external calls.
- Implement reentrancy guards: Use tools like
nonReentrantmodifiers to block recursive calls.
Examples¶
Vulnerable Contract Example (Reentrancy)¶
pragma solidity ^0.8.0;
contract Vulnerable {
mapping(address => uint) public balances;
function withdraw(uint _amount) public {
require(balances[msg.sender] >= _amount);
(bool success, ) = msg.sender.call{value: _amount}(""); // Vulnerable: external call before state update (reentrancy)
require(success, "Transfer failed");
balances[msg.sender] -= _amount;
}
function deposit() public payable {
balances[msg.sender] += msg.value;
}
}
Fixed Contract Example¶
pragma solidity ^0.8.0;
import "@openzeppelin/contracts/security/ReentrancyGuard.sol"; // OZ 5.x: utils/ReentrancyGuard.sol
contract Secure is ReentrancyGuard {
mapping(address => uint) public balances;
function withdraw(uint _amount) public nonReentrant {
require(balances[msg.sender] >= _amount, "Insufficient balance");
balances[msg.sender] -= _amount;
(bool success, ) = msg.sender.call{value: _amount}("");
require(success, "Transfer failed");
}
function deposit() public payable {
balances[msg.sender] += msg.value;
}
}