19 Oct 2026, Mon

Critical Vulnerability Discovered in Solidity Code Generator: Memory Byte Array Element Deletion Bug Prompts Urgent Compiler Update

A newly discovered vulnerability in Solidity, the primary programming language for writing smart contracts on the Ethereum blockchain, has revealed a serious flaw in how the compiler handles memory byte arrays. Tracked and reported through the Ethereum Foundation’s bug bounty program by developer Shaheen Fazim on August 14, 2026, the defect causes the deletion of a single element within a memory-based bytes or string array to inadvertently clear an entire 32-byte word. This unintended mass-clearing behavior can wipe out neighboring elements within the same array or overwrite adjacent data allocated in memory, introducing potential risks to smart contract integrity.

The Ethereum Foundation’s Solidity development team has assigned the issue a severity rating of low-to-medium on its internal scale. The vulnerability is isolated exclusively to the default evmasm compilation pipeline, while the newer IR (Intermediate Representation) pipeline remains entirely unaffected. Furthermore, enabling the Solidity optimizer is not required to trigger the defect, meaning standard, unoptimized builds compiled with the default settings are equally susceptible.

According to the security advisory, the affected code path is deeply embedded in the compiler’s history, predating the very first official release version of Solidity. Consequently, all compiler versions up to and including version 0.8.36 are impacted. The vulnerability has been formally addressed and patched in Solidity version 0.8.37.

To gauge the real-world impact of the bug, the Solidity team conducted a comprehensive scan of the Sourcify database, which catalogs roughly 870,000 verified smart contract compilations. The analysis searched for the specific source pattern known to trigger the defect. The scan revealed that the vulnerable pattern appeared in a mere 17 compilations, comprising only two distinct functions. Upon closer inspection, neither of those functions was found to be actively harmed by the behavior; in both cases, the overwritten memory bytes were either subsequently discarded, deleted regardless, or positioned in unallocated regions where no active data resided. To date, no deployed production contracts are known to have suffered malicious exploitation or unintended data corruption as a result of the bug.

Which Contracts Are Affected?

Understanding exposure to the vulnerability requires evaluating specific structural and environmental conditions within a codebase. A contract is only susceptible to the issue if it meets a precise set of criteria during compilation and execution. First, the project must be compiled using an affected version of Solidity, spanning from the earliest iterations up to version 0.8.36. Second, the compilation must utilize the default evmasm code generation pipeline rather than the IR pipeline. Third, the source code must explicitly apply the delete operator to an element of a dynamic byte array or string that resides specifically in memory.

If a smart contract never applies the delete keyword to an individual element of a memory-based bytes or string variable, it is entirely immune to the defect. Developers must also note that neither the optimizer settings nor the target Ethereum Virtual Machine (EVM) version alter whether the bug manifests. The defect lies fundamentally within the instructions emitted by the code generation stage, and the optimizer preserves these faulty execution semantics.

Clarifying the exact operational scope of the vulnerability highlights several important nuances. Only element-level deletions are affected; invoking delete b on an entire array routes through a completely separate code path and functions correctly. Additionally, the array must reside in memory. Byte arrays stored in contract storage utilize an entirely different handling mechanism and remain unaffected, while the delete operator cannot be applied to calldata arrays at all. Arrays whose individual elements naturally occupy a full 32-byte word in memory, such as bytes1[] memory, are also safe. The problem is strictly confined to bytes and string types, which pack their individual elements tightly back-to-back without word-boundary gaps.

Although direct index deletion like delete s[i] is not a syntactically valid compilation for a string memory variable, developers frequently cast strings into bytes to perform granular manipulations, such as delete bytes(s)[i]. Doing so invokes the exact same underlying memory code path and triggers the vulnerability, directly modifying the underlying string data in place. Conversely, fixed-byte types like bytesNN are unaffected because indexing them returns a copied value rather than a mutable reference to the underlying data buffer.

Technical Details of the Defect

To understand why the bug occurs, it is necessary to examine how Solidity manages memory allocation. Most values in memory occupy full 32-byte words. Dynamic byte arrays are a notable exception, consisting of a 32-byte length header followed immediately by packed data bytes with no internal gaps. Solidity allocates memory sequentially by advancing a global free memory pointer, ensuring that consecutive allocations sit flush against each other.

In standard scenarios, arrays created via new bytes(n) or initialized from literal values are followed by up to 31 zero bytes of padding, ensuring that the subsequent memory allocation aligns cleanly on a 32-byte word boundary. These are categorized as padded arrays. However, the default evmasm pipeline does not pad the results of certain operations, including bytes.concat, string.concat, and the entire family of abi.encode functions. Data produced by these expressions ends precisely where the next allocation begins, creating unpadded arrays. Relying on padding behavior is discouraged, as memory layout rules explicitly forbid inline assembly from tampering with padding boundaries.

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

