CVE-2026-79290
Overview
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.ccui/aura/window.hui/aura/window_unittest.cc
Patch
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 {
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.