Overview

High
Severity
7.8
CVSS
No
Exploited ITW
Fixed
Fix Status
ImpactUse after free in Aura
DescriptionUse after free in Aura
ComponentAura
Sub-componentAura
Bug ClassUAF
Action tierwatch — corruption primitive but gated (uncommon) — not default-reachable
Computed by PatchGap's deterministic exploitability ruler from the fixing commit. patch-now = RCE-class & attacker-reachable in a default config, or exploited in the wild; watch = real security signal below that bar; noise = no security relevance.
Tracker517661385
Fix commite7059223305c (chromium/src) +45/-0
CWECWE-416 (Use-after-free)
CVSS vectorCVSS:3.1/AV:L/AC:L/PR:N/UI:R/S:U/C:H/I:H/A:H
CISA KEVNot listed
CreditedGoogle
Reported2026-05-29 (per vendor advisory)
Fix landed2026-08-27 (public git)
Disclosed2026-09-22
Patch gap26 days — fix public in source before the advisory

Background

`WindowEventDispatcher`
The Aura object that routes events for a root window and its host, exposing UpdateCapture to transition capture between windows.
`Window::ScopedDeleteBlocker`
A RAII guard that pins an Aura Window alive for its scope, deferring or blocking any deletion attempted while it is held.
`OnCaptureLost`
A window-delegate callback invoked when a window loses capture, which runs synchronously in the middle of UpdateCapture and can re-enter into arbitrary client code.
Re-entrant deletion
Destruction of an object triggered synchronously from a callback that runs while an outer method is still executing on that same object’s memory.

Root Cause Analysis

The vulnerable path is WindowEventDispatcher::UpdateCapture in ui/aura/window_event_dispatcher.cc, which fires the old capture window’s OnCaptureLost while continuing to touch the dispatcher, its root window(), and its host afterward. Callers such as DesktopCaptureClient or CaptureController assume the root window and WindowTreeHost remain alive across a capture update, but OnCaptureLost can synchronously destroy the host and thus the root window and the dispatcher itself. This violates the invariant that this and the objects reachable through window() outlive the body of UpdateCapture, leaving the method operating on freed memory once control returns from the callback.

The fix constructs a Window::ScopedDeleteBlocker root_window_blocker(window()) before the capture transition, so the root window (and by extension its host and dispatcher) cannot be torn down mid-method. The added DeleteHostDuringOnCaptureLost death test confirms that a re-entrant host deletion during OnCaptureLost is now blocked rather than silently corrupting memory.

Key insight
The single mistake was running the untrusted OnCaptureLost callback in the middle of UpdateCapture without pinning the root window, allowing the callback to free the very object the method continues to use; the fix pins it with a Window::ScopedDeleteBlocker for the duration of the update.

Attack Path

  1. Establish capture Attacker-influenced code sets capture on a target window w under the root window’s capture client, so w becomes the active capture window.
  2. Trigger a capture transition A capture release or reassignment invokes WindowEventDispatcher::UpdateCapture, which synchronously calls OnCaptureLost on the outgoing window’s delegate.
  3. Free the root window from the callback The delegate’s OnCaptureLost destroys the owning WindowTreeHost, which tears down the root window() and the WindowEventDispatcher itself while UpdateCapture is still on the stack.
  4. Use freed memory Execution returns into UpdateCapture, which continues to dereference the freed dispatcher and root-window state, producing a use-after-free.

Impact Assessment

An attacker who can influence capture handling in the browser (or a windowing) process gains a use-after-free on the freed WindowEventDispatcher and root-window objects, which can corrupt memory and potentially be steered toward controlled execution. The precondition is the ability to drive a capture transition whose OnCaptureLost delegate synchronously destroys the host or root window. Because the affected code is core Aura windowing infrastructure, the primitive sits in a privileged UI context rather than a sandboxed renderer.

Changed Functions

FunctionChangeNotes
if
ui/aura/window_event_dispatcher.cc
modified
DeleteHostOnCaptureLostDelegate
ui/aura/window_event_dispatcher_unittest.cc
modified
TEST_F
ui/aura/window_event_dispatcher_unittest.cc
modified
RunLoopHandler
ui/aura/window_event_dispatcher_unittest.cc
modified

Files Changed

  • ui/aura/window_event_dispatcher_unittest.cc
  • ui/aura/window_event_dispatcher.cc

