CVE-2026-79290
Overview
Background
- `aura::Window`
- The core Aura UI object representing a node in the window hierarchy tree, carrying parent/child links and a list of observers.
- `ScopedDeleteBlocker`
- A stack-scoped helper that increments a target
Window’sdelete_block_count_in its constructor and decrements it in its destructor, turning any re-entrant delete of that window into aCHECKcrash instead of a silent free. - `WindowObserver::OnWindowHierarchyChanging`
- An observer callback fired during the “changing” phase of a reparent operation, before the hierarchy change is complete, into which arbitrary embedder code runs.
- `HierarchyChangeParams`
- The struct passed through the hierarchy-change notifications, holding raw pointers to
target,receiver,old_parent, andnew_parent.
Root Cause Analysis
When a window is reparented, Window::NotifyWindowHierarchyChange walked the subtree and delivered OnWindowHierarchyChanging to every observer, but its only guard was a single ScopedDeleteBlocker blocker(this) protecting the notifying window — it left params.old_parent, params.new_parent, and each per-node receiver unprotected. Because NotifyWindowHierarchyChangeDown recurses through the subtree and dispatches to receivers, a WindowObserver reached during that walk could synchronously delete its observed window (a receiver, or either parent) from inside OnWindowHierarchyChanging, freeing an object whose address the notification machinery still held in HierarchyChangeParams and continued to dereference.
The fix moves the receiver guard into Window::NotifyWindowHierarchyChangeAtReceiver as ScopedDeleteBlocker blocker(this) so each node is protected while it is being notified, and adds old_blocker/new_blocker in NotifyWindowHierarchyChange covering both parents across the whole subtree walk. To let those parent pointers (which may legitimately be null, e.g. an initial parent) be wrapped unconditionally, ScopedDeleteBlocker is made null-safe so a nullptr window is a no-op rather than a null-pointer crash. Together these convert a re-entrant use-after-free into a deterministic CHECK failure, which the new DeleteReceiverInOnWindowHierarchyChanging death test locks in.
ScopedDeleteBlocker coverage to the receiver and to old_parent/new_parent (made null-safe) so no participant in the notification can be freed mid-dispatch.Attack Path
- Install a malicious observer
Embedder-reachable code registers a
WindowObserveron a descendant window that will act as a receiver during an upcoming reparent, mirroring the test’sDeleteOnHierarchyChangingObserverongrandchild. - Trigger the reparent
A page or UI action causes a window hierarchy change (e.g.
AddChild), invokingNotifyWindowHierarchyChangeand the recursiveNotifyWindowHierarchyChangeDownwalk over the subtree. - Delete during the callback
When the observed receiver is notified, the observer’s
OnWindowHierarchyChangingsynchronouslydeletes that window while it is unprotected, freeing it out from under the in-flight notification. - Dangling dereference
The notification loop and
HierarchyChangeParamscontinue to use the freed receiver (or freedold_parent/new_parent) pointer, yielding a use-after-free the attacker can shape into a controlled crash or corruption.
Impact Assessment
aura::Window in the browser process, where Aura runs, enabling controlled freeing and reuse of window memory during hierarchy notifications. Exploitation requires the ability to install or reach a WindowObserver and induce a hierarchy change that notifies it, after which re-entrant deletion of the receiver or a parent triggers the flaw. Post-fix, such a deletion is caught as a CHECK/EXPECT_DEATH rather than being exploitable.Changed Functions
| Function | Change | Notes |
|---|---|---|
switchui/aura/window.cc |
modified | |
ScopedDeleteBlockerui/aura/window.h |
modified | |
ifui/aura/window.h |
modified | |
DeleteOnHierarchyChangingObserverui/aura/window_unittest.cc |
modified | |
TEST_Fui/aura/window_unittest.cc |
modified | |
DeleteOnVisibilityChangedObserverui/aura/window_unittest.cc |
modified |
Files Changed
ui/aura/window_unittest.ccui/aura/window.ccui/aura/window.h
Audit Directions
- Partial delete-blocker coverageAudit every notification or observer-dispatch path that uses
ScopedDeleteBlockerand confirm it guards all pointers it keeps live (receiver, parents, siblings), not just the notifyingthis. - Re-entrant deletion in observer callbacksReview other
WindowObservercallbacks such as visibility, bounds, and removal notifications for paths that dereference pointers after an embedder callback that could delete them. - Raw pointers in params structsScrutinize structs like
HierarchyChangeParamsthat carry rawWindow*fields across recursive dispatch, ensuring each referenced object’s lifetime is pinned for the full duration of the walk.
Patch
From b1a95e642207618fbc409a73ab5ad0d8b4ce42ac Mon Sep 17 00:00:00 2001
From: Mitsuru Oshima <oshima@chromium.org>
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 <oshima@chromium.org>
Reviewed-by: Achuith Bhandarkar <achuith@chromium.org>
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 {
Regression Test / PoC
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 {
Original Bug Report
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(atui/aura/window.cc:1224)Window::NotifyAddedToRootWindow(atui/aura/window.cc:1236)Window::NotifyWindowHierarchyChangeDown(atui/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.
- A window state modification (e.g., closing a panel or removing a tab) invokes
Window::RemoveChildImplonW’s parent (ui/aura/window.cc:1138). RemoveChildImpldetects that the root window is changing and invokeschild->NotifyRemovingFromRootWindow(new_root_window)onW(ui/aura/window.cc:1146).- Inside
Window::NotifyRemovingFromRootWindow(ui/aura/window.cc:1224), the method begins iterating throughobservers_and invokesobserver.OnWindowRemovingFromRootWindow(this, new_root). - One of the registered observers is the root window’s
WindowEventDispatcher(ui/aura/window_event_dispatcher.cc). This invokesOnWindowHidden(ui/aura/window_event_dispatcher.cc:362), which callsinvisible->CleanupGestureState(). Window::CleanupGestureState()invokesenv->gesture_recognizer()->CancelActiveTouches(this)(ui/aura/window.cc:1356).- The
GestureRecognizercancels active touches and dispatches a touch-cancel event (ui::EventType::kTouchCancelled) toW’s delegate. - The delegate synchronously deletes the window (e.g., calling
delete window), deallocatingWand its member vectorchildren_. - Control returns to
Window::NotifyRemovingFromRootWindow. The range-based loop exits, and the instructionWindowTracker tracker(children_)attempts to access the deallocatedchildren_member vector ofthis, 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.
Recommended Fix
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.