Medium CVSS 4.3 webkit Type Confusion 🔧 Commit mapped

Overview

Medium
Severity
4.3
CVSS
No
Exploited ITW
Fixed
Fix Status
DescriptionProcessing maliciously crafted web content may lead to an unexpected Safari crash
ComponentJSC FTL
Bug ClassType Confusion
Tracker301257
Fix commitef1aba9e847a (WebKit/WebKit) +29/-6
CWECWE-843 (Type confusion)
CVSS vectorCVSS:3.1/AV:N/AC:L/PR:N/UI:R/S:U/C:N/I:N/A:L
CISA KEVNot listed
CreditedHossein Lotfi (@hosselot) of Trend Micro Zero Day Initiative
Disclosed2025-12-12

Background

Patchpoint / stackmap
B3/FTL mechanism that reserves registers and records values so an OSR exit can recover the correct runtime state.
OSR exit argument offset
Index into the patchpoint’s children where exit arguments begin; it must account for the result value or the exit reads the wrong slot.
Scratch vs result register
Scratch registers are freely clobbered; reusing the result register as scratch across an exit-capable call corrupts the produced value.

Root Cause Analysis

This fixes a register-allocation / OSR-exit-argument miscompilation in the FTL lowering of DataView byteLength that led to a type-confusion-class wrong value on out-of-bounds exit. In FTLLowerDFGToB3’s typedArrayLength/byteLength patchpoint, the pre-patch code set up the patchpoint once for all typed arrays: appendSomeRegister(base), clobber(macroClobberedGPRs), numGPScratchRegisters = 2. For the DataView case it computed osrExitArgumentOffset = patchpoint->numChildren() (with no allowance for the result value) and then called jit.loadDataViewByteLength(baseGPR, resultGPR, scratch1GPR, scratch2GPR, TypeDataView) — passing resultGPR as a working register to the byteLength load even though resultGPR is also the patchpoint’s result register, and emitting the OSR exit (OutOfBounds) at an argument offset that did not account for the result. The consequence is that on the out-of-bounds OSR exit the exit machinery reads the wrong stackmap argument / a value from a register clobbered by the byteLength computation, so the DataView’s byteLength (or the exit’s recovered value) is wrong/attacker-influenced — a type-confusion-flavored miscompile.

The fix, for the DataView branch, appends base and clobbers there, raises numGPScratchRegisters to 3, computes osrExitArgumentOffset = numChildren() + 1 (accounting for the result), uses three scratch registers for loadDataViewByteLength (not resultGPR), and finally does jit.move(scratch1GPR, resultGPR) to place the length. The non-DataView path keeps 2 scratch registers.

The restored invariant is that the result register is not used as scratch across the OSR-exit-capable byteLength load and the exit argument offset correctly skips the result. The regression test resizes an ArrayBuffer to 1 to force the OOB exit on DataView.byteLength.

Key insight
The DataView byteLength lowering used the result register as a scratch register across the out-of-bounds OSR exit and mis-set the exit argument offset, so the exit could recover a clobbered/wrong length.

Attack Path

  1. Tier up DataView.byteLength Repeatedly read a resizable DataView’s byteLength so the FTL compiles the DataView byteLength patchpoint.
  2. Force an out-of-bounds exit Resize the backing ArrayBuffer so the length check fails and the OutOfBounds OSR exit fires.
  3. Read a clobbered/wrong value Because resultGPR doubled as scratch and the exit offset was off-by-one, the exit recovers a wrong/attacker-influenced value for byteLength.
  4. Escalate Use the miscompiled length as a type-confusion/OOB primitive in the WebContent process toward stronger corruption.

Impact Assessment

An FTL miscompilation in the WebContent process where a DataView byteLength OSR exit can recover a wrong, attacker-influenced value — a type-confusion-class primitive. JIT length/type miscompiles are historically strong stepping stones to arbitrary read/write in JSC; the advisory rates it a crash but the class is high-value.

Changed Functions

FunctionChangeNotes
FTL typedArrayLength/byteLength lowering (DataView patchpoint)
Source/JavaScriptCore/ftl/FTLLowerDFGToB3.cpp
modified For TypeDataView, sets numGPScratchRegisters=3, osrExitArgumentOffset=numChildren()+1 (for the result), runs loadDataViewByteLength on scratch1/2/3 instead of resultGPR, then moves scratch1 into resultGPR; the non-DataView path keeps 2 scratch registers.

Files Changed

  • JSTests/stress/data-view-byte-length-oob-exit.js
  • Source/JavaScriptCore/ftl/FTLLowerDFGToB3.cpp

Audit Directions

  • Same lowering: register reuse
    Audit other typed-array/length patchpoints in FTLLowerDFGToB3.cpp for the result register passed as a scratch to a load that can OSR-exit, and for osrExitArgumentOffset that omits the result.
  • loadDataViewByteLength callers
    Grep for loadDataViewByteLength / loadTypedArrayByteLength and verify the scratch registers are distinct from the result register.

Original Bug Report

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