CVE-2025-1011
Overview
Background
- MIR
- SpiderMonkey’s mid-level intermediate representation, in which every JIT/wasm operation is a node whose optimizer-visible attributes are declared in
MIROps.yaml. - `movable`
- an MIR-op attribute asserting the instruction is side-effect-free and may be freely reordered, hoisted, or deduplicated by the optimizer.
- `GVN`/`LICM`
- Global Value Numbering and Loop-Invariant Code Motion, passes that reuse or hoist any instruction flagged
movableand matched bycongruent_to. - `alias_set: none`
- a declaration that the op reads no aliasable memory, so the optimizer assumes it can never observe an intervening store.
Root Cause Analysis
The vulnerable code path was the widening WebAssembly GC field-load MIR op (a WasmLoad*KA-style node carrying wideningOp: wasm::FieldWideningOp and result_type: Int32) as declared in MIROps.yaml. That op was marked movable: true while also declaring alias_set: none and congruent_to: if_operands_equal, which told the optimizer the load was a pure, memory-independent computation. In reality the op reads a packed (i8/i16) field out of a GC-managed struct or array, so its result depends on program order relative to stores into that field and on the point at which the containing object is valid. Because the load was treated as pure, GVN could common two such loads that straddle a store, and LICM could hoist the load out of a loop or above the store that defines its value — a code-generation logic error that yields a stale or incorrect widened value.
The fix removes the single movable: true line, so the load is no longer eligible for that motion or deduplication and stays pinned to its original data- and control-flow position.
movable (side-effect-free) even though its value depends on surrounding stores and object validity; deleting movable: true forces the optimizer to leave the load where it was emitted rather than hoisting or CSE-ing it into an unsound position.Attack Path
- Craft a WebAssembly GC module
Define a struct or array with a packed
i8/i16field and a hot function that stores to and then re-reads that field inside a loop. - Reach the optimizing tier
Execute the function enough times that it is Ion/wasm-optimized, so
GVNandLICMprocess themovablewidening load. - Provoke the mis-optimization The load, treated as pure, is hoisted out of the loop or common-subexpression-eliminated with an earlier load, so it consumes a stale or wrong value rather than the current field contents.
- Corrupt program state The incorrect widened value flows into an index, length, or pointer computation, producing an out-of-bounds access or type confusion on the GC heap.
- Escalate to code execution The attacker shapes the resulting corruption into a controlled read/write primitive within the content process.
Impact Assessment
Files Changed
js/src/jit/MIROps.yaml
Audit Directions
- Purity vs. memoryReview every MIR op that pairs
alias_set: nonewithmovable: trueyet actually reads heap or GC memory, since that combination falsely declares the load side-effect-free. - Wasm GC accessorsAudit the widening/packed field and element load ops (
wasm::FieldWideningOpusers) for correctalias_setand movability, especially where a mutable field can be stored to between loads. - congruent_to overreachCheck memory-reading ops using
congruent_to: if_operands_equal, which can be CSE-merged across intervening stores when the alias/movable metadata understates their dependencies.
Patch
diff --git a/js/src/jit/MIROps.yaml b/js/src/jit/MIROps.yaml
index e450b90788d..7d175b8601b 100644
--- a/js/src/jit/MIROps.yaml
+++ b/js/src/jit/MIROps.yaml
@@ -736,7 +736,6 @@
wideningOp: wasm::FieldWideningOp
type_policy: none
result_type: Int32
- movable: true
congruent_to: if_operands_equal
alias_set: none