Unchecked Call Target Remediation
Overview
Related Detector: Unchecked Call Target
A low-level call, delegatecall, or staticcall to an address the caller can influence is dangerous in two ways: the target may be a contract that behaves maliciously, and — with delegatecall — the target’s code runs in your storage context, so it can rewrite any state. The remediation is to constrain which targets are reachable and to handle the result explicitly.
Recommended Fix
Before (Vulnerable)
contract Router {
function execute(address target, bytes calldata data) external {
// Arbitrary delegatecall — target runs with this contract's storage and balance
(bool ok, ) = target.delegatecall(data);
require(ok);
}
}
After (Fixed — allowlisted target, checked result, no arbitrary delegatecall)
contract Router {
mapping(address => bool) public allowedTarget;
error TargetNotAllowed();
error EmptyTarget();
function execute(address target, bytes calldata data) external returns (bytes memory) {
if (!allowedTarget[target]) revert TargetNotAllowed();
if (target.code.length == 0) revert EmptyTarget(); // reject EOAs / self-destructed code
(bool ok, bytes memory ret) = target.call(data); // call, not delegatecall
require(ok, "Call failed");
return ret;
}
}
Prefer a fixed, immutable target or a small owner-managed allowlist. Reserve delegatecall for trusted, audited implementations (e.g. a proxy pointing at a known logic contract), never a caller-supplied address.
Common Mistakes
- Checking
okbut ignoring that a call to an address with no code returnstrue— validatecode.lengthwhen the target must be a contract. - Allowlisting the target but still forwarding attacker-controlled
datainto adelegatecall, which can call privileged functions on the current contract. - Treating
staticcallas safe input validation; it prevents state changes but not a hostile return value.