Storage Collision
Detects proxy storage layout collisions where the implementation contract's variables overlap with the proxy contract's administrative slots, enabling state corruption and privilege escalation.
Storage Collision
Overview
Remediation Guide: How to Fix Storage Collision
The storage collision detector identifies proxy contract patterns where the proxy stores administrative data (implementation address, admin address) in sequential storage slots starting from slot 0, while the implementation contract also uses slot 0 for its own state variables. Because DELEGATECALL executes implementation code in the proxy’s storage context, any write to slot 0 inside an implementation function overwrites the proxy’s implementation pointer — allowing an attacker to redirect all future calls to a contract they control.
Sigvex detects this by analyzing both the proxy and implementation contracts’ storage layouts (via SLOAD/SSTORE pattern analysis) and checking for slot overlaps between the proxy’s administrative variables and the implementation’s sequential variable layout.
Why This Is an Issue
Storage collision in proxy patterns is one of the highest-impact vulnerability classes because it leads to complete contract takeover. An attacker does not need to find a bug in the business logic — they simply need to call any implementation function that writes to slot 0 (e.g., setTotalSupply, initialize, or any state update) while operating through the proxy. This overwrites the implementation address with an arbitrary value, redirecting all future delegated calls to a malicious contract.
The attack is particularly severe because it is irreversible without a full redeployment: once the implementation pointer is corrupted, legitimate administrators may be unable to recover control.
The Parity Wallet Library Hack (July 2017, $30M) was caused by a collision between a wallet’s proxy storage and the library’s initialization function. An attacker called initWallet on the shared library directly, became the owner, and called kill — permanently selfdestructing the library and bricking all wallets that delegated to it.
How to Resolve
Use EIP-1967 unstructured storage slots for all proxy administrative data. These slots are derived from a hash and placed at a position in storage that is effectively unreachable by sequential variable layout.
// Before: Vulnerable — sequential slots collide with implementation
contract NaiveProxy {
address public implementation; // slot 0 — collides with implementation's slot 0!
address public admin; // slot 1 — collides with implementation's slot 1!
fallback() external payable {
(bool success, ) = implementation.delegatecall(msg.data);
require(success);
}
}
// After: Fixed with EIP-1967 unstructured storage
contract ERC1967Proxy {
// Slot: keccak256('eip1967.proxy.implementation') - 1
bytes32 private constant _IMPLEMENTATION_SLOT =
0x360894a13ba1a3210667c828492db98dca3e2076cc3735a920a3ca505d382bbc;
function _getImplementation() internal view returns (address impl) {
assembly { impl := sload(_IMPLEMENTATION_SLOT) }
}
function _setImplementation(address newImpl) internal {
assembly { sstore(_IMPLEMENTATION_SLOT, newImpl) }
}
}
Examples
Vulnerable Code
// Naive proxy — slot 0 collision
contract NaiveProxy {
address public implementation; // slot 0
address public admin; // slot 1
fallback() external payable {
// When implementation.setTotalSupply(attacker) executes via delegatecall,
// it writes attacker's address to slot 0 — overwriting implementation pointer
(bool success, ) = implementation.delegatecall(msg.data);
require(success);
}
}
contract VulnerableImplementation {
uint256 public totalSupply; // slot 0 — COLLIDES with NaiveProxy.implementation!
address public owner; // slot 1 — COLLIDES with NaiveProxy.admin!
function setTotalSupply(uint256 amount) external {
totalSupply = amount; // Overwrites proxy.implementation address!
}
}
Fixed Code
// Option 1: OpenZeppelin Transparent Proxy (EIP-1967)
import "@openzeppelin/contracts/proxy/transparent/TransparentUpgradeableProxy.sol";
TransparentUpgradeableProxy proxy = new TransparentUpgradeableProxy(
address(implementation),
address(proxyAdmin),
initData
);
// Option 2: UUPS (Universal Upgradeable Proxy Standard)
import "@openzeppelin/contracts-upgradeable/proxy/utils/UUPSUpgradeable.sol";
contract MyImplementation is UUPSUpgradeable {
// Uses EIP-1967 slots — no collision with business logic variables
function _authorizeUpgrade(address newImplementation) internal override onlyOwner {}
}
Sample Sigvex Output
{
"detector_id": "storage-collision",
"severity": "critical",
"confidence": 0.92,
"description": "Proxy contract stores implementation address at storage slot 0x00. Implementation contract has state variable at slot 0x00 (totalSupply). Any DELEGATECALL write to slot 0x00 corrupts the proxy's implementation pointer.",
"location": {
"function": "setTotalSupply(uint256)",
"offset": 44
}
}
Detection Methodology
Sigvex identifies storage collisions through joint analysis of proxy and implementation contracts:
- Proxy identification: Detects proxy patterns by identifying contracts with
DELEGATECALLin the fallback function and an implementation address stored in a known slot. - Administrative slot extraction: Extracts the storage slots used by the proxy for its administrative variables (implementation address, admin address, beacon address).
- Implementation slot mapping: Analyzes the implementation contract’s storage layout by tracking SSTORE instructions and the sequential slot assignments for state variables.
- Collision detection: Computes the intersection of proxy administrative slots and implementation state variable slots. Any overlap is a collision.
- EIP-1967 check: Verifies that administrative slots use EIP-1967 pseudo-random positions rather than sequential slots.
Confidence is High when both contracts are available for analysis and the collision is concrete. Confidence is Medium when only one contract is available and the collision is inferred from slot zero usage.
Limitations
False positives:
- Implementations that intentionally use slot 0 for a variable that is never written through the proxy (e.g., a view-only variable) may be flagged even if no collision path exists.
False negatives:
- Diamond proxy (EIP-2535) storage collisions between facets are a separate pattern handled by a dedicated
diamond-collisiondetector. - Storage collisions introduced by Solidity inheritance ordering (where base contracts shift slot assignments unexpectedly) require inheritance-aware analysis.
Related Detectors
- Delegatecall — detects user-controlled delegatecall targets
- Access Control — detects missing authorization on proxy upgrade functions