CVE-2025-24158
Overview
Background
- BBQ JIT
- JavaScriptCore’s baseline optimizing wasm compiler with its own register allocator.
- allocate vs loadIfNecessary
- allocate() forces a fresh register for a value; loadIfNecessary() reuses the value’s existing location if it already has one.
- shouldThrow value
- A computed flag operand for bulk table/memory ops that must reside in a register for the trap check.
- Register pressure
- When few registers are free, forcing an allocation on an already-located value breaks allocator invariants.
Root Cause Analysis
JavaScriptCore’s Wasm BBQ JIT emits table and memory bulk operations (addTableSet, addTableInit, addTableFill, addTableCopy, addMemoryFill, addMemoryCopy, addMemoryInit) and some atomics/array-segment ops that materialize a ‘shouldThrow’/result value into a register. These call sites used allocate(shouldThrow) / allocate(result), which unconditionally allocates a fresh register location for the value. When the value is already materialized in a location (or when register pressure means allocate cannot proceed as assumed), forcing a new allocation drives the BBQ register allocator into an inconsistent/invalid state, hitting a release assertion or otherwise aborting – a reliably reachable crash (denial-of-service).
The fix replaces allocate(...) with loadIfNecessary(...) at these sites, which brings the value into a register only if it is not already in one, matching how the value is actually used.
The restored invariant is that these bulk table/memory operation operands are materialized with loadIfNecessary rather than a forced allocate, keeping the register allocator consistent and avoiding the crash.
Attack Path
- Craft wasm with bulk table/memory ops Build a module whose functions use table.set/init/fill/copy or memory.fill/copy/init under register pressure.
- Compile via BBQ JIT Instantiation compiles the ops, which call allocate() on a value that is already located.
- Corrupt allocator state The forced allocate drives the register allocator into an invalid state.
- Denial-of-service A release assertion/abort crashes the WebContent process (reliable DoS).
Impact Assessment
Changed Functions
| Function | Change | Notes |
|---|---|---|
BBQJIT::addTableSet/Init/Fill/Copy, addMemoryFill/Copy/Init, atomicWait/Notify, pushArrayNewFromSegmentSource/JavaScriptCore/wasm/WasmBBQJIT.cpp |
modified | Uses loadIfNecessary(shouldThrow)/loadIfNecessary(result) instead of allocate(...), materializing the value only when needed and keeping the register allocator consistent. |
Audit Directions
- Other allocate() misusegrep WasmBBQJIT for allocate(value) where the value may already be materialized and loadIfNecessary is the correct call.
- Bulk op operand handlingAudit table/memory/array bulk-op operand materialization for consistent load/allocate/consume discipline.
- Register-exhaustion assertsReview RELEASE_ASSERTs in the BBQ allocator that a crafted module could deterministically trip.