Dangerous Delegatecall
Detects delegatecall patterns where user-controlled input can specify the target address or data, potentially redirecting execution to a malicious contract.
Dangerous Delegatecall
Overview
Remediation Guide: How to Fix Dangerous Delegatecall
The delegatecall detector identifies uses of DELEGATECALL where either the target address or the call data can be controlled by an external caller. DELEGATECALL executes code from another contract in the calling contract’s storage and execution context — if an attacker can point this call at a malicious contract, they can modify any storage slot, drain Ether, or take ownership of the calling contract.
Sigvex detects two patterns: (1) user-controlled target addresses (delegatecall(user_input, ...)) and (2) user-controlled calldata passed to a trusted target where the callee’s function dispatch can be exploited (delegatecall(fixed_impl, user_calldata)). The unsafe-delegatecall detector is a companion that specifically targets the Parity wallet class of bugs.
Why This Is an Issue
DELEGATECALL is fundamental to proxy patterns, but it becomes critical when the target is not fixed. The Parity multisig wallet hack (July 2017, $30M) exploited a public initWallet function that called a library via delegatecall — attackers called it to take ownership. The Parity freeze (November 2017, $155M permanently frozen) was a different but related bug: calling kill() on the unprotected library contract made all dependent wallets permanently non-functional.
Any contract that proxies delegatecall to an attacker-controlled address in the same transaction effectively hands over all its storage.
How to Resolve
// Before: Vulnerable — user controls target address
contract VulnerableDispatcher {
function dispatch(address target, bytes calldata data) external {
// CRITICAL: anyone can delegatecall to any address with the dispatcher's privileges
(bool success, ) = target.delegatecall(data);
require(success);
}
}
// After: Fixed — only whitelisted implementations
contract SecureDispatcher {
address public immutable implementation;
address public owner;
constructor(address impl) {
implementation = impl;
owner = msg.sender;
}
modifier onlyOwner() {
require(msg.sender == owner, "Not owner");
_;
}
// Only delegates to the fixed, audited implementation
fallback() external payable {
address impl = implementation;
assembly {
calldatacopy(0, 0, calldatasize())
let result := delegatecall(gas(), impl, 0, calldatasize(), 0, 0)
returndatacopy(0, 0, returndatasize())
switch result
case 0 { revert(0, returndatasize()) }
default { return(0, returndatasize()) }
}
}
}
Examples
Vulnerable Code
// Parity-style vulnerability: publicly accessible delegatecall
contract VulnerableWallet {
address public owner;
address public library;
// Anyone can call this and delegatecall to the library
// with arbitrary data — including "initWallet(attacker)"
function forward(bytes calldata _data) external {
require(library.delegatecall(_data));
}
}
Fixed Code
contract SecureWallet {
address public owner;
address public immutable library; // Immutable — cannot be changed
bool private initialized;
modifier onlyOwner() {
require(msg.sender == owner, "Not owner");
_;
}
// Only owner can forward calls to the fixed library
function forward(bytes calldata _data) external onlyOwner {
require(library.delegatecall(_data));
}
}
Sample Sigvex Output
{
"detector_id": "delegatecall",
"severity": "critical",
"confidence": 0.93,
"description": "DELEGATECALL in function forward() uses target address loaded from calldata (CALLDATALOAD at offset 0x04). An attacker can specify a malicious implementation to take control of this contract's storage.",
"location": { "function": "forward(bytes)", "offset": 48 }
}
Detection Methodology
- DELEGATECALL identification: Scans for
DELEGATECALLopcodes in each function. - Target taint analysis: Traces whether the target address operand of
DELEGATECALLoriginates fromCALLDATALOAD,CALLDATACOPY, or other user-controlled inputs. - Data taint analysis: Checks whether the calldata passed to a fixed target is user-controlled without a function selector whitelist.
- Storage slot analysis: Checks whether the target address is loaded from storage (potentially manipulable) vs. hardcoded in the bytecode (safe).
- Access control context: Reduces confidence if the function is gated by an owner check immediately before the
DELEGATECALL.
Limitations
False positives:
- Proxy contracts where the implementation address is set only by the owner during deployment, but loaded from storage at call time, may be flagged.
- Minimal proxy (EIP-1167) patterns load the implementation from storage but are architecturally safe — these may produce lower-confidence findings.
False negatives:
CALLCODE(deprecated but functionally similar) is covered separately.- Indirect delegation through a registry that maps selectors to implementations may not be traced.
Related Detectors
- Access Control — detects missing access controls on proxy admin functions
- Storage Collision — detects proxy storage layout collisions