CVE-2026-7910
Overview
Background
- `DesktopWindowTreeHostPlatform` (`DWTHP`)
- the Ozone/Aura desktop window host on Linux that bridges a platform window to the Views widget stack.
- `OnActivationChanged`
- a
ui::PlatformWindowDelegatecallback invoked when the platform window gains or loses activation; the enclosing function is named only in the commit subject, not in the@@hunk context. - `HandleActivationChanged`
- the
DesktopNativeWidgetAuracall reached fromOnActivationChangedthat notifiesWidgetObservers, any of which can synchronously close (and thus destroy) the widget and itsDWTHP. - `base::WeakPtr` guard
- capturing a
WeakPtrtothisbefore a callback and testing it afterward so a method can detect its own synchronous destruction before continuing.
Root Cause Analysis
OnActivationChanged set is_active_, forwarded to aura::WindowTreeHostPlatform::OnActivationChanged, then called desktop_native_widget_aura_->HandleActivationChanged(active) and unconditionally called ScheduleRelayout() on this. On Linux/Ozone, HandleActivationChanged() can fire observer notifications that synchronously close the widget, destroying the DesktopWindowTreeHostPlatform instance; control then returned to OnActivationChanged, which called ScheduleRelayout() on the freed this, a use-after-free. The invariant violated is that this remains valid for the remainder of the method after dispatching a callback that can delete it.
The fix adds a base::WeakPtrFactory<DesktopWindowTreeHostPlatform> weak_factory_ member, captures auto weak_this = weak_factory_.GetWeakPtr(); before HandleActivationChanged(), and does if (!weak_this) return; before ScheduleRelayout(), so the method bails out safely when it was destroyed mid-call. The shipped regression test OnActivationChangedSurvivesSynchronousClose drives the exact trigger: a WidgetObserver that calls widget->CloseNow() from OnWidgetActivationChanged, after which the previously-crashing OnActivationChanged must not crash. The commit notes this mirrors an earlier analogous guard in the Windows hwnd_message_handler.cc.
this (ScheduleRelayout()) after a callback that can synchronously delete this; the fix detects that self-destruction with a WeakPtr guard and returns early instead of touching freed memory.Attack Path
- Hold an activation observer that closes
A widget has a
WidgetObserver(or equivalent) whoseOnWidgetActivationChanged/close path synchronously tears down the widget, as the regression test’sCloseOnActivationWidgetObserverdemonstrates. - Trigger activation change
An activation event on the platform window invokes
OnActivationChanged(active)on theDesktopWindowTreeHostPlatform. - Synchronous destruction
HandleActivationChanged()dispatches the notification, the observer closes the widget viaCloseNow(), and theDWTHPinstance is freed before the call returns. - Use-after-free
OnActivationChangedresumes and callsScheduleRelayout()on the freedthis, corrupting/reading freed browser-process memory. - Exploitation Grooming the freed allocation could escalate the UAF into a stronger primitive (inferred); the patch and test only show the crash.
Impact Assessment
DesktopWindowTreeHostPlatform in the browser process on Linux/Ozone, with the precondition of a synchronous close reachable from the activation-change notification. The advisory rates this high (“Use after free in Views”) and does not report in-the-wild exploitation. Memory corruption or code execution beyond the demonstrated dangling ScheduleRelayout() call is (inferred).Changed Functions
| Function | Change | Notes |
|---|---|---|
ifui/views/widget/desktop_aura/desktop_window_tree_host_platform.cc |
modified | |
CloseOnActivationWidgetObserverui/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 | |
VisibilityObserverui/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.hui/views/widget/desktop_aura/desktop_window_tree_host_platform_unittest.cc
Audit Directions
- Dereference after a self-deleting callbackFlag any method that calls
this->members after invoking observer/delegate notifications (activation, visibility, focus, bounds) that can synchronously close a widget; guard with aWeakPtror restructure so no work follows the callback. - Platform parity gapsWhen one platform (here Windows
hwnd_message_handler.cc) already has a self-destruction guard, audit the Ozone/Linux, macOS, and other platform equivalents for the missing mirror. - Widget close/observer reentrancyReview
WidgetObserverandPlatformWindowDelegatedispatch sites where observers may callCloseNow()/Close(), since those are the synchronous-destruction sources for host and widget objects.
Patch
From 3fc1ee6676b729cd5b00c1a58bed103437c71aec Mon Sep 17 00:00:00 2001
From: Tom Anderson <thomasanderson@chromium.org>
Date: Thu, 09 Apr 2026 12:12:20 -0700
Subject: [PATCH] views: Guard against UAF in DWTHP::OnActivationChanged
HandleActivationChanged() notifications can cause the widget to be
synchronously closed. On Linux/Ozone, this can result in the destruction
of the DesktopWindowTreeHostPlatform instance.
This change adds a base::WeakPtr guard around HandleActivationChanged()
to ensure 'this' is still valid before calling ScheduleRelayout().
This mirrors an analogous fix previously applied to the Windows
implementation in hwnd_message_handler.cc.
Fixed: 497543810
Change-Id: I58fcddf8542bc1aa30d0ad5508d16849fd56e3ac
Reviewed-on: https://chromium-review.googlesource.com/c/chromium/src/+/7737952
Reviewed-by: Lei Zhang <thestig@chromium.org>
Auto-Submit: Thomas Anderson <thomasanderson@chromium.org>
Commit-Queue: Lei Zhang <thestig@chromium.org>
Cr-Commit-Position: refs/heads/main@{#1612417}
---
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 942e685..e005cce1 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
@@ -1065,7 +1065,15 @@
}
is_active_ = active;
aura::WindowTreeHostPlatform::OnActivationChanged(active);
+
+ // HandleActivationChanged() notifications can cause the widget to be
+ // synchronously closed.
+ auto weak_this = weak_factory_.GetWeakPtr();
desktop_native_widget_aura_->HandleActivationChanged(active);
+ if (!weak_this) {
+ return;
+ }
+
ScheduleRelayout();
}
diff --git a/ui/views/widget/desktop_aura/desktop_window_tree_host_platform.h b/ui/views/widget/desktop_aura/desktop_window_tree_host_platform.h
index 185da8cd..efca27cd 100644
--- a/ui/views/widget/desktop_aura/desktop_window_tree_host_platform.h
+++ b/ui/views/widget/desktop_aura/desktop_window_tree_host_platform.h
@@ -273,6 +273,7 @@
base::WeakPtrFactory<DesktopWindowTreeHostPlatform> close_widget_factory_{
this};
+ base::WeakPtrFactory<DesktopWindowTreeHostPlatform> weak_factory_{this};
};
} // namespace views
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 1236cb1..950938b 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
@@ -137,6 +137,22 @@
return CreateWidgetWithNativeWidgetWithParams(std::move(params));
}
+class CloseOnActivationWidgetObserver : public WidgetObserver {
+ public:
+ explicit CloseOnActivationWidgetObserver(Widget* widget) {
+ observation_.Observe(widget);
+ }
+ ~CloseOnActivationWidgetObserver() override = default;
+
+ void OnWidgetActivationChanged(Widget* widget, bool active) override {
+ observation_.Reset();
+ widget->CloseNow();
+ }
+
+ private:
+ base::ScopedObservation<Widget, WidgetObserver> observation_{this};
+};
+
} // namespace
class DesktopWindowTreeHostPlatformTest : public ViewsTestBase {
@@ -606,6 +622,22 @@
EXPECT_TRUE(host_platform->IsActive());
}
+TEST_F(DesktopWindowTreeHostPlatformTest,
+ OnActivationChangedSurvivesSynchronousClose) {
+ std::unique_ptr<Widget> widget = CreateWidgetWithNativeWidget();
+ widget->Show();
+
+ auto* host_platform = DesktopWindowTreeHostPlatform::GetHostForWidget(
+ widget->GetNativeWindow()->GetHost()->GetAcceleratedWidget());
+ ASSERT_TRUE(host_platform);
+
+ CloseOnActivationWidgetObserver observer(widget.get());
+
+ // This should not crash.
+ static_cast<ui::PlatformWindowDelegate*>(host_platform)
+ ->OnActivationChanged(false);
+}
+
#endif // !BUILDFLAG(IS_FUCHSIA)
class VisibilityObserver : public aura::WindowObserver {
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 1236cb1..950938b 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
@@ -137,6 +137,22 @@
return CreateWidgetWithNativeWidgetWithParams(std::move(params));
}
+class CloseOnActivationWidgetObserver : public WidgetObserver {
+ public:
+ explicit CloseOnActivationWidgetObserver(Widget* widget) {
+ observation_.Observe(widget);
+ }
+ ~CloseOnActivationWidgetObserver() override = default;
+
+ void OnWidgetActivationChanged(Widget* widget, bool active) override {
+ observation_.Reset();
+ widget->CloseNow();
+ }
+
+ private:
+ base::ScopedObservation<Widget, WidgetObserver> observation_{this};
+};
+
} // namespace
class DesktopWindowTreeHostPlatformTest : public ViewsTestBase {
@@ -606,6 +622,22 @@
EXPECT_TRUE(host_platform->IsActive());
}
+TEST_F(DesktopWindowTreeHostPlatformTest,
+ OnActivationChangedSurvivesSynchronousClose) {
+ std::unique_ptr<Widget> widget = CreateWidgetWithNativeWidget();
+ widget->Show();
+
+ auto* host_platform = DesktopWindowTreeHostPlatform::GetHostForWidget(
+ widget->GetNativeWindow()->GetHost()->GetAcceleratedWidget());
+ ASSERT_TRUE(host_platform);
+
+ CloseOnActivationWidgetObserver observer(widget.get());
+
+ // This should not crash.
+ static_cast<ui::PlatformWindowDelegate*>(host_platform)
+ ->OnActivationChanged(false);
+}
+
#endif // !BUILDFLAG(IS_FUCHSIA)
class VisibilityObserver : public aura::WindowObserver {
Original Bug Report
Potential Use-After-Free in DesktopWindowTreeHostPlatform::OnActivationChanged
Project Fortify, an experimental security project, has identified the following potential security issue.
Overview: A potential Use-After-Free (UAF) vulnerability exists in DesktopWindowTreeHostPlatform::OnActivationChanged on Linux/Ozone. Synchronous widget destruction during activation handling can free the this pointer, which is subsequently accessed when calling ScheduleRelayout(). This could potentially allow a compromised renderer to achieve Remote Code Execution (RCE) in the browser process.
Affected files:
ui/views/widget/desktop_aura/desktop_window_tree_host_platform.ccui/views/widget/desktop_aura/desktop_window_tree_host_platform.h
Estimated timestamp from git blame: 2024-05-31
Summary
A potential Use-After-Free (UAF) vulnerability has been identified in the browser process on Linux (Ozone platform) within DesktopWindowTreeHostPlatform::OnActivationChanged. The issue occurs because a synchronous call to desktop_native_widget_aura_->HandleActivationChanged(active) can trigger a cascade of events that results in the destruction of the DesktopWindowTreeHostPlatform instance. Upon returning from this call, the method unconditionally invokes ScheduleRelayout() on the now-freed this pointer.
Technical Details
In ui/views/widget/desktop_aura/desktop_window_tree_host_platform.cc, OnActivationChanged is implemented as follows:
void DesktopWindowTreeHostPlatform::OnActivationChanged(bool active) {
// ...
is_active_ = active;
aura::WindowTreeHostPlatform::OnActivationChanged(active);
desktop_native_widget_aura_->HandleActivationChanged(active);
ScheduleRelayout();
}
The call to HandleActivationChanged(active) notifies WidgetObservers and focus listeners of the activation change. A specifically crafted UI interaction (or observer behavior) could respond to this notification by synchronously calling Widget::CloseNow().
This triggers the following synchronous destruction path:
Widget::CloseNow()callsnative_widget_->CloseNow(), which routes toDesktopWindowTreeHostPlatform::CloseNow().DesktopWindowTreeHostPlatform::CloseNow()callsplatform_window()->Close().- On Ozone/X11 or Wayland, the platform window synchronously notifies its delegate via
OnClosed(). DesktopWindowTreeHostPlatform::OnClosed()callsDesktopNativeWidgetAura::OnHostClosed().
Inside DesktopNativeWidgetAura::OnHostClosed() (desktop_native_widget_aura.cc:378):
// WindowEventDispatcher owns |desktop_window_tree_host_|.
desktop_window_tree_host_ = nullptr;
// Delete host after resetting `desktop_window_tree_host_` and
// `content_window_` to avoid accessing the stale instance during deletion.
host_.reset();
First, the raw_ptr tracking the host object (desktop_window_tree_host_) is set to nullptr. This explicitly drops the reference count before destruction, meaning MiraclePtr (BackupRefPtr) will not quarantine the memory for this specific pointer.
Second, host_.reset() destroys the DesktopWindowTreeHostPlatform object (this). The memory is returned to PartitionAlloc.
When the stack unwinds back to OnActivationChanged, the code unconditionally calls ScheduleRelayout() on the freed this pointer.
Inside ScheduleRelayout():
void DesktopWindowTreeHostPlatform::ScheduleRelayout() {
if (!native_widget_delegate_) {
return;
}
Widget* widget = native_widget_delegate_->AsWidget();
// ...
The code reads native_widget_delegate_ (a base::WeakPtr) from the freed memory and subsequently calls AsWidget(), a pure virtual function. If the memory has been reallocated and controlled by an attacker, this virtual function call can be hijacked.
Potential Exploitation Steps
Note: These are potential steps, as our tooling agent doesn’t yet have the ability to run code to confirm a working PoC.
- An attacker compromises a renderer process (e.g., via a v8 bug).
- The attacker uses the compromised renderer to open two popup windows (Popup A and Popup B).
- The attacker programmatically closes Popup A, causing the OS window manager (Wayland/X11) to deliver an activation change notification to Popup B’s platform window.
DesktopWindowTreeHostPlatform::OnActivationChanged(true)is invoked for Popup B.- During
HandleActivationChanged(true), a synchronous UI event or observer cascade triggersWidget::CloseNow(). - The
DesktopWindowTreeHostPlatforminstance is destroyed viahost_.reset(), and its memory is freed. - Concurrently, the attacker sprays the browser process heap via IPC messages to reallocate the freed memory chunk with attacker-controlled data.
- The attacker forges the
base::WeakPtr(native_widget_delegate_) within the sprayed memory to appear valid and points it to a fake object. - Control returns to
OnActivationChanged, andScheduleRelayout()is executed. - The forged
WeakPtrcheck passes, and the virtual methodAsWidget()is called on the attacker-controlled pointer, granting Remote Code Execution (RCE) in the browser process.
Suggested Fix
The call to ScheduleRelayout() in DesktopWindowTreeHostPlatform::OnActivationChanged must be guarded to ensure the this pointer has not been destroyed during the synchronous call to HandleActivationChanged(). A base::WeakPtr check should be used.
void DesktopWindowTreeHostPlatform::OnActivationChanged(bool active) {
// ...
is_active_ = active;
aura::WindowTreeHostPlatform::OnActivationChanged(active);
auto weak_this = close_widget_factory_.GetWeakPtr();
desktop_native_widget_aura_->HandleActivationChanged(active);
if (!weak_this) {
return;
}
ScheduleRelayout();
}
A similar fix was previously applied to the Windows equivalent (ui/views/win/hwnd_message_handler.cc) but is missing in the Linux/Ozone implementation.
Evaluated with Chrome root at commit: a9cbf6e8b275fe4147435aa905f3b7f5a656f5f0
Results from so far have been promising, but there can be wrong deductions. If this proves to be a false positive, please close as WAI; 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.