Not your Type! Detecting Storage Collision Vulnerabilities in Ethereum Smart Contracts
Nicola Ruaro
Network and Distributed System Security (NDSS) Symposium 2024 · Day 2 · Blockchain & Smart Contracts · Blockchain & Smart Contracts
Overview
In the rapidly evolving landscape of decentralized finance (DeFi) and Ethereum smart contracts, the flexibility offered by features like contract upgradability comes with inherent security complexities. This talk, "Not your Type! Detecting Storage Collision Vulnerabilities in Ethereum Smart Contracts," presented by Nicola Ruaro, addresses a critical and often overlooked class of vulnerabilities: storage collisions. These issues arise when two smart contracts, particularly in the context of the widely used proxy pattern, share the same underlying storage but interpret the data within specific storage slots differently, either due to type mismatches or semantic misunderstandings. Such discrepancies can lead to severe consequences, including denial of service, privilege escalation, and direct theft of digital assets, as tragically exemplified by the $6 million AUDIUS platform attack in July 2022.

Key moments
- 0:00 Introduction: Storage collision vulnerabilities and DELEGATECALL
- 0:40 AUDIUS attack: $6M loss from type misinterpretation
- 1:10 CRUSH: Novel system for detecting and exploiting collisions
- 2:10 DELEGATECALL and shared storage mechanisms explained
- 2:50 Contract storage layout: Slots, packing, dynamic types
- 4:00 Proxy pattern: Upgradability and collision vulnerability origin
- 4:30 Detailed example: NFT marketplace storage collision exploit
Not your Type! Detecting Storage Collision Vulnerabilities in Ethereum Smart Contracts
Speakers: Nicola Ruaro
Conference: NDSS Symposium
YouTube: (no public video)
Overview
In the rapidly evolving landscape of decentralized finance (DeFi) and Ethereum smart contracts, the flexibility offered by features like contract upgradability comes with inherent security complexities. This talk, "Not your Type! Detecting Storage Collision Vulnerabilities in Ethereum Smart Contracts," presented by Nicola Ruaro, addresses a critical and often overlooked class of vulnerabilities: storage collisions. These issues arise when two smart contracts, particularly in the context of the widely used proxy pattern, share the same underlying storage but interpret the data within specific storage slots differently, either due to type mismatches or semantic misunderstandings. Such discrepancies can lead to severe consequences, including denial of service, privilege escalation, and direct theft of digital assets, as tragically exemplified by the $6 million AUDIUS platform attack in July 2022.
The presentation introduces CRUSH, a novel analysis system designed to automatically detect and exploit storage collision vulnerabilities at an unprecedented scale. Traditional detection methods, primarily compiler warnings, are often insufficient because they rely on specific development frameworks and, crucially, require the source code of all interacting contracts—a condition rarely met in the real-world Ethereum ecosystem. CRUSH overcomes these limitations by performing bytecode-based analysis, enabling it to scrutinize millions of deployed contracts without requiring source code.
CRUSH's contributions are multifaceted: it automates the identification of contract groups sharing storage through on-chain interaction analysis, leverages symbolic execution and program slicing for precise collision detection, and automatically synthesizes proof-of-concept exploits. The system's evaluation across over 14 million Ethereum smart contracts revealed 14,891 potentially vulnerable contracts and successfully generated end-to-end exploits for 956 of them. This groundbreaking research not only quantifies the significant threat posed by storage collisions but also uncovered over $6 million in novel, previously unreported potential financial damage, underscoring the urgent need for robust security analysis in the DeFi space.
Background
[▶ Watch: Introduction: Storage collision vulnerabilities and DELEGATECALL (0:00)]()
Smart contracts, typically authored in high-level languages like Solidity or Vyper, are compiled into EVM bytecode and deployed on the Ethereum blockchain. These programs execute on demand, managing persistent storage, transferring funds, and interacting with other contracts. Contract interactions can be initiated externally by an externally-owned account (EOA) or internally by another smart contract. Public functions serve as the primary entry points, with calls encoded in calldata, comprising a 4-byte function selector and arguments.
The Ethereum Virtual Machine (EVM) provides several opcodes for inter-contract communication. The CALL opcode is fundamental, allowing function invocation and ETH transfers. STATICCALL provides a view-only invocation, preventing any storage modifications. Central to storage collision vulnerabilities is the DELEGATECALL opcode. When a source contract (often termed a proxy) uses DELEGATECALL to invoke a destination contract (the logic contract), the destination contract executes its code in the context of the caller. This means the logic contract gains direct read and write access to the proxy contract's persistent storage and utilizes the proxy's ETH balance. This shared storage mechanism is the fundamental enabler for the proxy pattern and, concurrently, the root cause of storage collision vulnerabilities.
Contract storage itself is a critical component. All persistent variables of a smart contract reside in its storage, which is conceptualized as a vast array of 32-byte (256-bit) slots, each uniquely identified by an index. The storage layout is deterministically computed by the compiler based on the variable's declaration order.
- Fixed-size variable types (e.g.,
uint,address,bool) are stored contiguously. To optimize space, multiple variables smaller than 32 bytes can be packed into a single storage slot. For instance, abooland anaddressmight occupy the same slot, with theboolaligned to the least significant bytes. If a variable doesn't fit, it spills into the next slot. The EVM instructionsSLOADandSSTOREalways operate on a full 32-byte slot. When variables are packed, the compiler generates specific bit-masking procedures to extract or update the relevant portion of the slot. - Dynamic-size variable types (e.g., arrays, mappings) are always allocated a new BASE slot. For arrays, this BASE slot stores the array's length, and individual elements are stored at
keccak256(BASE) + INDEX. For mappings, the BASE slot is typically unused, serving primarily to calculate the starting point for elements, which are stored atkeccak256(KEY.BASE).
The proxy pattern is a widely adopted design paradigm for smart contract upgradability. It decouples the application's logic from its persistent state. Users interact with a proxy contract, which holds the contract's storage and then forwards all function calls to an immutable logic contract using DELEGATECALL. This allows developers to deploy new logic contracts to fix bugs or add features without migrating the existing state, as the new logic contract will seamlessly operate on the proxy's storage. However, this shared storage mechanism introduces storage collision vulnerabilities when the proxy and logic contracts, or different versions of logic contracts, interpret the variables within the same storage slot differently. If contract A writes to a slot expecting one type or semantic, and contract B subsequently reads that same slot expecting another, it can lead to corrupted state and critical security flaws.
Key Findings
[▶ Watch: CRUSH: Novel system for detecting and exploiting collisions (1:10)]()
CRUSH's large-scale analysis of over 14 million smart contracts on Ethereum yielded significant findings:
- Prevalence of DELEGATECALL Interactions: Out of 53,580,899 deployed smart contracts, 14,237,696 actively participated in
DELEGATECALLinteractions. This highlights the widespread adoption of proxy patterns and similar shared-storage mechanisms. - Identification of Proxy/Logic Components: CRUSH identified 14,134,133 proxy contracts and 103,833 logic contracts, with some contracts serving dual roles.
- Source Code Scarcity: A crucial justification for CRUSH's bytecode-based approach is that while 54% of the 14 million contracts have available source code on Etherscan, over 90% of proxy contracts with source code interact with logic contracts without available source. This demonstrates the limitations of source-code-only analysis tools.
- Contract Characteristics: Proxy contracts are typically small (median of 3 basic blocks), primarily serving as storage holders. Logic contracts are significantly larger (median of 147 basic blocks), containing the application's functionality.
- Logic Target Diversity: The majority (88%) of deployed proxy contracts use at least one constant target logic. A substantial portion also uses external source targets (39%) or storage slot targets (14%), demonstrating CRUSH's robustness across various targeting mechanisms.
- Improved Type Inference: CRUSH's symbolic execution-based type inference module correctly identified 87.3% (484,465) of variable types, outperforming Gigahorse's 76.9% (425,740) by accurately tracking casting operations and packed variables.
- Widespread Collisions: CRUSH uncovered 15,092 distinct contracts with potential storage collisions, leading to 46,403 collision candidates. Of these, 11% (4,877) were attributed to cascading collisions, where the introduction of a new variable shifts the layout of subsequent variables.
- Security-Relevant Vulnerabilities: After impact analysis, 39,895 collisions across 14,891 contracts were deemed security-relevant, with 98% affecting sensitive slots (e.g., access control, financial balances) and 2% affecting guarding slots (e.g., initialization flags).
- Successful Exploit Generation: CRUSH successfully generated end-to-end proof-of-concept exploits for 956 contracts. This included 7,759 sensitive collisions in 878 contracts and 424 guarding collisions in 143 contracts.
- Substantial Financial Impact: The system uncovered over $6 million in novel, previously unreported potential financial damage. Notable examples included attacks like cfa7 ($4.6M) and 295b ($1.1M). While the AUDIUS attack was previously reported, CRUSH confirmed its nature.
- Historical Vulnerabilities: 132 of the 956 exploits are still active, while 768 were exploitable in the past, indicating a persistent threat that has grown linearly since 2018, coinciding with the popularization of OpenZeppelin's proxy pattern.
- Superiority over Existing Tools: CRUSH significantly outperformed USCHUNT, a state-of-the-art collision detection tool. On a benchmark dataset, CRUSH achieved 98 true positives, 40 false positives, and 4 false negatives, compared to USCHUNT's 21 true positives, 22 false positives, and 81 false negatives. This highlights the advantage of CRUSH's bytecode-based, precise analysis over source-code-only, rule-based approaches.
Technical Deep Dive
[▶ Watch: DELEGATECALL and shared storage mechanisms explained (2:10)]()
CRUSH's architecture is a sophisticated, multi-stage analysis system built upon the Gigahorse framework, which provides a register-based Intermediate Representation (IR) from EVM bytecode, enabling powerful static analysis without source code. The system operates in three primary stages: Component Discovery, Collision Discovery, and Vulnerability Discovery.
1. Component Discovery
This initial stage focuses on identifying groups of contracts that interact via DELEGATECALL and share storage.
- Proxy Detection: CRUSH first analyzes on-chain transactions to identify
DELEGATECALLoperations. It extracts (source, destination) pairs from these calls and constructs a directed graph where nodes represent contracts and edges representDELEGATECALLinvocations. From this graph, CRUSH identifies COMPONENTS, which are groups of contracts sharing a common root node (the proxy) and its associated logic contracts. This process revealed 14,134,133 proxy contracts and 103,833 logic contracts among 14,237,696DELEGATECALL-participating contracts. A significant finding was that over 90% of proxy contracts with available source code interact with logic contracts without source code, validating the necessity of a bytecode-based approach. Proxy contracts were found to be typically small (median 3 basic blocks), serving primarily as storage containers, while logic contracts were much larger (median 147 basic blocks), containing the application logic.
- Lifespan Analysis: CRUSH determines the active lifespan for each proxy and its associated logic contracts. A proxy's lifespan is straightforward (creation to a reference block). For logic contracts, whose activity depends on how the proxy targets them, CRUSH uses lightweight static analysis and backward slicing to determine how the
DELEGATECALLtarget address is set. Logic targets are classified into three categories: - Constant: The target address is hardcoded, implying the logic contract is active for the proxy's entire lifespan.
- Read from storage slot: The target address is stored in a storage slot. CRUSH analyzes how this slot's value changes over time to infer the logic's active period.
- External source: The target address originates from external input (e.g., calldata). In this conservative case, the logic is considered active for the proxy's entire lifespan.
Approximately 88% of proxies utilize at least one constant target logic, demonstrating the prevalence of this pattern. Median lifespans were 5 months for proxies and 4 months for logic contracts, with upgraded contracts showing significantly longer proxy lifespans (21 months).
2. Collision Discovery
This stage identifies actual storage collisions within the discovered COMPONENTS.
- Type Inference: The core of collision detection is understanding the types associated with each storage slot accessed by
SLOADandSSTOREinstructions. This involves two sub-steps:
- Identifying the target storage slot: Backward slicing is used to retrieve instructions involved in computing the slot address. Symbolic execution then classifies the access pattern:
- Constant: Indicates fixed-size types (e.g.,
uint,address,bool). keccak256(slot)+index: Indicates a dynamic array type.keccak256(key.slot): Indicates a mapping type.- Unknown: If the pattern cannot be determined.
- Identifying variable types and access masks within the target slot: For dynamic types, they occupy a whole 32-byte slot. For fixed-size types, where multiple variables can be packed, CRUSH determines their precise access masks. For
SLOADinstructions, a forward slice of instructions operating on theSLOADresult is computed. By symbolizing the 32 bytes of the slot and executing the forward slice, CRUSH infers the exact bit-mask used to extract the relevant variable. For example, if a 32-byte value0xAABBCCDDis masked to extract0x0000BBCCviaRSHIFTandANDoperations, CRUSH infers an access mask of0x00ffff00. These masks are expressed asbytes_offset#nbytes. CRUSH's type inference significantly outperforms Gigahorse (87.3% vs. 76.9% accuracy) by precisely tracking casting operations missed by Gigahorse's rule-based approach.
- Collision Detection: After inferring storage layouts and access masks for all contracts within a COMPONENT, CRUSH performs a pairwise comparison. A storage collision candidate is reported as a 3-tuple (A, B, S) if contracts A and B use different access masks for the same byte within a shared storage slot S. While different data types are sufficient, different semantics for the same data type can also trigger issues. A common cause is cascading collisions, where adding a new state variable in an upgraded logic contract shifts the layout of all subsequent variables, creating multiple collisions with the original logic contract. CRUSH identified 46,403 collision candidates across 15,092 contracts, with 11% stemming from cascading collisions. Simple collisions on mapping BASE slots, which typically have no security impact, were ignored, but 428 more subtle mapping collisions due to displacement of the BASE slot were considered.
3. Vulnerability Discovery and Exploit Generation
This final stage assesses the security impact of detected collisions and synthesizes proof-of-concept exploits.
- Impact Analysis: CRUSH identifies collisions that are security-relevant. A contract B is vulnerable if a
WRITEoperation in contract A to slot S can be followed by aUSEoperation in contract B on the same slot S, with conflicting interpretations. CRUSH leverages existing techniques to identify sensitive slots (involved in access control, restricted writes, read-only values likecreatoraddress, dynamic array sizes) and guarding slots (protecting writes to sensitive slots, like aninitializedflag). Collisions not impacting these critical slots are discarded. This filtering reduced the 46,403 candidates to 39,895 security-relevant collisions across 14,891 contracts, with 98% affecting sensitive slots.
- Exploit Generation: CRUSH attempts to confirm if a
WRITE-USEpair can trigger a vulnerable state and then synthesize an exploit. - Attack Preparation: Since all interactions happen through the proxy, CRUSH generates transactions that reach the relevant code in the logic contract via the proxy's
DELEGATECALL. - WRITE Operation: Symbolic execution is used to find a feasible path to an
SSTOREinstruction in contract A that writes to the colliding slot S. If successful, a concrete transaction is crafted. If not, historical on-chain writes are inspected. - USE Operation: If S is a sensitive slot, symbolic execution finds an
SLOADinstruction in contract B that uses S. If S is a guarding slot, CRUSH attempts to write any sensitive slot that S guards. - Exploit Synthesis & Verification: The discovered
WRITEandUSEoperations are combined. Path constraints from symbolic execution are solved to obtain concrete calldata inputs. Finally, an EVM implementation is used to verify that the generated exploits are reproducible on-chain within the contract's lifespan. If all steps succeed, a verified storage collision vulnerability is reported. CRUSH successfully generated 956 end-to-end exploits, uncovering over $6 million in novel financial damage.
Demo / Proof of Concept
[▶ Watch: Proxy pattern: Upgradability and collision vulnerability origin (4:00)]()
The talk illustrated the criticality of storage collision vulnerabilities through several compelling examples, starting with a simplified version of the AUDIUS attack and further detailing two examples from the appendix: Denial of Service and Theft of Funds.
AUDIUS-Inspired Example (Privilege Escalation)
This example highlights how a type misinterpretation (uint to bool) can lead to privilege escalation.
- Setup: Alice deploys a Proxy contract (holding
visitsasuint) and a Logic contract (holdinginitializedasboolandadminasaddress). Critically,visits,initialized, andadminall reside in storage slot0x0. - Deployment & Initialization: The Proxy's constructor calls
Logic.initialize()viaDELEGATECALL. This setsinitialized = 1andadmin = ALICEin slot0x0. Immediately after, the Proxy constructor setsvisits = 0, overwriting the entire slot0x0and corrupting the values set byLogic.initialize(). - Attacker Action: An attacker (EVIL) then calls
initialize()on the Proxy contract. This call isDELEGATECALLed to the Logic contract. - Collision Trigger: The Logic contract reads slot
0x0to checkinitialized. Becausevisitswas set to0by the Proxy, the Logic contract interprets the value in slot0x0asinitialized = false(0), even though it was previously set totrueby its owninitializefunction. - Exploit: The
require(!initialized)check passes.Logic.initialize()proceeds, settinginitialized = 1and, crucially,admin = EVIL(the attacker's address). The attacker is now the administrator and can invoke functions likewithdraw()to steal funds.
Denial of Service (Appendix A)
This example, involving ChiProxy and ChiToken contracts, demonstrates how a storage collision can render a contract inoperable.
- Setup:
ChiProxydeclareslogic,wallet, andownervariables, withownerat storage slot0.ChiTokendeclares_balances,_allowances, andtotalMinted, with_balancesalso at slot0. The developers deployChiProxyand point itslogictoChiToken. - Collision: A collision occurs because
ChiProxy.owner(slot0) collides withChiToken._balances(also slot0). ThetotalMintedvariable inChiTokenis positioned such that its update overlaps with theowneraddress in the proxy. - Attack: An attacker calls the
mintfunction inChiToken(viaChiProxy). This function incrementstotalMinted. Due to the collision, this write operation corrupts theChiProxy.owneraddress by overwriting the relevant part of slot0. - Impact: Subsequent calls to functions like
withdrawETHorwithdrawTokeninChiProxy(which are protected by anonlyOwnermodifier) will fail because theowneraddress has been corrupted to an invalid or zero value. This leads to a denial of service, preventing legitimate owners from withdrawing funds or upgrading the contract.
Theft of Funds (Appendix B)
This example, involving NFTParentProxy and NFTParentImpl contracts, illustrates a direct theft of funds.
- Setup:
NFTParentProxyinheritsOwnable, which declares_ownerat storage slot0.NFTParentImplinheritsInitializable, which declares_initializedand_initializing(both Booleans) at storage slot0. - Deployment & Collision: During deployment,
NFTParentProxy's constructor callsNFTParentImpl.initialize()viaDELEGATECALL. This sets_initialized = 1and_initializing = 1in slot0. Immediately after,NFTParentProxy's constructor setsowner = msg.sender(the deployer's address) in slot0, overwriting the boolean values. A collision exists between_initialized/_initializing(Logic) andowner(Proxy) in slot0. - Attack: An attacker calls
NFTParentImpl.initialize()(viaNFTParentProxy).NFTParentImpl, operating on the proxy's storage, reads slot0to check_initialized. Due to the type collision, theowneraddress (a non-zero 32-byte value) is misinterpreted by the Logic contract's specific packing and masking as_initialized = false(0). - Exploit: The
require(!initialized)check passes. Theinitialize()function proceeds, settingadmin = msg.sender, which is now the attacker's address. The attacker gains administrative privileges and can call restricted methods likemintNFTto mint arbitrary NFTs, effectively stealing funds. The fix would involve modifying the implementation contract to move the boolean variables after a storage padding, but once exploited, the upgrade functionality is compromised, preventing remediation.
These examples vividly demonstrate the severe and diverse consequences of storage collision vulnerabilities and underscore the critical importance of tools like CRUSH for their detection and exploitation analysis.
Defensive Implications
[▶ Watch: Detailed example: NFT marketplace storage collision exploit (4:30)]()
The findings presented by CRUSH highlight a critical and often underestimated vulnerability class in the Ethereum ecosystem, particularly for contracts employing the proxy pattern and other DELEGATECALL-based shared storage mechanisms. Defenders—including smart contract developers, auditors, and platform operators—must adopt proactive strategies to mitigate these risks.
- Careful Storage Layout Design: The primary defense lies in meticulous design and management of storage layouts. Developers must ensure that proxy and logic contracts, as well as different versions of logic contracts, have compatible storage layouts. This means:
- Never reuse storage slots for different purposes. If a slot is used for an
addressin the proxy, it should not be used for aboolin the logic contract, even if packing seems to allow it. - Utilize storage padding. Explicitly reserve storage slots to prevent cascading collisions when new variables are introduced in upgraded logic contracts. For example, OpenZeppelin's UUPS (Universal Upgradeable Proxy Standard) and Transparent Proxy patterns are designed to manage storage layout robustly, often by placing proxy-specific variables at the beginning of storage and implementation-specific variables at higher, potentially padded slots. However, even with these patterns, developers must be diligent.
- Always append new state variables. When upgrading logic contracts, new state variables should only be appended to the existing storage layout to avoid shifting the positions of previously declared variables. This is crucial to prevent cascading collisions.
- Advanced Static Analysis and Auditing: Relying solely on compiler warnings is insufficient, as CRUSH's findings demonstrate (they often require full source code and specific frameworks). Developers and auditors should integrate advanced static analysis tools like CRUSH into their development and auditing pipelines. These tools, capable of bytecode-level analysis, can detect subtle type and semantic mismatches across shared storage without requiring source code for all components. Regular security audits must specifically look for storage collision vulnerabilities, paying close attention to
DELEGATECALLusage, storage packing, and potential layout discrepancies between interacting contracts.
- Post-Deployment Monitoring and Incident Response: Even with robust pre-deployment checks, vulnerabilities can emerge or be discovered. Continuous monitoring of contract interactions and on-chain state changes can help identify suspicious activity that might indicate a storage collision exploit. A well-defined incident response plan is crucial to react swiftly if a vulnerability is exploited, potentially involving emergency upgrades (if the upgrade mechanism itself is not compromised) or coordinated communication with users.
- Responsible Disclosure: As CRUSH demonstrates, many vulnerabilities may be previously unreported. Security researchers should follow responsible disclosure guidelines, reporting findings to affected developers and relevant cybersecurity agencies (like CISA) to enable remediation before public exploitation.
- Education and Best Practices: Emphasizing the risks of
DELEGATECALLand the proxy pattern's storage implications through developer education is vital. Promoting established, secure upgradeable contract patterns and discouraging custom, untested implementations can significantly reduce the attack surface. Developers should be aware of how the EVM handles storage packing and bit-masking, as well as the deterministic nature of storage slot allocation.
By integrating these defensive strategies, the DeFi ecosystem can enhance its resilience against storage collision vulnerabilities, protecting billions of dollars in digital assets and fostering greater trust in decentralized applications.
Key Takeaways
- Storage collisions are a critical, under-investigated vulnerability class in Ethereum smart contracts, particularly prevalent in the context of the
DELEGATECALLopcode and the proxy pattern. - CRUSH is a novel, bytecode-based analysis system capable of automatically detecting storage collision vulnerabilities at scale and synthesizing proof-of-concept exploits, overcoming limitations of source-code-only tools.
- The financial impact of these vulnerabilities is substantial, with CRUSH uncovering over $6 million in novel, previously unreported potential financial damage across 956 exploitable contracts.
- Type inference and precise storage layout analysis are crucial for identifying collisions, with CRUSH's symbolic execution-based approach significantly outperforming existing state-of-the-art tools like USCHUNT.
- Defenders must prioritize careful storage layout design in upgradeable contracts, utilize advanced static analysis tools, and implement robust auditing processes to prevent and mitigate storage collision risks.
- Cascading collisions and subtle mapping-related issues represent complex challenges that require sophisticated analysis to fully understand their security implications.
About the Speaker(s)
Nicola Ruaro is a researcher who presented the work "Not your Type! Detecting Storage Collision Vulnerabilities in Ethereum Smart Contracts" at the NDSS Symposium. The talk highlights their expertise in smart contract security, static analysis, symbolic execution, and the detection of complex vulnerabilities within the Ethereum ecosystem. Their work, focusing on bytecode analysis and large-scale vulnerability discovery, contributes significantly to understanding and mitigating risks in decentralized finance.
All talks from Network and Distributed System Security (NDSS) Symposium 2024