Skip to main content
Sigvex

Unprotected Ether Withdrawal Remediation

How to stop arbitrary callers from draining contract funds by adding access control and constraining withdrawal destinations.

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.

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 onlyOwner modifier but still accepting an arbitrary to address — 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 of call.

References