Released November 3, 2025

CVE-2025-43440

CVSS 6.5 severity medium CWE-79 vector CVSS:3.1/AV:N/AC:L/PR:N/UI:R/S:U/C:N/I:N/A:H

Overview

Impact:

Description: Processing maliciously crafted web content may lead to an unexpected process crash

Researchers: Nan Wang (@eternalsakura13)

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.

Patch Details

diff --git a/Source/JavaScriptCore/dfg/DFGConstantFoldingPhase.cpp b/Source/JavaScriptCore/dfg/DFGConstantFoldingPhase.cpp
index 3b6f1d6efd6d..42dd2369f8d4 100644
--- a/Source/JavaScriptCore/dfg/DFGConstantFoldingPhase.cpp
+++ b/Source/JavaScriptCore/dfg/DFGConstantFoldingPhase.cpp
@@ -114,6 +114,10 @@ public:
 private:
     bool foldConstants(BasicBlock* block)
     {
+        // CFAUnreachable, skip
+        if (!block->cfaHasVisited)
+            return false;
+
         bool changed = false;
         m_state.beginBasicBlock(block);
         for (unsigned indexInBlock = 0; indexInBlock < block->size(); ++indexInBlock) {

Background

Advisory: Apple Security Advisory

Attribute Value
CVE CVE-2025-43440
Bugzilla 298126
Component JSC
Bug Class LogicError
Severity medium
Commit 78b31d59089f6578…
Advisory Apple Advisory

Terminology

  • 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

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.

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.

Changed Functions

Function File Change Note
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 Files

  • Source/JavaScriptCore/dfg/DFGConstantFoldingPhase.cpp