Arbitrary External Calls
Detects external calls to user-controlled addresses without validation, enabling attackers to redirect execution to malicious contracts.
Arbitrary External Calls
Overview
Remediation Guide: How to Fix Arbitrary External Calls
The arbitrary external calls detector identifies functions that execute CALL, DELEGATECALL, or CALLCODE to addresses derived from user input without validating the target against a whitelist. When a contract forwards execution to an attacker-chosen address, the target contract receives the calling contract’s context (including msg.sender, msg.value, and in the case of delegatecall, full storage access).
Sigvex performs taint analysis on the call target operand, tracing it back through the data-flow graph to determine whether the address originates from calldata, storage, or a hardcoded constant. Addresses sourced from calldata or untrusted storage without an intervening comparison against a known-good value are flagged.
Why This Is an Issue
Arbitrary external calls accounted for over $21M in losses across 18 incidents in 2024. The attack is straightforward: an attacker passes a malicious contract address as a function parameter, and the victim contract calls it without checking. The malicious contract then drains funds, manipulates state, or triggers reentrancy.
The three dangerous call types escalate in severity:
callto user address: The target receives ETH and can execute arbitrary logic with the caller asmsg.sender.delegatecallto user address: The target executes in the caller’s storage context, enabling complete contract takeover (Parity wallet hack, $31M).callwith user-controlled value: Enables direct ETH theft via forwarding.
How to Resolve
// Before: Vulnerable — call target from user input
function callExternal(address target, bytes calldata data) external {
(bool success, ) = target.call(data);
require(success);
}
// After: Fixed — whitelist validated targets
mapping(address => bool) public approvedTargets;
function callExternal(address target, bytes calldata data) external {
require(approvedTargets[target], "Target not approved");
(bool success, ) = target.call(data);
require(success);
}
Examples
Vulnerable Code
contract VulnerableForwarder {
// Anyone can call any contract as this contract
function execute(address target, bytes calldata payload) external payable {
(bool success, ) = target.call{value: msg.value}(payload);
require(success, "Call failed");
}
// Delegatecall to user address — complete takeover
function upgradeAndCall(address logic, bytes calldata data) external {
(bool success, ) = logic.delegatecall(data);
require(success, "Delegatecall failed");
}
}
Fixed Code
contract SecureForwarder {
address public immutable trustedTarget;
address public owner;
constructor(address _target) {
trustedTarget = _target;
owner = msg.sender;
}
function execute(bytes calldata payload) external payable {
// Target is immutable — cannot be user-controlled
(bool success, ) = trustedTarget.call{value: msg.value}(payload);
require(success, "Call failed");
}
}
Sample Sigvex Output
{
"detector_id": "arbitrary-external-calls",
"severity": "critical",
"confidence": 0.88,
"description": "Function execute(address,bytes) makes a CALL to a user-controlled address from calldata without whitelist validation. An attacker can redirect execution to a malicious contract.",
"location": { "function": "execute(address,bytes)", "offset": 96 }
}
Detection Methodology
Sigvex traces call target addresses through the data-flow graph to determine their origin:
- Identify external call sites: Locates
CALL,DELEGATECALL,CALLCODE, andSTATICCALLinstructions. - Taint analysis on target operand: Traces the address operand backward through assignments, stack operations, and memory loads to determine if it originates from
CALLDATALOAD(user input),SLOAD(storage), or a constant. - Whitelist check detection: Searches for comparison operations (
EQ) against known-good addresses before the call site in the control-flow path. - Authorization check detection: Identifies
msg.sendercomparisons that gate the call, reducing confidence when access control exists. - Severity escalation:
delegatecallto user input receives the highest confidence;callwith value is next; plaincallis lowest.
Limitations
False positives:
- Proxy contracts using
delegatecallto a storage-slot implementation address set by an admin may be flagged if the admin check is not on the same execution path. - Contracts that validate the target in a separate internal function (not inlined) may trigger false positives.
False negatives:
- Addresses computed through complex arithmetic or hashing may not be traced back to user input.
- Targets loaded from a mapping whose key is user-controlled but whose values are admin-set may be missed.
Related Detectors
- Delegatecall — detects dangerous delegatecall patterns broadly
- Unchecked Call — detects missing return value checks on external calls
- Access Control — detects missing caller authorization