High chrome Input Validation 📄 Reporter bug report 🔧 Commit mapped

Overview

High
Severity
—
CVSS
No
Exploited ITW
Fixed
Fix Status
ImpactImproper input validation in Views
DescriptionImproper input validation in Views
ComponentViews
Sub-componentViews
Bug ClassInput Validation
Tracker553140050
Fix commit97aa60e7d182 (chromium/src) +78/-0
CISA KEVNot listed
CreditedGoogle
Disclosed2026-10-20

Changed Functions

FunctionChangeNotes
if
ui/views/widget/native_widget_aura.cc
modified
SyncWidgetCloserOnVisibilityChange
ui/views/widget/native_widget_aura_unittest.cc
modified
if
ui/views/widget/native_widget_aura_unittest.cc
modified
TEST_F
ui/views/widget/native_widget_aura_unittest.cc
modified

Files Changed

  • ui/views/widget/native_widget_aura_unittest.cc
  • ui/views/widget/native_widget_aura.cc
From 97aa60e7d1820d5ed079732d89147352534df7dc Mon Sep 17 00:00:00 2001
From: Thomas Lukaszewicz <[email protected]>
Date: Mon, 21 Sep 2026 11:10:16 -0700
Subject: [PATCH] [Views] Prevent potential UAF in NativeWidgetAura::Show()

Both window_->Show() and Activate() notify aura::Window observers,
which are explicitly permitted to synchronously destroy window_ during
visibility notifications. Under NATIVE_WIDGET_OWNS_WIDGET or
CLIENT_OWNS_WIDGET ownership models, OnWindowDestroyed() deletes `this`.
Continuing execution in Show() after the window is destroyed results in
a heap-use-after-free when accessing member fields or invoking further
methods (e.g. Activate(), SetInitialFocus(), Minimize()).

Use a WeakPtr to verify that `this` and `window_` remain valid after
invoking window_->Show() and Activate(), returning early if destroyed.

Add a regression test covering synchronous widget destruction during
Show().

Fixed: 553140050
Change-Id: I855479577166b018d94388c3e5a0d6326c228390
Reviewed-on: https://chromium-review.googlesource.com/c/chromium/src/+/8423564
Reviewed-by: Allen Bauer <[email protected]>
Commit-Queue: Thomas Lukaszewicz <[email protected]>
Cr-Commit-Position: refs/heads/main@{#1701883}
---
diff --git a/ui/views/widget/native_widget_aura.cc b/ui/views/widget/native_widget_aura.cc
index 43c789c..4537dbb 100644
--- a/ui/views/widget/native_widget_aura.cc
+++ b/ui/views/widget/native_widget_aura.cc
@@ -795,10 +795,24 @@
     disabler.emplace(window_);
   }
 
+  // `window_->Show()` and `Activate()` below fan out to aura::Window
+  // observers, which are explicitly permitted to synchronously destroy
+  // `window_` (see Window::NotifyWindowVisibilityChangedAtReceiver()). When
+  // that happens under NATIVE_WIDGET_OWNS_WIDGET or CLIENT_OWNS_WIDGET
+  // ownership, OnWindowDestroyed() deletes `this`, so the rest of this method
+  // must not run.
+  auto weak_this = weak_factory.GetWeakPtr();
+
   window_->Show();
+  if (!weak_this || !window_) {
+    return;
+  }
   if (delegate_->CanActivate()) {
     if (show_state != ui::mojom::WindowShowState::kInactive) {
       Activate();
+      if (!weak_this || !window_) {
+        return;
+      }
     }
     // SetInitialFocus() should be always be called, even for
     // SHOW_STATE_INACTIVE. If the window has to stay inactive, the method will
diff --git a/ui/views/widget/native_widget_aura_unittest.cc b/ui/views/widget/native_widget_aura_unittest.cc
index 0ccbf8f..3a342a0 100644
--- a/ui/views/widget/native_widget_aura_unittest.cc
+++ b/ui/views/widget/native_widget_aura_unittest.cc
@@ -1261,4 +1261,68 @@
   widget->Close();
 }
 
