Home / Smart Contract Auditing / Critical Vulnerability Discovered in Solidity Code Generator: Memory Byte Array Element Deletion Bug Patched in Version 0.8.37

Critical Vulnerability Discovered in Solidity Code Generator: Memory Byte Array Element Deletion Bug Patched in Version 0.8.37

The Ethereum Foundation has released Solidity version 0.8.37, addressing a significant low-to-medium-severity security vulnerability within the compiler’s code generator. The defect, which predates the very first official release of the Solidity programming language, affects how memory byte arrays are handled when an individual element is cleared using the delete keyword. Under specific conditions, executing a deletion operation on a memory byte array or string element causes the compiler to overwrite a full 32-byte word instead of a single byte, leading to unintended memory corruption both within the array and potentially across adjacent memory allocations.

The security flaw was officially brought to light on August 14, 2026, by researcher Shaheen Fazim, who reported the unexpected compiler behavior through the Ethereum Foundation’s bug bounty program and supplied a functional reproducer. Following the disclosure, the Solidity development team investigated the scope of the issue, deployed a fix in version 0.8.37, and conducted extensive historical analysis to determine the potential impact on existing decentralized applications deployed across the Ethereum network.

Understanding the Technical Defect

To fully grasp the nature of the vulnerability, developers must understand how Solidity manages data structures in memory. Most data types and values in Solidity memory occupy neat, full 32-byte words. Dynamic byte arrays (bytes) and strings (string) serve as notable exceptions to this rule. In memory, a dynamic byte array consists of a 32-byte length header immediately followed by raw data packed consecutively without internal gaps or padding.

When a developer modifies an individual element within a memory byte array—such as writing b[i] = 0—the compiler correctly issues a single-byte store instruction (mstore8), ensuring that only the targeted byte is altered. However, when developers execute the equivalent delete operation via delete b[i], the compiler’s default code generation pipeline, known as the evmasm pipeline, handles the instruction through an entirely different code path. Instead of restricting the write operation to a single byte, the generated bytecode issues a full 32-byte zero write starting directly at the location of the specified element.

Because the write begins at the index of the deleted element rather than aligning with a standard 32-byte memory word boundary, the operation spills past its intended target. This results in the clearing of up to 31 subsequent bytes. Depending on the array’s configuration and the index chosen for deletion, this overflow can corrupt data residing further down inside the same array or overwrite whatever data happens to be allocated immediately following it in memory.

Crucially, the array’s length field remains entirely untouched during this process. Because the recorded length of the array does not change, any subsequent operations that rely on the array’s contents—such as computing cryptographic hashes via keccak256, performing ABI encoding, or returning data to an external caller—will quietly process the corrupted, zeroed-out byte values without triggering runtime reverts or compiler warnings.

Scope and Affected Configurations

The vulnerability affects all historical versions of Solidity up to and including version 0.8.36. The bug is strictly confined to the default evmasm compilation pipeline; projects utilizing the newer IR (Intermediate Representation) pipeline remain completely unaffected. Furthermore, the presence or absence of the compiler optimizer has no bearing on whether the bug manifests, as the optimizer preserves the flawed semantics emitted by the initial code generation phase.

The defect specifically targets element-level deletions on memory-resident structures. Whole-array deletions (delete b) utilize an entirely separate, correct code path and are unaffected. Similarly, byte arrays residing in storage handle deletions differently and remain safe, while calldata cannot be subjected to delete operations at all. Arrays whose elements occupy a full 32-byte word by default—such as bytes1[] memory—are also immune, because only packed formats like bytes and string cluster their elements tightly together.

While string variables cannot be deleted directly via standard index notation, developers frequently convert strings to bytes types to manipulate individual characters in place, such as writing delete bytes(s)[i]. Doing so triggers the identical underlying memory corruption bug. Fixed-size byte arrays like bytes32 are unaffected because indexing them yields an isolated copy of the byte rather than a direct reference to underlying memory.

Analyzing the Reach of Memory Corruption

The extent of the memory corruption depends heavily on two factors: the index of the deleted element and how the array was initially allocated in memory. Solidity memory is allocated sequentially by advancing a free memory pointer, meaning consecutive allocations sit flush against one another, separated only by padding bytes added to maintain word alignment.

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

