Overview

High
Severity
CVSS
No
Exploited ITW
Fixed
Fix Status
ImpactUse after free in Views
DescriptionUse after free in Views
ComponentViews
Bug ClassUAF
Tracker517508651
Fix commit9ba0edd33207 (chromium/src) +5/-2
CISA KEVNot listed
CreditedGoogle
Disclosed2026-07-08

Files Changed

  • ui/views/widget/widget.cc
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;
     }
   }
Loading diff…

Original Bug Report

reported by [email protected]

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 (or DesktopCaptureClient::SetCapture) invokes GestureRecognizer::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, the Widget is synchronously deleted under the default NATIVE_WIDGET_OWNS_WIDGET ownership 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_ (a base::WeakPtr) and calls the virtual HasCapture(). 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 virtual IsMouseButtonDown() through the dangling pointer.
  • Line 1628: Loads this->root_view_ (a std::unique_ptr) and invokes the virtual SetMouseAndGestureHandler() 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:

  1. A compromised renderer process initiates a multi-touch session, targeting a pop-up window or dialog owned by a specific browser-side Widget.
  2. The renderer dispatches a sequence of events (e.g., drag-and-drop or gesture event sequences) that forces the browser process to call Widget::SetCapture to capture future inputs.
  3. During the capture change, the browser window manager triggers CancelActiveTouchesExcept() to cancel outstanding touch inputs on the window.
  4. The synthetic touch cancel events propagate to pre-target event handlers, which respond by immediately closing the active widget (e.g., via Widget::CloseNow()).
  5. When the closed widget is synchronously deleted mid-call, control returns to Widget::SetCapture with a dangling this pointer.
  6. The attacker reclaims the freed Widget memory 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.

View on issue tracker