CVE-2026-64715
Overview
Background
- B3 / FTL JIT
- JavaScriptCore’s optimizing compiler tier; B3 is its low-level SSA IR on which optimizations like strength reduction and CSE run.
- PureCSE
- Common-subexpression elimination for pure values; it keys Value* pointers by ValueKey so a later identical computation can reuse an earlier one.
- deleteValue
- Removes a Value from the procedure. If a deleted Value is still referenced by the CSE map, a later lookup returns a dangling pointer.
Root Cause Analysis
This fixes a use-after-free in JavaScriptCore’s B3 optimizer between strength reduction’s select specialization and pure common-subexpression elimination (PureCSE). When reduceStrength specializes a Select that reaches a Check within selectSpecializationBound, it splits the block and processes the values between the source and the Check; Void values in that range (such as an intermediate Check) are removed from the block with m_proc.deleteValue(value).
Before the fix, those deleted Values were not removed from the PureCSE map (B3PureCSE::m_map stores raw Value* keyed by ValueKey), so a later CSE lookup (findMatch) on a matching key could return a pointer to an already-deleted Value and the compiler would dereference it — a use-after-free during compilation.
The fix adds PureCSE::remove(key, value) and calls it before deleteValue for the Void case; it also computes the ValueKey BEFORE cloneValue mutates the Value (the added comment notes this), so the correct key is removed.
The restored invariant is that the PureCSE map never retains a pointer to a freed Value. The new B3 unit test testCheckSelectAndDeadCheckCSE reproduces the pattern (a Select with a constant arm, an intermediate Check, and a later Check on the same condition that is a CSE candidate). Established by the diff.
Attack Path
- Run JS that reaches the FTL/B3 JIT Hot JavaScript is compiled through B3 so reduceStrength and PureCSE run.
- Hit the select-specialization pattern The compiled IR contains a Select with a constant arm reaching a Check within the specialization bound, plus a later Check on the same condition (a CSE candidate).
- Delete a Void value still in PureCSE Specialization deletes the intermediate Void Check but, pre-patch, leaves its entry in the PureCSE map.
- Use-after-free via CSE lookup A later PureCSE::findMatch returns the freed Value pointer, and the compiler dereferences it, crashing (or worse) during compilation.
Impact Assessment
Changed Functions
| Function | Change | Notes |
|---|---|---|
PureCSE::removeSource/JavaScriptCore/b3/B3PureCSE.cpp |
added | New method that removes a specific Value from the matches list for a ValueKey so a deleted Value is no longer reachable via CSE. |
ReduceStrength select specialization (Void-value handling)Source/JavaScriptCore/b3/B3ReduceStrength.cpp |
modified | Computes the ValueKey before cloneValue mutates the Value and calls m_pureCSE.remove(key, value) before m_proc.deleteValue(value), preventing a dangling Value* in the PureCSE map. |
PureCSE::remove (declaration)Source/JavaScriptCore/b3/B3PureCSE.h |
modified | Declares the new remove(const ValueKey&, Value*) method. |
Files Changed
Source/JavaScriptCore/b3/B3PureCSE.cppSource/JavaScriptCore/b3/B3PureCSE.hSource/JavaScriptCore/b3/B3ReduceStrength.cppSource/JavaScriptCore/b3/testb3.hSource/JavaScriptCore/b3/testb3_1.cppSource/JavaScriptCore/b3/testb3_6.cpp
Audit Directions
- deleteValue without CSE purgeAudit every deleteValue()/removal site in B3 passes to confirm the Value is also removed from PureCSE (and any other pointer-keyed side table) before deletion.
- Keys computed after mutationLook for ValueKey (or hash-key) computations performed after cloneValue/replaceWith mutates the underlying Value; the key must be captured before mutation.