CVE-2026-65331
Overview
Background
- Integer Range Optimization (IRO)
- A DFG phase that proves integer value ranges to eliminate redundant overflow/bounds checks.
- Relationship / range proof
- IRO derives a node’s range from relationships to other nodes; those other nodes’ computations underpin the proof.
- NodeMustGenerate / DCE
- A flag that forces a node to be kept; without it dead-code elimination can remove a producer whose result an eliminated check silently depended on.
Root Cause Analysis
This fixes an unsafe check elimination in JavaScriptCore’s DFG Integer Range Optimization (IRO) phase: it removed overflow/bounds checks based on a proven integer range but let the nodes that PROVED that range be deleted, so the runtime condition IRO relied on could vanish. IRO’s rangeFor(node) computes a [min, max] range for a node by intersecting the Relationships known about it, and IRO uses that range to, for example, convert a checked arithmetic op to Arith::Unchecked.
Pre-patch, rangeFor returned only the numeric bounds and did not track WHICH relationship established each bound; the producer nodes behind those relationships were not kept alive. A later phase (dead-code elimination) could then remove those producer nodes because nothing referenced them, even though IRO had already dropped a check that depended on the values they compute — so the unchecked op runs without the guarantee the removed producers provided, allowing the very integer overflow / out-of-bounds the check was meant to catch.
The fix augments rangeFor to return RangeBound { value, proof } where proof points to the Relationship that set each bound, and adds pinRangeBounds/pinRangeBoundProof which mark the upstream left/right nodes of those proof relationships with NodeMustGenerate before a checked op is flipped to unchecked, so DCE keeps the range-proving producers alive.
The restored invariant is that when IRO removes a check based on a range proof, the computations establishing that proof are pinned and cannot be eliminated. (A FIXME notes NodeMustGenerate is a conservative, sticky pin pending reference-counted effect edges.)
Attack Path
- Reach the DFG IRO phase Run JS whose arithmetic/array indexing the DFG optimizes with Integer Range Optimization.
- Prove a range, drop a check IRO proves a value’s range from relationships and converts a checked op to Arith::Unchecked (removing the overflow/bounds check).
- Delete the proof producers DCE removes the now-unreferenced nodes that established the range, since IRO didn’t pin them.
- Overflow / out-of-bounds The unchecked op runs without the guaranteeing computation, producing the integer overflow / OOB the check prevented — memory corruption in WebContent.
Impact Assessment
Changed Functions
| Function | Change | Notes |
|---|---|---|
IntegerRangeOptimization rangeFor / RangeBoundSource/JavaScriptCore/dfg/DFGIntegerRangeOptimizationPhase.cpp |
modified | Returns RangeBound { value, proof } tracking the Relationship that set each min/max bound instead of bare int32 bounds. |
pinRangeBounds / pinRangeBoundProofSource/JavaScriptCore/dfg/DFGIntegerRangeOptimizationPhase.cpp |
added | Marks the upstream left/right nodes of a bound's proof relationship with NodeMustGenerate before flipping a checked op to unchecked, so DCE cannot remove the range-proving producers. |
Files Changed
JSTests/stress/arith-abs-checked-input-range.jsSource/JavaScriptCore/dfg/DFGIntegerRangeOptimizationPhase.cpp
Audit Directions
- Range-proof lifetimeAudit IRO (and other range/relationship-based check-removal) for cases that drop a check based on a proof whose producer nodes are not pinned or otherwise kept alive.
- Check-to-unchecked conversionsGrep the DFG for Arith::Unchecked / check-removal transforms and verify the values justifying them survive later DCE.