Medium CVSS 6.5 webkit Type Confusion 🔧 Commit mapped

Overview

Medium
Severity
6.5
CVSS
No
Exploited ITW
Fixed
Fix Status
DescriptionProcessing maliciously crafted web content may lead to an unexpected process crash
ComponentJSC B3
Bug ClassType Confusion
Tracker310303
Fix commitfa0214fe9a50 (WebKit/WebKit) +76/-30
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
CreditedMateusz Krzywicki (iVerify.io)
Disclosed2026-05-11

Background

Air / B3 backend
JavaScriptCore’s low-level JIT IR (Air) and optimizer that allocates registers and rewrites instructions.
fixObviousSpills
An Air pass that replaces spilled stack-slot operands with equivalent registers/constants it knows are aliased.
Early vs late def
An early def writes its result before the instruction reads its inputs (clobbering at the early point), while a late def writes after; alias state must clobber each at the right time.
Aliasing/spill state
The per-point set of reg/slot/constant equalities fixObviousSpills uses to justify operand substitutions.

Root Cause Analysis

fixObviousSpills is an Air (B3 backend) optimization that tracks which registers, stack slots and constants alias each other so it can replace spilled-slot operands with registers/immediates. Its per-instruction dataflow originally used a single executeInst() that, for each instruction, clobbered all of the instruction’s defs and then added the instruction’s aliases — treating every def uniformly. That ignores the distinction between an instruction’s early defs (registers/slots written before the instruction consumes its inputs, i.e. clobbered at the ’early’ point) and its late defs. Because early defs were not clobbered before fixInst() rewrote the instruction’s arguments, the alias state could still believe a register or slot held a value that the instruction’s early def had already overwritten, so fixInst could replace an operand with a register/constant that no longer holds the expected value. That is a miscompilation: the optimized code reads a stale aliased value.

The fix splits def clobbering into clobberEarlyDefs() (clobber before fixInst) and clobberLateDefs() plus addInstAliases() (after), implemented via clobberDefs(prevInst, nextInst) calling forEachDefWithExtraClobberedRegs/forEachDef with the early (nullptr,&inst) and late (&inst,nullptr) roles; the fixCode loop now does clobberEarlyDefs(); fixInst(); clobberLateDefs(); addInstAliases().

The restored invariant is that the aliasing/spill state accounts for early defs before the instruction is fixed, so no operand is rewritten to a value invalidated by an early def.

Key insight
fixObviousSpills clobbered an instruction’s defs uniformly and only after fixing it, ignoring early defs; an operand could be rewritten to a value the instruction’s early def had already overwritten — a JIT aliasing miscompilation. Clobbering early defs before fixing the instruction restores correctness.

Attack Path

  1. Shape hot code with early-def instructions Run JS (e.g. DataView/ArrayBuffer arithmetic) that the JIT compiles through B3/Air producing instructions with early-def operands and obvious spills.
  2. Trigger the miscompilation fixObviousSpills replaces an operand with a register/slot/constant it wrongly believes is still live across an early def, baking a stale value into the compiled code.
  3. Use the wrong value The optimized function computes on the stale aliased value (e.g. an index/length), producing results inconsistent with the intended semantics.
  4. Corrupt memory A miscomputed index/bound turns into an out-of-bounds or type-confused access in JIT code in the WebContent process.

Impact Assessment

A JIT correctness bug in the Air register/spill-aliasing pass: failing to clobber early defs before rewriting an instruction lets the optimizer substitute a stale value, i.e. a miscompilation. Such JIT miscompiles are a strong exploitation primitive class — a wrong index/length or type can become an out-of-bounds or type-confused access — though building it requires steering the register allocator into the exact aliasing shape. Confined to the WebContent process. Rated medium.

Changed Functions

FunctionChangeNotes
FixObviousSpills::fixCode / run
Source/JavaScriptCore/b3/air/AirFixObviousSpills.cpp
modified Reworks the per-instruction sequence to clobberEarlyDefs() before fixInst(), then clobberLateDefs() and addInstAliases(), replacing the single executeInst().
FixObviousSpills::clobberDefs / clobberEarlyDefs / clobberLateDefs / addInstAliases
Source/JavaScriptCore/b3/air/AirFixObviousSpills.cpp
added Split def-clobbering into early vs late via forEachDefWithExtraClobberedRegs(prevInst,nextInst,...), so early defs are clobbered from the alias state before instruction fixing.

Files Changed

  • JSTests/stress/fixobviousspills-earlydefs.js
  • Source/JavaScriptCore/b3/air/AirFixObviousSpills.cpp

Audit Directions

  • Other passes ignoring early defs
    grep Air passes for forEachDef usage that doesn’t distinguish early vs late roles (Arg::Role early/late) where operand rewriting or liveness depends on it.
  • Alias/spill state consumers
    Audit code around m_state.clobber/addAlias in fixObviousSpills and sibling passes (e.g. lowerAfterRegAlloc) for ordering of clobber vs operand substitution.
  • forEachDefWithExtraClobberedRegs callers
    Review callers passing (&inst,&inst) that should instead split into (nullptr,&inst) early and (&inst,nullptr) late to model clobbering correctly.

Original Bug Report

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