Overview

Critical
Severity
CVSS
No
Exploited ITW
Fixed
Fix Status
ImpactUse after free in Aura
DescriptionUse after free in Aura
ComponentAura
Bug ClassUAF
Tracker516427761
Fix commitb1a95e642207 (chromium/src) +65/-4
CISA KEVNot listed
CreditedGoogle
Disclosed2026-08-25

Changed Functions

FunctionChangeNotes
switch
ui/aura/window.cc
modified
ScopedDeleteBlocker
ui/aura/window.h
modified
if
ui/aura/window.h
modified
DeleteOnHierarchyChangingObserver
ui/aura/window_unittest.cc
modified
TEST_F
ui/aura/window_unittest.cc
modified
DeleteOnVisibilityChangedObserver
ui/aura/window_unittest.cc
modified

Files Changed

  • ui/aura/window.cc
  • ui/aura/window.h
  • ui/aura/window_unittest.cc
From b1a95e642207618fbc409a73ab5ad0d8b4ce42ac Mon Sep 17 00:00:00 2001
From: Mitsuru Oshima <[email protected]>
Date: Tue, 21 Jul 2026 23:08:37 -0700
Subject: [PATCH] Block the deletion of new/old_parent, and receiver window

during the window hierachy change notifications.

Fixed: 516427761
Test: covered by unittest
Change-Id: I72aaf6c940ed8883eecaa3efac3db3fe0d20d333
Reviewed-on: https://chromium-review.googlesource.com/c/chromium/src/+/8127340
Commit-Queue: Mitsuru Oshima <[email protected]>
Reviewed-by: Achuith Bhandarkar <[email protected]>
Cr-Commit-Position: refs/heads/main@{#1666030}
---

diff --git a/ui/aura/window.cc b/ui/aura/window.cc
index fa0951a..80574027 100644
--- a/ui/aura/window.cc
+++ b/ui/aura/window.cc
@@ -1501,7 +1501,12 @@
 
 void Window::NotifyWindowHierarchyChange(
     const WindowObserver::HierarchyChangeParams& params) {
-  ScopedDeleteBlocker blocker(this);
+
+  // Block deletion of old_parent and new_parent across all target
+  // sub-tree callbacks to ensure neither parent is destroyed before
+  // NotifyWindowHierarchyChangeUp() runs.
+  ScopedDeleteBlocker old_blocker(params.old_parent);
+  ScopedDeleteBlocker new_blocker(params.new_parent);
 
   params.target->NotifyWindowHierarchyChangeDown(params);
   switch (params.phase) {
@@ -1533,6 +1538,8 @@
 
 void Window::NotifyWindowHierarchyChangeAtReceiver(
     const WindowObserver::HierarchyChangeParams& params) {
+  ScopedDeleteBlocker blocker(this);
+
   WindowObserver::HierarchyChangeParams local_params = params;
   local_params.receiver = this;
 
diff --git a/ui/aura/window.h b/ui/aura/window.h
index ae8efc79..f743126 100644
--- a/ui/aura/window.h
+++ b/ui/aura/window.h
@@ -127,15 +127,22 @@
   // A helper class to ensure that the Window is not deleted while it is
   // notifying observers or doing other operations where re-entrant deletion
   // would be problematic. Attempting to delete the Window while a
-  // ScopedDeleteBlocker is active will cause a crash.
+  // ScopedDeleteBlocker is active will cause a crash. This is no-op if
+  // if nullptr is passed.
   class ScopedDeleteBlocker {
    public:
     explicit ScopedDeleteBlocker(aura::Window* window) : window_(window) {
-      window_->delete_block_count_++;
+      if (window_) {
+        window_->delete_block_count_++;
+      }
     }
     ScopedDeleteBlocker(const ScopedDeleteBlocker&) = delete;
     ScopedDeleteBlocker& operator=(const ScopedDeleteBlocker&) = delete;
-    ~ScopedDeleteBlocker() { window_->delete_block_count_--; }
+    ~ScopedDeleteBlocker() {
+      if (window_) {
+        window_->delete_block_count_--;
+      }
+    }
     raw_ptr<aura::Window> window_;
   };
 
diff --git a/ui/aura/window_unittest.cc b/ui/aura/window_unittest.cc
index 1324b2d..4c4538b 100644
--- a/ui/aura/window_unittest.cc
+++ b/ui/aura/window_unittest.cc
@@ -3595,6 +3595,53 @@
   EXPECT_TRUE(weak_window);
 }
 
+// WindowObserver implementation that deletes the observed window in
+// OnWindowHierarchyChanging().
+class DeleteOnHierarchyChangingObserver : public WindowObserver {
+ public:
+  explicit DeleteOnHierarchyChangingObserver(Window* window) : window_(window) {
+    window_->AddObserver(this);
+  }
+
+  DeleteOnHierarchyChangingObserver(const DeleteOnHierarchyChangingObserver&) =
+      delete;
+  DeleteOnHierarchyChangingObserver& operator=(
+      const DeleteOnHierarchyChangingObserver&) = delete;
+
+  ~DeleteOnHierarchyChangingObserver() override {
+    CHECK(window_);
+    window_->RemoveObserver(this);
+  }
+
+  // WindowObserver:
+  void OnWindowHierarchyChanging(const HierarchyChangeParams& params) override {
+    Window* window = window_;
+    window_ = nullptr;
+    window->RemoveObserver(this);
+    // This will fail with CHECK.
+    delete window;
+  }
+
+ private:
+  raw_ptr<Window> window_;
+};
+
+TEST_F(WindowDeathTest, DeleteReceiverInOnWindowHierarchyChanging) {
+  std::unique_ptr<Window> parent = std::make_unique<Window>(nullptr);
+  parent->Init(ui::LAYER_NOT_DRAWN);
+  std::unique_ptr<Window> child = std::make_unique<Window>(nullptr);
+  child->Init(ui::LAYER_NOT_DRAWN);
+  Window* grandchild = new Window(nullptr);
+  grandchild->Init(ui::LAYER_NOT_DRAWN);
+  child->AddChild(grandchild);
+
+  // |grandchild| is notified as a receiver while recursing down through the
+  // subtree rooted at |child|; deleting it from within the notification must
+  // not be allowed.
+  DeleteOnHierarchyChangingObserver observer(grandchild);
+  EXPECT_DEATH(parent->AddChild(child.get()), "");
+}
+
 // WindowObserver implementation that deletes a window in
 // OnWindowVisibilityChanged().
 class DeleteOnVisibilityChangedObserver : public WindowObserver {
Loading diff…

Regression Test / PoC

shipped with the fix
diff --git a/ui/aura/window_unittest.cc b/ui/aura/window_unittest.cc
index 1324b2d..4c4538b 100644
--- a/ui/aura/window_unittest.cc
+++ b/ui/aura/window_unittest.cc
@@ -3595,6 +3595,53 @@
   EXPECT_TRUE(weak_window);
 }
 
+// WindowObserver implementation that deletes the observed window in
+// OnWindowHierarchyChanging().
+class DeleteOnHierarchyChangingObserver : public WindowObserver {
+ public:
+  explicit DeleteOnHierarchyChangingObserver(Window* window) : window_(window) {
+    window_->AddObserver(this);
+  }
+
+  DeleteOnHierarchyChangingObserver(const DeleteOnHierarchyChangingObserver&) =
+      delete;
+  DeleteOnHierarchyChangingObserver& operator=(
+      const DeleteOnHierarchyChangingObserver&) = delete;
+
+  ~DeleteOnHierarchyChangingObserver() override {
+    CHECK(window_);
+    window_->RemoveObserver(this);
+  }
+
+  // WindowObserver:
+  void OnWindowHierarchyChanging(const HierarchyChangeParams& params) override {
+    Window* window = window_;
+    window_ = nullptr;
+    window->RemoveObserver(this);
+    // This will fail with CHECK.
+    delete window;
+  }
+
+ private:
+  raw_ptr<Window> window_;
+};
+
+TEST_F(WindowDeathTest, DeleteReceiverInOnWindowHierarchyChanging) {
+  std::unique_ptr<Window> parent = std::make_unique<Window>(nullptr);
+  parent->Init(ui::LAYER_NOT_DRAWN);
+  std::unique_ptr<Window> child = std::make_unique<Window>(nullptr);
+  child->Init(ui::LAYER_NOT_DRAWN);
+  Window* grandchild = new Window(nullptr);
+  grandchild->Init(ui::LAYER_NOT_DRAWN);
+  child->AddChild(grandchild);
+
+  // |grandchild| is notified as a receiver while recursing down through the
+  // subtree rooted at |child|; deleting it from within the notification must
+  // not be allowed.
+  DeleteOnHierarchyChangingObserver observer(grandchild);
+  EXPECT_DEATH(parent->AddChild(child.get()), "");
+}
+
 // WindowObserver implementation that deletes a window in
 // OnWindowVisibilityChanged().
 class DeleteOnVisibilityChangedObserver : public WindowObserver {
Loading diff…

Original Bug Report

reported by [email protected]

Potential UAF in aura::Window notification traversal due to synchronous window destruction

Project Fortify, an experimental security project, has identified the following potential security issue. If you’re a feature owner CC-ed on this bug, please do your best to review these reports. Please see https://chromium.googlesource.com/chromium/src/+/main/docs/security/ai-generated-security-bugs-faq.md for more information.

Overview: A potential Use-After-Free (UAF) vulnerability exists in the browser process within aura::Window notification methods. Observer callbacks can synchronously destroy the Window object before the methods attempt to read its children vector to instantiate a WindowTracker. This allows reading freed heap memory and can lead to a wild memory write in the browser process.

Affected files:

  • ui/aura/window.cc

Estimated timestamp from git blame: 2026-04-03

Root Cause

In ui/aura/window.cc, three recursive traversal functions are potentially vulnerable to a Use-After-Free (UAF) on the this pointer:

  • Window::NotifyRemovingFromRootWindow (at ui/aura/window.cc:1224)
  • Window::NotifyAddedToRootWindow (at ui/aura/window.cc:1236)
  • Window::NotifyWindowHierarchyChangeDown (at ui/aura/window.cc:1263)

These functions fail to establish a WindowTracker to track the liveness of this prior to executing their observer loops (unlike the fully hardened sibling NotifyWindowVisibilityChangedDown at ui/aura/window.cc:1314). If an observer callback synchronously deletes the window this, returning from the observer loop to initialize WindowTracker tracker(children_) results in a UAF read of the freed children_ member vector.

Potential Trigger Steps

Please note that these are potential trigger sequences identified via static code flow analysis; our automated tooling does not currently have the capability to execute code or provide a dynamic Proof of Concept.

  1. A window state modification (e.g., closing a panel or removing a tab) invokes Window::RemoveChildImpl on W’s parent (ui/aura/window.cc:1138).
  2. RemoveChildImpl detects that the root window is changing and invokes child->NotifyRemovingFromRootWindow(new_root_window) on W (ui/aura/window.cc:1146).
  3. Inside Window::NotifyRemovingFromRootWindow (ui/aura/window.cc:1224), the method begins iterating through observers_ and invokes observer.OnWindowRemovingFromRootWindow(this, new_root).
  4. One of the registered observers is the root window’s WindowEventDispatcher (ui/aura/window_event_dispatcher.cc). This invokes OnWindowHidden (ui/aura/window_event_dispatcher.cc:362), which calls invisible->CleanupGestureState().
  5. Window::CleanupGestureState() invokes env->gesture_recognizer()->CancelActiveTouches(this) (ui/aura/window.cc:1356).
  6. The GestureRecognizer cancels active touches and dispatches a touch-cancel event (ui::EventType::kTouchCancelled) to W’s delegate.
  7. The delegate synchronously deletes the window (e.g., calling delete window), deallocating W and its member vector children_.
  8. Control returns to Window::NotifyRemovingFromRootWindow. The range-based loop exits, and the instruction WindowTracker tracker(children_) attempts to access the deallocated children_ member vector of this, triggering the Use-After-Free.

Impact

This vulnerability runs in the unsandboxed Browser process. Furthermore, because this involves an implicit this dereference of an in-object member vector rather than a raw pointer field, MiraclePtr does not mitigate this access. If the freed heap slot is reclaimed before the vector constructor executes, an attacker can leverage this to trigger a wild memory write when WindowTracker::Add invokes window->AddObserver(this) on a reclaimed pointer.

Ensure that a WindowTracker is instantiated to track the liveness of this before executing observer loops in all three affected functions, and abort further processing if this is deleted. For example, implement the following pattern used in NotifyWindowVisibilityChangedDown:

void Window::NotifyRemovingFromRootWindow(Window* new_root) {
  if (frame_sink_id_.is_valid())
    UnregisterFrameSinkId();

  WindowTracker this_tracker;
  this_tracker.Add(this);

  for (WindowObserver& observer : observers_)
    observer.OnWindowRemovingFromRootWindow(this, new_root);

  if (!this_tracker.Contains(this))
    return;

  WindowTracker tracker(children_);
  while (!tracker.windows().empty()) {
    tracker.Pop()->NotifyRemovingFromRootWindow(new_root);
  }
}

Evaluated with Chrome root at commit: a2bea94528f4bd6cc57739c43fa3bb890b8367d3


Results so far have been promising, but there can be wrong deductions. Feel free to adjust as follows:

  • If you are familiar with the severity guidelines, you may adjust the severity.
  • If this is a false positive, and there’s no work to be done, please close as WAI.
  • If there is work to do here but not a vulnerability, please change the issue type to Task/Bug/FR.

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