Audit Directions

  • Callback-driven re-entrancy
    Audit methods that invoke delegate or client callbacks (like OnCaptureLost) partway through and then keep using this or shared state, since the callback may free those objects.
  • Unpinned host/root-window lifetimes
    Look for Aura and windowing code paths that assume the WindowTreeHost or root window survives a transition without holding a Window::ScopedDeleteBlocker or equivalent guard.
  • Capture and focus transitions
    Review capture, focus, and activation transition code where ownership can be relinquished mid-update, as these commonly re-enter client code capable of synchronous destruction.
From e7059223305cf6f2b182a41d763b62290e4dd350 Mon Sep 17 00:00:00 2001
From: Mitsuru Oshima <[email protected]>
Date: Wed, 26 Aug 2026 18:09:42 -0700
Subject: [PATCH] aura: Block root window deletion in WindowEventDispatcher::UpdateCapture

Callers of UpdateCapture (such as DesktopCaptureClient or
CaptureController) expect the root window and host to remain alive
during capture updates. Synchronous destruction of the host/root
window during OnCaptureLost() can lead to use-after-free.

This CL adds a ScopedDeleteBlocker for the root window in
WindowEventDispatcher::UpdateCapture to block re-entrant deletion of
the root window and its host, and adds a regression test.

Fixed=517661385
TAG=agy
CONV=fb9598b9-6723-4194-bd95-436415146211

Change-Id: I0bf1e30bc818515a55914160c1952715fec68191
Reviewed-on: https://chromium-review.googlesource.com/c/chromium/src/+/8294739
Commit-Queue: Mitsuru Oshima <[email protected]>
Reviewed-by: Thomas Lukaszewicz <[email protected]>
Cr-Commit-Position: refs/heads/main@{#1686824}
---
diff --git a/ui/aura/window_event_dispatcher.cc b/ui/aura/window_event_dispatcher.cc
index 5c1a14c..558785e 100644
--- a/ui/aura/window_event_dispatcher.cc
+++ b/ui/aura/window_event_dispatcher.cc
@@ -430,6 +430,9 @@
   if (mouse_moved_handler_ && !window()->Contains(mouse_moved_handler_))
     mouse_moved_handler_ = nullptr;
 
+  // Block the deletion of the root window, its host thus this dispatcher.
+  Window::ScopedDeleteBlocker root_window_blocker(window());
+
   std::unique_ptr<Window::ScopedDeleteBlocker> new_capture_blocker;
   if (new_capture) {
     new_capture_blocker =
diff --git a/ui/aura/window_event_dispatcher_unittest.cc b/ui/aura/window_event_dispatcher_unittest.cc
index 33e1c5e..6f88f91 100644
--- a/ui/aura/window_event_dispatcher_unittest.cc
+++ b/ui/aura/window_event_dispatcher_unittest.cc
@@ -22,6 +22,7 @@
 #include "testing/gtest/include/gtest/gtest.h"
 #include "ui/aura/client/aura_constants.h"
 #include "ui/aura/client/capture_client.h"
+#include "ui/aura/client/default_capture_client.h"
 #include "ui/aura/client/event_client.h"
 #include "ui/aura/client/focus_client.h"
 #include "ui/aura/env.h"
@@ -2613,6 +2614,22 @@
   raw_ptr<Window> other_ = nullptr;
 };
 
+class DeleteHostOnCaptureLostDelegate : public test::TestWindowDelegate {
+ public:
+  DeleteHostOnCaptureLostDelegate() = default;
+  ~DeleteHostOnCaptureLostDelegate() override = default;
+
+  void set_host(std::unique_ptr<WindowTreeHost> host) {
+    host_ = std::move(host);
+  }
+
+  // test::TestWindowDelegate:
+  void OnCaptureLost() override { host_.reset(); }
+
+ private:
+  std::unique_ptr<WindowTreeHost> host_;
+};
+
 }  // namespace
 
 // Verifies handling loss of capture by the capture window being hidden.
@@ -2654,6 +2671,31 @@
   EXPECT_DEATH(client::GetCaptureClient(root_window())->SetCapture(w2), "");
 }
 
