Critical CVSS 8.8 webkit Type Confusion CISA KEV 🔧 Commit mapped

Overview

Critical
Severity
8.8
CVSS
No
Exploited ITW
Fixed
Fix Status
DescriptionProcessing maliciously crafted web content may lead to arbitrary code execution. Apple is aware of a report that this issue may have been exploited.
ComponentJSC DFG
Bug ClassType Confusion
Tracker267134
Fix commit66f60deae730 (WebKit/WebKit) +3/-151
CWECWE-843 (Type confusion)
CVSS vectorCVSS:3.1/AV:N/AC:L/PR:N/UI:R/S:U/C:H/I:H/A:H
CISA KEVListed
CreditedAn anonymous researcher
Disclosed2024-01-22

Background

Concurrent DFG/FTL JIT
JavaScriptCore compiles hot code on a background thread while the mutator keeps running and can mutate objects the compiler is reasoning about.
Structure / structure transition
A JSObject’s shape; adding/changing properties transitions it S1->S2->S3, each a distinct Structure.
CheckStructure + constant folding
The JIT guards a property load with CheckStructure and may replace the load with the constant value it observed, assuming the guard makes that value safe.
Transition / property-replacement watchpoint
Watchpoints that fire when a watched structure transitions or a property is replaced, invalidating compiled code; an unwatched intermediate structure escapes them.

Root Cause Analysis

This fixes a concurrent-compiler type confusion in JavaScriptCore’s DFG constant-property folding — the bug behind an exploited-in-the-wild attack (bug 267134), shipped as two commits (64714692 adds the safety check; 66f60dea simplifies it). DFG/FTL compile on a background thread while the mutator keeps running. Graph::tryGetConstantProperty tries to constant-fold a property load guarded by a CheckStructure, e.g. CheckStructure O, S1|S3; GetByOffset O, offset. Pre-patch it simply took the object’s cellLock, read object->structure(), checked structureSet.contains(structure), and returned object->getDirectConcurrently(cellLock, structure, offset) — i.e. it folded whatever value the property held at the moment the background compiler observed it. The problem: with a live structure transition S1 -> S2 -> S3, the compiler can observe the object at an intermediate structure and fold the property value seen there, while WebKit only registered transition watchpoints for the structures in the CheckStructure set (S1 and S3), not the intermediate S2. An attacker mutating the object concurrently can make the compiler bake in the S2-era value, then transition O to S3 before the code runs; CheckStructure(S1|S3) then passes for S3, but the code uses the stale value folded from S2 — a type confusion (the JIT treats attacker-chosen bytes as a trusted constant, e.g. a pointer).

The fix reworks tryGetConstantProperty: it reads the value under the cell lock, then only keeps the constant fold if EITHER every structure in the CheckStructure set is watched (structure->dfgShouldWatch() for all — any transition then invalidates the compilation), OR the set contains exactly one structure (structureSet.size()==1), because with a single required structure there is no way to change the property while keeping that structure without firing the property-replacement watchpoint that invalidates the code; otherwise it bails (returns JSValue()). The first commit additionally recorded the object/offset/value/structure in a new DesiredObjectProperties and re-validated it on the main thread at end of compilation; the second commit removes DesiredObjectProperties, since the single-structure case is already covered by the property-replacement watchpoint.

The restored invariant is that the DFG only constant-folds a property when a watchpoint guarantees the folded value cannot go stale before the code runs.

Key insight
DFG constant-folding trusted whatever property value the concurrent compiler observed, but only structures in the CheckStructure set were watched — a value folded from an unwatched intermediate structure during a live transition stayed valid after the object reached a checked structure. The fix folds only when a watchpoint guarantees the value cannot go stale.

Attack Path

  1. Set up a foldable property load Run JS with a hot property access on an object so the DFG/FTL compiles a CheckStructure + GetByOffset it wants to constant-fold.
  2. Race the background compiler While compilation runs on another thread, drive the object through a structure transition chain (S1 -> S2 -> S3) so the compiler observes and folds the property value at an intermediate structure S2 that is not watched.
  3. Reach a checked structure Finish transitioning the object to S3 (a member of the CheckStructure set) before the compiled code executes.
  4. Use the stale value CheckStructure(S1|S3) passes for S3, but the code uses the constant folded from S2 — a type confusion where attacker-controlled bytes are treated as a trusted constant.
  5. Escalate to code execution Leverage the confused value (e.g. a fake object/pointer) into arbitrary read/write and code execution in the WebContent process.

