Critical firefox Logic Error 🔧 Commit mapped

Overview

Critical
Severity
—
CVSS
No
Exploited ITW
Fixed
Fix Status
Impactcritical
DescriptionWe are aware that exploit code for this is public however we are not aware of any attacks in the wild abusing this flaw.
ComponentSpiderMonkey
Bug ClassLogic Error
Tracker2045443
Fix commit4441102e62e8 (firefox) +2/-0
CISA KEVNot listed
CreditedChristian Holler
Disclosed2026-07-14

Background

SpiderMonkey
Mozilla’s JavaScript and WebAssembly engine embedded in Firefox/Gecko.
WASM baseline compiler
the fast, single-pass BaseCompiler in WasmBaselineCompile.cpp that emits machine code for WebAssembly with a register-allocating value stack.
`sync()`
a baseline-compiler operation that spills the in-register portion of the abstract value stack to memory so the physical machine state matches the compiler’s model before a control-flow join or shared helper sequence.
`array.fill`
the WebAssembly GC instruction, compiled by BaseCompiler::emitArrayFill, that writes a repeated value into a contiguous range of a WASM GC array.

Root Cause Analysis

BaseCompiler::emitArrayFill compiled the array.fill operation by emitting a fill loop that used numElements as a countdown loop variable, but it entered that loop without first calling sync() to reconcile the abstract value stack with the physical register/stack state. Because the loop contains a control-flow join (the done label and the back-edge) and manipulates registers such as PreBarrierReg, the baseline compiler’s invariant that operand-stack values held in registers are spilled before branch targets was violated, so the compiler’s model of where operands lived diverged from the code actually generated. This let generated code read or write the wrong registers/stack slots across the loop boundary, corrupting engine state.

The fix adds a single sync() call immediately before the fill loop is emitted, forcing all live register-resident stack values to be flushed so the machine state is consistent at the loop’s control-flow join. With the spill in place the loop’s back-edge and exit see a well-defined stack layout, restoring the compiler’s core register-allocation invariant.

Key insight
The single mistake was emitting a loop with a control-flow join in emitArrayFill without spilling the register-resident value stack first, leaving the physical machine state inconsistent with the compiler’s abstract model. Inserting sync() before the loop restores the invariant that all live stack operands are materialized in memory across the join.

Attack Path

  1. Deliver malicious module A remote page ships a crafted WebAssembly (GC) module containing an array.fill instruction positioned so its operands are register-resident on the value stack.
  2. Trigger baseline compilation The module is compiled by the baseline (BaseCompiler) tier, invoking emitArrayFill and emitting the fill loop without the missing sync().
  3. Diverge machine and model state The un-synced loop join causes generated code to use registers/stack slots that no longer match the compiler’s tracking, producing type- or memory-confused JIT code.
  4. Achieve memory corruption Executing the miscompiled function reads/writes memory outside the intended bounds or with a confused value, giving a controllable corruption primitive.
  5. Escalate The attacker leverages the corruption (public exploit code exists) toward arbitrary read/write and code execution inside the content process.

Impact Assessment

An attacker who lures a victim to a malicious web page can drive the WASM baseline compiler into producing miscompiled array.fill code, yielding memory corruption inside the sandboxed content process where SpiderMonkey runs. The metadata rates this critical and notes public exploit code exists; preconditions are only that WebAssembly GC compilation via the baseline tier is reachable and the crafted module is executed. No special privileges or user interaction beyond visiting the page are indicated by the diff.

Files Changed

  • js/src/wasm/WasmBaselineCompile.cpp

Audit Directions

  • Missing `sync()` before control-flow joins
    Audit other BaseCompiler::emit* handlers that emit loops, Labels, or branch back-edges to confirm each calls sync() (or equivalent spill) before the join when the value stack may hold register-resident operands.
  • Helper sequences touching special registers
    Review paths that reserve or free registers like PreBarrierReg mid-instruction and ensure the abstract stack is reconciled before and after such register churn.
  • WASM GC array/struct opcodes
    Give focused review to newer GC opcode emitters (array.fill, array.copy, array.new, struct writes) whose loops and barriers are prone to the same spill-before-join invariant breaks.
diff --git a/js/src/wasm/WasmBaselineCompile.cpp b/js/src/wasm/WasmBaselineCompile.cpp
index 6f6fbd72eba..6d94c781ebf 100644
--- a/js/src/wasm/WasmBaselineCompile.cpp
+++ b/js/src/wasm/WasmBaselineCompile.cpp
@@ -8976,6 +8976,8 @@ bool BaseCompiler::emitArrayFill() {
     freePtr(RegPtr(PreBarrierReg));
   }
 
+  sync();
+
   // Perform the fill loop using `numElements` as the loop variable, counting
   // down to zero.
   Label done;
Loading diff…