+namespace {
+
+// Closes (and thereby destroys) a widget's aura::Window synchronously from
+// within the window-visibility-changed observer fan-out that
+// aura::Window::Show() dispatches. Aura explicitly permits observers to
+// delete the window during this notification (see
+// Window::NotifyWindowVisibilityChangedAtReceiver). This models production
+// destroyer chains in which showing a window synchronously re-enters
+// hide/teardown logic that closes the widget (e.g. an overview-force-hidden
+// window being re-hidden by ScopedOverviewHideWindows, whose nested Hide()
+// runs WindowEventDispatcher::OnWindowHidden gesture-cancel/capture-release
+// handlers that close the widget).
+class SyncWidgetCloserOnVisibilityChange : public aura::WindowObserver {
+ public:
+  SyncWidgetCloserOnVisibilityChange(aura::Window* window, Widget* widget)
+      : widget_(widget) {
+    observation_.Observe(window);
+  }
+
+  // aura::WindowObserver:
+  void OnWindowVisibilityChanged(aura::Window* window, bool visible) override {
+    if (!visible || !widget_) {
+      return;
+    }
+    observation_.Reset();
+    Widget* widget = widget_;
+    widget_ = nullptr;
+    // Widget::CloseNow() -> NativeWidgetAura::CloseNow() -> delete window_ ->
+    // ~Window -> NativeWidgetAura::OnWindowDestroyed() -> delete this
+    // (ownership is CLIENT_OWNS_WIDGET), all synchronously while
+    // NativeWidgetAura::Show() is still on the stack.
+    widget->CloseNow();
+  }
+
+ private:
+  raw_ptr<Widget> widget_;
+  base::ScopedObservation<aura::Window, aura::WindowObserver> observation_{
+      this};
+};
+
+}  // namespace
+
+// Regression test for a use-after-free in NativeWidgetAura::Show(): the
+// window-visibility observer fan-out triggered by window_->Show() may
+// synchronously destroy the aura::Window. Under NATIVE_WIDGET_OWNS_WIDGET or
+// CLIENT_OWNS_WIDGET ownership, NativeWidgetAura::OnWindowDestroyed() then
+// runs `delete this`, and the remainder of Show() (delegate_->CanActivate(),
+// Activate(), IsActive(), SetInitialFocus(), Minimize()) executes on the
+// freed NativeWidgetAura (heap-use-after-free under ASAN).
+TEST_F(NativeWidgetAuraTest, ShowWithSynchronousWindowDestruction) {
+  auto widget = std::make_unique<Widget>();
+  Widget::InitParams params(Widget::InitParams::Ownership::CLIENT_OWNS_WIDGET,
+                            Widget::InitParams::TYPE_POPUP);
+  params.parent = root_window();
+  widget->Init(std::move(params));
+
+  SyncWidgetCloserOnVisibilityChange closer(widget->GetNativeView(),
+                                            widget.get());
+
+  // The NativeWidgetAura is deleted inside window_->Show(); the tail of
+  // NativeWidgetAura::Show() must not touch |this| afterwards.
+  widget->Show();
+}
+
 }  // namespace views
Loading diff…

Regression Test / PoC

shipped with the fix
diff --git a/ui/views/widget/native_widget_aura_unittest.cc b/ui/views/widget/native_widget_aura_unittest.cc
index 0ccbf8f..3a342a0 100644
--- a/ui/views/widget/native_widget_aura_unittest.cc
+++ b/ui/views/widget/native_widget_aura_unittest.cc
@@ -1261,4 +1261,68 @@
   widget->Close();
 }
 