+// Verifies that deleting the host/root window during OnCaptureLost of the
+// capture window causes a crash (due to ScopedDeleteBlocker).
+TEST_F(WindowEventDispatcherDeathTest, DeleteHostDuringOnCaptureLost) {
+  DeleteHostOnCaptureLostDelegate delegate;
+  std::unique_ptr<WindowTreeHost> host = WindowTreeHost::Create(
+      ui::PlatformWindowInitProperties{gfx::Rect{100, 100}});
+  host->InitHost();
+  host->window()->Show();
+
+  client::DefaultCaptureClient capture_client(host->window());
+
+  Window* w = CreateNormalWindow(1, host->window(), &delegate);
+  delegate.set_host(std::move(host));
+
+  client::GetCaptureClient(w->GetRootWindow())->SetCapture(w);
+  EXPECT_EQ(w,
+            client::GetCaptureClient(w->GetRootWindow())->GetCaptureWindow());
+
+  // This will trigger UpdateCapture(w, nullptr).
+  // w's OnCaptureLost will attempt to delete the host, which should crash due
+  // to ScopedDeleteBlocker on the root window.
+  EXPECT_DEATH(client::GetCaptureClient(w->GetRootWindow())->ReleaseCapture(w),
+               "");
+}
+
 namespace {
 
 class RunLoopHandler : public ui::EventHandler {
Loading diff…

Regression Test / PoC

shipped with the fix
diff --git a/ui/aura/window_event_dispatcher_unittest.cc b/ui/aura/window_event_dispatcher_unittest.cc
index 33e1c5e..6f88f91 100644
--- a/ui/aura/window_event_dispatcher_unittest.cc
+++ b/ui/aura/window_event_dispatcher_unittest.cc
@@ -22,6 +22,7 @@
 #include "testing/gtest/include/gtest/gtest.h"
 #include "ui/aura/client/aura_constants.h"
 #include "ui/aura/client/capture_client.h"
+#include "ui/aura/client/default_capture_client.h"
 #include "ui/aura/client/event_client.h"
 #include "ui/aura/client/focus_client.h"
 #include "ui/aura/env.h"
@@ -2613,6 +2614,22 @@
   raw_ptr<Window> other_ = nullptr;
 };
 
+class DeleteHostOnCaptureLostDelegate : public test::TestWindowDelegate {
+ public:
+  DeleteHostOnCaptureLostDelegate() = default;
+  ~DeleteHostOnCaptureLostDelegate() override = default;
+
+  void set_host(std::unique_ptr<WindowTreeHost> host) {
+    host_ = std::move(host);
+  }
+
+  // test::TestWindowDelegate:
+  void OnCaptureLost() override { host_.reset(); }
+
+ private:
+  std::unique_ptr<WindowTreeHost> host_;
+};
+
 }  // namespace
 
 // Verifies handling loss of capture by the capture window being hidden.
@@ -2654,6 +2671,31 @@
   EXPECT_DEATH(client::GetCaptureClient(root_window())->SetCapture(w2), "");
 }
 
