CVE-2026-78983
Overview
Changed Functions
| Function | Change | Notes |
|---|---|---|
ifui/views/widget/widget.cc |
modified | |
TEST_Fui/views/widget/widget_unittest.cc |
modified | |
DestroyOnVisibleObserverui/views/widget/widget_unittest.cc |
modified | |
ifui/views/widget/widget_unittest.cc |
modified |
Files Changed
ui/views/widget/widget.ccui/views/widget/widget_unittest.cc
Patch
From e9e0e0657060cf34c6bb8be63f68247bef53ed8e Mon Sep 17 00:00:00 2001
From: David Bienvenu <davidbienvenu@chromium.org>
Date: Mon, 20 Jul 2026 16:32:24 -0700
Subject: [PATCH] views: Handle Widget destruction during native show/hide
Use ScopedCallStackLock to handle widget destruction during show/hide.
Add a unit tests that expects the CHECK on widget destruction.
Bug: 519369088
Change-Id: I4653e4211ff68140487ce94689ddf5bffa3c88d3
Reviewed-on: https://chromium-review.googlesource.com/c/chromium/src/+/8112867
Reviewed-by: Allen Bauer <kylixrd@chromium.org>
Commit-Queue: David Bienvenu <davidbienvenu@chromium.org>
Cr-Commit-Position: refs/heads/main@{#1665021}
---
diff --git a/ui/views/widget/widget.cc b/ui/views/widget/widget.cc
index 455c727..f2592df 100644
--- a/ui/views/widget/widget.cc
+++ b/ui/views/widget/widget.cc
@@ -1991,6 +1991,7 @@
if (root) {
root->PropagateVisibilityNotifications(nullptr, visible);
}
+ ScopedCallStackLock on_stack(this);
observers_.Notify(&WidgetObserver::OnWidgetVisibilityChanged, this, visible);
if (GetCompositor() && root && root->layer()) {
root->layer()->SetVisible(visible);
diff --git a/ui/views/widget/widget_unittest.cc b/ui/views/widget/widget_unittest.cc
index 5250ae2..9334e97 100644
--- a/ui/views/widget/widget_unittest.cc
+++ b/ui/views/widget/widget_unittest.cc
@@ -3148,6 +3148,49 @@
EXPECT_EQ(0, cursor_view->GetEventCount(ui::EventType::kMousewheel));
}
+TEST_F(DesktopWidgetTest, ShowSurvivesWidgetDestructionInVisibilityChange) {
+ // Observer that synchronously destroys the owning unique_ptr when the widget
+ // becomes visible.
+ class DestroyOnVisibleObserver : public WidgetObserver {
+ public:
+ explicit DestroyOnVisibleObserver(std::unique_ptr<Widget> widget)
+ : widget_(std::move(widget)) {
+ widget_->AddObserver(this);
+ }
+ ~DestroyOnVisibleObserver() override {
+ if (widget_) {
+ widget_->RemoveObserver(this);
+ }
+ }
+
+ void OnWidgetVisibilityChanged(Widget* widget, bool visible) override {
+ if (visible) {
+ widget->RemoveObserver(this);
+ widget_.reset();
+ }
+ }
+
+ Widget* widget() { return widget_.get(); }
+
+ private:
+ std::unique_ptr<Widget> widget_;
+ };
+
+ {
+ DestroyOnVisibleObserver observer(
+ CreateTestWidget(Widget::InitParams::CLIENT_OWNS_WIDGET,
+ Widget::InitParams::TYPE_WINDOW));
+ EXPECT_CHECK_DEATH(observer.widget()->Show());
+ }
+
+ {
+ DestroyOnVisibleObserver observer(
+ CreateTestWidget(Widget::InitParams::CLIENT_OWNS_WIDGET,
+ Widget::InitParams::TYPE_WINDOW));
+ EXPECT_CHECK_DEATH(observer.widget()->ShowInactive());
+ }
+}
+
// Tests that if a scroll-begin gesture is not handled, then subsequent scroll
// events are not dispatched to any view.
TEST_F(WidgetTest, GestureScrollEventDispatching) {
Regression Test / PoC
diff --git a/ui/views/widget/widget_unittest.cc b/ui/views/widget/widget_unittest.cc
index 5250ae2..9334e97 100644
--- a/ui/views/widget/widget_unittest.cc
+++ b/ui/views/widget/widget_unittest.cc
@@ -3148,6 +3148,49 @@
EXPECT_EQ(0, cursor_view->GetEventCount(ui::EventType::kMousewheel));
}
+TEST_F(DesktopWidgetTest, ShowSurvivesWidgetDestructionInVisibilityChange) {
+ // Observer that synchronously destroys the owning unique_ptr when the widget
+ // becomes visible.
+ class DestroyOnVisibleObserver : public WidgetObserver {
+ public:
+ explicit DestroyOnVisibleObserver(std::unique_ptr<Widget> widget)
+ : widget_(std::move(widget)) {
+ widget_->AddObserver(this);
+ }
+ ~DestroyOnVisibleObserver() override {
+ if (widget_) {
+ widget_->RemoveObserver(this);
+ }
+ }
+
+ void OnWidgetVisibilityChanged(Widget* widget, bool visible) override {
+ if (visible) {
+ widget->RemoveObserver(this);
+ widget_.reset();
+ }
+ }
+
+ Widget* widget() { return widget_.get(); }
+
+ private:
+ std::unique_ptr<Widget> widget_;
+ };
+
+ {
+ DestroyOnVisibleObserver observer(
+ CreateTestWidget(Widget::InitParams::CLIENT_OWNS_WIDGET,
+ Widget::InitParams::TYPE_WINDOW));
+ EXPECT_CHECK_DEATH(observer.widget()->Show());
+ }
+
+ {
+ DestroyOnVisibleObserver observer(
+ CreateTestWidget(Widget::InitParams::CLIENT_OWNS_WIDGET,
+ Widget::InitParams::TYPE_WINDOW));
+ EXPECT_CHECK_DEATH(observer.widget()->ShowInactive());
+ }
+}
+
// Tests that if a scroll-begin gesture is not handled, then subsequent scroll
// events are not dispatched to any view.
TEST_F(WidgetTest, GestureScrollEventDispatching) {
Original Bug Report
Potential Use-After-Free in Widget::Show due to missing WeakPtr guard after native 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 Widget::Show() and Widget::ShowInactive() due to a missing WeakPtr guard after returning from native_widget_->Show(). During synchronous Win32 window message dispatch, observer notifications can be synchronously processed, allowing a malicious observer or compromised renderer IPC to destroy the Widget. Upon return, the code continues to perform member writes and virtual method calls on the freed Widget instance.
Affected files:
ui/views/widget/widget.ccui/views/widget/widget.h
Estimated timestamp from git blame: 2025-09-04
Detailed Description
A potential Use-After-Free (UAF) vulnerability exists in Widget::Show() and Widget::ShowInactive() (ui/views/widget/widget.cc) due to the lack of safety checks after invoking native_widget_->Show().
On Windows, native_widget_->Show() triggers synchronous OS calls such as ::ShowWindow() or ::SetWindowPos() (via SetFullscreen()). These Win32 APIs synchronously dispatch window messages (e.g., WM_WINDOWPOSCHANGED) directly to the window’s procedure HWNDMessageHandler::OnWndProc on the UI thread.
When processing these messages, HWNDMessageHandler::OnWindowPosChanged invokes delegate_->HandleVisibilityChanged(true), which routes to Widget::OnNativeWidgetVisibilityChanged().
Crucially, unlike OnNativeWidgetSizeChanged(), OnNativeWidgetVisibilityChanged() does not instantiate a ScopedCallStackLock. This means the security assertion CHECK(!on_call_stack_) inside Widget::~Widget() is not triggered if destruction is initiated. If a registered WidgetObserver responds to the visibility change by synchronously invoking Widget::CloseNow(), the window is synchronously destroyed.
Because the browser window has CLIENT_OWNS_WIDGET ownership, the destruction flow synchronously deletes both the DesktopWindowTreeHostWin and the parent Widget / BrowserWidget instances.
When the synchronous message dispatch completes, control unwinds back to the in-flight execution frames of DesktopWindowTreeHostWin::Show() and Widget::Show(). Since both instances have been deallocated, this results in a cascade of Use-After-Frees:
- In
DesktopWindowTreeHostWin::Show(): Line 344 attempts to callGetWidget()->CanActivate()on the deleted host. - In
Widget::Show(): Line 1082 performs a member write (saved_show_state_ = preferred_show_state;) on the freedWidgetallocation. - In
Widget::Show(): Line 1087 callsHandleShowRequested(), which dereferences the freedsublevel_manager_unique pointer to executeEnsureOwnerSublevel()on a reclaimed pointer.
Note that since our tooling agent does not yet have the capability to execute code in a live environment, these findings represent a potential vulnerability and the trigger steps are theoretical.
Suggested Potential Trigger Steps
- An attacker-controlled extension or a compromised renderer initiates the creation of a popup window with
state: 'fullscreen'. - The browser process maps the initial show state to
ui::mojom::WindowShowState::kFullscreenand initiates the first-show of theBrowserWidget. - The attacker immediately triggers a window close action (or registers a
WidgetObserverthat reacts to the first visibility/activation notification). - During
Widget::Show(),native_widget_->Show(kFullscreen)invokes Win32 synchronous window repositioning. - The synchronous visibility change event dispatches, triggering the registered observer to synchronously call
Widget::CloseNow(). - The
Widgetand host objects are freed. The call stack returns toWidget::Show(), resulting in a member write and virtual method dereference on the freed allocations.
Proposed Fix
To remediate this issue, introduce a WeakPtr guard in both Widget::Show() and Widget::ShowInactive() immediately before calling native_widget_->Show(...), and return early if the widget was destroyed during the native call. This aligns with the sibling guard pattern used in Widget::SetFullscreen().
void Widget::Show() {
if (!native_widget_) {
return;
}
...
auto weak_ptr = GetWeakPtr();
if (non_client_view_) {
if (saved_show_state_ == ui::mojom::WindowShowState::kMaximized &&
!initial_restored_bounds_.IsEmpty() && !IsFullscreen()) {
native_widget_->Show(ui::mojom::WindowShowState::kMaximized,
initial_restored_bounds_);
} else {
native_widget_->Show(saved_show_state_, gfx::Rect());
}
} else {
native_widget_->Show(preferred_show_state, gfx::Rect());
}
if (!weak_ptr) {
return;
}
if (non_client_view_) {
saved_show_state_ = preferred_show_state;
}
HandleShowRequested();
}
Evaluated with Chrome root at commit: 87214e6721f6c34afd9181b80769a24c0c601c50
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.