19 Oct 2026, Mon

Critical Memory Corruption Vulnerability Discovered in Solidity Code Generator Affects Versions up to 0.8.37

A longstanding and subtle memory corruption vulnerability has been discovered in the Solidity programming language’s code generator, impacting all historical versions of the compiler up to and including version 0.8.36. The security flaw, which affects how memory-based byte arrays handle element deletions, was uncovered and responsibly disclosed through the Ethereum Foundation’s bug bounty program by developer Shaheen Fazim on August 14, 2026.

While the bug has been assigned a low-to-medium severity rating internally by the Solidity team due to its narrow conditions for exploitation and low real-world prevalence, it introduces a dangerous semantic mismatch. Specifically, when the delete operator is applied to an element within a bytes array located in memory, the underlying compiler-generated code erroneously writes 32 zero bytes starting precisely at that element. This overzealous clearing behavior wipes out not only the targeted byte but also up to 31 subsequent bytes. These overwritten bytes can either reside elsewhere within the same dynamic array or, when the deleted element is positioned near the end of the array, spill over entirely into adjacent memory allocations.

Intriguingly, the equivalent manual assignment operation—such as writing b[i] = 0—remains entirely unaffected by the defect, correctly targeting and clearing a single byte as intended. Compilers running under the IR (Intermediate Representation) pipeline are also completely immune to the issue, as the bug is exclusively localized to the default legacy evmasm code generation pipeline. Furthermore, the presence or absence of the Solidity optimizer does not influence whether the vulnerability manifests, because the code generation stage itself emits the faulty semantics, which the optimizer subsequently preserves without alteration.

Understanding the Scope and Affected Code Paths

The vulnerable code path has existed within the Solidity compiler infrastructure for an extended period, predating the very first official public release of the language. Because the vulnerability remained latent for years, developers maintaining smart contracts written across nearly the entire lifespan of Solidity needed to assess their codebases. However, the conditions required to actually trigger the vulnerability are remarkably specific.

For a smart contract to be considered vulnerable, multiple strict criteria must align. First, the compilation must utilize the default evmasm pipeline rather than the IR pipeline. Second, the operation must specifically involve applying the delete operator to an individual element of a dynamic byte array stored in memory. If a contract never utilizes delete on an element of a memory-based bytes or string value, it faces zero risk from this vulnerability.

The Solidity development team has clarified several critical nuances regarding how arrays and data types interact with this specific code path. For instance, executing delete b on an entire array functions entirely normally because it routes through a completely different code generation path. Similarly, byte arrays residing in persistent contract storage are handled by separate logic and remain unaffected, while the delete keyword cannot be applied to calldata arrays at all. Furthermore, arrays whose individual elements naturally occupy a full 32-byte word in memory—such as bytes1[] memory—are completely immune because their elements are never packed tightly together. Only types that pack their data elements back-to-back without word-boundary gaps, namely bytes and string, are susceptible.

While direct index-based deletion like delete s[i] is not natively permitted for string memory variables, developers frequently cast strings to bytes type wrappers to perform low-level manipulations. Executing delete bytes(s)[i] on these converted instances triggers the exact same underlying vulnerability, modifying the string’s internal memory representation in-place. Conversely, fixed-size byte arrays like bytes32 are unaffected because indexing them yields an isolated copy of a single byte rather than a mutable reference into the underlying memory buffer.

Technical Details and Compiler Internals

To fully understand why the vulnerability occurs, one must examine how Solidity manages memory allocation. In the Ethereum Virtual Machine (EVM), most values residing in memory naturally consume full 32-byte words. Dynamic bytes arrays represent a notable exception to this rule. A memory-based byte array consists of an initial 32-byte length descriptor followed immediately by its raw data payload, packed back-to-back without intervening padding gaps.

Solidity allocates memory sequentially by advancing a global free memory pointer, ensuring that consecutive allocations sit flush against one another. To maintain clean alignment boundaries, the compiler typically appends up to 31 zero bytes of padding at the end of allocations created via literals or expressions like new bytes(n). These are referred to as padded arrays. However, the legacy evmasm pipeline deliberately omits this trailing padding for the results of certain operations, including bytes.concat, string.concat, and various ABI encoding functions such as abi.encode, abi.encodePacked, and abi.encodeCall. Arrays lacking this padding sit directly against whatever memory allocation follows them, representing unpadded arrays.

Because byte array elements occupy single bytes rather than full words, storing a value to memory cannot rely on standard word-level writes. The compiler ordinarily handles this by issuing a single-byte store instruction (mstore8) whenever it is aware of the narrow element width. However, the core defect lies within the internal delete code path. While delete x is semantically intended to assign the default zero value of that type—making delete b[i] functionally identical to b[i] = 0—the legacy code generator failed to share the proper single-byte store implementation between the two operations.

