Opcode Summary
| Property | Value |
|---|---|
| Opcode | 0x52 |
| Mnemonic | MSTORE |
| Gas | 3 (static) + memory expansion cost |
| Stack Input | offset (byte offset into memory), value (32-byte word to write) |
| Stack Output | (none) |
| Behavior | Writes a 32-byte (256-bit) word to memory at the given byte offset. If the write extends beyond the current memory size, memory is expanded in 32-byte word increments to accommodate it. The expansion cost is quadratic: Gmemory = 3 * words + words² / 512. Memory is zero-initialized on expansion and persists only within the current execution context (call frame). |
Threat Surface
MSTORE is the EVM’s primary memory write primitive. Every Solidity contract uses it extensively — for ABI encoding/decoding, hashing inputs to KECCAK256, preparing return data, and managing the free memory pointer. Because the EVM’s memory model is a flat, unprotected byte array with no access controls, segmentation, or bounds checking, MSTORE is the mechanism through which memory corruption occurs.
The threat surface centers on four properties:
-
Memory expansion has quadratic gas cost. An attacker who controls the
offsetargument to MSTORE can force the contract to allocate an enormous memory region in a single instruction. Because expansion cost grows quadratically (3n + n²/512where n is the number of 32-byte words), a write to offset0xFFFFFF(~16 MB) costs millions of gas. An attacker-controlled offset can turn a single MSTORE into a gas bomb that exhausts the block gas limit, causing the transaction to revert or consuming the caller’s entire gas allowance. -
Solidity’s memory layout is convention, not enforcement. Solidity reserves offsets
0x00-0x3Fas scratch space,0x40-0x5Ffor the free memory pointer, and0x60-0x7Fas the zero slot. Allocations start at0x80and grow upward. These conventions exist purely in the compiler’s codegen — the EVM itself does not enforce them. Any inline assemblymstoreto offset0x40overwrites the free memory pointer, causing all subsequent Solidity memory allocations to corrupt or be corrupted. Similarly, writing to scratch space (0x00-0x3F) while Solidity is using it for intermediate computations produces silent data corruption. -
No memory isolation between operations. Within a single call frame, all code shares the same flat memory space. There are no guards between data written by different parts of a function. An MSTORE that writes 32 bytes starting at offset N will silently overwrite whatever was at offsets N through N+31, including previously allocated structs, arrays, or ABI-encoded calldata. This is especially dangerous in assembly blocks that compute offsets from untrusted inputs.
-
MSTORE is the vector for calldata/returndata injection. Before making an external call, contracts use MSTORE to build calldata in memory. If an attacker can influence the offset or value written, they can corrupt the calldata, changing function selectors, target addresses, or token amounts. The 1inch Fusion v1 exploit ($5M, March 2025) demonstrated exactly this: an integer underflow in an MSTORE offset caused the legitimate resolver address to be overwritten in memory, redirecting funds to the attacker.
Smart Contract Threats
T1: Free Memory Pointer Corruption via Assembly MSTORE (Critical)
The free memory pointer at offset 0x40 is the cornerstone of Solidity’s memory allocator. It stores the address of the next available memory byte. When inline assembly writes to 0x40 without properly saving and restoring the original value, all subsequent high-level Solidity memory operations (ABI encoding, struct allocation, dynamic array creation, string concatenation) allocate memory from the corrupted pointer location.
Attack vectors:
-
Pointer set too low. If assembly sets
mstore(0x40, X)where X is less than the current pointer value, new allocations overwrite existing data. Abytes memoryarray encoded for an external call might overwrite a previously allocated return buffer, corrupting return values or calldata for subsequent calls. -
Pointer set to attacker-controlled value. If the value written to
0x40is derived from user input (e.g., calldata offset, array length), an attacker can aim the pointer at any memory region. Subsequent Solidity allocations then write into that region, potentially overwriting function arguments, hash preimages, or ABI-encoded parameters. -
Pointer not restored after temporary override. A common pattern in optimized assembly is to temporarily reset the free memory pointer for scratch operations, then restore it. If an early
revertor conditional branch skips the restoration, the pointer remains corrupted for the remainder of execution.
Why it matters: This is the most common memory corruption vulnerability in audited codebases. The Solidity GitHub issue tracker (ethereum/solidity#15391) documents that writing to 0x40 causes dirty reads and undefined behavior across code generation pipelines.
T2: Memory Expansion Gas Griefing (High)
An attacker who controls the offset argument to MSTORE can force extreme memory expansion. The quadratic cost formula means:
| Offset | Memory Words | Gas Cost |
|---|---|---|
| 0x100 (256 B) | 8 | ~24 |
| 0x10000 (64 KB) | 2,048 | ~14,336 |
| 0x100000 (1 MB) | 32,768 | ~2,194,432 |
| 0xFFFFFF (~16 MB) | 524,288 | ~537,919,488 |
Attack vectors:
-
Unbounded offset from calldata. If a function takes a memory offset as a parameter (common in low-level router and aggregator contracts) and does not cap it, the caller can pass a massive offset to drain the transaction’s gas.
-
Length-derived offsets. When the offset is computed as
base + length * 0x20andlengthcomes from calldata, an attacker provides a huge length. Even if the data loop is bounded, the single MSTORE that initializes the length field at the computed offset triggers the full expansion cost. -
Gas griefing in relayer/meta-tx patterns. In systems where a relayer pays gas on behalf of a user (ERC-2771, EIP-4337), the user crafts a transaction that intentionally triggers excessive memory expansion. The relayer’s gas is consumed, the inner transaction reverts, and the user pays nothing.
Why it matters: Memory expansion is the primary mechanism for gas griefing in the EVM because the cost is quadratic and a single MSTORE can trigger it.
T3: Silent Overwriting of Existing Memory Data (High)
MSTORE writes 32 bytes unconditionally. There is no check that the target memory region is “free” or “unallocated.” When inline assembly writes to an offset that overlaps with data previously written by Solidity, that data is silently destroyed.
Attack vectors:
-
Overlapping ABI-encoded parameters. A function builds calldata for an external call in memory, then an assembly block writes to an offset that overlaps the calldata. The external call goes through with corrupted arguments — potentially a different function selector, a different recipient address, or a different token amount.
-
Hash preimage corruption. Solidity’s
keccak256(abi.encodePacked(...))writes the preimage to memory before hashing. If an intervening MSTORE (from assembly or a compiler bug) modifies bytes within the preimage region, the hash is computed over corrupted data. This can break mapping lookups, signature verification, or Merkle proof validation. -
Return data corruption. If a function stores its return value in memory and a later MSTORE overwrites that region (e.g., during cleanup or event emission), the caller receives corrupted data. This is particularly insidious because the function appears to succeed.
Why it matters: Unlike storage corruption, memory corruption is transient and leaves no on-chain trace, making it extremely difficult to detect in post-mortem analysis.
T4: Assembly Memory Writes Violating Solidity Conventions (Medium)
Inline assembly (assembly { ... }) gives developers raw access to MSTORE, bypassing all of Solidity’s memory management. This creates a class of vulnerabilities where assembly code and Solidity code have incompatible assumptions about memory layout:
-
Writing below the free memory pointer. Assembly that writes to a hardcoded offset (e.g.,
mstore(0x00, value)in scratch space) is safe only if no high-level Solidity code uses that memory concurrently. But Solidity uses scratch space (0x00-0x3F) for intermediate hash computations and ABI encoding. If assembly writes to scratch space and then Solidity code reads it expecting its own data, the result is corrupted. -
Allocating memory without updating the free memory pointer. Assembly that reserves memory by manually computing offsets but does not advance
mload(0x40)creates a situation where the next Solidity allocation overlaps the assembly’s data. -
Solidity optimizer removing assembly MSTORE. In Solidity versions 0.8.13-0.8.14, the Yul optimizer could incorrectly remove MSTORE instructions in inline assembly blocks that did not reference surrounding Solidity variables, treating them as dead stores. This was a compiler-level bug (fixed in 0.8.15) where valid memory writes were silently dropped, causing subsequent memory reads to return zero instead of the expected value.
Why it matters: Inline assembly is used extensively in gas-optimized DeFi protocols, token standards, and aggregator contracts. The tension between assembly’s raw memory access and Solidity’s managed allocator is a recurring source of audit findings.
T5: Hash Collision via Memory Layout Manipulation (Medium)
MSTORE is used to build preimages for KECCAK256 hashing. If an attacker can influence the memory layout of a hash preimage, they can engineer hash collisions or force specific hash outputs:
-
ABI encoding ambiguity.
abi.encodePackeddoes not length-prefix dynamic types, soabi.encodePacked(a, b)whereaandbarebytescan produce identical packed encodings for different inputs (e.g.,a="abc", b="def"vsa="ab", b="cdef"). The MSTORE instructions building these preimages produce identical memory contents, yielding the same KECCAK256 hash. -
Storage slot collision engineering. Solidity uses
keccak256(abi.encode(key, slot))to compute storage locations for mappings. If assembly code manipulates the memory used to build this preimage, it can redirect storage reads/writes to different slots, potentially aliasing unrelated state variables. -
Dirty memory in hash preimages. If memory is not properly zeroed before building a hash preimage, leftover bytes from previous operations become part of the preimage. An attacker who controls what data previously occupied that memory can influence the hash output.
Why it matters: Hash-based data structures (mappings, Merkle trees, EIP-712 typed data) are foundational to smart contract security. Memory-level manipulation of hash preimages can undermine their integrity.
Protocol-Level Threats
P1: Quadratic Memory Expansion as Block-Level DoS (Medium)
At the protocol level, memory expansion is bounded by the block gas limit (currently 30M gas on Ethereum mainnet). A single transaction can allocate at most ~4 MB of memory before exceeding the gas limit. While this prevents unbounded memory allocation, it means:
- A malicious validator/sequencer can include transactions that fill memory to the maximum, consuming disproportionate block resources.
- On L2s with higher gas limits (Arbitrum, Optimism), the effective memory ceiling is higher, increasing the attack surface for memory-based gas griefing.
P2: Memory Expansion Across EVM Clients (Low)
All major EVM clients (Geth, Erigon, Nethermind, Besu, Reth) implement MSTORE identically. Memory expansion cost calculation is deterministic and specified in the Yellow Paper. No consensus bugs have been attributed to MSTORE implementation differences.
P3: MSTORE in EOF/EVM Upgrades (Low)
EVM Object Format (EOF, EIP-7692) does not change MSTORE semantics. The proposed MCOPY (0x5E, EIP-5656, live since Cancun) provides a memory-to-memory copy but does not replace MSTORE. No upcoming EIPs alter MSTORE’s behavior or gas model.
Edge Cases
| Edge Case | Behavior | Security Implication |
|---|---|---|
Writing to offset 0x40 (free memory pointer) | Overwrites Solidity’s free memory pointer value | All subsequent Solidity memory allocations use the corrupted pointer, causing silent data corruption across the entire function |
Writing to offset 0x00-0x3F (scratch space) | Overwrites Solidity’s scratch space | Safe in isolated assembly, but corrupts intermediate values if Solidity is using scratch space concurrently (e.g., during ABI encoding or hashing) |
Writing to offset 0x60 (zero slot) | Overwrites the 32-byte zero value Solidity uses for empty dynamic types | Corrupts default values for bytes memory(""), empty arrays, and zero-length strings until the slot is re-zeroed |
Very large offset (e.g., 0xFFFFFF) | Memory expands to accommodate the write; gas cost is quadratic | Single MSTORE can consume millions of gas; if offset is user-controlled, enables gas griefing DoS |
Offset > 2^64 | Practical EVM implementations use 64-bit memory addressing; most clients will OOM or hit gas limit before reaching such offsets | Theoretical edge case; gas cost would exceed any block gas limit long before this offset is reached |
| Overlapping writes (two MSTOREs within 32 bytes of each other) | Second write partially overwrites first; bytes are written big-endian | Data from the first write is silently corrupted in the overlapping region; no error or warning |
MSTORE in STATICCALL context | Allowed; memory writes are local to the call frame | Memory is call-frame-local, so STATICCALL’s read-only restriction applies only to storage and value transfers, not memory |
| MSTORE with offset = current free memory pointer | Writes exactly where Solidity would allocate next | If the free memory pointer is not advanced, the next new bytes(...) or ABI encoding overwrites this data |
| MSTORE during contract creation (constructor) | Behaves identically to runtime; constructor memory is discarded after deployment | Memory corruption during constructor affects initialization logic; corrupted return data from constructor alters deployed bytecode |
Real-World Exploits
Exploit 1: 1inch Fusion v1 Settlement — $5M via Assembly Memory Corruption (March 2025)
Root cause: Integer underflow in Yul assembly offset calculation caused MSTORE to write a legitimate resolver suffix to the wrong memory location, allowing an attacker-crafted suffix to be read instead.
Details: The 1inch Fusion v1 Settlement contract’s _settleOrder() function used inline Yul assembly to copy order data into memory and append a “suffix” containing the resolver address and token transfer parameters. The suffix write offset was calculated as ptr + interactionOffset + interactionLength, where interactionLength was read directly from untrusted calldata without validation.
The attacker provided interactionLength = 0xFFFF...FE00 (-512 in two’s complement). This caused the offset calculation to underflow:
Normal: offset = ptr + 0x480 + positive_value → writes at END of calldata
Attack: offset = ptr + 0x480 + (-512) → writes at ptr + 0x280 (over zero padding)
The legitimate suffix (containing resolver = attacker_contract) was written into a zero-padding region and ignored. Meanwhile, the DynamicSuffix library read from the end of calldata, where the attacker had placed a fake suffix with the victim’s resolver address (TrustedVolumes). The Settlement contract then called victim.resolveOrders(), and since msg.sender was the trusted Settlement contract, the victim transferred ~$5M in USDC and WETH to the attacker.
The attack used 6 nested orders to build the internal tokensAndAmounts array to exactly 512 bytes, making the -512 underflow precisely overwrite the zero-padding region.
MSTORE’s role: The vulnerable code used mstore(add(offset, 0x24), resolver) to write the resolver address into memory. The underflowed offset caused this MSTORE to write the legitimate resolver address into an ignored memory region, while the attacker’s forged resolver was read from the un-overwritten end of calldata.
Impact: ~$5M stolen across 10 transactions. Most funds were returned after on-chain negotiation, with the attacker keeping a bounty.
References:
Exploit 2: Solidity Optimizer Removing MSTORE — Compiler Bug (June 2022)
Root cause: The Yul optimizer in Solidity 0.8.13-0.8.14 incorrectly identified MSTORE instructions in isolated inline assembly blocks as dead stores and removed them.
Details: Solidity 0.8.13 introduced an optimizer step to eliminate unused memory and storage writes. In the via-IR pipeline, the optimizer correctly treats each Yul block as self-contained. But in the legacy code generation pipeline, inline assembly blocks that do not reference surrounding Solidity variables are optimized in isolation — the optimizer cannot see that memory written in one assembly block is read in a subsequent block.
contract C {
function f() external pure returns (uint256 x) {
assembly { mstore(0, 0x42) } // Optimizer removes this MSTORE
assembly { x := mload(0) } // Returns 0 instead of 0x42
}
}The MSTORE at offset 0 is removed because no mload occurs within the same assembly block. The second block’s mload(0) reads zero-initialized memory, returning an incorrect value.
MSTORE’s role: The optimizer’s incorrect removal of MSTORE instructions is the direct cause. The bug demonstrates that even compiler-generated or compiler-optimized MSTORE operations can be silently dropped, introducing memory corruption without any developer error.
Impact: Classified as medium severity. Fixed in Solidity 0.8.15. In practice, affected code is uncommon because most assembly blocks either reference Solidity variables (preventing isolated optimization) or are self-contained.
References:
- Solidity Blog: Optimizer Bug Regarding Memory Side Effects of Inline Assembly
- Solidity GitHub PR #13100
Exploit 3: Vyper concat() Buffer Overflow — CVE-2024-22419 (Critical, January 2024)
Root cause: Vyper’s concat built-in wrote beyond the allocated memory buffer due to incorrect adherence to the internal copy_bytes API, causing MSTORE operations to overwrite valid data past the buffer boundary.
Details: The Vyper compiler’s implementation of the concat() built-in function miscalculated buffer boundaries when concatenating byte arrays. The generated EVM bytecode used MSTORE to write concatenated data, but the write extended beyond the allocated buffer. This buffer overflow could overwrite adjacent memory regions, including ABI-encoded return values, function arguments, and other allocated data.
The vulnerability (CVSS 9.8 Critical) affected Vyper versions 0.3.0 through 0.3.10 and was exploitable remotely without privileges. An attacker could craft inputs to a contract using concat() that triggered the overflow, corrupting return data or internal state within the same transaction.
MSTORE’s role: The Vyper compiler generated MSTORE instructions to write concatenated bytes. The incorrect offset calculations meant these MSTOREs wrote past the intended buffer boundary, directly causing the memory corruption.
Impact: All Vyper contracts using concat() on versions 0.3.0-0.3.10 were potentially vulnerable. Discovered during a CodeHawks security competition. Fixed in Vyper 0.4.0.
References:
- CVE-2024-22419: concat built-in can corrupt memory in vyper
- Vyper Security Blog: State of Vyper Security (September 2024)
Exploit 4: wagmi-leverage patchAmountAndCall — Arbitrary Memory Write via Unchecked Assembly Offset (2023)
Root cause: Inline assembly in the patchAmountAndCall() function computed an MSTORE offset from user-controlled calldata without overflow checks, allowing writes to arbitrary memory locations.
Details: The wagmi-leverage protocol’s patchAmountAndCall() function used Yul assembly to patch a swap amount into calldata before forwarding it to an external router. The offset calculation add(add(ptr, 0x24), mul(swapAmountInDataIndex, 0x20)) used swapAmountInDataIndex directly from calldata. Since Yul lacks overflow protection, a large swapAmountInDataIndex caused mul(swapAmountInDataIndex, 0x20) to overflow, wrapping around to a small value that pointed to the function selector or other critical calldata fields.
By choosing swapAmountInDataIndex precisely, an attacker could use MSTORE to overwrite the function selector of the external call, redirecting it to a different function (e.g., transfer() instead of swap()), or overwrite the recipient address in the calldata.
MSTORE’s role: The mstore(add(offset, ...), patchedAmount) instruction was the direct mechanism for writing to the attacker-controlled memory location.
Impact: Identified by Sherlock auditors before exploitation. Classified as High severity due to the ability to redirect external calls to arbitrary functions.
References:
- Sherlock: Vulnerability Originating in Inline Assembly
- Dacian: Solidity Inline Assembly Vulnerabilities
Attack Scenarios
Scenario A: Free Memory Pointer Corruption via Assembly
contract VulnerableAllocator {
function processData(bytes calldata data) external returns (bytes32) {
bytes memory buffer;
assembly {
// "Optimize" by resetting free memory pointer to reuse memory
mstore(0x40, 0x80) // DANGER: resets allocator to start
}
// Solidity allocates 'decoded' starting at 0x80 -- same region
// that may contain 'buffer' or other data from earlier operations
(address to, uint256 amount) = abi.decode(data, (address, uint256));
// This hash is computed over potentially corrupted memory
// because the ABI decode wrote over memory that other code expected
// to remain intact
return keccak256(abi.encode(to, amount));
}
}Scenario B: Gas Griefing via Unbounded Memory Offset
contract VulnerableRouter {
function routeCall(
address target,
uint256 memOffset, // Attacker-controlled
bytes calldata payload
) external {
assembly {
// Single MSTORE to a massive offset consumes all gas
// memOffset = 0xFFFFFF → ~537M gas (exceeds block limit)
mstore(memOffset, 0x01)
// Copy payload to memory (never reached if gas exhausted above)
calldatacopy(add(memOffset, 0x20), payload.offset, payload.length)
let success := call(gas(), target, 0, memOffset,
add(payload.length, 0x20), 0, 0)
}
}
}
contract Attacker {
function grief(VulnerableRouter router) external {
// Pass enormous offset to force quadratic memory expansion
// Relayer/msg.sender pays the gas, transaction reverts
router.routeCall(
address(0),
0xFFFFFF, // ~16 MB memory expansion
hex""
);
}
}Scenario C: Overlapping Memory Write Corrupts External Call
contract VulnerableVault {
function withdraw(address token, address recipient, uint256 amount) external {
// Solidity builds transfer calldata in memory:
// [selector (4)] [recipient (32)] [amount (32)] at some offset
bytes memory callData = abi.encodeWithSelector(
IERC20.transfer.selector,
recipient,
amount
);
// Assembly block writes a log value to scratch space,
// but miscalculates offset and overwrites calldata
assembly {
let ptr := mload(0x40)
// BUG: developer intended to write at ptr, but ptr points
// into the callData region because free memory pointer
// was already advanced past callData by abi.encodeWithSelector
mstore(ptr, amount) // Overwrites bytes in callData
}
// External call uses corrupted callData
(bool success,) = token.call(callData);
require(success, "transfer failed");
}
}Scenario D: 1inch-Style Suffix Corruption
contract VulnerableSettlement {
struct Suffix {
address resolver;
address token;
uint256 amount;
}
function settleOrder(bytes calldata orderData) external {
assembly {
let ptr := mload(0x40)
// Copy order data to memory
calldatacopy(ptr, orderData.offset, orderData.length)
// Read interaction length from calldata (UNTRUSTED!)
let interactionLength := calldataload(
add(orderData.offset, 0x40)
)
// Calculate where to write the suffix
// If interactionLength is negative (huge uint), this underflows
let suffixOffset := add(add(ptr, 0x60), interactionLength)
// Write suffix -- resolver address ends up at wrong location
mstore(suffixOffset, sload(0)) // resolver
mstore(add(suffixOffset, 0x20), 0x0) // token
mstore(add(suffixOffset, 0x40), 0x0) // amount
// Later: read suffix from END of buffer
// Attacker placed fake suffix there -- resolver is victim
let fakeResolver := mload(add(ptr, sub(orderData.length, 0x40)))
// ... calls fakeResolver with trusted msg.sender ...
}
}
}Mitigations
| Threat | Mitigation | Implementation |
|---|---|---|
| T1: Free memory pointer corruption | Never write to 0x40 in assembly without save/restore | let fmp := mload(0x40) ... mstore(0x40, fmp) — or avoid touching 0x40 entirely |
| T1: Pointer set to invalid value | Validate pointer only moves forward | require(newPtr >= mload(0x40)) before overwriting; or let Solidity manage allocation |
| T2: Memory expansion gas griefing | Cap memory offsets from untrusted input | require(offset < MAX_SAFE_OFFSET) before any MSTORE with user-controlled offset; practical limit ~64 KB for most use cases |
| T2: Relayer gas drain | Set gas limits on inner calls; validate memory usage upfront | Use call(gasLimit, ...) with bounded gas; validate calldata lengths before processing |
| T3: Overlapping memory writes | Use Solidity’s allocator instead of manual offsets | Avoid manual mstore into regions managed by Solidity; always read mload(0x40) for the current allocation boundary |
| T3: Corrupted calldata for external calls | Build calldata atomically; verify before calling | Use abi.encodeWithSelector() in high-level Solidity; avoid interleaving assembly writes with ABI-encoded data |
| T4: Assembly violating Solidity conventions | Follow Solidity memory layout in all assembly | Only use scratch space (0x00-0x3F) for temporary computations; always advance free memory pointer when allocating |
| T4: Optimizer removing MSTORE | Use Solidity >= 0.8.15; reference Solidity variables in assembly | let x := mload(0x40) in the same block as MSTORE prevents incorrect dead-store elimination |
| T5: Hash collision via encodePacked | Use abi.encode instead of abi.encodePacked for hash preimages with dynamic types | abi.encode length-prefixes all dynamic types, preventing concatenation ambiguity |
| T5: Dirty memory in preimages | Zero memory before writing hash preimages | calldatacopy or manual zeroing before building preimages in assembly |
| General | Validate all assembly offset arithmetic | Add bounds checks: if gt(offset, MAX_OFFSET) { revert(0, 0) } before every MSTORE with computed offsets |
| General | Fuzz assembly memory operations | Use Foundry’s fuzzer to test assembly blocks with random offsets and lengths; check invariants after each operation |
Compiler/EIP-Based Protections
- Solidity >= 0.8.15: Fixes the optimizer bug that removed MSTORE instructions in isolated assembly blocks.
- Vyper >= 0.4.0: Fixes the
concat()buffer overflow (CVE-2024-22419) and theextract32dirty memory read (CVE-2024-24564). - EIP-5656 / MCOPY (0x5E, Cancun): Provides a dedicated memory-to-memory copy instruction that is safer and more gas-efficient than manual MSTORE loops for bulk copies.
- EIP-1153 (Transient Storage, Dencun): For data that needs to survive across internal calls but not across transactions, transient storage avoids memory layout concerns entirely.
- Solidity
memory-safeassembly annotation: Marking assembly blocks as/// @solidity memory-safetells the optimizer the block respects Solidity’s memory model, enabling better optimization while documenting intent.
Severity Summary
| Threat ID | Category | Severity | Likelihood | Real-World Precedent |
|---|---|---|---|---|
| T1 | Smart Contract | Critical | Medium | Free memory pointer corruption found in numerous audits; Solidity issue #15391 |
| T2 | Smart Contract | High | High | Common gas griefing vector in router/aggregator contracts |
| T3 | Smart Contract | High | Medium | 1inch Fusion v1 ($5M, March 2025); wagmi-leverage (caught in audit) |
| T4 | Smart Contract | Medium | Medium | Solidity optimizer bug (0.8.13-0.8.14); Vyper concat CVE-2024-22419 |
| T5 | Smart Contract | Medium | Low | abi.encodePacked collision documented in Solidity docs as known footgun |
| P1 | Protocol | Medium | Low | Theoretical; bounded by block gas limit |
| P2 | Protocol | Low | N/A | No known client disagreements on MSTORE |
| P3 | Protocol | Low | N/A | No upcoming changes to MSTORE semantics |
Related Opcodes
| Opcode | Relationship |
|---|---|
| MLOAD (0x51) | Reads 32 bytes from memory. MLOAD reads what MSTORE writes; memory corruption via MSTORE is observed when MLOAD returns unexpected values. The pair forms the basic memory read/write interface. |
| MSTORE8 (0x53) | Writes a single byte to memory. Lower-granularity alternative to MSTORE; can cause the same memory expansion and corruption issues but at byte-level precision. Used for byte-level memory packing. |
| MSIZE (0x59) | Returns the current memory size (highest accessed offset, rounded up to 32-byte words). MSTORE advances MSIZE as a side effect. Contracts that use MSIZE for allocation instead of the free memory pointer can be disrupted by unexpected MSTORE calls. |
| MCOPY (0x5E) | Copies a region of memory to another region (EIP-5656, Cancun). Safer and cheaper than MSTORE loops for bulk memory copies. Shares the same memory expansion cost model. |
| KECCAK256 (0x20) | Hashes a memory region. MSTORE builds the preimage that KECCAK256 hashes. Memory corruption via MSTORE directly corrupts hash inputs, breaking mappings, Merkle proofs, and EIP-712 signatures. |
| CALLDATACOPY (0x37) | Copies calldata into memory. Alternative to MSTORE for moving external input into memory. Both share the same memory expansion cost model and corruption risks when writing to managed memory regions. |
| RETURN (0xF3) | Returns data from memory to the caller. MSTORE typically builds the return data that RETURN sends. Corrupted memory from MSTORE means corrupted return values. |
| CALL (0xF1) | Makes an external call using calldata from memory. MSTORE builds the calldata (function selector + arguments) that CALL sends. Memory corruption can redirect calls, change arguments, or alter amounts. |