When a value narrower than a word is stored in memory, its alignment depends on its type. Left-aligned types like bytesN begin at the first byte of a word, while right-aligned types—including integers, addresses, memory pointers, and array length headers—sit at the far end of the word, padded with leading zeroes. Because byte array elements are single bytes, storing values into them deviates from standard full-word memory operations. The compiler ordinarily manages this via single-byte store instructions (mstore8) when it recognizes the element width.

The core of the bug resides in the implementation of the delete operator. Conceptually, delete x assigns the default zero value of that type, meaning delete b[i] should behave identically to an explicit assignment like b[i] = 0. Both operations ultimately conclude with a write operation targeting the element’s location. However, within the vulnerable evmasm pipeline, these two actions diverge sharply in how they write to memory. Standard assignments correctly reference the element width and execute a single-byte store (mstore8). In contrast, the delete path erroneously executes a full 32-byte zero-word write (mstore).

Because this massive zero-write begins directly at the target element’s memory offset rather than aligning to a 32-byte word boundary, it spills far past the intended single byte. It systematically overwrites up to 31 subsequent bytes. Because the array’s length header precedes the data block and remains untouched, the array retains its original length value. Consequently, any subsequent operations that read the array—such as computing cryptographic hashes via keccak256, performing ABI encoding, or returning data to an external caller—will process the corrupted byte stream. The IR pipeline, which utilizes a unified storage path for both assignments and deletions, does not suffer from this discrepancy and remains completely safe.

How the Overwrite Reaches Neighboring Data

The extent of the memory corruption depends heavily on the index of the deleted element and how the array was originally allocated in memory. When an element is deleted deep inside a padded array, the 32-byte zero-write clears the target byte along with up to 31 following elements within the same array. Unless the array happens to contain exclusively zero bytes beyond the deletion point, this instantly corrupts the array’s contents.

When the deleted element sits near the tail end of a padded array, the zero-write can punch completely past the array’s boundary and into whatever data structure was allocated immediately afterward in memory. For padded arrays, the built-in padding absorbs a portion of the write. Deleting elements in arrays with lengths that are multiples of 32 leaves no padding, meaning deleting any of the final 31 elements will spill over into subsequent memory allocations. For unpadded arrays, because there is no trailing padding buffer whatsoever, deleting any of the last 31 elements—or even the very first element in arrays smaller than 31 bytes—will immediately overwrite the beginning of the next memory allocation.

When an adjacent memory allocation is compromised, the nature of the corruption depends on the data type stored there. For left-aligned types, the most significant bytes are wiped out. For right-aligned types like numeric integers or addresses, the leading bytes of the word are zeroed, meaning the survival of the original value depends on the magnitude of the overwritten range and the size of the target variable. A minor spill might leave a standard Ethereum address intact, whereas a maximal 31-byte overwrite will reduce large numerical values down to negligible numbers.

Demonstrating the Bug Through Code

A simple smart contract serves to illustrate both forms of corruption. Consider a contract containing a helper function that declares a short memory byte array, deletes its first element, and returns the result. In an affected compiler version, invoking delete d[0] on a five-byte hex array intended to clear only the initial byte instead wipes out all subsequent bytes in the array, returning a string of zeroes rather than retaining the trailing data bytes.

A second demonstration involves a contract allocating a 32-byte padded array alongside a memory structure holding a maximum-value integer (type(uint256).max). When the contract executes a deletion on the final element of the byte array, the 32-byte zero-write spills over into the adjacent integer structure. Because Solidity integers are stored with their most significant bytes first, the overwrite destroys the upper 31 bytes of the integer while leaving the final single byte untouched. Instead of returning the expected maximum uint256 value, the function returns a truncated value of 255. Replacing these delete statements with explicit zero assignments (d[0] = 0 and b[31] = 0) yields the correct, expected behavior across all tested compiler versions.

Severity Assessment and Recommended Actions

The absence of compiler warnings and runtime revert exceptions makes this vulnerability particularly insidious, as corrupted state manifests as incorrect computational results rather than outright transaction failures. Nevertheless, internal analysis and broad ecosystem scans suggest that the real-world probability of accidental exploitation remains remarkably low. The flaw persisted undetected for the entire lifespan of the Solidity language, and database scans confirmed that the vulnerable code pattern is exceptionally rare in deployed codebases.

To mitigate any potential risk, developers currently utilizing any version of Solidity up to 0.8.37 are strongly advised to upgrade their toolchains to Solidity 0.8.37 or later. Codebases that cannot immediately upgrade should audit their internal smart contract logic to ensure that memory-based byte arrays and strings do not experience element-level deletions via the delete keyword, substituting explicit scalar assignments where necessary to maintain absolute computational reliability.

Leave a Reply

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