CVE-2026-19172
Overview
Changed Functions
| Function | Change | Notes |
|---|---|---|
ifui/wm/core/transient_window_manager.cc |
modified | |
DeleteParentOnHideObserverui/wm/core/transient_window_manager_unittest.cc |
modified | |
ifui/wm/core/transient_window_manager_unittest.cc |
modified |
Files Changed
ui/wm/core/transient_window_manager.ccui/wm/core/transient_window_manager_unittest.cc
Patch
From c17110d74d39df22037424c68fb620884d68f7e2 Mon Sep 17 00:00:00 2001 From: Mitsuru Oshima <[email protected]> Date: Mon, 27 Jul 2026 12:11:59 -0700 Subject: [PATCH] Fix Use-After-Free in TransientWindowManager hide cascade TransientWindowManager could be freed while updating child visibility during parent hide cascade. This happens if the child has an observer that synchronously deletes the parent. This adds weak ptr check inside the iteration to prevent use after free. TAG=agy CONV=6a632318-b810-4c6e-a0e2-1135bf67758d Fixed: 537838324 Change-Id: Ic5f4b3c32a03f75e95ce7c26dd2bc7f41b9ae097 Reviewed-on: https://chromium-review.googlesource.com/c/chromium/src/+/8143759 Commit-Queue: Mitsuru Oshima <[email protected]> Reviewed-by: Achuith Bhandarkar <[email protected]> Cr-Commit-Position: refs/heads/main@{#1668869} --- diff --git a/ui/wm/core/transient_window_manager.cc b/ui/wm/core/transient_window_manager.cc index ef261c6..a4b6b26 100644 --- a/ui/wm/core/transient_window_manager.cc +++ b/ui/wm/core/transient_window_manager.cc @@ -238,6 +238,9 @@ if (window_ != window) return; + // Hiding transient children may delete parent as well. + base::WeakPtr<TransientWindowManager> weak_this = weak_factory_.GetWeakPtr(); + // If the window has transient children, updates the transient children's // visiblity as well. // WindowTracker is used because child window @@ -246,6 +249,10 @@ while (!tracker.windows().empty()) GetOrCreate(tracker.Pop())->UpdateTransientChildVisibility(visible); + if (!weak_this) { + return; + } + // Remember the show request in |show_on_parent_visible_| and hide it again // if the following conditions are met // - |parent_controls_visibility| is set to true. diff --git a/ui/wm/core/transient_window_manager_unittest.cc b/ui/wm/core/transient_window_manager_unittest.cc index 28c7de54..9e901c0b 100644 --- a/ui/wm/core/transient_window_manager_unittest.cc +++ b/ui/wm/core/transient_window_manager_unittest.cc @@ -728,4 +728,60 @@ child->Show(); } +namespace { + +class DeleteParentOnHideObserver : public aura::WindowObserver { + public: + DeleteParentOnHideObserver(aura::Window* child, + std::unique_ptr<aura::Window> parent) + : child_(child), parent_(std::move(parent)) { + child_->AddObserver(this); + } + ~DeleteParentOnHideObserver() override { + if (child_) { + child_->RemoveObserver(this); + } + } + // WindowObserver: + void OnWindowVisibilityChanged(aura::Window* window, + bool visible) override { + if (window == child_ && !visible) { + parent_.reset(); + } + } + void OnWindowDestroyed(aura::Window* window) override { + if (window == child_) { + child_ = nullptr; + } + } + + private: + raw_ptr<aura::Window> child_; + std::unique_ptr<aura::Window> parent_; +}; + +} // namespace + +// Tests that there is no UAF if a window is destroyed while its transient +// children are being hidden in a cascade update. +TEST_F(TransientWindowManagerTest, + ParentDestroyedDuringTransientChildHideCascade) { + std::unique_ptr<aura::Window> parent = CreateTestWindow( + {.parent = root_window(), .bounds = {100, 100}, .window_id = 0}); + aura::Window* parent_ptr = parent.get(); + + std::unique_ptr<aura::Window> child_ptr = CreateTestWindow( + {.parent = root_window(), .bounds = {100, 100}, .window_id = 1}); + aura::Window* child = child_ptr.release(); + + TransientWindowManager::GetOrCreate(child)->set_parent_controls_visibility( + true); + AddTransientChild(parent_ptr, child); + + auto observer = + std::make_unique<DeleteParentOnHideObserver>(child, std::move(parent)); + + parent_ptr->Hide(); +} + } // namespace wm
Regression Test / PoC
diff --git a/ui/wm/core/transient_window_manager_unittest.cc b/ui/wm/core/transient_window_manager_unittest.cc
index 28c7de54..9e901c0b 100644
--- a/ui/wm/core/transient_window_manager_unittest.cc
+++ b/ui/wm/core/transient_window_manager_unittest.cc
@@ -728,4 +728,60 @@
child->Show();
}
+namespace {
+
+class DeleteParentOnHideObserver : public aura::WindowObserver {
+ public:
+ DeleteParentOnHideObserver(aura::Window* child,
+ std::unique_ptr<aura::Window> parent)
+ : child_(child), parent_(std::move(parent)) {
+ child_->AddObserver(this);
+ }
+ ~DeleteParentOnHideObserver() override {
+ if (child_) {
+ child_->RemoveObserver(this);
+ }
+ }
+ // WindowObserver:
+ void OnWindowVisibilityChanged(aura::Window* window,
+ bool visible) override {
+ if (window == child_ && !visible) {
+ parent_.reset();
+ }
+ }
+ void OnWindowDestroyed(aura::Window* window) override {
+ if (window == child_) {
+ child_ = nullptr;
+ }
+ }
+
+ private:
+ raw_ptr<aura::Window> child_;
+ std::unique_ptr<aura::Window> parent_;
+};
+
+} // namespace
+
+// Tests that there is no UAF if a window is destroyed while its transient
+// children are being hidden in a cascade update.
+TEST_F(TransientWindowManagerTest,
+ ParentDestroyedDuringTransientChildHideCascade) {
+ std::unique_ptr<aura::Window> parent = CreateTestWindow(
+ {.parent = root_window(), .bounds = {100, 100}, .window_id = 0});
+ aura::Window* parent_ptr = parent.get();
+
+ std::unique_ptr<aura::Window> child_ptr = CreateTestWindow(
+ {.parent = root_window(), .bounds = {100, 100}, .window_id = 1});
+ aura::Window* child = child_ptr.release();
+
+ TransientWindowManager::GetOrCreate(child)->set_parent_controls_visibility(
+ true);
+ AddTransientChild(parent_ptr, child);
+
+ auto observer =
+ std::make_unique<DeleteParentOnHideObserver>(child, std::move(parent));
+
+ parent_ptr->Hide();
+}
+
} // namespace wm
Original Bug Report
Potential Use-After-Free in TransientWindowManager::OnWindowVisibilityChanged
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 TransientWindowManager::OnWindowVisibilityChanged due to a lack of liveness verification after propagating visibility updates to transient children. Hiding a transient child window can trigger synchronous destruction of its parent window, which in turn deallocates the owned TransientWindowManager instance. Subsequent member reads and writes on the freed manager can result in a Use-After-Free in the browser process.
Affected files:
ui/wm/core/transient_window_manager.ccui/wm/core/transient_window_manager.h
Estimated timestamp from git blame: 2014-11-12
Root Cause Analysis
In ui/wm/core/transient_window_manager.cc, the method TransientWindowManager::OnWindowVisibilityChanged propagates visibility changes to transient children:
// ui/wm/core/transient_window_manager.cc:236-268
void TransientWindowManager::OnWindowVisibilityChanged(Window* window, bool visible) {
if (window_ != window)
return;
...
aura::WindowTracker tracker(transient_children_);
while (!tracker.windows().empty())
GetOrCreate(tracker.Pop())->UpdateTransientChildVisibility(visible); // Hides/shows children
// NO liveness check for |this| before accessing member variables
if (ignore_visibility_changed_event_ ||
!transient_parent_ || !parent_controls_visibility_) {
return;
}
...
}
TransientWindowManager is owned by aura::Window (the parent window window_) as a window property (kPropertyKey defined with DEFINE_OWNED_UI_CLASS_PROPERTY_KEY). Therefore, when the parent window_ is destroyed, its property is cleared, destroying the TransientWindowManager instance synchronously.
During the child-update loop at line 247, hiding a transient child window can trigger synchronous destruction of the parent window (for example, if a child popup/bubble loses focus or capture, triggering associated handlers that close the parent widget). When the parent window is destroyed, its TransientWindowManager (this) is freed. When control returns from the loop, line 254 unconditionally accesses members of the freed TransientWindowManager instance, leading to a Use-After-Free.
Security Impact
Since this UAF occurs in the browser process, an attacker who is able to manipulate window hierarchy and visibility transitions (e.g., from a compromised renderer process or an untrusted ChromeOS guest Wayland client) could potentially exploit this memory corruption to achieve Remote Code Execution (RCE) in the unsandboxed browser process.
Because the UAF is on the implicit this pointer, standard MiraclePtr protections do not mitigate the vulnerability (the this pointer is a bare C++ pointer on the stack, and MiraclePtr only protects raw_ptr members). This allows an attacker who reallocates the freed block to hijack control flow.
Potential Steps to Trigger
Note: These are potential steps as our tooling does not currently support running code to dynamically verify this flow.:
- Construct a transient window hierarchy containing a parent window
Pand a transient child windowCthat holds event/mouse capture. - Bind a capture-lost or gesture-cancellation handler to
C(or its widget) that closes/destroysP’s widget synchronously upon capture loss. - Hide
P, which dispatches a visibility update throughTWM(P)->OnWindowVisibilityChanged. - The loop updates child visibility by calling
C->Hide(), triggering capture release or gesture cleanup onC. - The capture-lost handler synchronously destroys the parent window
P. This executes~WindowonP, clearing properties and freeingTWM(P)whileTWM(P)::OnWindowVisibilityChangedis still on the stack. - The loop finishes and execution reaches line 254, attempting to read
ignore_visibility_changed_event_and other member variables from the freedTWM(P)memory.
Suggested Fix
To prevent this issue, verify the liveness of this before accessing any member variables after the child-visibility update loop. This can be accomplished by keeping a weak pointer to the manager:
void TransientWindowManager::OnWindowVisibilityChanged(Window* window, bool visible) {
if (window_ != window)
return;
base::WeakPtr<TransientWindowManager> weak_this = weak_factory_.GetWeakPtr();
aura::WindowTracker tracker(transient_children_);
while (!tracker.windows().empty())
GetOrCreate(tracker.Pop())->UpdateTransientChildVisibility(visible);
if (!weak_this)
return;
if (ignore_visibility_changed_event_ ||
!transient_parent_ || !parent_controls_visibility_) {
return;
}
...
}
Evaluated with Chrome root at commit: 5a99d0c5d2ec6c066f5131e7868ff637440cc3dc
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.