Impact Assessment

A critical, exploited-in-the-wild type confusion in the WebContent process: by racing the background compiler against a structure transition, an attacker makes the JIT bake a stale/attacker-chosen value in as a trusted constant, then passes CheckStructure with a different structure. This yields a controllable type-confusion primitive — treating attacker bytes as a pointer/constant — a classic and reliable route to arbitrary read/write and remote code execution in WebContent.

Changed Functions

FunctionChangeNotes
Graph::tryGetConstantProperty
Source/JavaScriptCore/dfg/DFGGraph.cpp
modified Only keeps a constant-folded property if all CheckStructure structures are watched (dfgShouldWatch) or the set has exactly one structure; otherwise bails, closing the concurrent-transition stale-value fold.
DesiredObjectProperties (added then removed)
Source/JavaScriptCore/dfg/DFGDesiredObjectProperties.h
added Commit 64714692 added it to record JSObject*/offset/value/structure and re-check on the main thread (areStillValidOnMainThread); commit 66f60dea removes it since the single-structure case is covered by the property-replacement watchpoint.
Plan::isStillValidOnMainThread / cancel
Source/JavaScriptCore/dfg/DFGPlan.cpp
modified Wires the end-of-compilation main-thread validity check (and its teardown) for the recorded object properties in the first commit.

Files Changed

  • Source/JavaScriptCore/JavaScriptCore.xcodeproj/project.pbxproj
  • Source/JavaScriptCore/Sources.txt
  • Source/JavaScriptCore/dfg/DFGDesiredObjectProperties.cpp
  • Source/JavaScriptCore/dfg/DFGDesiredObjectProperties.h
  • Source/JavaScriptCore/dfg/DFGGraph.cpp
  • Source/JavaScriptCore/dfg/DFGPlan.cpp
  • Source/JavaScriptCore/dfg/DFGPlan.h

Audit Directions

  • Same function: concurrent reads
    Audit tryGetConstantProperty and other getDirectConcurrently / concurrent object reads in DFGGraph for folding values without proving every relevant structure is watched or uniquely constrained.
  • Watchpoint coverage vs CheckStructure sets
    Review DFG/FTL optimizations that assume a CheckStructure set makes an observed value safe; confirm intermediate/transitional structures are watched (dfgShouldWatch) or the set is size 1.
  • Concurrent-compiler invariants
    Grep for cellLock()/getDirectConcurrently and main-thread revalidation (isStillValidOnMainThread) to find places relying on background-observed state that the mutator can change before execution.
diff --git a/Source/WebCore/ChangeLog b/Source/WebCore/ChangeLog
index 399d9430452c..3a1a74aa194a 100644
--- a/Source/WebCore/ChangeLog
+++ b/Source/WebCore/ChangeLog
@@ -1,3 +1,15 @@
+2020-09-15  Zalan Bujtas  <zalan@apple.com>
+
+        [LFC][FFC] 'display: flex' generates a flex container box that is block-level when placed in flow layout
+        https://bugs.webkit.org/show_bug.cgi?id=216596
+
+        Reviewed by Simon Fraser.
+
+        See https://www.w3.org/TR/css-flexbox-1/#flex-containers.
+
+        * layout/layouttree/LayoutBox.cpp:
+        (WebCore::Layout::Box::isBlockLevelBox const):
+
 2020-09-15  Zalan Bujtas  <zalan@apple.com>
 
         [LFC] Use isBlockBox() in Box::establishesBlockFormattingContext
diff --git a/Source/WebCore/layout/layouttree/LayoutBox.cpp b/Source/WebCore/layout/layouttree/LayoutBox.cpp
index ae3b1a4e1369..6f2f203834cf 100644
--- a/Source/WebCore/layout/layouttree/LayoutBox.cpp
+++ b/Source/WebCore/layout/layouttree/LayoutBox.cpp
@@ -295,7 +295,7 @@ bool Box::isBlockLevelBox() const
 {
     // Block level elements generate block level boxes.
     auto display = m_style.display();
-    return display == DisplayType::Block || display == DisplayType::ListItem || display == DisplayType::Table;
+    return display == DisplayType::Block || display == DisplayType::ListItem || display == DisplayType::Table || display == DisplayType::Flex;
 }
 
 bool Box::isBlockBox() const
Loading diff…

Original Bug Report

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