Home / Smart Contract Auditing / Critical Solidity Compiler Bug Discovered: Memory Byte Array Deletions Overwrite Surrounding Data

Critical Solidity Compiler Bug Discovered: Memory Byte Array Deletions Overwrite Surrounding Data

Introduction and Discovery

On August 14, 2026, security researcher Shaheen Fazim identified and responsibly reported a critical code generation bug in Solidity through the Ethereum Foundation’s bug bounty program. The vulnerability concerns how the Solidity compiler handles the delete operator when it is applied to specific elements of dynamic byte arrays residing in memory. Under the default compilation pipeline, executing a deletion on an individual element of a memory bytes or string array triggers a severe memory corruption issue. Instead of clearing just the single targeted byte, the generated EVM assembly code writes 32 zero bytes starting directly at the location of that element.

This unintended behavior cascades forward, wiping out up to 31 additional bytes that follow the deleted index. Depending on where the target element sits within the array, this massive zero-write can destroy adjacent elements within the same data structure or bleed past the boundaries of the array altogether, overwriting whatever independent data happens to be allocated immediately afterward in the Ethereum Virtual Machine’s memory. By contrast, the equivalent direct assignment operation—such as writing b[i] = 0—operates correctly and clears precisely one byte without collateral damage.

The Ethereum Foundation’s Solidity development team assigned this defect a severity rating of low-to-medium on their internal scale. This assessment stems largely from the specific conditions required to trigger the bug, the behavior of the default compilation pipeline versus the modern intermediate representation (IR) pipeline, and extensive historical scans showing minimal presence in actual deployed smart contracts. Nevertheless, because the bug compromises low-level memory integrity without throwing runtime errors or compiler warnings, it presents a subtle risk for affected smart contracts compiled with vulnerable toolchains.

Scope of Affected Versions and Pipelines

The underlying code path responsible for this erroneous memory handling predates the very first publicly released version of Solidity. Consequently, every compiler release up to and including version 0.8.36 is vulnerable to the defect. The issue was officially resolved and patched in Solidity version 0.8.37. Developers relying on any legacy compiler version within the 0.8.x branch—or earlier—must take note if their applications manipulate memory-based byte arrays using the delete keyword.

Crucially, the bug is strictly confined to the default evmasm compilation pipeline. The newer IR (Intermediate Representation) pipeline, which utilizes modern compilation strategies, is entirely unaffected by the flaw because it handles element deletion through a different code path that correctly respects element boundaries. Furthermore, enabling or disabling the Solidity optimizer has no bearing on whether the bug manifests. The defect exists intrinsically within the base code emission stage of the legacy compiler, and the optimizer faithfully preserves these faulty semantics during optimization passes.

To gauge the real-world impact of the discovery, the Solidity team conducted a comprehensive scan of the Sourcify database, analyzing roughly 870,000 verified smart contract compilations to search for the specific source code pattern capable of triggering the flaw. Out of nearly a million verified compilations, the pattern surfaced in a mere 17 instances, which collectively accounted for only two distinct functions across all scanned codebases. Further investigation revealed that neither of those two functions was actually vulnerable to malicious exploitation or unintended data corruption. In one instance, the corrupted bytes were subsequently discarded or overwritten by design, while in the other, the memory located immediately after the array was entirely unused at the time of execution. Consequently, the team found no evidence of any deployed smart contract in the wild being actively impacted by the bug.

Technical Details: Memory Layout and the Delete Path

To fully understand why the bug occurs, it is necessary to examine how Solidity manages memory allocation and data structures. In the EVM memory model, most primitive values and complex types occupy full 32-byte words. Dynamic byte arrays (bytes) and strings (string) represent notable exceptions to this rule. A byte array in memory consists of an initial 32-byte length field followed sequentially by its actual payload data, packed tightly back-to-back without internal word-alignment gaps between individual elements.

Solidity allocates memory dynamically by incrementing a global free memory pointer, meaning that consecutive memory allocations sit directly adjacent to one another, separated only by necessary word-padding. When an array is created using memory allocation mechanisms such as new bytes(n) or initialized from string and hex literals, the compiler typically appends up to 31 zero bytes of padding at the end of the data payload. This ensures that any subsequent memory allocation begins cleanly on a proper 32-byte word boundary. These are referred to as padded arrays.

Conversely, certain modern constructs do not include this trailing padding. The current evmasm pipeline leaves the outputs of bytes.concat, string.concat, and all family members of abi.encode functions completely unpadded. For these unpadded arrays, the underlying data payload ends precisely where the next memory allocation begins. Relying on the presence or absence of this padding in smart contract code is strongly discouraged, as internal compiler allocation behaviors can vary between pipelines and future software releases.

