CVE-2026-17699
Overview
Changed Functions
| Function | Change | Notes |
|---|---|---|
DeleteWindowOnTouchCancelDelegateash/wm/window_modality_controller_unittest.cc |
modified | |
TEST_Fash/wm/window_modality_controller_unittest.cc |
modified | |
ifui/wm/core/window_modality_controller.cc |
modified |
Files Changed
ash/wm/window_modality_controller_unittest.ccui/wm/core/window_modality_controller.cc
Patch
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.
Regression Test / PoC
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
Original Bug Report
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 callswindow->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
- Establish active touches on a window in the transient tree of the browser.
- Trigger a modal dialog or visibility change that invokes
WindowModalityController::OnWindowVisibilityChangedon a modal window. - Register an event handler (or utilize existing Aura/Widget close paths) that synchronously closes or destroys the window/widget upon receiving the
ET_TOUCH_CANCELLEDevent. - Observe the Use-After-Free crash on the browser’s UI thread when
OnWindowVisibilityChangedresumes 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.
Recommended Remediation
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.