CVE-2025-43440
Overview
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.
Attack Path
- 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.
- Force DFG compilation Warm the function so it tiers up to the DFG JIT, causing ConstantFoldingPhase to iterate every block including the unreachable one.
- 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.
- Observe the crash Downstream phases or assertions detect the inconsistency, yielding an unexpected process crash (the stated impact).
- (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
Changed Functions
| Function | Change | Notes |
|---|---|---|
ConstantFoldingPhase::foldConstantsSource/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 foldingIn 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 gateGrep 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 parallelsCheck 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 simplificationAudit 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.