CVE-2025-31206
Overview
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.
Attack Path
- 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.
- 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.
- 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.
- Escalate the type confusion Use the confused pointer as a primitive to build fake objects / arbitrary read-write within the WebContent process.
Impact Assessment
Changed Functions
| Function | Change | Notes |
|---|---|---|
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::compileGetGlobalObjectSource/JavaScriptCore/dfg/DFGSpeculativeJIT.cpp |
modified | Adds speculateObject(node->child1(), objectGPR) before emitLoadStructure so the operand is proven to be an object at runtime. |
FTL compileGetGlobalObjectSource/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.jsSource/JavaScriptCore/dfg/DFGFixupPhase.cppSource/JavaScriptCore/dfg/DFGSpeculativeJIT.cppSource/JavaScriptCore/ftl/FTLLowerDFGToB3.cpp
Audit Directions
- Same file: other cell-vs-object nodesGrep 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 loadsIn DFGSpeculativeJIT/FTL, audit emitLoadStructure / loadStructure call sites for a preceding speculateObject/lowObject; a lowCell feeding loadStructure is a red flag.
- Scope/global accessorsReview GetScope, SkipScope, UnwrapGlobalProxy and similar for correct use-kind assumptions about object vs proxy vs cell operands.