Medium CVSS 6.5 webkit UAF 🔧 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 B3
Bug ClassUAF
Tracker316347
Fix commit1d5c10e2f9c8 (WebKit/WebKit) +96/-1
CWECWE-416 (Use-after-free)
CVSS vectorCVSS:3.1/AV:N/AC:L/PR:N/UI:R/S:U/C:N/I:N/A:H
CISA KEVNot listed
CreditedHossein Lotfi (@hosselot) of TrendAI Zero Day Initiative
Disclosed2026-08-17

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.

Key insight
Two optimizer passes disagreed about a Value’s lifetime: strength reduction deleted a Void value while PureCSE still held a raw pointer to it. The fix adds PureCSE::remove() and calls it before deleteValue — and crucially computes the ValueKey before cloneValue mutates the Value, so the correct entry is purged.

Attack Path

  1. Run JS that reaches the FTL/B3 JIT Hot JavaScript is compiled through B3 so reduceStrength and PureCSE run.
  2. 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).
  3. Delete a Void value still in PureCSE Specialization deletes the intermediate Void Check but, pre-patch, leaves its entry in the PureCSE map.
  4. 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

A use-after-free during JIT compilation of attacker-controlled JavaScript. Compile-time UAFs in JSC are high-value: the attacker shapes both the freed object and the reuse, and the corruption occurs inside the engine’s own compiler, historically a reliable route to arbitrary read/write and code execution within the WebContent process.

Changed Functions

FunctionChangeNotes
PureCSE::remove
Source/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.cpp
  • Source/JavaScriptCore/b3/B3PureCSE.h
  • Source/JavaScriptCore/b3/B3ReduceStrength.cpp
  • Source/JavaScriptCore/b3/testb3.h
  • Source/JavaScriptCore/b3/testb3_1.cpp
  • Source/JavaScriptCore/b3/testb3_6.cpp

Audit Directions

  • deleteValue without CSE purge
    Audit 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 mutation
    Look for ValueKey (or hash-key) computations performed after cloneValue/replaceWith mutates the underlying Value; the key must be captured before mutation.

Original Bug Report

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