Medium CVSS 6.5 webkit Integer Overflow 🔧 Commit mapped

Overview

Medium
Severity
6.5
CVSS
No
Exploited ITW
Fixed
Fix Status
DescriptionProcessing maliciously crafted web content may lead to an unexpected Safari crash
ComponentJSC Wasm
Bug ClassInteger Overflow
Tracker292599
Fix commit666190cff29d (WebKit/WebKit) +70/-10
CWECWE-119, CWE-120 (Buffer bounds error, Buffer overflow)
CVSS vectorCVSS:3.1/AV:N/AC:L/PR:N/UI:R/S:U/C:N/I:N/A:H
CISA KEVNot listed
Creditedshandikri working with Trend Micro Zero Day Initiative, Google V8 Security Team
Disclosed2025-07-29

Background

IPInt (In-Place Interpreter)
JSC’s low-tier WebAssembly interpreter that executes Wasm bytecode directly using precomputed side metadata for control flow, before any JIT tier-up.
BlockMetadata delta (PC/MC)
A pair of 32-bit offsets added to the bytecode program counter (PC) and metadata cursor (MC) to jump the interpreter from a branch source to its target.
Sign extension (sxi2q)
Copying the top bit of a 32-bit value into the upper 32 bits of a 64-bit register so that a negative 32-bit number keeps its negative value; loadi zero-extends and thus loses negative sign.
try_table / catch_all
A Wasm exception-handling construct whose catch clause can transfer control to an enclosing label, producing a backward branch when that label is a preceding loop header.
loadpairi
An instruction that loads two adjacent 32-bit fields in one operation, which is why the patch stresses that deltaPC/deltaMC field order must be preserved.

Root Cause Analysis

The IPInt (In-Place Interpreter) tier of JSC’s WebAssembly engine encodes control-flow transfers as BlockMetadata records containing a delta to add to the bytecode PC and to the metadata MC. In WasmIPIntGenerator.h these two fields were declared as unsigned (uint32_t deltaPC/deltaMC). The generator computes each delta as a subtraction of two IPIntLocation offsets (e.g. target.m_entryTarget.pc - loc.pc, here.pc - src.pc, loc.pc - src.pc) in WasmIPIntGenerator.cpp. For most control constructs the branch target lies forward of the source, so the delta is non-negative and the sign of the storage type does not matter. However, for a backward branch – notably the loop entry resolution path (resolveEntryTarget, the isLoop case) and the exception-handling coalescing path (coalesceControlFlow / try_table catch that re-enters an enclosing loop) – the target precedes the source and the delta is genuinely negative. Because the field was uint32_t and the interpreter loaded it with the 32-bit zero-extending ’loadi’ in InPlaceInterpreter64.asm, a small negative delta such as -8 was reinterpreted as a huge positive 32-bit value (e.g. 0xFFFFFFF8) and then added to the 64-bit PC/MC via advancePCByReg/advanceMCByReg.

The invariant that was violated is that a backward control transfer must move the interpreter PC/MC backward; instead it flung them far forward out of the bytecode/metadata buffer.

The fix redeclares deltaPC/deltaMC as int32_t (documenting that field order matters because both are fetched by a single loadpairi, and that negative deltas require sign extension to 64 bits before the add), casts every computed delta to int32_t in the .cpp, and inserts ‘sxi2q t0, t0 / sxi2q t1, t1’ sign-extension in the one asm dispatch path that can take a backward branch, while explicitly commenting the forward-only paths that they may safely skip sign extension. This restores correct backward branching so the loop/try_table re-entry lands on the intended instruction.

Key insight
A signedness bug: control-flow deltas that are legitimately negative for backward branches were stored unsigned and loaded with a zero-extending 32-bit load, so backward loop/try_table re-entries jumped massively forward instead of back – the fix is simply to make the deltas int32_t and sign-extend them before the 64-bit add.

Attack Path

  1. Deliver a crafted WebAssembly module Serve a page that instantiates a small Wasm module whose control flow forces a backward branch whose metadata delta is negative, as in the added regression test wasm-try-table-catching-backwards.js.
  2. Construct a backward exception re-entry Use a (loop) containing a (try_table (catch_all $top …)) that catches an exception and branches back to the enclosing loop header, so the coalescing pass emits a BlockMetadata with a negative deltaPC/deltaMC (target before source).
  3. Force IPInt execution Keep the function in the interpreter tier (small/first-run) so it executes via IPInt rather than being tiered up, ensuring the buggy loadi/zero-extended dispatch runs.
  4. Trigger the throw Call an imported function (here returnYourself) inside the try_table and have the catch handler execute, causing the interpreter to take the backward branch with the corrupted delta.
  5. Corrupt PC/MC The zero-extended negative delta advances PC/MC far outside the module’s bytecode/metadata buffers, so the interpreter fetches an out-of-bounds opcode and typically faults or executes ‘unreachable’, crashing WebContent.