Instead of issuing an mstore8 instruction, the generated code for delete b[i] executes a full 32-byte zero-write originating directly at the index of the deleted element. Because this write begins at an arbitrary byte offset rather than aligned to a 32-byte word boundary, it spills forward across multiple memory addresses. Because the array’s 32-byte length descriptor located at the header remains untouched, the array retains its original recorded length. Consequently, subsequent operations that read from the array—such as computing cryptographic hashes via keccak256, performing ABI encoding, or returning data to an external caller—silently process the corrupted byte stream.

Memory Byte Array Element Delete Clears Whole Word Bug | Solidity Programming Language

Analyzing Memory Corruption Inside and Beyond the Array

Depending on where the deleted element resides within the array, the 32-byte zero-write creates two distinct forms of memory corruption: internal array corruption and external allocation overflow.

Internal corruption occurs when the write stays entirely within the boundaries of the byte array itself. Any subsequent element following the deleted index—up to a maximum of 31 elements—is unconditionally overwritten with zeros. Unless the array natively contained all zeros past that specific index, this behavior alters the data contents of the array without requiring any external environmental factors.

External corruption manifests when the write reaches past the end of the array into whatever memory allocation immediately follows it. For padded arrays, the trailing padding absorbs a portion of the 32-byte write. Deletes targeting elements near the end of an array can breach the allocation boundary by a margin equal to 31 bytes minus the existing padding size. For instance, arrays whose lengths are exact multiples of 32 possess zero padding, meaning a deletion of any of the final 31 elements will overwrite adjacent memory.

The exact nature of what gets overwritten in adjacent memory depends heavily on the alignment and type of the subsequent allocation. Left-aligned values lose their most significant bytes, while right-aligned values—such as integer counters, contract addresses, and memory pointers—experience corruption in their leading bytes. Depending on the size of the write and the target data type, critical variables stored immediately after the array can be catastrophically altered or zeroed out entirely. For unpadded arrays generated by concatenation or encoding functions, deleting any of the final 31 elements will invariably breach the adjacent memory allocation regardless of the array’s overall length.

Real-World Demonstration and Assessment

To illustrate how these dual corruption pathways manifest in practice, security analysts constructed a demonstration contract leveraging adjacent memory declarations. When compiling and executing functions containing memory array element deletions under vulnerable compiler versions, the results diverge sharply from developer expectations.

In scenarios where an array occupies a single padded word, executing a delete operation on an early index clears out the designated element while simultaneously wiping out subsequent sibling elements in the same buffer. In scenarios involving adjacent variables—such as allocating a 32-byte array followed immediately by a memory structure containing a large integer—deleting the final element of the array writes 31 zero bytes directly into the most significant bytes of the adjacent integer variable. This transforms a maximum-value integer into a negligible value, demonstrating how silently destructive the compilation bug can be.

Despite the technical severity of the memory corruption flaw, practical risk assessment indicates that actual exploitation in deployed decentralized applications is exceptionally low. A comprehensive automated scan conducted across the Sourcify database, which encompasses approximately 870,000 verified smart contract compilations, searched specifically for the vulnerable source code pattern. The scan identified the pattern in a mere 17 compilations, containing just two distinct functions overall. Furthermore, deep inspection revealed that neither of those deployed functions was actually vulnerable in practice; in one instance, the corrupted bytes were subsequently discarded or overwritten, while in the other, no critical data structures resided in the memory space immediately following the array.

Resolution and Recommended Actions

The Solidity core development team acted swiftly to address the vulnerability, releasing version 0.8.37 to permanently patch the issue. The fix modifies the compiler’s internal code generation logic so that the delete path properly consults the element width, aligning its behavior with standard assignments. When clearing a packed byte-array element, the compiler now correctly emits a single-byte store instruction (mstore8) rather than a full 32-byte word write, ensuring complete behavioral parity between delete b[i] and b[i] = 0 across all compilation pipelines.

Developers maintaining active Ethereum and EVM-compatible smart contract projects are strongly advised to take immediate remediation steps. Projects utilizing Solidity versions up to 0.8.36 should upgrade their compiler toolchains to version 0.8.37 or later. Codebases that cannot immediately upgrade should perform thorough audits of their codebase to verify whether the delete operator is ever applied to individual elements of memory-based bytes or string variables, particularly when those arrays lack padding or sit adjacent to sensitive memory structures.

The Ethereum Foundation and the Solidity engineering team extended their formal appreciation to Shaheen Fazim for discovering the vulnerability, responsibly submitting the bug report through the official bug bounty program, and providing clear reproduction code that facilitated a rapid patch.

Leave a Reply

Your email address will not be published. Required fields are marked *