CVE-2026-43715
Overview
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.
Attack Path
- 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.
- Grab the FontFace Get the corresponding FontFace object from document.fonts.
- 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.
- Trigger client iteration Call face.load(); its promise machinery reads .then and iterateClients invokes callbacks; the getter removes a client from the WeakHashSet.
- Use-after-free A later callback in the same iteration is invoked on the removed, torn-down client — memory corruption in WebContent.
Impact Assessment
Changed Functions
| Function | Change | Notes |
|---|---|---|
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.txtLayoutTests/fonts/font-face-load-crash.htmlSource/WebCore/css/CSSFontFace.cpp
Audit Directions
- Other CSSFontFace iterationsReview other loops in CSSFontFace/CSSFontFaceSet that copyToVectorOf clients and invoke callbacks without re-checking contains().
- copy-then-callback across scriptgrep WebCore for
copyToVectorOffeeding a loop that runs a callback which can execute script; each is a candidate for a mid-iteration removal UAF. - Font loading promise pathsAudit FontFace/FontFaceSet promise resolution (load(), ready) where JS accessors like then can run during client notification.