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 DFG
Bug ClassType Confusion
Tracker290834
Fix commit2a545562709a (WebKit/WebKit) +28/-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
CreditedYuhao Hu, Yan Kang, Chenggang Wu, Xiaojie Wei
Disclosed2025-05-12

Background

DFG/FTL
JavaScriptCore’s optimizing JIT tiers that speculate on value types; wrong speculation must trigger an OSR exit rather than execute unsafe code.
Use kind / edge
A DFG edge’s use kind (KnownCellUse, ObjectUse, …) tells the backend what to assume and check about an operand; too weak a use kind omits a needed check.
Structure
JSC’s per-object shape descriptor; only JSObjects have one, and GetGlobalObject reads the global object pointer stored in it.
Type confusion
Treating memory of one type as another; here a non-object cell is treated as a JSObject with a Structure.

Root Cause Analysis

This fixes a type confusion in JavaScriptCore’s DFG/FTL compilation of the GetGlobalObject node. In DFGFixupPhase, GetGlobalObject was grouped with SkipScope/GetScope/GetGetter/GetSetter and given fixEdge<KnownCellUse> on its operand — a use kind that only guarantees the operand is a cell, not that it is a JSObject. compileGetGlobalObject then did emitLoadStructure(vm, object, result) and loadPtr(Address(result, Structure::globalObjectOffset())), i.e. it read the operand’s Structure and pulled the globalObject pointer out of it, which is only meaningful for a JSObject. Because only KnownCellUse was enforced, a non-object cell (for example a string or symbol) could flow into GetGlobalObject; loading a ‘structure’ from such a cell and dereferencing Structure::globalObjectOffset() reinterprets unrelated memory as a Structure and yields an attacker-influenced pointer — classic type confusion.

The fix splits GetGlobalObject out of the KnownCellUse group and gives it fixEdge<ObjectUse>, adds an explicit speculateObject(node->child1(), objectGPR) in the DFG backend before loading the structure, and changes the FTL lowering from loadStructure(lowCell(…)) to loadStructure(lowObject(…)).

The restored invariant is that GetGlobalObject only ever operates on a proven JSObject; a non-object operand now triggers a speculation failure / OSR exit instead of being treated as an object. The regression test opt() calls arg.test with an object then a regexp so the compiled GetGlobalObject sees a non-object shape, exercising the guard.

Key insight
GetGlobalObject was validated only as a cell (KnownCellUse) when it actually requires a JSObject; the missing ObjectUse speculation let a non-object cell be treated as an object and its ‘structure’ dereferenced.

Attack Path

  1. Warm up the JIT Repeatedly call a function so a GetGlobalObject node is compiled in DFG/FTL for a receiver the profiler believes is a cell.
  2. Feed a non-object cell Arrange for the operand to be a non-object cell (e.g. a string) that satisfies KnownCellUse but is not a JSObject.
  3. Force the structure load GetGlobalObject loads a ‘Structure’ from the non-object cell and reads Structure::globalObjectOffset(), reinterpreting controlled bytes as a Structure/global pointer.
  4. Escalate the type confusion Use the confused pointer as a primitive to build fake objects / arbitrary read-write within the WebContent process.

Impact Assessment

A JIT type-confusion primitive in the WebContent process: reading a Structure and its globalObject field from an attacker-chosen non-object cell yields a controlled pointer, the usual starting point for fake-object construction and arbitrary read/write in JSC. Although the advisory rates it a crash, type confusions in the DFG/FTL are historically reliable routes to full WebContent code execution.

Changed Functions

FunctionChangeNotes
fixupNode (GetGlobalObject case)
Source/JavaScriptCore/dfg/DFGFixupPhase.cpp
modified Moves GetGlobalObject out of the KnownCellUse group and fixes its child edge as ObjectUse so a non-object operand is rejected.
SpeculativeJIT::compileGetGlobalObject
Source/JavaScriptCore/dfg/DFGSpeculativeJIT.cpp
modified Adds speculateObject(node->child1(), objectGPR) before emitLoadStructure so the operand is proven to be an object at runtime.
FTL compileGetGlobalObject
Source/JavaScriptCore/ftl/FTLLowerDFGToB3.cpp
modified Changes loadStructure(lowCell(...)) to loadStructure(lowObject(...)), enforcing the object type in the FTL tier.

Files Changed

  • JSTests/stress/dfg-get-global-object-should-use-object-edge.js
  • Source/JavaScriptCore/dfg/DFGFixupPhase.cpp
  • Source/JavaScriptCore/dfg/DFGSpeculativeJIT.cpp
  • Source/JavaScriptCore/ftl/FTLLowerDFGToB3.cpp

Audit Directions

  • Same file: other cell-vs-object nodes
    Grep DFGFixupPhase.cpp for nodes grouped under KnownCellUse that later load a Structure or object field (emitLoadStructure, Structure::*Offset); confirm each truly accepts any cell, not just objects.
  • Backend structure loads
    In DFGSpeculativeJIT/FTL, audit emitLoadStructure / loadStructure call sites for a preceding speculateObject/lowObject; a lowCell feeding loadStructure is a red flag.
  • Scope/global accessors
    Review GetScope, SkipScope, UnwrapGlobalProxy and similar for correct use-kind assumptions about object vs proxy vs cell operands.

Original Bug Report

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