Overview

High
Severity
CVSS
No
Exploited ITW
Fixed
Fix Status
ImpactUse after free in Views
DescriptionUse after free in Views
ComponentViews
Bug ClassUAF
Tracker517785292
Fix commitb4088183e168 (chromium/src) +61/-0
CISA KEVNot listed
CreditedGoogle
Disclosed2026-07-29

Changed Functions

FunctionChangeNotes
DeleteWindowOnTouchCancelDelegate
ash/wm/window_modality_controller_unittest.cc
modified
TEST_F
ash/wm/window_modality_controller_unittest.cc
modified
if
ui/wm/core/window_modality_controller.cc
modified

Files Changed

  • ash/wm/window_modality_controller_unittest.cc
  • ui/wm/core/window_modality_controller.cc
From b4088183e168e849b0fe214593789f4b78d9167d Mon Sep 17 00:00:00 2001
From: Achuith Bhandarkar <achuith@chromium.org>
Date: Sat, 20 Jun 2026 17:01:49 -0700
Subject: [PATCH] wm: Prevent UAF in WindowModalityController with base::WeakPtr

Synchronous touch cancellation during modal visibility changes can
destroy the modal window, causing a UAF when control returns. We use a
weak ptr check to return early in this case. We don't need a similar
check in OnWindowPropertyChanged because it is already guarded by an
active ScopedDeleteBlocker in the caller.

Bug: 517785292
Change-Id: I19a4ed2cca81c33f0ead528db4bbf01819dea327
Reviewed-on: https://chromium-review.googlesource.com/c/chromium/src/+/7904996
Reviewed-by: Mitsuru Oshima <oshima@chromium.org>
Commit-Queue: Achuith Bhandarkar <achuith@chromium.org>
Cr-Commit-Position: refs/heads/main@{#1649974}
---

diff --git a/ash/wm/window_modality_controller_unittest.cc b/ash/wm/window_modality_controller_unittest.cc
index ab0c10f..b81ab63 100644
--- a/ash/wm/window_modality_controller_unittest.cc
+++ b/ash/wm/window_modality_controller_unittest.cc
@@ -44,6 +44,34 @@
   return true;
 }
 
+class DeleteWindowOnTouchCancelDelegate
+    : public aura::test::TestWindowDelegate {
+ public:
+  DeleteWindowOnTouchCancelDelegate() = default;
+  ~DeleteWindowOnTouchCancelDelegate() override = default;
+  DeleteWindowOnTouchCancelDelegate(const DeleteWindowOnTouchCancelDelegate&) =
+      delete;
+  DeleteWindowOnTouchCancelDelegate& operator=(
+      const DeleteWindowOnTouchCancelDelegate&) = delete;
+
+  void SetWindowToDelete(std::unique_ptr<aura::Window> w) {
+    window_to_delete_ = std::move(w);
+  }
+
+  bool IsWindowDeleted() const { return !window_to_delete_; }
+
+  // Overridden from aura::test::TestWindowDelegate.
+  void OnTouchEvent(ui::TouchEvent* event) override {
+    if (event->type() == ui::EventType::kTouchCancelled) {
+      window_to_delete_.reset();
+    }
+    aura::test::TestWindowDelegate::OnTouchEvent(event);
+  }
+
+ private:
+  std::unique_ptr<aura::Window> window_to_delete_;
+};
+
 }  // namespace
 
 // Creates three windows, w1, w11, and w12. w11 is a non-modal transient, w12 is
@@ -726,4 +754,30 @@
   EXPECT_TRUE(wm::IsActiveWindow(w4.get()));
 }
 
+// Verifies that destroying a modal window during the touch cancellation
+// (dispatched synchronously when the modal window becomes visible) is handled
+// gracefully and does not cause a UAF crash.
+TEST_F(WindowModalityControllerTest, DeleteModalWindowDuringTouchCancel) {
+  DeleteWindowOnTouchCancelDelegate d1;
+  std::unique_ptr<aura::Window> w1(
+      CreateTestWindowInShell({.delegate = &d1, .bounds = {100, 100}}));
+
+  aura::test::TestWindowDelegate d2;
+  std::unique_ptr<aura::Window> w2(CreateTestWindowInShell(
+      {.delegate = &d2, .bounds = {20, 20, 20, 20}, .show = false}));
+  w2->SetProperty(aura::client::kModalKey, ui::mojom::ModalType::kWindow);
+  ::wm::AddTransientChild(w1.get(), w2.get());
+
+  ui::test::EventGenerator generator(Shell::GetPrimaryRootWindow(),
+                                     gfx::Point(10, 10));
+  generator.PressTouch();
+
+  aura::Window* w2_ptr = w2.get();
+  d1.SetWindowToDelete(std::move(w2));
+
+  w2_ptr->Show();
+
+  EXPECT_TRUE(d1.IsWindowDeleted());
+}
+
 }  // namespace ash
