Unprotected Ether Withdrawal Remediation
Overview
Related Detector: Unprotected Ether Withdrawal
A withdrawal function that moves ether without checking who is calling — or that sends to a caller-supplied address — lets anyone drain the contract. The remediation has two parts: authorize the caller, and constrain the destination and amount to what the caller is actually entitled to.
Recommended Fix
Before (Vulnerable)
contract Bank {
function withdraw(address payable to, uint256 amount) external {
// No authorization, no accounting — any caller drains any amount to anywhere
to.transfer(amount);
}
}
After (Fixed — per-account accounting, pull pattern)
contract Bank {
mapping(address => uint256) public balances;
error InsufficientBalance();
function withdraw(uint256 amount) external {
if (balances[msg.sender] < amount) revert InsufficientBalance();
balances[msg.sender] -= amount; // effect before interaction
(bool ok, ) = msg.sender.call{value: amount}("");
require(ok, "Transfer failed");
}
}
Each caller can withdraw only their own credited balance, to their own address. For privileged sweeps (e.g. rescuing stuck tokens), gate the function with an ownership or role modifier instead.
Common Mistakes
- Adding an
onlyOwnermodifier but still accepting an arbitrarytoaddress — a compromised owner key then routes funds anywhere. - Updating the balance after the external call, reintroducing a reentrancy window. Keep Checks-Effects-Interactions.
- Using
transfer/send(2300 gas) and assuming it always succeeds; check the return value ofcall.