CVE-2026-19142
Overview
Changed Functions
| Function | Change | Notes |
|---|---|---|
ifui/aura/client/default_capture_client.cc |
modified | |
ifui/aura/window_event_dispatcher.cc |
modified | |
DeleteOtherWindowOnCaptureLostDelegateui/aura/window_event_dispatcher_unittest.cc |
modified | |
ifui/aura/window_event_dispatcher_unittest.cc |
modified | |
TEST_Fui/aura/window_event_dispatcher_unittest.cc |
modified |
Files Changed
ui/aura/client/default_capture_client.ccui/aura/client/default_capture_client.hui/aura/window_event_dispatcher.ccui/aura/window_event_dispatcher_unittest.cc
Patch
From 5c2010f1a7ea14ead6d4a9848cce9f783ddd9fc1 Mon Sep 17 00:00:00 2001 From: Mitsuru Oshima <[email protected]> Date: Thu, 30 Jul 2026 13:21:50 -0700 Subject: [PATCH] views/aura/wm: Use weak pointer for capture window This CL changes capture_window_ in DesktopCaptureClient, DefaultCaptureClient, and CaptureController to use base::WeakPtr instead of raw_ptr to avoid dangling pointers when the capture window is destroyed. It also updates WindowEventDispatcher::UpdateCapture to block the deletion of new_capture_window, because deleting it and recursively call UpdateCapture can cause unexpected result. If this ever happens in the field, we'll work on a fix for that scenario. Bug: 515428251, 502041642 TAG=agy CONV=c8f9d59f-22c6-456c-9f6d-52b1cd075207 Change-Id: I9740e11f2d8f93a94f4a1aa0de6ce8795c34a09e Reviewed-on: https://chromium-review.googlesource.com/c/chromium/src/+/8127178 Reviewed-by: Thomas Lukaszewicz <[email protected]> Commit-Queue: Mitsuru Oshima <[email protected]> Cr-Commit-Position: refs/heads/main@{#1671366} --- diff --git a/ui/aura/client/default_capture_client.cc b/ui/aura/client/default_capture_client.cc index ee11652..413b328 100644 --- a/ui/aura/client/default_capture_client.cc +++ b/ui/aura/client/default_capture_client.cc @@ -4,6 +4,7 @@ #include "ui/aura/client/default_capture_client.h" +#include "base/no_destructor.h" #include "base/observer_list.h" #include "ui/aura/client/capture_client_observer.h" #include "ui/aura/env.h" @@ -16,7 +17,10 @@ namespace { // Track the active capture window across root windows. -Window* global_capture_window_ = nullptr; +base::WeakPtr<Window>& GetGlobalCaptureWindowRef() { + static base::NoDestructor<base::WeakPtr<Window>> global_capture_window; + return *global_capture_window; +} } // namespace @@ -27,20 +31,25 @@ } DefaultCaptureClient::~DefaultCaptureClient() { - if (global_capture_window_ == capture_window_) - global_capture_window_ = nullptr; + if (GetGlobalCaptureWindowRef().get() == capture_window_.get()) { + GetGlobalCaptureWindowRef() = nullptr; + } SetCaptureClient(root_window_, nullptr); } void DefaultCaptureClient::SetCapture(Window* window) { - if (capture_window_ == window) + if (capture_window_.get() == window) { return; + } + auto new_capture_window_weak = + window ? window->GetWeakPtrAsWindow() : nullptr; if (window) Env::GetInstance()->gesture_recognizer()->CancelActiveTouchesExcept(window); - Window* old_capture_window = capture_window_; - capture_window_ = window; - global_capture_window_ = window; + auto old_capture_window = capture_window_; + + capture_window_ = new_capture_window_weak; + GetGlobalCaptureWindowRef() = new_capture_window_weak; CaptureDelegate* capture_delegate = nullptr; if (capture_window_) { @@ -54,24 +63,26 @@ capture_delegate->ReleaseNativeCapture(); } - capture_delegate->UpdateCapture(old_capture_window, capture_window_); + capture_delegate->UpdateCapture(old_capture_window.get(), + capture_window_.get()); observers_.Notify(&CaptureClientObserver::OnCaptureChanged, - old_capture_window, capture_window_); + old_capture_window.get(), capture_window_.get()); } void DefaultCaptureClient::ReleaseCapture(Window* window) { - if (capture_window_ != window) + if (capture_window_.get() != window) { return; - SetCapture(NULL); + } + SetCapture(nullptr); } Window* DefaultCaptureClient::GetCaptureWindow() { - return capture_window_; + return capture_window_.get(); } Window* DefaultCaptureClient::GetGlobalCaptureWindow() { - return global_capture_window_; + return GetGlobalCaptureWindowRef().get(); } void DefaultCaptureClient::AddObserver(CaptureClientObserver* observer) { diff --git a/ui/aura/client/default_capture_client.h b/ui/aura/client/default_capture_client.h index 8a0c3cd..b283438 100644 --- a/ui/aura/client/default_capture_client.h +++ b/ui/aura/client/default_capture_client.h @@ -6,6 +6,7 @@ #define UI_AURA_CLIENT_DEFAULT_CAPTURE_CLIENT_H_ #include "base/memory/raw_ptr.h" +#include "base/memory/weak_ptr.h" #include "base/observer_list.h" #include "ui/aura/aura_export.h" #include "ui/aura/client/capture_client.h" @@ -33,7 +34,7 @@ private: raw_ptr<Window> root_window_; // May be null. - raw_ptr<Window> capture_window_; + base::WeakPtr<Window> capture_window_; base::ObserverList<CaptureClientObserver>::Unchecked observers_; }; diff --git a/ui/aura/window_event_dispatcher.cc b/ui/aura/window_event_dispatcher.cc index 35fd163f..99178114 100644 --- a/ui/aura/window_event_dispatcher.cc +++ b/ui/aura/window_event_dispatcher.cc @@ -430,6 +430,12 @@ if (mouse_moved_handler_ && !window()->Contains(mouse_moved_handler_)) mouse_moved_handler_ = nullptr; + std::unique_ptr<Window::ScopedDeleteBlocker> new_capture_blocker; + if (new_capture) { + new_capture_blocker = + std::make_unique<Window::ScopedDeleteBlocker>(new_capture); + } + if (old_capture && old_capture->GetRootWindow() == window() && old_capture->delegate()) { // Send a capture changed event with the most recent mouse screen location. diff --git a/ui/aura/window_event_dispatcher_unittest.cc b/ui/aura/window_event_dispatcher_unittest.cc index 2f0ea0c5..33e1c5e 100644 --- a/ui/aura/window_event_dispatcher_unittest.cc +++ b/ui/aura/window_event_dispatcher_unittest.cc @@ -2594,6 +2594,25 @@ std::unique_ptr<aura::Window> capture_window_; }; +class DeleteOtherWindowOnCaptureLostDelegate : public test::TestWindowDelegate { + public: + DeleteOtherWindowOnCaptureLostDelegate() = default; + ~DeleteOtherWindowOnCaptureLostDelegate() override = default; + + void set_other_window(Window* other) { other_ = other; } + + // test::TestWindowDelegate: + void OnCaptureLost() override { + if (other_) { + Window* raw_other = std::exchange(other_, nullptr); + delete raw_other; + } + } + + private: + raw_ptr<Window> other_ = nullptr; +}; + } // namespace // Verifies handling loss of capture by the capture window being hidden. @@ -2612,6 +2631,29 @@ EXPECT_EQ(NULL, capture_window_tracker.capture_window()); } +using WindowEventDispatcherDeathTest = WindowEventDispatcherTest; + +// Verifies that deleting the new capture window during OnCaptureLost of the +// old capture window causes a crash (due to ScopedDeleteBlocker). +TEST_F(WindowEventDispatcherDeathTest, DeleteNewCaptureDuringOnCaptureLost) { + DeleteOtherWindowOnCaptureLostDelegate d1; + test::TestWindowDelegate d2; + std::unique_ptr<Window> w1(CreateNormalWindow(1, root_window(), &d1)); +
Regression Test / PoC
diff --git a/ui/aura/window_event_dispatcher_unittest.cc b/ui/aura/window_event_dispatcher_unittest.cc
index 2f0ea0c5..33e1c5e 100644
--- a/ui/aura/window_event_dispatcher_unittest.cc
+++ b/ui/aura/window_event_dispatcher_unittest.cc
@@ -2594,6 +2594,25 @@
std::unique_ptr<aura::Window> capture_window_;
};
+class DeleteOtherWindowOnCaptureLostDelegate : public test::TestWindowDelegate {
+ public:
+ DeleteOtherWindowOnCaptureLostDelegate() = default;
+ ~DeleteOtherWindowOnCaptureLostDelegate() override = default;
+
+ void set_other_window(Window* other) { other_ = other; }
+
+ // test::TestWindowDelegate:
+ void OnCaptureLost() override {
+ if (other_) {
+ Window* raw_other = std::exchange(other_, nullptr);
+ delete raw_other;
+ }
+ }
+
+ private:
+ raw_ptr<Window> other_ = nullptr;
+};
+
} // namespace
// Verifies handling loss of capture by the capture window being hidden.
@@ -2612,6 +2631,29 @@
EXPECT_EQ(NULL, capture_window_tracker.capture_window());
}
+using WindowEventDispatcherDeathTest = WindowEventDispatcherTest;
+
+// Verifies that deleting the new capture window during OnCaptureLost of the
+// old capture window causes a crash (due to ScopedDeleteBlocker).
+TEST_F(WindowEventDispatcherDeathTest, DeleteNewCaptureDuringOnCaptureLost) {
+ DeleteOtherWindowOnCaptureLostDelegate d1;
+ test::TestWindowDelegate d2;
+ std::unique_ptr<Window> w1(CreateNormalWindow(1, root_window(), &d1));
+
+ Window* w2 = CreateNormalWindow(2, root_window(), &d2);
+
+ d1.set_other_window(w2);
+
+ client::GetCaptureClient(root_window())->SetCapture(w1.get());
+ EXPECT_EQ(w1.get(),
+ client::GetCaptureClient(root_window())->GetCaptureWindow());
+
+ // This will trigger UpdateCapture(w1, w2).
+ // w1's OnCaptureLost will delete w2, which should crash due to
+ // ScopedDeleteBlocker.
+ EXPECT_DEATH(client::GetCaptureClient(root_window())->SetCapture(w2), "");
+}
+
namespace {
class RunLoopHandler : public ui::EventHandler {
Original Bug Report
Potential Use-After-Free in CaptureController and WindowEventDispatcher via reentrant destruction
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 Use-After-Free vulnerability exists in the browser process due to a missing liveness check for a window acquiring mouse capture during reentrant event dispatch. If the new capture window is destroyed reentrantly, a dangling pointer is assigned to WindowEventDispatcher members, bypassing MiraclePtr protections. This can potentially lead to arbitrary code execution in the unsandboxed browser process.
Affected files:
ui/wm/core/capture_controller.ccui/aura/window_event_dispatcher.ccui/aura/window_event_dispatcher.h
Estimated timestamp from git blame: 2022-07-18
Root Cause Analysis
A potential Use-After-Free (UAF) vulnerability exists in wm::CaptureController::SetCapture and aura::WindowEventDispatcher::UpdateCapture. When changing mouse capture, the system iterates through multiple CaptureDelegate (WindowEventDispatcher) objects to update their state.
In ui/wm/core/capture_controller.cc, the SetCapture method specifically re-checks the liveness of the old_capture_window after each delegate call using a base::WeakPtr. However, it neglects to perform a similar check for the new_capture_window:
for (const auto& it : delegates) {
it.second->UpdateCapture(old_capture_window, new_capture_window);
if (old_capture_window && !old_capture_window_weak)
old_capture_window = nullptr;
// Missing: liveness check for new_capture_window
}
During the call to UpdateCapture, the dispatcher may synchronously dispatch events (like kMouseCaptureChanged) and execute callbacks (like OnCaptureLost). These callbacks can trigger reentrant UI logic—such as cancelling a menu—that destroys the window intended to receive the new capture.
MiraclePtr Bypass Mechanism
This vulnerability potentially bypasses MiraclePtr (BackupRefPtr). When new_capture_window is destroyed during the reentrant portion of UpdateCapture, the Aura architecture ensures that all raw_ptr<Window> references (in CaptureController, WindowEventDispatcher, etc.) are cleared via observers. If no other raw_ptr references exist, the MiraclePtr refcount hits zero and the memory is immediately freed.
Upon returning from the reentrant call, UpdateCapture continues using a stack-based bare pointer (new_capture) which is now dangling. It proceeds to assign this dangling address to the mouse_moved_handler_ member (a raw_ptr<Window>). Since the assignment occurs after the free, MiraclePtr cannot prevent the UAF.
Potential Attack Scenario
- An attacker triggers a capture change to a specific window (e.g., by interacting with a complex menu or widget).
- During the synchronous
OnCaptureLostcallback for the previous capture window, the attacker-controlled logic triggers a reentrant menu cancellation or widget destruction that targets thenew_capture_window. - The
new_capture_windowis destroyed and its memory is freed because allraw_ptrreferences are cleared reentrantly. - The original
UpdateCapturecall resumes and assigns the dangling pointer tomouse_moved_handler_. - A subsequent mouse move event dereferences
mouse_moved_handler_inDispatchMouseEnterOrExit, potentially leading to control flow hijacking in the browser process.
Suggested Fix
Add a liveness check for new_capture_window inside the delegate loop in CaptureController::SetCapture using the existing new_capture_window_weak tracker:
for (const auto& it : delegates) {
it.second->UpdateCapture(old_capture_window, new_capture_window);
if (old_capture_window && !old_capture_window_weak)
old_capture_window = nullptr;
if (new_capture_window && !new_capture_window_weak)
new_capture_window = nullptr;
}
Evaluated with Chrome root at commit: 29093e11cf509e3593f6229e4b1b075cca356049
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.