diff --git a/ui/wm/core/window_modality_controller.cc b/ui/wm/core/window_modality_controller.cc
index 5373950..8204f350 100644
--- a/ui/wm/core/window_modality_controller.cc
+++ b/ui/wm/core/window_modality_controller.cc
@@ -178,7 +178,14 @@
                                                          bool visible) {
   if (visible && window->GetProperty(aura::client::kModalKey) !=
                      ui::mojom::ModalType::kNone) {
+    // CancelTouchesOnTransientWindowTree() dispatches touch cancellation events
+    // synchronously, which can run arbitrary handlers that destroy the window.
+    // Use base::WeakPtr to check if the window is still alive.
+    base::WeakPtr<aura::Window> weak_window = window->GetWeakPtrAsWindow();
     CancelTouchesOnTransientWindowTree(window);
+    if (!weak_window) {
+      return;
+    }
 
     // Make sure no other window has capture, otherwise |window| won't get mouse
     // events.
Loading diff…

Regression Test / PoC

shipped with the fix
diff --git a/ash/wm/window_modality_controller_unittest.cc b/ash/wm/window_modality_controller_unittest.cc
index ab0c10f..b81ab63 100644
--- a/ash/wm/window_modality_controller_unittest.cc
+++ b/ash/wm/window_modality_controller_unittest.cc
@@ -44,6 +44,34 @@
   return true;
 }
 
+class DeleteWindowOnTouchCancelDelegate
+    : public aura::test::TestWindowDelegate {
+ public:
+  DeleteWindowOnTouchCancelDelegate() = default;
+  ~DeleteWindowOnTouchCancelDelegate() override = default;
+  DeleteWindowOnTouchCancelDelegate(const DeleteWindowOnTouchCancelDelegate&) =
+      delete;
+  DeleteWindowOnTouchCancelDelegate& operator=(
+      const DeleteWindowOnTouchCancelDelegate&) = delete;
+
+  void SetWindowToDelete(std::unique_ptr<aura::Window> w) {
+    window_to_delete_ = std::move(w);
+  }
+
+  bool IsWindowDeleted() const { return !window_to_delete_; }
+
+  // Overridden from aura::test::TestWindowDelegate.
+  void OnTouchEvent(ui::TouchEvent* event) override {
+    if (event->type() == ui::EventType::kTouchCancelled) {
+      window_to_delete_.reset();
+    }
+    aura::test::TestWindowDelegate::OnTouchEvent(event);
+  }
+
+ private:
+  std::unique_ptr<aura::Window> window_to_delete_;
+};
+
 }  // namespace
 
 // Creates three windows, w1, w11, and w12. w11 is a non-modal transient, w12 is
@@ -726,4 +754,30 @@
   EXPECT_TRUE(wm::IsActiveWindow(w4.get()));
 }
 
+// Verifies that destroying a modal window during the touch cancellation
+// (dispatched synchronously when the modal window becomes visible) is handled
+// gracefully and does not cause a UAF crash.
+TEST_F(WindowModalityControllerTest, DeleteModalWindowDuringTouchCancel) {
+  DeleteWindowOnTouchCancelDelegate d1;
+  std::unique_ptr<aura::Window> w1(
+      CreateTestWindowInShell({.delegate = &d1, .bounds = {100, 100}}));
+
+  aura::test::TestWindowDelegate d2;
+  std::unique_ptr<aura::Window> w2(CreateTestWindowInShell(
+      {.delegate = &d2, .bounds = {20, 20, 20, 20}, .show = false}));
+  w2->SetProperty(aura::client::kModalKey, ui::mojom::ModalType::kWindow);
+  ::wm::AddTransientChild(w1.get(), w2.get());
+
+  ui::test::EventGenerator generator(Shell::GetPrimaryRootWindow(),
+                                     gfx::Point(10, 10));
+  generator.PressTouch();
+
+  aura::Window* w2_ptr = w2.get();
+  d1.SetWindowToDelete(std::move(w2));
+
+  w2_ptr->Show();
+
+  EXPECT_TRUE(d1.IsWindowDeleted());
+}
+
 }  // namespace ash
