CVE-2026-15111
Overview
Files Changed
ui/views/widget/widget.cc
Patch
From 9ba0edd332072fb0ab749d3a0ec21c371e2c52a6 Mon Sep 17 00:00:00 2001 From: David Yeung <[email protected]> Date: Mon, 29 Jun 2026 13:33:50 -0700 Subject: [PATCH] Handle widget destruction during Widget::SetCapture NativeWidgetPrivate::SetCapture() dispatches capture-change events to the previously capturing window, which may close the widget that is acquiring capture. This is more of a stability bug addressing a potential reentrancy point and improves widget robustness. Fixed: 517508651 Change-Id: I910efa1b65d7e2d11c676c5aab47ef5830dad8bf Reviewed-on: https://chromium-review.googlesource.com/c/chromium/src/+/8021217 Reviewed-by: Keren Zhu <[email protected]> Commit-Queue: David Yeung <[email protected]> Cr-Commit-Position: refs/heads/main@{#1654319} --- diff --git a/ui/views/widget/widget.cc b/ui/views/widget/widget.cc index f63e6e8f..3461abe 100644 --- a/ui/views/widget/widget.cc +++ b/ui/views/widget/widget.cc @@ -1623,10 +1623,13 @@ } if (!native_widget_->HasCapture()) { + WidgetDeletionObserver widget_deletion_observer(this); native_widget_->SetCapture(); - // Early return if setting capture was unsuccessful. - if (!native_widget_->HasCapture()) { + // Early return if this widget was destroyed, the native widget was torn + // down, or setting capture was unsuccessful. + if (!widget_deletion_observer.IsWidgetAlive() || !native_widget_ || + !native_widget_->HasCapture()) { return; } }
Original Bug Report
Potential Use-After-Free in Widget::SetCapture due to synchronous 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 potential Use-After-Free (UAF) vulnerability exists in Widget::SetCapture due to reentrant, synchronous destruction of the Widget during the native_widget_->SetCapture() call. If the widget is synchronously closed/deleted during nested event dispatch or touch cancellation, control flow returns to a dangling this pointer in Widget::SetCapture. This can lead to subsequent memory corruption, UAF writes, and virtual method hijacking in the browser process.
Affected files:
ui/views/widget/widget.cc
Estimated timestamp from git blame: 2014-07-17
Problem Description
A potential Use-After-Free (UAF) vulnerability exists in Widget::SetCapture (located in ui/views/widget/widget.cc). The method calls native_widget_->SetCapture() to initiate native input capture, but does not verify whether this is still alive after the call.
During the synchronous, reentrant execution of native_widget_->SetCapture(), the underlying Widget can be synchronously destroyed. This can happen through synchronous touch-cancel or gesture event dispatch:
- In Aura-based window systems,
CaptureController::SetCapture(orDesktopCaptureClient::SetCapture) invokesGestureRecognizer::CancelActiveTouchesExcept(new_capture_window). This synchronously dispatches synthetic touch-cancel/gesture-end events to other windows. If the target widget (or its event handlers) reacts by closing itself, theWidgetis synchronously deleted under the defaultNATIVE_WIDGET_OWNS_WIDGETownership model.
Once the Widget is destroyed, control flow unwinds back to Widget::SetCapture. At this point, this is a dangling pointer pointing to freed memory. The function then performs multiple operations using the freed memory:
- Line 1622: Loads
this->native_widget_(abase::WeakPtr) and calls the virtualHasCapture(). If the memory has been reclaimed or corrupted, this leads to a virtual method call on an attacker-influenced pointer. - Line 1627: Writes to
this->is_mouse_button_pressed_(a 1-byte write into freed memory) and calls virtualIsMouseButtonDown()through the dangling pointer. - Line 1628: Loads
this->root_view_(astd::unique_ptr) and invokes the virtualSetMouseAndGestureHandler()on it.
Note that MiraclePtr (BackupRefPtr) provides no protection here because this is a bare stack pointer, and the dangerous loads/writes are performed on fields stored inside the freed object, not through a raw_ptr<> pointing to it.
Vulnerable Code
// ui/views/widget/widget.cc:1613-1629
void Widget::SetCapture(View* view) {
if (!native_widget_) {
return;
}
if (!native_widget_->HasCapture()) {
native_widget_->SetCapture(); // <--- Reentrant call can synchronously destroy `this`
// Early return if setting capture was unsuccessful.
if (!native_widget_->HasCapture()) { // <--- UAF read and virtual call
return;
}
}
is_mouse_button_pressed_ = native_widget_->IsMouseButtonDown(); // <--- UAF write and virtual call
root_view_->SetMouseAndGestureHandler(view);
}
Potential Steps to Trigger
Since our analysis is static and our tooling does not yet have the ability to run code, these are potential steps an attacker would follow to trigger the vulnerability:
- A compromised renderer process initiates a multi-touch session, targeting a pop-up window or dialog owned by a specific browser-side
Widget. - The renderer dispatches a sequence of events (e.g., drag-and-drop or gesture event sequences) that forces the browser process to call
Widget::SetCaptureto capture future inputs. - During the capture change, the browser window manager triggers
CancelActiveTouchesExcept()to cancel outstanding touch inputs on the window. - The synthetic touch cancel events propagate to pre-target event handlers, which respond by immediately closing the active widget (e.g., via
Widget::CloseNow()). - When the closed widget is synchronously deleted mid-call, control returns to
Widget::SetCapturewith a danglingthispointer. - The attacker reclaims the freed
Widgetmemory allocation in the browser process heap (for example, by loading specific media, images, or large strings) to overwrite the object’s memory with controlled data, gaining control over virtual table pointers or smart pointer structures to hijack control flow.
Proposed Fix
To prevent this vulnerability, Widget::SetCapture should check whether the widget was destroyed during the synchronous call, similar to how it is handled in Widget::OnMouseEvent using a WidgetDeletionObserver:
void Widget::SetCapture(View* view) {
if (!native_widget_) {
return;
}
if (!native_widget_->HasCapture()) {
WidgetDeletionObserver native_widget_observer(this);
native_widget_->SetCapture();
if (!native_widget_observer.IsWidgetAlive()) {
return;
}
// Early return if setting capture was unsuccessful.
if (!native_widget_->HasCapture()) {
return;
}
}
is_mouse_button_pressed_ = native_widget_->IsMouseButtonDown();
root_view_->SetMouseAndGestureHandler(view);
}
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.