High patch-now ~ plausible CVSS 8.8 chrome UAF 📄 Reporter bug report 🔧 Commit mapped

Overview

High
Severity
8.8
CVSS
No
Exploited ITW
Fixed
Fix Status
ImpactUse after free in SVG
DescriptionUse after free in SVG
ComponentSVG
Sub-componentTerracotta-Phase-1
Bug ClassUAF
Action tierpatch-now — corruption primitive, web-reachable in default config (plausible) — upheld by independent review by AI
Computed by PatchGap's deterministic exploitability ruler from the fixing commit. patch-now = RCE-class & attacker-reachable in a default config, or exploited in the wild; watch = real security signal below that bar; noise = no security relevance.
Independent review by AIPlausible
Reviewer notes: Commit is labelled a speculative fix; the diff only swaps a const reference for a copied HeapHashSet snapshot. No reentrant path that mutates InstancesForElement() or frees an instance is shown, yet the claim states it as fact.
A second, independent AI reviewer (not a human) re-derived this analysis from the fixing commit and the vendor advisory, looking for errors.
Tracker496284584
Fix commit122679a5ea41 (chromium/src) +2/-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
CreditedGoogle
Reported2026-03-25 (per vendor advisory)
Fix landed2026-03-27 (public git)
Disclosed2026-05-05
Patch gap39 days — fix public in source before the advisory

Background

SVG use/shadow instances
an SVG <use> element clones a referenced element into a shadow tree, and Blink keeps the set of these cloned instances so attribute changes on the original can be mirrored to each clone.
`InstancesForElement()`
returns the live collection of an element’s shadow instances as a HeapHashSet<WeakMember<SVGElement>>, i.e. a set of garbage-collector-tracked weak pointers to SVGElements.
`WeakMember`
an Oilpan reference that does not keep its target alive and is cleared when the target is collected, meaning entries in the set can disappear if a GC or DOM mutation occurs mid-use.
`SetAttributeWithoutValidation()`
applies an attribute value to an element, which can run further synchronization, DOM mutation, or script-observable side effects.

Root Cause Analysis

Verify before building a PoC

This analysis is consistent with the fixing commit but not fully provable from it: part of the vulnerable path lives in code the fix commit does not touch. Confirm the trigger and the sink against the source before relying on it for exploit development.
What the commit does not establish: Commit is labelled a speculative fix; the diff only swaps a const reference for a copied HeapHashSet snapshot. No reentrant path that mutates InstancesForElement() or frees an instance is shown, yet the claim states it as fact.

SVGElement::SynchronizeAttributeInShadowInstances bound a const reference directly to the live set returned by InstancesForElement() and iterated it in place, calling instance->SetAttributeWithoutValidation(name, value) on each entry. Because the loop body mutates DOM state, it can re-enter code that modifies the very instance collection being iterated — adding or removing shadow instances, or clearing WeakMember entries — which invalidates the iterator or frees an element while the loop still references it, the classic use-after-free / iterator-invalidation pattern. The commit, described as a “speculative fix,” replaces the reference with a by-value copy, HeapHashSet<WeakMember<SVGElement>> instances = InstancesForElement();, and iterates that snapshot so the loop is no longer affected by mutations to the underlying collection.

The fix works because the copy decouples the iteration from any reentrant modification of the element’s live instance set.

Key insight
The core mistake was iterating a live mutable collection by reference while the loop body (SetAttributeWithoutValidation) could reentrantly modify that same collection; copying the set into a local snapshot before iterating removes the aliasing and makes the traversal safe.

Attack Path

  1. Set up `<use>`-based shadow instances A page constructs an SVG element referenced by one or more <use> elements so it has a non-empty shadow-instance set tracked by InstancesForElement().
  2. Trigger attribute synchronization An attribute change on the original element causes SynchronizeAttributeInShadowInstances to iterate the live instance set.
  3. Reenter and mutate the set Applying the attribute via SetAttributeWithoutValidation on an instance causes further DOM mutation or synchronization that modifies or collects entries of the same instance collection during iteration (inferred mechanism; the commit calls it a “potential issue”).
  4. Use the stale reference The still-in-progress loop continues over an invalidated iterator or a freed SVGElement, yielding a use-after-free consistent with the advisory’s “use after free in SVG.”