Impact Assessment

The primitive is a control-flow-only corruption of the interpreter’s PC/MC by a fixed, attacker-influenced-but-not-freely-chosen amount (the zero-extended magnitude of a negative delta). It does not by itself give an attacker-controlled read/write of heap objects; the most likely outcome is that the interpreter dereferences an out-of-bounds bytecode/metadata address and crashes (segfault or a spurious ‘unreachable’ trap), matching the advisory’s ‘unexpected Safari crash’. Escalation to RCE would require turning the mis-advanced PC/MC into a reliable OOB read/write of interpreter state, which is not demonstrated and would be difficult given the delta is derived from module layout rather than attacker data. The bug is confined to the WebContent process’s JS/Wasm engine and is still subject to the WebContent sandbox.

Changed Functions

FunctionChangeNotes
IPInt::BlockMetadata (struct)
Source/JavaScriptCore/wasm/WasmIPIntGenerator.h
modified deltaPC/deltaMC changed from uint32_t to int32_t; comment added noting field order matters (loadpairi) and negative deltas need sign extension.
IPInt::IfMetadata (struct)
Source/JavaScriptCore/wasm/WasmIPIntGenerator.h
modified Kept unsigned but comments clarified as 'added to PC/MC' and that field order is significant for the paired load; behavior unchanged (else deltas are always forward).
IPIntGenerator::resolveEntryTarget
Source/JavaScriptCore/wasm/WasmIPIntGenerator.cpp
modified Casts loc.pc-src.pc and loc.mc-src.mc to int32_t when writing BlockMetadata for entry and (loop) branch targets, which can be backward.
IPIntGenerator::resolveExitTarget
Source/JavaScriptCore/wasm/WasmIPIntGenerator.cpp
modified Same int32_t cast for exit-target and non-loop branch-target metadata writes.
IPIntGenerator::coalesceControlFlow
Source/JavaScriptCore/wasm/WasmIPIntGenerator.cpp
modified Casts here.pc-src.pc / here.mc-src.mc to int32_t for exit-handler coalescing (the try_table catch path that can branch backward).
IPIntGenerator (loop-metadata writer, ~line 579)
Source/JavaScriptCore/wasm/WasmIPIntGenerator.cpp
modified Casts the loop entry-target deltas (m_entryTarget.pc/mc - loc.pc/mc) to int32_t; these are the canonical backward-delta case guarded by isLoop.
IPInt interpreter branch dispatch (InPlaceInterpreter64.asm)
Source/JavaScriptCore/llint/InPlaceInterpreter64.asm
modified Adds sxi2q sign-extension of t0/t1 after loadi in the one backward-capable dispatch; annotates the five forward-only dispatches that sign extension is unnecessary.

Files Changed

  • JSTests/wasm/stress/wasm-try-table-catching-backwards.js
  • Source/JavaScriptCore/llint/InPlaceInterpreter64.asm
  • Source/JavaScriptCore/wasm/WasmIPIntGenerator.cpp
  • Source/JavaScriptCore/wasm/WasmIPIntGenerator.h

Audit Directions

  • Other IPInt metadata deltas
    In WasmIPIntGenerator.h/.cpp audit every metadata field that stores a computed offset (grep for ‘delta’, ‘BlockMetadata’, ‘IfMetadata’, ‘InstructionLengthMetadata’) and confirm that any field ever produced by an a.pc - b.pc subtraction where target can precede source is signed and sign-extended; IfMetadata was left unsigned on the claim that else-branches are always forward – verify that.
  • loadi vs sign-extension in InPlaceInterpreter*.asm
    Grep InPlaceInterpreter64.asm / InPlaceInterpreter32_64.asm for every ’loadi …deltaPC/deltaMC’ or advancePCByReg/advanceMCByReg site and check whether a backward branch can reach it without a preceding sxi2q; the patch only fixed one of six near-identical dispatch blocks.
  • Unsigned storage of interpreter jump offsets across tiers
    Compare the LLInt/bytecode branch-offset encodings (grep for ‘deltaPC’, ’targetOffset’, BoundLabel/jump-target math) for the same zero-extend-a-negative-delta pattern in non-Wasm interpreter paths.
  • Backward-branch Wasm control constructs
    Fuzz/audit constructs that legitimately branch backward – loop headers, br/br_if/br_table targeting outer loops, and try_table/catch re-entry – across all Wasm tiers to find metadata paths that assume forward-only deltas.

Original Bug Report

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