In dynamic reentrancy, the reentrant attack doesn’t target a specific function directly but instead exploits more complex, user-controlled logic in the contract.
This type of reentrancy often arises when a contract allows external calls that influence its state unpredictably, based on dynamic or user-supplied data.
Game
What could happen if msg.sender sets target as a contract they control, with a fallback function that calls dynamicPayout again?
// SPDX-License-Identifier: MIT
// Open me in VSCode and really think before opening the hints!
// Add @audit tags wherever suspicious
// Go to the solidity docs to complete missing knowledge of what's happening here
// Solve by drafting a fix!
pragma solidity ^0.8.0;
contract DynamicReentrancyGame {
mapping(address => uint256) public balances;
function deposit() public payable {
balances[msg.sender] += msg.value;
}
function dynamicPayout(address target, uint256 amount) public {
require(balances[msg.sender] >= amount, "Insufficient balance");
balances[msg.sender] -= amount;
(bool success, ) = target.call{value: amount}("");
require(success, "Transfer failed");
}
}
Dynamic reentrancy often exploits user-controlled parameters that determine the target of an external call. Here, dynamicPayout allows the user to specify target.
Consider what happens if target is a contract that re-calls dynamicPayout.
Think about whether balances[msg.sender] is updated before the external call. If dynamicPayout is reentered, would the balance deduction prevent repeated calls, or is there a way to exploit this order?
function dynamicPayout(address target, uint256 amount) public {
require(balances[msg.sender] >= amount, "Insufficient balance");
// Fix: Set balance to zero before any external call to prevent reentrancy
balances[msg.sender] -= amount;
(bool success, ) = target.call{value: amount}("");
require(success, "Transfer failed");
}