Impact Assessment

An attacker-controlled page can, through SVG <use> instance synchronization, cause iteration over a collection that is mutated mid-loop, leading to a use-after-free in the renderer process; the advisory rates this high severity and does not report in-the-wild exploitation. The patch is explicitly a speculative fix and does not demonstrate a concrete controlled read/write or any escalation beyond the UAF, so exploitation primitives are inferred rather than established. The precondition is a document with SVG <use> shadow instances whose attribute synchronization can be driven from script or markup.

Changed Functions

FunctionChangeNotes
for
third_party/blink/renderer/core/svg/svg_element.cc
modified

Files Changed

  • third_party/blink/renderer/core/svg/svg_element.cc

Audit Directions

  • Reference-iterating a live Heap collection
    search for const HeapHashSet<...>& / const HeapVector<...>& (or similar) bound to a getter’s return value and iterated while the loop body makes DOM, style, or attribute mutations that could modify that same collection.
  • `WeakMember` iteration with side-effecting bodies
    flag loops over HeapHashSet<WeakMember<...>> where the body can trigger GC or object removal, since weak entries may be cleared mid-traversal; copying or snapshotting is the safe pattern.
  • Synchronization/mirroring routines
    review other SVG and shadow-tree synchronization functions that fan an operation out across an element’s instance or observer lists while allowing reentrancy into list-modifying code.
From 122679a5ea41c567989005e90ae0bcd27d590b95 Mon Sep 17 00:00:00 2001
From: Kalvin Lee <[email protected]>
Date: Thu, 26 Mar 2026 21:29:40 -0700
Subject: [PATCH] Terracotta-Phase-1: Copy vector in `SVGElement::SynchronizeAttributeInShadowInstances()`

Speculative fix for potential issue (more context in the bug).

