Overview

Critical
Severity
CVSS
No
Exploited ITW
Fixed
Fix Status
ImpactUse after free in Blink
DescriptionUse after free in Blink
ComponentBlink
Bug ClassUAF
Tracker497830330
Fix commit9674640f1813 (chromium/src) +2/-1
CISA KEVNot listed
CreditedGoogle
Disclosed2026-05-12

Changed Functions

FunctionChangeNotes
for
third_party/blink/renderer/core/html/html_slot_element.cc
modified

Files Changed

  • third_party/blink/renderer/core/html/html_slot_element.cc
From 9674640f1813e9f10b97a9270108079d2fe525aa Mon Sep 17 00:00:00 2001
From: Keishi Hattori <keishi@google.com>
Date: Thu, 02 Apr 2026 19:17:37 -0700
Subject: [PATCH] Fix realloc while iterating in HTMLSlotElement::DetachLayoutTree

Bug: 497830330
Change-Id: Id29714983245bcb58f3f3c2174f4b098c963d948
Reviewed-on: https://chromium-review.googlesource.com/c/chromium/src/+/7718078
Commit-Queue: Keishi Hattori <keishi@chromium.org>
Reviewed-by: Takashi Sakamoto <tasak@google.com>
Cr-Commit-Position: refs/heads/main@{#1609626}
---

diff --git a/third_party/blink/renderer/core/html/html_slot_element.cc b/third_party/blink/renderer/core/html/html_slot_element.cc
index 9347ee4..deb9ad3e 100644
--- a/third_party/blink/renderer/core/html/html_slot_element.cc
+++ b/third_party/blink/renderer/core/html/html_slot_element.cc
@@ -365,7 +365,8 @@
 void HTMLSlotElement::DetachLayoutTree(bool performing_reattach) {
   if (SupportsAssignment()) {
     auto* host = OwnerShadowHost();
-    const HeapVector<Member<Node>>& flat_tree_children = assigned_nodes_;
+    // Defensive copy to prevent UAF from sync recalc. See crbug.com/497830330.
+    const HeapVector<Member<Node>> flat_tree_children = assigned_nodes_;
     for (auto& node : flat_tree_children) {
       // Don't detach the assigned node if the node is no longer a child of the
       // host.
Loading diff…

Original Bug Report

reported by vm...@google.com

Use-After-Free in HTMLSlotElement::DetachLayoutTree during AX update

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

Overview: A potential Use-After-Free exists in HTMLSlotElement::DetachLayoutTree when iterating over assigned nodes. On platforms where AriaModalPrunesAXTree is enabled (macOS/Android), detaching a node can trigger an accessibility update that synchronously recalculates slot assignments. This recalculation clears the vector being iterated, leaving a dangling iterator that leads to a Use-After-Free.

Affected files:

  • third_party/blink/renderer/core/html/html_slot_element.cc
  • third_party/blink/renderer/core/dom/node.cc
  • third_party/blink/renderer/core/dom/slot_assignment.cc
  • third_party/blink/renderer/modules/accessibility/ax_object_cache_impl.cc
  • third_party/blink/renderer/core/dom/flat_tree_traversal.cc

Estimated timestamp from git blame: 2021-02-16

Disclaimer

Please note: This vulnerability report is based on a static analysis of the Chromium codebase using an automated agent. While the code paths and logic have been meticulously verified step-by-step, we have not yet developed or executed a working Proof of Concept (PoC) exploit. Therefore, this issue is described as a ‘potential’ vulnerability.

Vulnerability Details

A potential Use-After-Free (UAF) exists in HTMLSlotElement::DetachLayoutTree(). The method uses a C++ range-based for loop to iterate over the assigned_nodes_ vector:

void HTMLSlotElement::DetachLayoutTree(bool performing_reattach) {
  if (SupportsAssignment()) {
    auto* host = OwnerShadowHost();
    const HeapVector<Member<Node>>& flat_tree_children = assigned_nodes_;
    for (auto& node : flat_tree_children) {
      if (host == node->parentNode())
        node->DetachLayoutTree(performing_reattach);
    }
  }
  // ...
}

The range-based for loop implicitly caches the begin() and end() iterators of the assigned_nodes_ backing buffer. However, the call to node->DetachLayoutTree() can trigger re-entrant code that clears this vector.

Specifically, when detaching a node, Node::DetachLayoutTree calls cache->RemoveSubtree(this) to update the accessibility (AX) tree. On macOS and Android, the AriaModalPrunesAXTree setting is enabled by default. If the node being detached is currently tracked as the active_aria_modal_dialog_, AXObjectCacheImpl::Remove() invokes UpdateActiveAriaModalDialog(FocusedNode()) to find the new active modal.

If the newly focused node is a slottable element in the light DOM, UpdateActiveAriaModalDialog uses FlatTreeTraversal::Parent() to walk up the flat tree. This traversal calls Node::AssignedSlot(), which checks if (GetDocument().IsInSlotAssignmentRecalc()).

The Crux of the Vulnerability: During DOM removal (ContainerNode::RemoveChild), Blink uses a SlotAssignmentRecalcForbiddenScope to prevent recalcs. However, in Release builds, this scope is defined as an empty class and does not increment the document’s slot_assignment_recalc_depth_ counter (it only enforces safety via DCHECKs in Debug builds).

Consequently, IsInSlotAssignmentRecalc() returns false in Release builds. If the slot assignment is marked as dirty, AssignedSlot() proceeds to synchronously call root->GetSlotAssignment().RecalcAssignment().

This recalculation invokes HTMLSlotElement::WillRecalcAssignedNodes(), which calls assigned_nodes_.clear(). In Blink’s HeapVector, clear() calls ShrinkCapacity(0), immediately freeing the backing buffer via cppgc::subtle::FreeUnreferencedObject().

When the stack unwinds back to the range-based for loop in HTMLSlotElement::DetachLayoutTree, the iterator is left dangling. Accessing the next element reads from freed memory, leading to a virtual method call (node->DetachLayoutTree) on a potentially attacker-controlled object, providing a primitive for Remote Code Execution (RCE).

Potential Attack Scenario

An attacker could theoretically trigger this sequence via the following steps:

  1. Create a shadow host with an open ShadowRoot containing a <slot name="s1">.
  2. In the host’s light DOM, insert two children assigned to the slot: Child A (<div role="dialog" aria-modal="true">) and Child B (<div tabindex="0">).
  3. Focus Child A. Accessibility updates track it as the active aria-modal dialog.
  4. Remove the shadow host from the DOM (host.remove()).
  5. During the removal setup, RemoveFocusedElementOfSubtree fires a synchronous blur event on Child A.
  6. The attacker intercepts the blur event. Inside the handler, they append a new element to the host (setting needs_assignment_recalc_ = true on the shadow root) and focus Child B.
  7. When the detachment phase begins, Child A is detached first in the vulnerable loop.
  8. The AX tree update fires, searching for the new modal (now Child B), triggering a flat tree traversal.
  9. The traversal forces a synchronous slot recalculation, which clears the assigned_nodes_ vector and frees its backing buffer.
  10. The loop in HTMLSlotElement::DetachLayoutTree advances to Child B, dereferencing the dangling iterator and using the freed memory.

Suggested Fix

The most robust fix is to iterate over a copy of the assigned_nodes_ vector within HTMLSlotElement::DetachLayoutTree, rather than maintaining iterators over a collection that might be modified synchronously during detachment:

void HTMLSlotElement::DetachLayoutTree(bool performing_reattach) {
  if (SupportsAssignment()) {
    auto* host = OwnerShadowHost();
    HeapVector<Member<Node>> assigned_nodes_copy = assigned_nodes_;
    for (auto& node : assigned_nodes_copy) {
      if (host == node->parentNode())
        node->DetachLayoutTree(performing_reattach);
    }
  }
  // ...
}

Alternatively, SlotAssignmentRecalcForbiddenScope could be modified to increment the slot_assignment_recalc_depth_ counter in Release builds as well, preventing the re-entrant recalculation entirely.

Evaluated with Chrome root at commit: a9cbf6e8b275fe4147435aa905f3b7f5a656f5f0


Results from 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. And please feel free to reach out to me directly if you have concerns or feedback on the project.

View on issue tracker