Medium CVSS 8.8 webkit UAF 🔧 Commit mapped

Overview

Medium
Severity
8.8
CVSS
No
Exploited ITW
Fixed
Fix Status
DescriptionProcessing maliciously crafted web content may lead to memory corruption
ComponentWebCore CSS
Bug ClassUAF
Tracker313577
Fix commit5aedb82710ba (WebKit/WebKit) +22/-2
CWECWE-416 (Use-after-free)
CVSS vectorCVSS:3.1/AV:N/AC:L/PR:N/UI:R/S:U/C:H/I:H/A:H
CISA KEVNot listed
CreditedMilad Nasr and Nicholas Carlini with Claude, Anthropic
Disclosed2026-06-29

Background

CSSFontFace clients
Objects observing a CSSFontFace (e.g. font selectors / FontFace wrappers) tracked in a WeakHashSet<CSSFontFaceClient>.
copyToVectorOf snapshot
iterateClients copies the WeakHashSet into a Vector<Ref<>> so it can iterate while callbacks mutate the set; the Refs keep objects alive but not registered.
Re-entrancy via callbacks
A callback can run author script (FontFace promise/then hooks) that mutates the DOM and unregisters a client during iteration.
WeakHashSet::contains
A liveness/membership check the fix uses to confirm a client is still registered before calling it.

Root Cause Analysis

iterateClients() in CSSFontFace.cpp snapshots the live WeakHashSet<CSSFontFaceClient>& clients into a Vector<Ref<CSSFontFaceClient>> via copyToVectorOf and then calls callback(client) for each entry. Taking Ref<>s keeps the client objects alive for the duration, but it does not account for a client being unregistered (logically detached / torn down) by script that runs during one of the callbacks. A callback can synchronously execute author script — for example through a then accessor on FontFace during FontFace.load() — which mutates the DOM and removes a font-face client from the set (the layout test removes the <style> element mid-load). Because the loop iterates the pre-taken Vector, a client that has already been removed from the WeakHashSet and partially destroyed is still handed to callback, and operating on that removed/half-torn-down client is a use-after-free / memory corruption.

The fix adds if (clients.contains(client)) before each callback(client), so any client removed from the set during an earlier callback is skipped rather than invoked.

The restored invariant is that iterateClients only calls back clients that are still registered at the moment of the call, not merely those present when the snapshot was captured.

Key insight
Snapshotting clients into a Ref vector kept them alive but not registered; a client removed mid-iteration was still invoked. Re-checking set membership (clients.contains) before each callback is the fix.

Attack Path

  1. Register a font face Insert a <style> with @font-face { font-family: t; src: local(Helvetica); } and force layout so a CSSFontFace with clients exists.
  2. Grab the FontFace Get the corresponding FontFace object from document.fonts.
  3. Arm a re-entrant hook Object.defineProperty(FontFace.prototype, ’then’, { get() { document.getElementById(‘v’).remove(); document.body.offsetHeight; } }) so touching .then runs script that removes a client mid-iteration.
  4. Trigger client iteration Call face.load(); its promise machinery reads .then and iterateClients invokes callbacks; the getter removes a client from the WeakHashSet.
  5. Use-after-free A later callback in the same iteration is invoked on the removed, torn-down client — memory corruption in WebContent.

Impact Assessment

A re-entrancy use-after-free reachable from ordinary CSS @font-face plus the CSS Font Loading API. The higher CVSS (8.8) reflects that the attacker controls exactly when the client is removed (script in the then getter), yielding a groomable UAF that can be steered toward memory corruption rather than a mere crash, though escalation to code execution still needs heap shaping. Confined to the WebContent process. Reported by Milad Nasr and Nicholas Carlini with Claude, Anthropic.

Changed Functions

FunctionChangeNotes
iterateClients (file-local static)
Source/WebCore/css/CSSFontFace.cpp
modified Re-checks clients.contains(client) before invoking callback, so a client removed from the WeakHashSet during a prior callback is skipped instead of called on a stale object.

Files Changed

  • LayoutTests/fonts/font-face-load-crash-expected.txt
  • LayoutTests/fonts/font-face-load-crash.html
  • Source/WebCore/css/CSSFontFace.cpp

Audit Directions

  • Other CSSFontFace iterations
    Review other loops in CSSFontFace/CSSFontFaceSet that copyToVectorOf clients and invoke callbacks without re-checking contains().
  • copy-then-callback across script
    grep WebCore for copyToVectorOf feeding a loop that runs a callback which can execute script; each is a candidate for a mid-iteration removal UAF.
  • Font loading promise paths
    Audit FontFace/FontFaceSet promise resolution (load(), ready) where JS accessors like then can run during client notification.

Original Bug Report

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