+namespace {
+
+// Closes (and thereby destroys) a widget's aura::Window synchronously from
+// within the window-visibility-changed observer fan-out that
+// aura::Window::Show() dispatches. Aura explicitly permits observers to
+// delete the window during this notification (see
+// Window::NotifyWindowVisibilityChangedAtReceiver). This models production
+// destroyer chains in which showing a window synchronously re-enters
+// hide/teardown logic that closes the widget (e.g. an overview-force-hidden
+// window being re-hidden by ScopedOverviewHideWindows, whose nested Hide()
+// runs WindowEventDispatcher::OnWindowHidden gesture-cancel/capture-release
+// handlers that close the widget).
+class SyncWidgetCloserOnVisibilityChange : public aura::WindowObserver {
+ public:
+  SyncWidgetCloserOnVisibilityChange(aura::Window* window, Widget* widget)
+      : widget_(widget) {
+    observation_.Observe(window);
+  }
+
+  // aura::WindowObserver:
+  void OnWindowVisibilityChanged(aura::Window* window, bool visible) override {
+    if (!visible || !widget_) {
+      return;
+    }
+    observation_.Reset();
+    Widget* widget = widget_;
+    widget_ = nullptr;
+    // Widget::CloseNow() -> NativeWidgetAura::CloseNow() -> delete window_ ->
+    // ~Window -> NativeWidgetAura::OnWindowDestroyed() -> delete this
+    // (ownership is CLIENT_OWNS_WIDGET), all synchronously while
+    // NativeWidgetAura::Show() is still on the stack.
+    widget->CloseNow();
+  }
+
+ private:
+  raw_ptr<Widget> widget_;
+  base::ScopedObservation<aura::Window, aura::WindowObserver> observation_{
+      this};
+};
+
+}  // namespace
+
+// Regression test for a use-after-free in NativeWidgetAura::Show(): the
+// window-visibility observer fan-out triggered by window_->Show() may
+// synchronously destroy the aura::Window. Under NATIVE_WIDGET_OWNS_WIDGET or
+// CLIENT_OWNS_WIDGET ownership, NativeWidgetAura::OnWindowDestroyed() then
+// runs `delete this`, and the remainder of Show() (delegate_->CanActivate(),
+// Activate(), IsActive(), SetInitialFocus(), Minimize()) executes on the
+// freed NativeWidgetAura (heap-use-after-free under ASAN).
+TEST_F(NativeWidgetAuraTest, ShowWithSynchronousWindowDestruction) {
+  auto widget = std::make_unique<Widget>();
+  Widget::InitParams params(Widget::InitParams::Ownership::CLIENT_OWNS_WIDGET,
+                            Widget::InitParams::TYPE_POPUP);
+  params.parent = root_window();
+  widget->Init(std::move(params));
+
+  SyncWidgetCloserOnVisibilityChange closer(widget->GetNativeView(),
+                                            widget.get());
+
+  // The NativeWidgetAura is deleted inside window_->Show(); the tail of
+  // NativeWidgetAura::Show() must not touch |this| afterwards.
+  widget->Show();
+}
+
 }  // namespace views
Loading diff…

Original Bug Report

reported by [email protected]

Potential Use-After-Free in NativeWidgetAura::Show() via Synchronous Window Deletion

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: NativeWidgetAura::Show() invokes window_->Show() and Activate() without verifying whether the NativeWidgetAura instance remains alive after re-entrant calls. Under NATIVE_WIDGET_OWNS_WIDGET or CLIENT_OWNS_WIDGET ownership models, synchronous destruction of the underlying aura::Window triggers OnWindowDestroyed(), which executes delete this. Subsequent member access in the tail of Show() executes on freed memory in the unsandboxed browser process.

Affected files:

  • ui/views/widget/native_widget_aura.cc
  • ui/views/widget/native_widget_aura.h

Estimated timestamp from git blame: 2018-08-15

Summary

In ui/views/widget/native_widget_aura.cc, NativeWidgetAura::Show() invokes methods that can trigger synchronous window destruction (window_->Show() at line 794 and Activate() at line 797). When the widget uses NATIVE_WIDGET_OWNS_WIDGET or CLIENT_OWNS_WIDGET ownership, synchronous window destruction causes NativeWidgetAura::OnWindowDestroyed() to execute delete this. Because NativeWidgetAura::Show() lacks a base::WeakPtr liveness check, subsequent tail code executes on a freed this pointer.

Note: The reproduction steps described below are potential/suggested steps derived from static code analysis; our automated tooling does not currently have the capability to execute an end-to-end dynamic proof of concept.


Vulnerability Details

Root Cause

In ui/views/widget/native_widget_aura.cc:771-814:

void NativeWidgetAura::Show(ui::mojom::WindowShowState show_state,
                            const gfx::Rect& restore_bounds) {
  if (!window_) {
    return;
  }
  ...
  window_->Show(); // Line 794: Re-entrant synchronous destruction possible

  if (delegate_->CanActivate()) { // Line 795: UAF on freed this
    if (show_state != ui::mojom::WindowShowState::kInactive) {
      Activate(); // Line 797: Re-entrant synchronous destruction possible
    }
    SetInitialFocus(IsActive() ? show_state
                               : ui::mojom::WindowShowState::kInactive);
  }

  if (show_state == ui::mojom::WindowShowState::kMinimized) {
    Minimize();
  }
}

