Medium CVSS 4.3 webkit Logic Error 🔧 Commit mapped

Overview

Medium
Severity
4.3
CVSS
No
Exploited ITW
Fixed
Fix Status
DescriptionProcessing maliciously crafted web content may lead to an unexpected process crash
ComponentJSC Wasm
Bug ClassLogic Error
Tracker298196
Fix commitb107f7698299 (WebKit/WebKit) +156/-9
CWECWE-20, CWE-703 (Improper input validation)
CVSS vectorCVSS:3.1/AV:N/AC:L/PR:N/UI:R/S:U/C:N/I:N/A:L
CISA KEVNot listed
CreditedGoogle Big Sleep
Disclosed2025-11-03

Background

OSR (On-Stack Replacement)
The mechanism that transfers a running function from one execution tier to another mid-execution (here, from IPInt into BBQ-compiled code at a loop header) by copying live values into their new locations.
StackMap / OSREntryValue
A description, produced at compile time, of every live value at an OSR point and where it resides, consumed at runtime to reconstruct the target tier’s register/stack state.
IPInt
JavaScriptCore’s in-place WebAssembly interpreter, which keeps exception/rethrow values on a separate shadow stack from ordinary operand values.
BBQ JIT
The Build-Bytecode-Quickly baseline WebAssembly JIT tier that OSR loop entry can jump into from the interpreter.
try implicit slot
An extra stack slot a wasm try control frame reserves for its in-flight exception (an EncodedJSValue), which does not hold a valid value until an exception is actually caught.

Root Cause Analysis

The patch fixes an OSR (on-stack replacement) stackmap layout mismatch between the IPInt (in-place interpreter) and the BBQ WebAssembly JIT for functions containing try blocks. BBQJIT::makeStackMap builds a StackMap describing where every live value lives (locals, exception/rethrow slots, enclosed expression stacks, and argument locations) so that operationWasmLoopOSREnterBBQJIT can copy those values into the correct registers/stack when transferring execution into BBQ-compiled code at a loop header. IPInt keeps exception values on a separate ‘shadow’ rethrow stack, and each try control frame carries a number of implicit slots. The old makeStackMap treated the try’s exception slot by simply doing ++numSlots and then, in the implicit-slot loop, emitting an OSREntryValue of type B3::Int64 for the exception (comment: ‘Exceptions are EncodedJSValues, so they are always Int64’). Because the try’s implicit exception slot could overlap the way IPInt lays out that shadow slot, the stackmap described a live Int64 exception where no real exception value exists at that program point, so on OSR the runtime would load/store a bogus value into an overlapping slot — the regression test is named ipint-bbq-osr-check-try-implicit-slot-overlap. The invariant violated is that every OSREntryValue in the stackmap must correspond to a value that genuinely exists and is safe to load at the OSR point.

The fix restructures the loop: for try frames it now emits the exception slot separately as an OSREntryValue with type B3::Void (‘We don’t actually have a real value here, so use Void to signify we shouldn’t load it’) and no longer inflates numSlots by one; then operationWasmLoopOSREnterBBQJIT’s writeValueToRep early-returns when the value type is B3::Void (‘Void signifies an unused exception slot in try’), so that slot is skipped instead of being written from an arbitrary buffer word. The remaining changes are whitespace-only (stackMapIndex ++ -> stackMapIndex++). Net effect: the try implicit exception slot is marked as a do-not-load placeholder, eliminating the overlapping/bogus write during OSR entry.

Key insight
Every OSREntryValue must correspond to a value that actually exists at the OSR point; the try frame’s implicit exception slot held no real value, so describing it as a loadable Int64 (rather than a Void do-not-load placeholder) caused an overlapping, bogus write during loop OSR entry.

Attack Path

  1. Craft wasm with try inside a loop Build a module (as in the test) whose function has a loop containing a try block with parameters/results, so BBQ must generate an OSR loop-entry stackmap that includes the try’s implicit exception slot.
  2. Instantiate with matching tags/tables Set up the tags, tables and shared memory the test uses so the specific control-stack layout that causes the implicit try slot to overlap another live slot is produced.
  3. Drive loop OSR into BBQ Execute the exported function enough times (the harness loops the call) to trigger tier-up and loop OSR entry into BBQ-compiled code, invoking operationWasmLoopOSREnterBBQJIT with the flawed stackmap.
  4. Overlapping slot write corrupts OSR state The runtime writes an Int64 ’exception’ value into the overlapping slot, clobbering the real value that belongs there and producing an unexpected process crash (the stated impact).
  5. (Inference) Toward a stronger primitive If the overwritten slot corresponds to an attacker-influenced live value, the corrupted OSR transfer could in theory yield a controlled wrong value in a register; the diff only shows the placeholder/void fix and a crash, so escalation beyond a crash is inference.

Impact Assessment

The commit establishes an OSR-entry state-reconstruction bug: for wasm functions with a try in a loop, BBQ’s stackmap described a nonexistent Int64 exception in an overlapping slot, and the runtime copied a bogus value into it, whose demonstrated effect is an unexpected crash in the WebContent process. Because the corruption is a wrong value written into a live OSR slot rather than an out-of-bounds buffer access, the realistic outcome is a crash or a corrupted register value; turning it into a controlled type confusion would require the overwritten slot to carry an attacker-typed value, which the diff does not demonstrate. It is confined to the wasm engine in the WebContent process and does not by itself escape the sandbox.

Changed Functions

FunctionChangeNotes
BBQJIT::makeStackMap
Source/JavaScriptCore/wasm/WasmBBQJIT.cpp
modified For try control frames it now emits the exception slot as a separate OSREntryValue with B3::Void (do-not-load) instead of inflating implicitSlots by one and emitting a bogus Int64 exception value; other changes are whitespace.
operationWasmLoopOSREnterBBQJIT (writeValueToRep lambda)
Source/JavaScriptCore/wasm/WasmOperations.cpp
modified Added an early return in writeValueToRep when the OSREntryValue type is B3::TypeKind::Void, so the unused try exception slot is skipped rather than copied from an arbitrary scratch-buffer word.

Files Changed

  • JSTests/wasm/stress/ipint-bbq-osr-check-try-implicit-slot-overlap.js
  • Source/JavaScriptCore/wasm/WasmBBQJIT.cpp
  • Source/JavaScriptCore/wasm/WasmOperations.cpp

Audit Directions

  • Same-function: other implicit/shadow slots in makeStackMap
    In WasmBBQJIT.cpp review every category makeStackMap enumerates (rethrow slots, enclosed expression stacks, argument locations) for slots that may not hold a valid value at the OSR point and confirm they use the B3::Void placeholder rather than a fabricated type; grep for implicitSlots() and locationOf(exception).
  • writeValueToRep and OSR value copiers
    Grep WasmOperations.cpp and OSR entry/exit helpers for loops that consume OSREntryValue/StackMap and verify they all honor the B3::Void ‘skip’ convention (search for TypeKind::Void, writeValueToRep, entryData.values()).
  • IPInt vs BBQ/OMG slot-layout parity
    Audit places where IPInt’s shadow rethrow stack layout must match a JIT’s stackmap (try/catch, delegate, rethrow), grepping for exception(controlData), isTry, and implicitSlots to find other spots where the two tiers could disagree on slot counts or overlap.
  • JS-side OSR stackmaps with placeholder values
    Check the DFG/FTL OSR stackmap builders for the analogous pattern of describing a slot that has no live value yet, ensuring such slots are marked dead/unused rather than given a concrete type that forces a load.

Original Bug Report

The reporter's bug is still restricted on the tracker.