CVE-2026-12321
Overview
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) plusi32.atomic.load/i32.atomic.storethat operate on shared linear memory. - Static offset
- the constant
offset=8192immediate 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.
--fast-warmup regression test.Attack Path
- Supply crafted module
An attacker delivers a WebAssembly module using shared memory and atomic operations with a large static
offseton the target. - Force tier-up Repeatedly invoke the atomic-using functions so the optimizing JIT compiles them, reaching the miscompiled code path.
- Trigger miscompilation Execute the atomic RMW/load/store sequence whose JIT-emitted lowering diverges from correct semantics.
- 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
Files Changed
js/src/jit-test/tests/wasm/bug2032943.js
Audit Directions
- Atomic access loweringReview JIT lowering of every WebAssembly atomic op (
load,store,rmw.add,rmw.cmpxchg,rmw.xchg) to confirm the folded staticoffsetand returned old value are computed identically to baseline. - Static-offset foldingAudit 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 testingExtend
--fast-warmupdifferential tests comparing interpreter, baseline, and optimizing-tier results for atomic and shared-memory operations to catch tier-specific miscompilations.
Patch
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);
Regression Test / PoC
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);