When a value in memory is narrower than a standard 32-byte word, its alignment within that word depends entirely on its specific data type. Left-aligned types, such as fixed-size bytesN variables, begin at the very first byte of the word. Right-aligned types, such as integers, addresses, memory pointers, and dynamic array length descriptors, sit flush against the right side of the word, preceded by leading zero bytes. Because byte array elements are strictly single-byte values, they represent the unique scenario where storing a value to memory is not equivalent to writing a full word. The compiler normally addresses this by issuing a single-byte store instruction (mstore8) whenever it is explicitly aware of the element width.

The core of the vulnerability lies within the specific code generation path dedicated to the delete operator. Conceptually, executing delete x should assign the default zero value corresponding to x‘s data type. Therefore, applying deletion to an array element via delete b[i] ought to be functionally identical to performing an explicit assignment like b[i] = 0. While both operations ultimately culminate in a write operation targeting the element’s memory address, the legacy evmasm pipeline routes them through completely separate internal mechanisms.

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

While assignments correctly utilize single-byte store routines, the legacy delete path incorrectly emits a full 32-byte word store instruction (mstore). Because this 32-byte write initiates at the specific byte offset of the targeted array element rather than aligning to a clean 32-byte word boundary, it immediately spills past its intended destination. The 32-byte zero-fill erases the target element along with the next 31 bytes of data. Because the 32-byte length descriptor located at the beginning of the array is untouched, the array retains its original length property, but downstream operations—such as calculating cryptographic hashes via keccak256, performing ABI encoding, or returning data to external callers—will read the corrupted and zeroed byte sequence.

Practical Manifestation and Code Examples

The consequences of this memory corruption manifest in two distinct ways depending on the position of the deleted element: internal array corruption and overflow corruption into subsequent memory allocations.

Consider a smart contract containing a function that allocates a short memory byte array, deletes its very first element, and returns the modified array:

contract C 
    function clearElement() public pure returns (bytes memory) 
        bytes memory d = hex"0102030405";
        delete d[0];
        return d;
    

In an affected version of Solidity using the default evmasm pipeline, executing clearElement() does not merely yield hex"0002030405" as expected. Instead, because the five-byte array occupies a single padded word, the 32-byte zero write initiates at index zero and clears the entire word, resulting in hex"0000000000". All subsequent elements within the same array are wiped out simultaneously.

A more insidious scenario involves corruption extending past the boundaries of the array into adjacent memory structures:

contract C 
    struct S  uint256 x; 

    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;
    

In this scenario, a 32-byte array is allocated directly adjacent to a memory struct containing a maximum-value 256-bit unsigned integer. Because the array length is a multiple of 32, it contains no trailing padding, placing struct s immediately flush against the end of array b. When delete b[31] is invoked, the compiler emits a 32-byte zero write starting at the final element of the array. Since the array has only 32 bytes of data, this write overflows past the array boundary, spilling 31 bytes deep into the memory occupied by struct s.

Because integers in memory are stored in big-endian format with their most significant bytes positioned first, overwriting the leading 31 bytes of s.x with zeros leaves only the single lowest byte intact. Consequently, rather than returning the expected maximum uint256 value, the function returns 255. If the overwritten memory happened to hold critical reference pointers or operational flags, the consequences could easily escalate into logical failures or unexpected contract states.

Recommended Actions and Mitigation

Because the bug produces erroneous calculations and corrupted data structures silently at runtime without triggering compiler warnings or transaction reverts, developers must verify their codebases against the vulnerability.

Projects utilizing legacy Solidity versions up to 0.8.36 should immediately audit their smart contract codebases to check if any logic applies the delete keyword to elements of memory-based bytes or string arrays. If such patterns are identified, developers have two primary paths for remediation. The most robust and permanent solution is to upgrade the Solidity compiler toolchain to version 0.8.37 or higher, which contains the official fix ensuring that element deletions correctly invoke single-byte store operations.

Alternatively, for codebases unable to undergo an immediate compiler version upgrade, developers can refactor their source code to avoid utilizing the delete operator on individual memory byte array elements. Replacing expressions such as delete b[i] with explicit assignments like b[i] = 0 successfully bypasses the flawed code path in the legacy pipeline, guaranteeing that only the single targeted byte is cleared without risking collateral memory corruption. Ensuring adherence to these practices safeguards smart contract memory integrity across all execution environments.

Tagged:

Leave a Reply

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