CVE-2026-10988
Overview
Changed Functions
| Function | Change | Notes |
|---|---|---|
ifui/views/widget/desktop_aura/desktop_window_tree_host_platform.cc |
modified | |
switchui/views/widget/desktop_aura/desktop_window_tree_host_platform.cc |
modified | |
CloseOnBoundsChangedWidgetObserverui/views/widget/desktop_aura/desktop_window_tree_host_platform_unittest.cc |
modified | |
DesktopWindowTreeHostPlatformTestui/views/widget/desktop_aura/desktop_window_tree_host_platform_unittest.cc |
modified | |
MaximizeBoundsChangeStubWindowui/views/widget/desktop_aura/desktop_window_tree_host_platform_unittest.cc |
modified | |
MaximizeBoundsChangePlatformWindowFactoryDelegateui/views/widget/desktop_aura/desktop_window_tree_host_platform_unittest.cc |
modified | |
MaximizeBoundsChangePlatformWindowFactoryDelegateui/views/widget/desktop_aura/desktop_window_tree_host_platform_unittest.cc |
modified |
Files Changed
ui/views/widget/desktop_aura/desktop_window_tree_host_platform.ccui/views/widget/desktop_aura/desktop_window_tree_host_platform_unittest.cc
Patch
From fef5694ce5f0c3ab4da4af32754ff54bcde3d0f0 Mon Sep 17 00:00:00 2001
From: Tom Anderson <thomasanderson@chromium.org>
Date: Tue, 26 May 2026 15:27:20 -0700
Subject: [PATCH] views: Fix use-after-free in DesktopWindowTreeHostPlatform Maximize/Show
DesktopWindowTreeHostPlatform::Maximize() and Show() can synchronously
notify observers/delegates during platform-level window operations,
which can in turn trigger synchronous host deletion. When this occurs,
subsequent code inside these methods continues executing with a freed
'this' pointer, leading to a UAF.
This CL adds base::WeakPtr guards inside Maximize() and Show() to safely
return early if the host is destroyed during platform operations,
matching the pattern used in SetFullscreen().
Fixed: 515465685
Change-Id: Ie8477d072fdf6775e83fbcd9c14ecfa1892ffc15
Reviewed-on: https://chromium-review.googlesource.com/c/chromium/src/+/7868863
Reviewed-by: Lei Zhang <thestig@chromium.org>
Auto-Submit: Thomas Anderson <thomasanderson@chromium.org>
Commit-Queue: Thomas Anderson <thomasanderson@chromium.org>
Commit-Queue: Lei Zhang <thestig@chromium.org>
Cr-Commit-Position: refs/heads/main@{#1636525}
---
diff --git a/ui/views/widget/desktop_aura/desktop_window_tree_host_platform.cc b/ui/views/widget/desktop_aura/desktop_window_tree_host_platform.cc
index 8ba7443..b958f49 100644
--- a/ui/views/widget/desktop_aura/desktop_window_tree_host_platform.cc
+++ b/ui/views/widget/desktop_aura/desktop_window_tree_host_platform.cc
@@ -463,9 +463,17 @@
platform_window()->Show(DetermineInactivity(show_state));
+ auto weak_ptr = weak_factory_.GetWeakPtr();
+ if (!weak_ptr) {
+ return;
+ }
+
switch (show_state) {
case ui::mojom::WindowShowState::kMaximized:
platform_window()->Maximize();
+ if (!weak_ptr) {
+ return;
+ }
if (!restore_bounds.IsEmpty()) {
// Enforce |restored_bounds_in_pixels_| since calling Maximize() could
// have reset it.
@@ -482,6 +490,10 @@
break;
}
+ if (!weak_ptr) {
+ return;
+ }
+
if (WidgetActivationDelegate::Get()) {
WidgetActivationDelegate::Get()->MaybeActivate(GetWidget(), false);
}
@@ -661,7 +673,11 @@
}
void DesktopWindowTreeHostPlatform::Maximize() {
+ auto weak_ptr = weak_factory_.GetWeakPtr();
platform_window()->Maximize();
+ if (!weak_ptr) {
+ return;
+ }
if (IsMinimized()) {
Show(ui::mojom::WindowShowState::kNormal, gfx::Rect());
}
diff --git a/ui/views/widget/desktop_aura/desktop_window_tree_host_platform_unittest.cc b/ui/views/widget/desktop_aura/desktop_window_tree_host_platform_unittest.cc
index 3307ffa..5bdaf338 100644
--- a/ui/views/widget/desktop_aura/desktop_window_tree_host_platform_unittest.cc
+++ b/ui/views/widget/desktop_aura/desktop_window_tree_host_platform_unittest.cc
@@ -156,6 +156,23 @@
base::ScopedObservation<Widget, WidgetObserver> observation_{this};
};
+class CloseOnBoundsChangedWidgetObserver : public WidgetObserver {
+ public:
+ explicit CloseOnBoundsChangedWidgetObserver(Widget* widget) {
+ observation_.Observe(widget);
+ }
+ ~CloseOnBoundsChangedWidgetObserver() override = default;
+
+ void OnWidgetBoundsChanged(Widget* widget,
+ const gfx::Rect& new_bounds) override {
+ observation_.Reset();
+ widget->CloseNow();
+ }
+
+ private:
+ base::ScopedObservation<Widget, WidgetObserver> observation_{this};
+};
+
} // namespace
class DesktopWindowTreeHostPlatformTest : public ViewsTestBase {
@@ -722,4 +739,64 @@
host_platform->GetContentWindow()->RemoveObserver(&observer);
}
+#if !BUILDFLAG(IS_FUCHSIA)
+class MaximizeBoundsChangeStubWindow : public ui::StubWindow {
+ public:
+ explicit MaximizeBoundsChangeStubWindow(ui::PlatformWindowDelegate* delegate,
+ gfx::AcceleratedWidget widget,
+ const gfx::Rect& bounds)
+ : StubWindow(delegate, false, bounds) {
+ InitDelegateWithWidget(delegate, widget);
+ }
+
+ void Maximize() override {
+ delegate()->OnBoundsChanged({/*origin_changed=*/true});
+ }
+};
+
+class MaximizeBoundsChangePlatformWindowFactoryDelegate
+ : public aura::WindowTreeHostPlatform::
+ PlatformWindowFactoryDelegateForTesting {
+ public:
+ MaximizeBoundsChangePlatformWindowFactoryDelegate() {
+ aura::WindowTreeHostPlatform::SetPlatformWindowFactoryDelegateForTesting(
+ this);
+ }
+ MaximizeBoundsChangePlatformWindowFactoryDelegate(
+ const MaximizeBoundsChangePlatformWindowFactoryDelegate&) = delete;
+ MaximizeBoundsChangePlatformWindowFactoryDelegate& operator=(
+ const MaximizeBoundsChangePlatformWindowFactoryDelegate&) = delete;
+ ~MaximizeBoundsChangePlatformWindowFactoryDelegate() override {
+ aura::WindowTreeHostPlatform::SetPlatformWindowFactoryDelegateForTesting(
+ nullptr);
+ }
+
+ std::unique_ptr<ui::PlatformWindow> Create(
+ aura::WindowTreeHostPlatform* host) override {
+ return std::make_unique<MaximizeBoundsChangeStubWindow>(
+ host, ++last_accelerated_widget_, gfx::Rect(100, 100, 100, 100));
+ }
+
+ gfx::AcceleratedWidget last_accelerated_widget_ = gfx::kNullAcceleratedWidget;
+};
+
+TEST_F(DesktopWindowTreeHostPlatformTest,
+ MaximizeSurvivesSynchronousCloseDuringBoundsChange) {
+ auto scoped_platform_window_factory_delegate =
+ std::make_unique<MaximizeBoundsChangePlatformWindowFactoryDelegate>();
+
+ std::unique_ptr<Widget> widget = CreateWidgetWithNativeWidget();
+ widget->Show();
+
+ auto* host_platform = DesktopWindowTreeHostPlatform::GetHostForWidget(
+ widget->GetNativeWindow()->GetHost()->GetAcceleratedWidget());
+ ASSERT_TRUE(host_platform);
+
+ CloseOnBoundsChangedWidgetObserver observer(widget.get());
+
+ // This should not crash or trigger UAF.
+ host_platform->Maximize();
+}
+#endif // !BUILDFLAG(IS_FUCHSIA)
+
} // namespace views
Regression Test / PoC
diff --git a/ui/views/widget/desktop_aura/desktop_window_tree_host_platform_unittest.cc b/ui/views/widget/desktop_aura/desktop_window_tree_host_platform_unittest.cc
index 3307ffa..5bdaf338 100644
--- a/ui/views/widget/desktop_aura/desktop_window_tree_host_platform_unittest.cc
+++ b/ui/views/widget/desktop_aura/desktop_window_tree_host_platform_unittest.cc
@@ -156,6 +156,23 @@
base::ScopedObservation<Widget, WidgetObserver> observation_{this};
};
+class CloseOnBoundsChangedWidgetObserver : public WidgetObserver {
+ public:
+ explicit CloseOnBoundsChangedWidgetObserver(Widget* widget) {
+ observation_.Observe(widget);
+ }
+ ~CloseOnBoundsChangedWidgetObserver() override = default;
+
+ void OnWidgetBoundsChanged(Widget* widget,
+ const gfx::Rect& new_bounds) override {
+ observation_.Reset();
+ widget->CloseNow();
+ }
+
+ private:
+ base::ScopedObservation<Widget, WidgetObserver> observation_{this};
+};
+
} // namespace
class DesktopWindowTreeHostPlatformTest : public ViewsTestBase {
@@ -722,4 +739,64 @@
host_platform->GetContentWindow()->RemoveObserver(&observer);
}
+#if !BUILDFLAG(IS_FUCHSIA)
+class MaximizeBoundsChangeStubWindow : public ui::StubWindow {
+ public:
+ explicit MaximizeBoundsChangeStubWindow(ui::PlatformWindowDelegate* delegate,
+ gfx::AcceleratedWidget widget,
+ const gfx::Rect& bounds)
+ : StubWindow(delegate, false, bounds) {
+ InitDelegateWithWidget(delegate, widget);
+ }
+
+ void Maximize() override {
+ delegate()->OnBoundsChanged({/*origin_changed=*/true});
+ }
+};
+
+class MaximizeBoundsChangePlatformWindowFactoryDelegate
+ : public aura::WindowTreeHostPlatform::
+ PlatformWindowFactoryDelegateForTesting {
+ public:
+ MaximizeBoundsChangePlatformWindowFactoryDelegate() {
+ aura::WindowTreeHostPlatform::SetPlatformWindowFactoryDelegateForTesting(
+ this);
+ }
+ MaximizeBoundsChangePlatformWindowFactoryDelegate(
+ const MaximizeBoundsChangePlatformWindowFactoryDelegate&) = delete;
+ MaximizeBoundsChangePlatformWindowFactoryDelegate& operator=(
+ const MaximizeBoundsChangePlatformWindowFactoryDelegate&) = delete;
+ ~MaximizeBoundsChangePlatformWindowFactoryDelegate() override {
+ aura::WindowTreeHostPlatform::SetPlatformWindowFactoryDelegateForTesting(
+ nullptr);
+ }
+
+ std::unique_ptr<ui::PlatformWindow> Create(
+ aura::WindowTreeHostPlatform* host) override {
+ return std::make_unique<MaximizeBoundsChangeStubWindow>(
+ host, ++last_accelerated_widget_, gfx::Rect(100, 100, 100, 100));
+ }
+
+ gfx::AcceleratedWidget last_accelerated_widget_ = gfx::kNullAcceleratedWidget;
+};
+
+TEST_F(DesktopWindowTreeHostPlatformTest,
+ MaximizeSurvivesSynchronousCloseDuringBoundsChange) {
+ auto scoped_platform_window_factory_delegate =
+ std::make_unique<MaximizeBoundsChangePlatformWindowFactoryDelegate>();
+
+ std::unique_ptr<Widget> widget = CreateWidgetWithNativeWidget();
+ widget->Show();
+
+ auto* host_platform = DesktopWindowTreeHostPlatform::GetHostForWidget(
+ widget->GetNativeWindow()->GetHost()->GetAcceleratedWidget());
+ ASSERT_TRUE(host_platform);
+
+ CloseOnBoundsChangedWidgetObserver observer(widget.get());
+
+ // This should not crash or trigger UAF.
+ host_platform->Maximize();
+}
+#endif // !BUILDFLAG(IS_FUCHSIA)
+
} // namespace views
Original Bug Report
Potential Use-After-Free in DesktopWindowTreeHostPlatform::Maximize and Show
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 vulnerability exists in the browser process due to synchronous object destruction during window maximization. Certain platform window events can trigger the deletion of the host object, leaving subsequent code executing with a dangling ’this’ pointer. This issue affects DesktopWindowTreeHostPlatform on Linux systems using Ozone/X11.
Affected files:
ui/views/widget/desktop_aura/desktop_window_tree_host_platform.ccui/aura/window_tree_host_platform.h
Estimated timestamp from git blame: 2019-09-27
Summary
A potential Use-After-Free (UAF) vulnerability has been identified in DesktopWindowTreeHostPlatform::Maximize() and DesktopWindowTreeHostPlatform::Show() within the Chrome browser process. On Linux (Ozone/X11), these methods invoke platform-level window operations that can synchronously trigger observer events. If an observer destroys the window host during these events, the methods continue to execute using a freed this pointer, leading to memory corruption.
Technical Details
In ui/views/widget/desktop_aura/desktop_window_tree_host_platform.cc, the Maximize() method is implemented as follows:
void DesktopWindowTreeHostPlatform::Maximize() {
platform_window()->Maximize();
if (IsMinimized()) {
Show(ui::mojom::WindowShowState::kNormal, gfx::Rect());
}
}
When platform_window()->Maximize() is called on Ozone/X11, it invokes ui::X11Window::Maximize(). If the window is currently in a fullscreen state, X11Window::Maximize() synchronously calls SetFullscreen(false), which in turn notifies its delegate via OnBoundsChanged().
In ui/aura/window_tree_host_platform.cc, the OnBoundsChanged() implementation notifies aura::WindowTreeHostObserver objects. It is a known behavior in Aura that observers may synchronously trigger the destruction of the WindowTreeHost (e.g., via Widget::CloseNow()). While the base class WindowTreeHostPlatform uses base::WeakPtr guards to handle this possibility safely, the specialized DesktopWindowTreeHostPlatform::Maximize() method does not.
After platform_window()->Maximize() returns, the code immediately calls IsMinimized(). If the host was destroyed during the call, this is a dangling pointer. IsMinimized() dereferences this to access the platform_window_ member, leading to a UAF. A similar pattern exists in DesktopWindowTreeHostPlatform::Show(), where platform_window()->Maximize() is followed by an access to platform_window().
Potential Impact and Reachability
This is a potential high-severity UAF in the browser process. An attacker who can influence window state changes may be able to trigger this path. Potential attack vectors include:
- A malicious or compromised extension using the
chrome.windows.updateAPI to toggle a window between fullscreen and maximized states. - A Progressive Web App (PWA) using the
window.maximize()API (if theDesktopPWAsAdditionalWindowingControlsfeature is enabled).
If an attacker can groom the heap to control the memory of the freed host object, the virtual calls performed during the UAF (e.g., on the platform_window_ pointer) could potentially be leveraged for remote code execution (RCE) in the browser process. MiraclePtr does not mitigate this specific issue because the dangling reference is the implicit this pointer, and the member being accessed is a std::unique_ptr.
Suggested Fix
Apply a base::WeakPtr guard in both Maximize() and Show() to check if the object is still valid after the call to platform_window()->Maximize(). This pattern is already correctly implemented in DesktopWindowTreeHostPlatform::SetFullscreen().
Example fix for Maximize():
void DesktopWindowTreeHostPlatform::Maximize() {
auto weak_ptr = GetWeakPtr();
platform_window()->Maximize();
if (!weak_ptr) {
return;
}
if (IsMinimized()) {
Show(ui::mojom::WindowShowState::kNormal, gfx::Rect());
}
}
Evaluated with Chrome root at commit: 29093e11cf509e3593f6229e4b1b075cca356049
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.