CVE-2025-31204
Overview
Background
- BBQ JIT
- JavaScriptCore’s baseline WebAssembly JIT; the 64-bit variant keeps values in 64-bit registers.
- branch32 bounds check
- A 32-bit comparison; if the index register holds a 64-bit value, only its low 32 bits are validated.
- zeroExtend32ToWord
- Clears the upper 32 bits of a register so a subsequent 64-bit address computation matches the checked 32-bit value.
Root Cause Analysis
This fixes an out-of-bounds access in the WebAssembly BBQ JIT (64-bit) because the array bounds check compared only the low 32 bits of the index while the element address used the full 64-bit register. In BBQJIT::addArrayGet and addArraySet, the index is bounds-checked with a 32-bit compare — branch32(AboveOrEqual, indexGPR, array->size) — but the index register can hold a 64-bit value whose upper 32 bits are non-zero. Pre-patch the code proceeded to compute the element address from the full 64-bit register, so an index such as 0x7fffffff00000000 passes the 32-bit check (low 32 bits = 0 < size) yet the actual access uses the full 64-bit value as the offset, reading or writing far outside the array — an out-of-bounds memory access.
The fix inserts m_jit.zeroExtend32ToWord(indexGPR, indexGPR) immediately after the bounds check in both addArrayGet and addArraySet, clearing the upper 32 bits so the access uses only the validated low 32 bits.
The restored invariant is that the index used for the element address matches the 32-bit value that was bounds-checked. The regression test passes 0x7fffffff00000000n to a wasm array access.
Attack Path
- Reach the BBQ JIT Run a WebAssembly module with array get/set so it is compiled by the BBQ (64-bit) tier.
- Supply a 64-bit index with high bits set Provide an index like 0x7fffffff00000000 whose low 32 bits are in-bounds but upper bits are non-zero.
- Pass the 32-bit check The branch32 bounds check only inspects the low 32 bits and succeeds.
- Out-of-bounds access The element address is computed from the full 64-bit register, reading/writing far outside the array — a controllable OOB in WebContent.
Impact Assessment
Changed Functions
| Function | Change | Notes |
|---|---|---|
BBQJIT::addArrayGetSource/JavaScriptCore/wasm/WasmBBQJIT64.cpp |
modified | Zero-extends the index to the full word after the 32-bit bounds check so the element address uses only the validated low 32 bits. |
BBQJIT::addArraySetSource/JavaScriptCore/wasm/WasmBBQJIT64.cpp |
modified | Same zeroExtend32ToWord after the bounds check to prevent a 64-bit index bypassing the 32-bit check. |
Files Changed
JSTests/wasm/stress/array-get-large-i64-index.jsSource/JavaScriptCore/wasm/WasmBBQJIT64.cpp
Audit Directions
- Same file: 32-bit checks on 64-bit valuesAudit WasmBBQJIT64 for other branch32 bounds/type checks on index/length registers that are later used as 64-bit addresses without zero-extension.
- Index provenanceGrep for loadIfNecessary(index) uses where an i32 index may carry garbage/high bits from an i64 source before an address computation.