Arrays created via explicit instantiation like new bytes(n) or initialized from literal data are typically followed by up to 31 zero bytes of padding, ensuring that the subsequent memory allocation starts cleanly on a 32-byte word boundary. The evmasm pipeline does not, however, add this protective padding to arrays generated by bytes.concat, string.concat, or family functions under abi.encode*. These unpadded arrays end abruptly where the next allocation begins, meaning that deleting any of their last 31 elements will instantly bleed into whatever data structure was allocated directly afterward.

For padded arrays, the safety buffer absorbs part of the rogue 32-byte write. Deleting elements near the beginning of a long array causes internal corruption by wiping out up to 31 following elements within the same array. Deleting elements near the very end of a padded array, however, can cause the write to push past the array’s boundary. What gets overwritten depends on whether the adjacent memory holds left-aligned values, which lose their most significant bytes, or right-aligned values like integers, addresses, or pointers, where leading bytes are systematically zeroed out.

Practical Demonstration of the Vulnerability

To illustrate how this memory corruption manifests in real-world code, security researchers provided a representative contract demonstrating both internal array corruption and overflow into adjacent variables:

contract C 
    struct S  uint256 x; 

    // Corruption inside the array.
    function clearElement() public pure returns (bytes memory) 
        bytes memory d = hex"0102030405";
        delete d[0];
        return d;
    

    // Corruption beyond the array.
    function clearLastElement() public pure returns (uint256) 
        bytes memory b = new bytes(32);
        S memory s = S(type(uint256).max);
        delete b[31];
        return s.x;
    

When compiled and executed using the standard evmasm pipeline, clearElement was intended to return hex"0002030405" by clearing only the initial byte. Instead, it returns hex"0000000000", as the 32-byte write wipes out all subsequent bytes in the compact five-byte array.

In the second function, clearLastElement, the 32-byte array occupies a full word without padding. Deleting the final element (b[31]) causes the write to bleed 31 bytes into the adjacent memory structure s, which holds a maximum 256-bit unsigned integer (2**256 - 1). Because integers store their most significant bytes first, the overflow zeroes out the top 31 bytes of s.x, leaving only the lowest byte intact and causing the function to erroneously return 255 instead of the maximum possible integer value. Replacing these delete statements with explicit assignments like d[0] = 0 yields the correct, expected behavior across all versions.

Historical Impact and Ecosystem Assessment

Because the bug fails silently without generating compiler warnings or triggering runtime reverts, identifying historical instances can be challenging. To measure the real-world impact of the vulnerability, the Solidity team performed a comprehensive scan of the Sourcify database, examining approximately 870,000 verified smart contract compilations for the exact source pattern capable of triggering the bug.

The audit revealed that the vulnerable code pattern appeared in only 17 verified compilations across the entire dataset, representing just two distinct functions. Further manual inspection of those functions confirmed that neither was actively compromised in a damaging way. In both instances, the bytes cleared inside the arrays were either immediately discarded or deleted anyway, while adjacent memory allocations were either protected by safe array length limits or resided in entirely unused memory space. Consequently, the Solidity team found no evidence of any deployed smart contracts actively suffering from exploitable damage due to this defect.

The Fix and Recommended Actions

The core fix implemented in Solidity version 0.8.37 aligns the compiler’s delete path with its existing assignment path. When clearing an element belonging to a packed byte array or string, the compiler now correctly emits a single-byte store (mstore8) rather than a full-word memory operation, ensuring that delete b[i] and b[i] = 0 execute identically across both compilation pipelines.

Developers and smart contract development teams maintaining codebases written in Solidity are strongly encouraged to update their compiler toolchains to version 0.8.37 or later. Projects relying on earlier compiler versions should carefully audit their codebase to verify whether memory byte arrays or strings undergo element-level deletions via the delete keyword. If upgrading immediately is not feasible, developers can refactor affected deletion statements into explicit zero-assignments, such as replacing delete b[i] with b[i] = 0, to bypass the flawed code generation path entirely.

Tagged:

Leave a Reply

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