+// Verifies that deleting the host/root window during OnCaptureLost of the
+// capture window causes a crash (due to ScopedDeleteBlocker).
+TEST_F(WindowEventDispatcherDeathTest, DeleteHostDuringOnCaptureLost) {
+  DeleteHostOnCaptureLostDelegate delegate;
+  std::unique_ptr<WindowTreeHost> host = WindowTreeHost::Create(
+      ui::PlatformWindowInitProperties{gfx::Rect{100, 100}});
+  host->InitHost();
+  host->window()->Show();
+
+  client::DefaultCaptureClient capture_client(host->window());
+
+  Window* w = CreateNormalWindow(1, host->window(), &delegate);
+  delegate.set_host(std::move(host));
+
+  client::GetCaptureClient(w->GetRootWindow())->SetCapture(w);
+  EXPECT_EQ(w,
+            client::GetCaptureClient(w->GetRootWindow())->GetCaptureWindow());
+
+  // This will trigger UpdateCapture(w, nullptr).
+  // w's OnCaptureLost will attempt to delete the host, which should crash due
+  // to ScopedDeleteBlocker on the root window.
+  EXPECT_DEATH(client::GetCaptureClient(w->GetRootWindow())->ReleaseCapture(w),
+               "");
+}
+
 namespace {
 
 class RunLoopHandler : public ui::EventHandler {
Loading diff…

Original Bug Report

reported by [email protected]

Potential Use-After-Free in WindowEventDispatcher::UpdateCapture after OnCaptureLost()

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 WindowEventDispatcher::UpdateCapture. When a capture change is dispatched, the synchronous invocation of OnCaptureLost() can lead to widget destruction. If the widget is synchronously destroyed, the owning WindowEventDispatcher is deleted, resulting in UAF reads/writes upon returning to the method.

Affected files:

  • ui/aura/window_event_dispatcher.cc

Estimated timestamp from git blame: 2012-05-14

Root Cause Analysis

In ui/aura/window_event_dispatcher.cc, the method WindowEventDispatcher::UpdateCapture is responsible for updating mouse/pointer capture state. When transitioning capture away from an old window, the code synchronously dispatches capture loss to the old capture window’s delegate:

void WindowEventDispatcher::UpdateCapture(Window* old_capture,
                                          Window* new_capture) {
  ...
  if (old_capture && old_capture->GetRootWindow() == window() &&
      old_capture->delegate()) {
    ...
    DispatchDetails details = DispatchEvent(old_capture, &event);
    if (details.dispatcher_destroyed)
      return;

    if (!details.target_destroyed)
      old_capture->delegate()->OnCaptureLost(); // Synchronous Re-entrancy Point
  }

  if (new_capture) {
    if (mouse_moved_handler_ || Env::GetInstance()->IsMouseButtonDown())
      mouse_moved_handler_ = new_capture; // Potential UAF write / read
  } else {
    DispatchDetails details = SynthesizeMouseMoveEvent(); // Potential UAF method call
    if (details.dispatcher_destroyed) return;
  }
  mouse_pressed_handler_ = nullptr; // Potential UAF write
}

For desktop widgets on Linux (under X11 or Wayland), old_capture->delegate()->OnCaptureLost() propagates through DesktopNativeWidgetAura::OnCaptureLost() -> Widget::OnMouseCaptureLost() -> RootView::OnMouseCaptureLost().

Views are permitted to respond to capture loss by synchronously destroying the widget (e.g., calling Widget::CloseNow()). When this occurs, it initiates a synchronous destruction chain:

  1. Widget::CloseNow() calls DesktopNativeWidgetAura::CloseNow(), which calls DesktopWindowTreeHostPlatform::CloseNow().
  2. This triggers the platform window to close, executing platform_window_delegate_->OnClosed(), leading to DesktopNativeWidgetAura::OnHostClosed().
  3. OnHostClosed() resets the host_ unique pointer (host_.reset()), triggering the destruction of the WindowTreeHost instance.
  4. During destruction, WindowTreeHost::DestroyDispatcher() is invoked, resetting dispatcher_ (the std::unique_ptr that owns the WindowEventDispatcher).

At this point, the WindowEventDispatcher instance (this) is synchronously deleted. When the execution returns from OnCaptureLost(), the stack frames unwind back into UpdateCapture(), which proceeds to access and modify member fields (mouse_moved_handler_, mouse_pressed_handler_) or invoke SynthesizeMouseMoveEvent() on the now-freed this object.

Potential Steps to Trigger

Note: These are potential steps. Our analysis is based on static code review, as our tooling does not currently have the ability to run code or execute a live proof-of-concept.

  1. Create a View inside a desktop widget that requests and obtains mouse capture.
  2. Trigger an action (or simulated event) that forces a mouse capture transfer or release.
  3. In the View’s OnMouseCaptureLost() handler, synchronously call GetWidget()->CloseNow() to force widget destruction.
  4. Observe that the platform host synchronously destroys the WindowTreeHost and its WindowEventDispatcher during the OnCaptureLost() call stack.
  5. Upon return from OnCaptureLost(), UpdateCapture() continues execution, resulting in a Use-After-Free when accessing or writing to member variables of the deleted WindowEventDispatcher.

Impact

This is a potential Use-After-Free occurring in the unsandboxed browser process on desktop platforms.

  • Memory Corruption: The assignment operators on mouse_moved_handler_ and mouse_pressed_handler_ execute on a freed object structure. Since these are raw_ptr objects, writing to them on freed memory can corrupt the allocator state.
  • Control Flow Hijack: When new_capture is null, SynthesizeMouseMoveEvent() is called, accessing host_ (which is already deleted) and executing virtual functions, potentially allowing for control flow redirection if the freed memory is reclaimed and groomed.

Suggested Fix

To remediate this issue, implement a liveness check immediately following the re-entrant OnCaptureLost() call using the class’s existing weak_ptr_factory_:

    base::WeakPtr<WindowEventDispatcher> weak_this = weak_ptr_factory_.GetWeakPtr();
    if (!details.target_destroyed)
      old_capture->delegate()->OnCaptureLost();

    if (!weak_this)
      return;

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