Bug: 496284584
Change-Id: I4906a3269afe37aec524f36cd7aa64327327f9c5
Reviewed-on: https://chromium-review.googlesource.com/c/chromium/src/+/7703714
Commit-Queue: Kalvin Lee <[email protected]>
Reviewed-by: Keishi Hattori <[email protected]>
Cr-Commit-Position: refs/heads/main@{#1605966}
---

diff --git a/third_party/blink/renderer/core/svg/svg_element.cc b/third_party/blink/renderer/core/svg/svg_element.cc
index 611df88b..be0394d 100644
--- a/third_party/blink/renderer/core/svg/svg_element.cc
+++ b/third_party/blink/renderer/core/svg/svg_element.cc
@@ -1166,8 +1166,8 @@
 void SVGElement::SynchronizeAttributeInShadowInstances(
     const QualifiedName& name,
     const AtomicString& value) {
-  const HeapHashSet<WeakMember<SVGElement>>& set = InstancesForElement();
-  for (SVGElement* instance : set) {
+  HeapHashSet<WeakMember<SVGElement>> instances = InstancesForElement();
+  for (SVGElement* instance : instances) {
     instance->SetAttributeWithoutValidation(name, value);
   }
 }
Loading diff…

Original Bug Report

reported by [email protected]

Use-After-Free in SVGElement::SynchronizeAttributeInShadowInstances via sync blur

Project Fortify, an experimental security project, has identified the following potential security issue.

Overview: Iterating over InstancesForElement() in SVGElement::SynchronizeAttributeInShadowInstances uses an unprotected iterator. Synchronizing the removal of a tabindex attribute can trigger a synchronous blur event, allowing JavaScript to mutate the instances set, rehash its backing store, and cause a Use-After-Free when the loop resumes.

Affected files:

  • third_party/blink/renderer/core/svg/svg_element.cc
  • third_party/blink/renderer/core/svg/svg_svg_element.cc

Estimated timestamp from git blame: 2025-10-08

Summary

A potential highly reliable Use-After-Free (UAF) exists in SVGElement::SynchronizeAttributeInShadowInstances within Blink’s SVG implementation. The vulnerability is caused by iterating over a live HeapHashSet (InstancesForElement()) without snapshotting it. A synchronous DOM event (blur) can be triggered during this iteration, allowing an attacker to execute JavaScript, mutate the HeapHashSet, and force a rehash. This explicitly frees the collection’s backing store, leaving the C++ iterator with a dangling pointer that is subsequently dereferenced.

Vulnerability Details

The vulnerable method is SVGElement::SynchronizeAttributeInShadowInstances:

void SVGElement::SynchronizeAttributeInShadowInstances(
    const QualifiedName& name,
    const AtomicString& value) {
  const HeapHashSet<WeakMember<SVGElement>>& set = InstancesForElement();
  for (SVGElement* instance : set) {
    instance->SetAttributeWithoutValidation(name, value);
  }
}

The range-based for loop uses a HashTableConstIterator which holds a raw pointer directly into the HeapHashSet’s contiguous backing store array. Concurrent modification checks (container_modifications_ == container_->Modifications()) exist but are guarded by DCHECK_IS_ON() and are entirely compiled out in Release builds.

When instance->SetAttributeWithoutValidation is called to synchronize the removal of the tabindex attribute (value is null), it propagates down to Element::AttributeChanged. If the shadow instance is currently focused, removing tabindex makes it unfocusable (!IsFocusable()), which causes Element::AttributeChanged to synchronously call blur().

The blur() method synchronously dispatches a blur event. An attacker can attach an event listener to capture this event, suspending the C++ iteration and executing arbitrary JavaScript.

Inside the JavaScript handler, the attacker can add new <use> elements to the DOM that reference the original <svg> target. This causes SVGElement::AddInstance to be called, inserting new instances into the HeapHashSet. If enough elements are added to exceed the load factor, the HeapHashSet performs a rehash.

During rehashing, the old backing store is explicitly freed via cppgc::subtle::FreeUnreferencedObject. The attacker can then use standard Oilpan heap spraying techniques within the same JavaScript execution to reclaim the freed backing store memory and populate it with fake SVGElement pointers.

When the JavaScript handler finishes and control returns to the C++ for loop, the iterator advances using its dangling pointer. It retrieves an attacker-controlled SVGElement* and calls instance->SetAttributeWithoutValidation, leading to virtual method calls on the fake object and granting arbitrary Remote Code Execution (RCE) in the renderer process.

Potential Trigger Steps

Note: This sequence has been formulated by an AI agent and represents a theoretical trigger path; no working PoC has been executed yet.

  1. Create an <svg id="target" tabindex="0"> element.
  2. Create a <use href="#target"> element, which instantiates a shadow instance.
  3. Register a blur event listener on the window (capturing phase).
  4. Programmatically or manually focus the rendered <use> element (the shadow instance inherits the tabindex).
  5. Use JavaScript to remove the tabindex from the target: document.getElementById('target').removeAttribute('tabindex');.
  6. The SynchronizeAttributeInShadowInstances loop begins in C++.
  7. The tabindex removal is synchronized to the shadow instance, synchronously triggering the blur event.
  8. In the JS blur handler, append many new <use href="#target"> elements to the DOM and force a synchronous layout (e.g., by reading offsetTop) to ensure the shadow trees are built and added to InstancesForElement().
  9. Still in the JS handler, allocate many objects of the same size as the backing store to reclaim the explicitly freed memory.
  10. The JS handler returns, the C++ loop resumes, and the UAF is triggered.

Suggested Fix

Do not iterate directly over the live HeapHashSet if the loop body can execute script. Instead, snapshot the collection into a local HeapVector before iterating:

void SVGElement::SynchronizeAttributeInShadowInstances(
    const QualifiedName& name,
    const AtomicString& value) {
  HeapVector<Member<SVGElement>> instances;
  CopyToVector(InstancesForElement(), instances);
  for (SVGElement* instance : instances) {
    if (instance) {
      instance->SetAttributeWithoutValidation(name, value);
    }
  }
}

Evaluated with Chrome root at commit: a3f5fcb392f2902650ca2b71820e7e418787e18b


Results so far have been promising, but there can be wrong deductions. If this proves to be a false positive, please close as WAI; data from false positives will be used to improve accuracy over time. Please feel free to reach out to me if you have concerns or feedback.

View on issue tracker