When aura::Window::Show() is called (ui/aura/window.cc:445), it executes SetVisibleInternal(true) (ui/aura/window.cc:1323). While internal delegate calls are protected by a ScopedDeleteBlocker, the subsequent observer notification NotifyWindowVisibilityChanged(this, visible) (ui/aura/window.cc:1355) executes with no delete blocker active. aura::Window explicitly documents that observers may delete the window synchronously during this notification (ui/aura/window.cc:1579-1581).

When aura::Window::~Window() executes, it invokes delegate_->OnWindowDestroyed(this) (ui/aura/window.cc:290), routing to NativeWidgetAura::OnWindowDestroyed() (ui/views/widget/native_widget_aura.cc:1227-1240):

void NativeWidgetAura::OnWindowDestroyed(aura::Window* window) {
  window_ = nullptr;
  bool should_delete_this =
      (ownership_ == Widget::InitParams::NATIVE_WIDGET_OWNS_WIDGET) ||
      (ownership_ == Widget::InitParams::CLIENT_OWNS_WIDGET);
  if (delegate_) {
    delegate_->OnNativeWidgetDestroyed();
  }
  if (should_delete_this) {
    delete this;
  }
}

After delete this frees the NativeWidgetAura instance, the stack unwinds to NativeWidgetAura::Show() at line 795, which immediately attempts to dereference this->delegate_ (base::WeakPtr<internal::NativeWidgetDelegate>) and perform a virtual method dispatch (CanActivate()), followed by SetInitialFocus() and Minimize().

Mitigation Analysis (MiraclePtr / BRP)

BackupRefPtr does not mitigate this UAF because:

  1. The dangling reference being accessed is the bare C++ this pointer on the call stack, not a raw_ptr<T> field.
  2. In views::Widget, the reference to NativeWidgetPrivate is held as base::WeakPtr<internal::NativeWidgetPrivate> native_widget_ (ui/views/widget/widget.h:1681). Thus, there are zero active raw_ptr references pinning the NativeWidgetAura allocation when it is deleted, allowing PartitionAlloc to reclaim the memory slot immediately.

Potential Trigger Steps

  1. Target a ChromeOS / Desktop Aura widget with NATIVE_WIDGET_OWNS_WIDGET or CLIENT_OWNS_WIDGET ownership (such as a ChromeOS browser window or system UI widget).
  2. Place the widget in a state where a visibility transition or activation triggers a synchronous destruction callback (for example, via ScopedOverviewHideWindows with forced_hidden_ == true, where a nested Hide() leads to gesture state cleanup / capture release that closes the widget, or via an activation observer cascade during ActivateWindow()).
  3. Cause Widget::Show() to be invoked (e.g., via extension APIs such as chrome.windows.update or window management transitions).
  4. window_->Show() triggers synchronous destruction of the aura::Window and delete this on NativeWidgetAura.
  5. Execution resumes at ui/views/widget/native_widget_aura.cc:795, accessing members of the freed NativeWidgetAura.

Suggested Fix

Guard NativeWidgetAura::Show() using base::WeakPtr<NativeWidgetAura> before calling destruction-capable methods:

--- a/ui/views/widget/native_widget_aura.cc
+++ b/ui/views/widget/native_widget_aura.cc
@@ -791,10 +791,18 @@ void NativeWidgetAura::Show(ui::mojom::WindowShowState show_state,
     disabler.emplace(window_);
   }
 
+  base::WeakPtr<NativeWidgetAura> weak_this = weak_factory.GetWeakPtr();
   window_->Show();
+  if (!weak_this) {
+    return;
+  }
+
   if (delegate_->CanActivate()) {
     if (show_state != ui::mojom::WindowShowState::kInactive) {
       Activate();
+      if (!weak_this) {
+        return;
+      }
     }
     SetInitialFocus(IsActive() ? show_state
                                : ui::mojom::WindowShowState::kInactive);

Evaluated with Chrome root at commit: c6718ebbf2c677c6f63691e28c7801c563f6b19c


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.

View on issue tracker