Low firefox Logic Error 🔧 Commit mapped

Overview

Low
Severity
—
CVSS
No
Exploited ITW
Fixed
Fix Status
Impactlow
DescriptionJIT miscompilation in the JavaScript: WebAssembly component
ComponentSpiderMonkey
Bug ClassLogic Error
Tracker2032943
Fix commit167e27f02b30 (firefox) +40/-0
CISA KEVNot listed
CreditedJunYoung Park
Disclosed2026-06-16

Background

SpiderMonkey
Mozilla’s JavaScript and WebAssembly engine that includes the JIT compilers targeted by this fix.
Atomic RMW
WebAssembly read-modify-write operations (i32.atomic.rmw.cmpxchg, i32.atomic.rmw.add, i32.atomic.rmw.xchg) plus i32.atomic.load/i32.atomic.store that operate on shared linear memory.
Static offset
the constant offset=8192 immediate folded into each atomic access’s effective address at compile time.
`--fast-warmup`
a jit-test flag that lowers warm-up thresholds so functions are tier-up compiled by the optimizing JIT (Warp/Ion) almost immediately.

Root Cause Analysis

The metadata classifies this as a LogicError — a JIT miscompilation in the WebAssembly component — and the added test bug2032943.js exercises the optimizing-JIT code path for atomic memory accesses on a shared memory using a large constant offset=8192. Under --fast-warmup the five exported functions (cas, add, store, load, xchg) are compiled by the optimizing tier, and the test asserts an exact sequenced result chain (store 0 → load == 0 → add returns old 0 → cmpxchg(1→2) returns old 1 → xchg returns old 2). The violated invariant is that a JIT-compiled atomic RMW/load/store must compute the same effective address and produce the same old-value result as the interpreter/baseline for every combination of dynamic index and folded static offset. A miscompilation in the atomic-access lowering (for example, mishandling the constant offset or the returned old value) would break this chain, yielding a wrong result rather than the semantically correct atomic value. The regression test locks in correct sequenced behavior so any codegen that computes the atomic address or old value incorrectly is caught.

The fix works by correcting that lowering so the JIT-emitted atomic ops match the specified WebAssembly semantics; only the regression test is present in this diff.

Key insight
The single core mistake is a miscompilation in how the optimizing JIT lowers WebAssembly atomic memory accesses with a folded static offset, producing a result inconsistent with correct atomic semantics; the fix restores correct address/old-value computation and pins it with a --fast-warmup regression test.

Attack Path

  1. Supply crafted module An attacker delivers a WebAssembly module using shared memory and atomic operations with a large static offset on the target.
  2. Force tier-up Repeatedly invoke the atomic-using functions so the optimizing JIT compiles them, reaching the miscompiled code path.
  3. Trigger miscompilation Execute the atomic RMW/load/store sequence whose JIT-emitted lowering diverges from correct semantics.
  4. Observe wrong result The atomic op returns an incorrect old value or targets a wrong address, corrupting program logic that trusts atomic correctness.

Impact Assessment

An attacker gains a logic-level correctness break: JIT-compiled WebAssembly atomic operations can return wrong results, all confined to the content process running the attacker’s WebAssembly. The precondition is that the victim runs the crafted module long enough for the optimizing JIT to compile the affected functions. Consistent with the metadata (severity/impact low, no CWE or CVSS assigned), this is a correctness defect rather than a demonstrated memory-safety primitive.

Files Changed

  • js/src/jit-test/tests/wasm/bug2032943.js

Audit Directions

  • Atomic access lowering
    Review JIT lowering of every WebAssembly atomic op (load, store, rmw.add, rmw.cmpxchg, rmw.xchg) to confirm the folded static offset and returned old value are computed identically to baseline.
  • Static-offset folding
    Audit constant-offset folding into effective addresses across memory access kinds for boundary values like large offsets, checking for truncation or sign/width mismatches.
  • Cross-tier differential testing
    Extend --fast-warmup differential tests comparing interpreter, baseline, and optimizing-tier results for atomic and shared-memory operations to catch tier-specific miscompilations.
diff --git a/js/src/jit-test/tests/wasm/bug2032943.js b/js/src/jit-test/tests/wasm/bug2032943.js
new file mode 100644
index 00000000000..57e76cddb3c
--- /dev/null
+++ b/js/src/jit-test/tests/wasm/bug2032943.js
@@ -0,0 +1,40 @@
+// |jit-test| --fast-warmup
+
+const code = `
+(module
+  (memory 2 10 shared)
+  (export "mem" (memory 0))
+
+  (func (export "cas") (param i32 i32 i32) (result i32)
+    local.get 0  local.get 1  local.get 2
+    i32.atomic.rmw.cmpxchg offset=8192)
+
+  (func (export "add") (param i32 i32) (result i32)
+    local.get 0  local.get 1
+    i32.atomic.rmw.add offset=8192)
+
+  (func (export "store") (param i32 i32)
+    local.get 0  local.get 1
+    i32.atomic.store offset=8192)
+
+  (func (export "load") (param i32) (result i32)
+    local.get 0
+    i32.atomic.load offset=8192)
+
+  (func (export "xchg") (param i32 i32) (result i32)
+    local.get 0  local.get 1
+    i32.atomic.rmw.xchg offset=8192)
+)`;
+
+const mod = new WebAssembly.Module(wasmTextToBinary(code));
+const inst = new WebAssembly.Instance(mod);
+
+inst.exports.store(0, 0);
+const loaded = inst.exports.load(0);
+assertEq(loaded, 0);
+const add_old = inst.exports.add(0, 1);
+assertEq(add_old, 0);
+const cas_old = inst.exports.cas(0, 1, 2);
+assertEq(cas_old, 1);
+const xchg_old = inst.exports.xchg(0, 0);
+assertEq(xchg_old, 2);
Loading diff…

Regression Test / PoC

shipped with the fix
diff --git a/js/src/jit-test/tests/wasm/bug2032943.js b/js/src/jit-test/tests/wasm/bug2032943.js
new file mode 100644
index 00000000000..57e76cddb3c
--- /dev/null
+++ b/js/src/jit-test/tests/wasm/bug2032943.js
@@ -0,0 +1,40 @@
+// |jit-test| --fast-warmup
+
+const code = `
+(module
+  (memory 2 10 shared)
+  (export "mem" (memory 0))
+
+  (func (export "cas") (param i32 i32 i32) (result i32)
+    local.get 0  local.get 1  local.get 2
+    i32.atomic.rmw.cmpxchg offset=8192)
+
+  (func (export "add") (param i32 i32) (result i32)
+    local.get 0  local.get 1
+    i32.atomic.rmw.add offset=8192)
+
+  (func (export "store") (param i32 i32)
+    local.get 0  local.get 1
+    i32.atomic.store offset=8192)
+
+  (func (export "load") (param i32) (result i32)
+    local.get 0
+    i32.atomic.load offset=8192)
+
+  (func (export "xchg") (param i32 i32) (result i32)
+    local.get 0  local.get 1
+    i32.atomic.rmw.xchg offset=8192)
+)`;
+
+const mod = new WebAssembly.Module(wasmTextToBinary(code));
+const inst = new WebAssembly.Instance(mod);
+
+inst.exports.store(0, 0);
+const loaded = inst.exports.load(0);
+assertEq(loaded, 0);
+const add_old = inst.exports.add(0, 1);
+assertEq(add_old, 0);
+const cas_old = inst.exports.cas(0, 1, 2);
+assertEq(cas_old, 1);
+const xchg_old = inst.exports.xchg(0, 0);
+assertEq(xchg_old, 2);
Loading diff…