Skip to main content
Sigvex

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:

  • call to user address: The target receives ETH and can execute arbitrary logic with the caller as msg.sender.
  • delegatecall to user address: The target executes in the caller’s storage context, enabling complete contract takeover (Parity wallet hack, $31M).
  • call with 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:

  1. Identify external call sites: Locates CALL, DELEGATECALL, CALLCODE, and STATICCALL instructions.
  2. 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.
  3. Whitelist check detection: Searches for comparison operations (EQ) against known-good addresses before the call site in the control-flow path.
  4. Authorization check detection: Identifies msg.sender comparisons that gate the call, reducing confidence when access control exists.
  5. Severity escalation: delegatecall to user input receives the highest confidence; call with value is next; plain call is lowest.

Limitations

False positives:

  • Proxy contracts using delegatecall to 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.

References