Skip to main content
Sigvex

Cross-Contract Taint Flow

Detects tainted data flowing from untrusted external calls into security-critical operations like access control checks or transfer amounts.

Cross-Contract Taint Flow

Overview

The cross-contract taint detector tracks data flowing from external call return values into security-sensitive operations. Findings carry per-sink severities: a tainted delegatecall target is critical, tainted call targets and call values are high, and tainted calldata or untrusted return values used in less sensitive positions are medium. When a contract uses the return value of an untrusted external call as input to access control decisions, transfer amounts, or storage keys without validation, an attacker controlling the external contract can manipulate the calling contract’s behavior.

Why This Is an Issue

External calls return attacker-controlled data when the target is user-supplied or upgradeable. If this data flows into:

  • Access control: require(externalContract.isAdmin(msg.sender)) – attacker’s contract always returns true
  • Transfer amounts: token.transfer(user, oracle.getPrice()) – attacker’s oracle returns inflated prices
  • Storage indices: data[externalContract.getIndex()] – attacker controls which slot is read/written

The Poly Network attack ($611M, 2021) exploited cross-contract data flow where return values from a user-controlled contract were used in authentication decisions.

How to Resolve

// Before: Trusts return value from user-supplied contract
function withdraw(address oracle, uint256 id) external {
    uint256 amount = IOracle(oracle).valueOf(id); // Tainted
    token.transfer(msg.sender, amount); // Attacker controls amount
}

// After: Use allowlisted oracle, validate returned value
function withdraw(uint256 id) external {
    require(allowedOracles[oracle], "Untrusted oracle");
    uint256 amount = IOracle(trustedOracle).valueOf(id);
    require(amount <= maxWithdrawal, "Amount exceeds limit");
    token.transfer(msg.sender, amount);
}

Examples

Sample Sigvex Output

{
  "detector_id": "cross-contract-taint",
  "severity": "high",
  "confidence": 0.70,
  "description": "Return value from external call to user-supplied address at offset 0x5c flows into transfer amount at offset 0x8a without validation. Attacker can control the transferred amount.",
  "location": { "function": "withdraw(address,uint256)", "offset": 92 }
}

Detection Methodology

  1. Taint source identification: Marks return values from CALL/STATICCALL to non-constant addresses as tainted.
  2. Taint propagation: Tracks tainted values through arithmetic operations, memory copies, and variable assignments.
  3. Sink detection: Identifies security-sensitive sinks: CALL value parameters, SSTORE, conditional jumps guarding access control.
  4. Sanitizer recognition: Removes taint when values pass through validation (range checks, allowlist lookups).

Limitations

  • Inter-procedural taint analysis is bounded; taint flowing through multiple internal function calls may be lost.
  • Allowlisted contracts that are themselves upgradeable remain tainted but may not be flagged.
  • Assembly blocks that copy return data via RETURNDATACOPY may break taint tracking.

References