Medium CVSS 6.5 webkit Logic Error 🔧 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 DFG
Bug ClassLogic Error
Tracker298126
Fix commit78b31d59089f (WebKit/WebKit) +4/-0
CWECWE-79 (Cross-site scripting)
CVSS vectorCVSS:3.1/AV:N/AC:L/PR:N/UI:R/S:U/C:N/I:N/A:H
CISA KEVNot listed
CreditedNan Wang (@eternalsakura13)
Disclosed2025-11-03

Background

DFG JIT
JavaScriptCore’s Data Flow Graph optimizing compiler tier that speculates on observed types and inserts guards, recompiling if speculation fails.
Control Flow Analysis / AbstractInterpreter (CFA)
A DFG dataflow pass that propagates abstract value sets through the graph and marks each block with cfaHasVisited when it is reachable and its state has been computed.
ConstantFoldingPhase
A DFG phase that uses per-block abstract state to fold nodes into constants, eliminate proven-redundant checks, and simplify the IR.
cfaHasVisited
A per-BasicBlock flag set by the CFA indicating the block is reachable and its abstract-value state is valid to consume; false means unreachable/uninitialized.
beginBasicBlock
AbstractInterpreter/InPlaceAbstractState entry point that loads a block’s abstract state so a phase can reason about the values live at its head.

Root Cause Analysis

The patch adds a single guard to ConstantFoldingPhase::foldConstants in the DFG (Data Flow Graph) optimizing JIT: if the basic block has not been visited by the abstract interpreter (Control Flow Analysis), it now returns false immediately with the comment ‘CFAUnreachable, skip’. DFG optimization runs the AbstractInterpreter/CFA over the graph to compute, per block, the set of abstract values flowing through it; the ConstantFoldingPhase then walks each block, calls m_state.beginBasicBlock(block), and uses that abstract state to replace nodes with constants, strip redundant type checks, and simplify control flow.

The invariant that was violated is that constant folding must only consume abstract state for blocks the CFA actually reached and populated (block->cfaHasVisited == true). For a block the CFA marked unreachable, cfaHasVisited is false and the block’s abstract-value state is not valid/initialized, yet the old code still called beginBasicBlock and folded over its nodes. Operating on that stale or default abstract state can cause folding decisions that are inconsistent with reality — e.g. treating a value as a proven constant/type when it is not — which corrupts the IR and later manifests as an assertion failure or process crash.

The fix restores the invariant by skipping CFA-unreachable blocks entirely, matching how other DFG phases gate work on cfaHasVisited. The specific miscompilation this prevents is not shown in the diff (the folding logic that mishandled the invalid state lives in the unchanged remainder of the function and in AbstractInterpreter), so the exact incorrect fold is an inference; what the commit establishes is that folding a never-visited block was possible and is now forbidden.

Key insight
DFG optimization phases must only trust abstract state for blocks the CFA actually reached; constant-folding a block with cfaHasVisited == false reasons over uninitialized abstract values and corrupts the IR.

Attack Path

  1. Author JS that produces an unreachable block Craft a function whose control-flow, after earlier DFG phases (e.g. constant propagation, CFG simplification), leaves a basic block that the abstract interpreter proves unreachable so its cfaHasVisited stays false while the block still exists in the graph.
  2. Force DFG compilation Warm the function so it tiers up to the DFG JIT, causing ConstantFoldingPhase to iterate every block including the unreachable one.
  3. Trigger folding on invalid abstract state The phase calls beginBasicBlock and folds nodes in the unreachable block using uninitialized/stale abstract values, producing IR that is internally inconsistent.
  4. Observe the crash Downstream phases or assertions detect the inconsistency, yielding an unexpected process crash (the stated impact).
  5. (Inference) Pursue a type-confusion primitive If the invalid fold could remove a genuine type/bounds check on a reachable path, it could in principle yield a type confusion; the diff does not demonstrate this, so any such escalation is inferred, not shown by the commit.

Impact Assessment

The commit demonstrates an IR-correctness bug in the DFG whose established effect is an unexpected crash in the WebContent process when constant folding consumes the invalid abstract state of an unreachable block. Whether it can be turned into a stronger primitive depends on whether an invalid fold can delete a needed type/bounds check on a path that actually executes; the diff does not show such a case, so the grounded assessment is a controlled crash, with type-confusion escalation being speculative. It is confined to the JavaScript engine in the WebContent process and does not itself escape the sandbox.

Changed Functions

FunctionChangeNotes
ConstantFoldingPhase::foldConstants
Source/JavaScriptCore/dfg/DFGConstantFoldingPhase.cpp
modified Added an early 'return false' when !block->cfaHasVisited, so the constant folder no longer runs on basic blocks the abstract interpreter proved unreachable (whose abstract state is invalid).

Files Changed

  • Source/JavaScriptCore/dfg/DFGConstantFoldingPhase.cpp

Audit Directions

  • Same-file: other consumers of abstract state in folding
    In DFGConstantFoldingPhase.cpp verify every path that reads m_state after beginBasicBlock is now unreachable for unvisited blocks, and check helper lambdas invoked from foldConstants for independent entry that bypasses the new guard.
  • Other DFG phases lacking a cfaHasVisited gate
    Grep DFG phases (DFG*Phase.cpp) for ’m_state.beginBasicBlock’ / ‘forNode(’ usage that is not preceded by a ‘if (!block->cfaHasVisited) continue/return’ check; those phases may reason over invalid abstract state on unreachable blocks.
  • FTL abstract-interpreter parallels
    Check the FTL/B3 lowering and any abstract-value consumers for the analogous ‘reachable-only’ assumption, grepping for cfaHasVisited, isReachable, and beginBasicBlock to confirm unreachable blocks are uniformly skipped.
  • Block liveness after CFG simplification
    Audit phases that run after CFGSimplificationPhase/constant propagation which can create newly-unreachable-but-still-present blocks, ensuring each subsequent CFA-dependent pass re-checks cfaHasVisited rather than assuming all blocks in the graph are live.

Original Bug Report

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