Loading diff…

Original Bug Report

reported by vm...@google.com

Potential Use-After-Free in WindowModalityController::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) exists in WindowModalityController::OnWindowVisibilityChanged in ash-chrome and desktop Aura. When a modal window becomes visible, the controller synchronously dispatches touch cancellation events, which can run arbitrary event handlers and destroy the window. Upon return, the code continues to dereference the potentially freed window pointer without any liveness checks.

Affected files:

  • ui/wm/core/window_modality_controller.cc

Estimated timestamp from git blame: 2018-11-14

Analysis

In ui/wm/core/window_modality_controller.cc, WindowModalityController::OnWindowVisibilityChanged performs multiple operations on a raw aura::Window* pointer without tracking its lifecycle across synchronous event dispatches.

When a modal window is shown, the function calls CancelTouchesOnTransientWindowTree(window):

void WindowModalityController::OnWindowVisibilityChanged(aura::Window* window,
                                                         bool visible) {
  if (visible && window->GetProperty(aura::client::kModalKey) !=
                     ui::mojom::ModalType::kNone) {
    CancelTouchesOnTransientWindowTree(window);

    // Make sure no other window has capture, otherwise |window| won't get mouse
    // events.
    aura::Window* capture_window = aura::client::GetCaptureWindow(window);
...

The call to CancelTouchesOnTransientWindowTree(window) invokes env_->gesture_recognizer()->CancelActiveTouchesOn(blocked_consumers), which synthesizes ET_TOUCH_CANCELLED events and dispatches them synchronously through the Aura event handler chain.

In Aura, it is a known and supported behavior for event handlers to synchronously destroy associated windows or hosts during a touch cancellation dispatch (this is explicitly verified by unit tests such as CancelActiveTouchesUAFWhenHandlerDeletesHost in ui/aura/window_event_dispatcher_unittest.cc, and commented on in aura::Window::CleanupGestureState).

If an event handler synchronously destroys the modal window (or its transient parent, which recursively deletes the child) during this event dispatch, the window pointer is freed. When execution returns to OnWindowVisibilityChanged, the function immediately proceeds to dereference window at:

  • Line 185: aura::client::GetCaptureWindow(window) (which calls window->GetRootWindow())
  • Line 188: window->GetProperty(aura::client::kModalKey)
  • Line 190: GetModalParent(window)

Because window is passed as a raw C++ pointer on the stack, MiraclePtr (BackupRefPtr) does not protect it, resulting in a Use-After-Free (UAF) in the unsandboxed browser/Ash process.

Potential Steps to Trigger

  1. Establish active touches on a window in the transient tree of the browser.
  2. Trigger a modal dialog or visibility change that invokes WindowModalityController::OnWindowVisibilityChanged on a modal window.
  3. Register an event handler (or utilize existing Aura/Widget close paths) that synchronously closes or destroys the window/widget upon receiving the ET_TOUCH_CANCELLED event.
  4. Observe the Use-After-Free crash on the browser’s UI thread when OnWindowVisibilityChanged resumes and dereferences the deleted window.

Note: These are potential/suggested steps; our tooling does not currently have the capability to run code or dynamically validate a proof of concept.

Use aura::WindowTracker to verify the liveness of the window across the synchronous touch cancellation call:

void WindowModalityController::OnWindowVisibilityChanged(aura::Window* window,
                                                         bool visible) {
  if (visible && window->GetProperty(aura::client::kModalKey) !=
                     ui::mojom::ModalType::kNone) {
    aura::WindowTracker tracker({window});
    CancelTouchesOnTransientWindowTree(window);
    if (!tracker.Contains(window)) {
      return;
    }

    // Make sure no other window has capture, otherwise |window| won't get mouse
    // events.
    aura::Window* capture_window = aura::client::GetCaptureWindow(window);
...

Evaluated with Chrome root at commit: 5133b93d189b383c37805b1cf3a9d2dbfe8d7379


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