Overview

High
Severity
CVSS
No
Exploited ITW
Fixed
Fix Status
ImpactUse after free in Aura
DescriptionUse after free in Aura
ComponentAura
Bug ClassUAF
Tracker534591074
Fix commitfd2f2f3bced2 (chromium/src) +17/-3
CISA KEVNot listed
CreditedGoogle
Disclosed2026-08-25

Changed Functions

FunctionChangeNotes
if
content/browser/renderer_host/render_widget_host_view_event_handler.cc
modified

Files Changed

  • content/browser/renderer_host/render_widget_host_view_aura.cc
  • content/browser/renderer_host/render_widget_host_view_event_handler.cc
From fd2f2f3bced2100c27f78f9a2fa9df92b4ea8073 Mon Sep 17 00:00:00 2001
From: Mitsuru Oshima <[email protected]>
Date: Wed, 22 Jul 2026 09:18:43 -0700
Subject: [PATCH] Handle window destruction in RenderWidgetHostViewEventHandler

- Reset window pointer in RenderWidgetHostViewEventHandler when
  RenderWidgetHostViewAura is destroying its window.
- Added null checks in RenderWidgetHostViewEventHandler to handle
  cases where the window is destroyed.

BUG=534591074
TAG=agy
CONV=7731470c-3827-45f1-b883-03aa42a07d4f

Change-Id: I0c1775088afc3373ae3a6093d7cdb1b3c5264193
Reviewed-on: https://chromium-review.googlesource.com/c/chromium/src/+/8127703
Reviewed-by: Colin Blundell <[email protected]>
Reviewed-by: Alex Moshchuk <[email protected]>
Commit-Queue: Mitsuru Oshima <[email protected]>
Cr-Commit-Position: refs/heads/main@{#1666382}
---

diff --git a/content/browser/renderer_host/render_widget_host_view_aura.cc b/content/browser/renderer_host/render_widget_host_view_aura.cc
index 10a8816..85835cbb 100644
--- a/content/browser/renderer_host/render_widget_host_view_aura.cc
+++ b/content/browser/renderer_host/render_widget_host_view_aura.cc
@@ -2387,6 +2387,8 @@
 }
 
 void RenderWidgetHostViewAura::OnWindowDestroying(aura::Window* window) {
+  event_handler_->set_window(nullptr);
+
   // Make sure that the input method no longer references to this object before
   // this object is removed from the root window (i.e. this object loses access
   // to the input method).
diff --git a/content/browser/renderer_host/render_widget_host_view_event_handler.cc b/content/browser/renderer_host/render_widget_host_view_event_handler.cc
index ae820a9..5fc3b632 100644
--- a/content/browser/renderer_host/render_widget_host_view_event_handler.cc
+++ b/content/browser/renderer_host/render_widget_host_view_event_handler.cc
@@ -192,13 +192,18 @@
 void RenderWidgetHostViewEventHandler::UnlockPointer() {
   delegate_->SetTooltipsEnabled(true);
 
-  aura::Window* root_window = window_->GetRootWindow();
-  if (!mouse_locked_ || !root_window)
+  if (!mouse_locked_) {
     return;
+  }
 
   mouse_locked_ = false;
   mouse_locked_unadjusted_movement_.reset();
 
+  aura::Window* root_window = window_ ? window_->GetRootWindow() : nullptr;
+  if (!root_window) {
+    return;
+  }
+
   window_->GetHost()->UnlockMouse(window_);
 
   // Ensure that the global mouse position is updated here to its original
@@ -206,7 +211,9 @@
   // after the cursor is moved ends up getting a large movement delta which is
   // not what sites expect. The delta is computed in the
   // ModifyEventMovementAndCoords function.
-  window_->MoveCursorTo(gfx::ToFlooredPoint(unlocked_mouse_position_));
+  if (window_) {
+    window_->MoveCursorTo(gfx::ToFlooredPoint(unlocked_mouse_position_));
+  }
   synthetic_move_position_ =
       gfx::ToFlooredPoint(unlocked_global_mouse_position_);
 
@@ -318,6 +325,11 @@
 void RenderWidgetHostViewEventHandler::OnMouseEvent(ui::MouseEvent* event) {
   TRACE_EVENT0("input", "RenderWidgetHostViewBase::OnMouseEvent");
 
+  // A synthesized event may be generated during window destruction.
+  if (!window_) {
+    return;
+  }
+
   // CrOS will send a mouse exit event to update hover state when mouse is
   // hidden which we want to filter out in renderer. crbug.com/723535.
   if (event->flags() & ui::EF_CURSOR_HIDE)
Loading diff…

Original Bug Report

reported by [email protected]

Potential Use-After-Free in WindowTreeHost::UnlockMouse and RenderWidgetHostViewEventHandler

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 the browser process due to a raw window pointer being used after a synchronous capture-release call. Releasing capture can cause transient widgets to synchronously close, destroying the window, the parent window tree host, and the rendering event handler. This results in potential memory corruption when the execution resumes and accesses these freed objects.

Affected files:

  • ui/aura/window_tree_host.cc
  • content/browser/renderer_host/render_widget_host_view_event_handler.cc
  • ui/views/widget/desktop_aura/desktop_window_tree_host_linux.cc
  • ui/views/widget/desktop_aura/desktop_capture_client.cc
  • ui/aura/window_event_dispatcher.cc

Estimated timestamp from git blame: 2021-07-02

Summary

There is a potential Use-After-Free (UAF) vulnerability in WindowTreeHost::UnlockMouse and related event handlers. When window->ReleaseCapture() is called to unlock the mouse cursor, it synchronously dispatches capture change and mouse movement events. Under certain conditions (such as with transient widgets or popups designed to close upon capture loss), this dispatch can trigger synchronous widget and host destruction. When execution resumes, multiple components (WindowTreeHost, DesktopWindowTreeHostLinux, and RenderWidgetHostViewEventHandler) proceed to dereference members and execute virtual function calls on freed memory.

Root Cause Analysis

In ui/aura/window_tree_host.cc:

void WindowTreeHost::UnlockMouse(Window* window) {
  Window* root_window = window->GetRootWindow();
  DCHECK(root_window);

  if (window->HasCapture())
    window->ReleaseCapture(); // <--- Synchronous Teardown Sink

  auto* cursor_client = client::GetCursorClient(root_window); // <--- UAF Read
  if (cursor_client) {
    cursor_client->UnlockCursor(); // <--- Potential Wild Virtual Call
    cursor_client->ShowCursor();   // <--- Potential Wild Virtual Call
  }
}
  1. Synchronous Destruction Sink: window->ReleaseCapture() dispatches a synchronous ui::EventType::kMouseCaptureChanged event to notify the window of capture loss. Views and widgets are explicitly permitted to self-destroy synchronously during capture loss (e.g., see the comment in Widget::OnMouseCaptureLost at ui/views/widget/widget.cc:2296: // Widget may be deleted upon the capture lost event.).

  2. The Deallocation Chain: If the widget is synchronously closed via CloseNow(), views::DesktopNativeWidgetAura::OnHostClosed() resets the unique pointer to the host (host_.reset();), which deletes the DesktopWindowTreeHostLinux instance. This in turn deletes the root window (delete window_;), which recursively deletes the child window_ and destroys the RenderWidgetHostViewAura (and its nested RenderWidgetHostViewEventHandler owner).

  3. Post-Sink Use-After-Free Execution Paths:

    • In WindowTreeHost::UnlockMouse: Resuming on line 445, root_window is now a dangling pointer. client::GetCursorClient(root_window) performs a property lookup on freed memory and can return the stale address of cursor_manager_ (which is deleted during root window teardown). Calling UnlockCursor() and ShowCursor() executes virtual functions on a freed object.
    • In DesktopWindowTreeHostLinux::UnlockMouse: At ui/views/widget/desktop_aura/desktop_window_tree_host_linux.cc:530:
      DesktopWindowTreeHostPlatform::UnlockMouse(window);
      if (SupportsMouseLock()) { // <--- UAF on `this`
        auto* wayland_extension =
            ui::GetWaylandToplevelExtension(*platform_window()); // <--- UAF on `this`
        wayland_extension->LockPointer(false);
      }
      
      If the base class call destroys the host, the subsequent calls to SupportsMouseLock() and platform_window() are executed on a freed this pointer.
    • In RenderWidgetHostViewEventHandler::UnlockPointer: At content/browser/renderer_host/render_widget_host_view_event_handler.cc:209-213:
      window_->GetHost()->UnlockMouse(window_);
      window_->MoveCursorTo(...); // <--- UAF Read on `this->window_`
      synthetic_move_position_ = ...; // <--- UAF Write to `this->synthetic_move_position_`
      host_->LostPointerLock(); // <--- UAF Read on `this->host_` and virtual call
      
      Since the event handler is deleted along with RenderWidgetHostViewAura, executing these lines results in UAF reads, a UAF write, and a virtual call via a deleted pointer.

Potential Trigger Path

Note: These steps are logical and theoretical based on code-flow analysis; our tooling does not currently have the capability to execute and validate runtime behavior.

  1. A webpage or compromised renderer requests pointer lock on a transient widget/window (such as a dropdown menu or transient bubble) which has capture capabilities.
  2. The browser grants pointer lock, causing the Aura window to acquire capture.
  3. The renderer dispatches an IPC requesting to exit pointer lock.
  4. The browser process receives this request and invokes RenderWidgetHostViewEventHandler::UnlockPointer(), which calls the capture-release sink.
  5. During capture release, the transient widget processes capture loss and synchronously closes itself via CloseNow(), freeing the host, window, and event handler.
  6. Control unwinds, leading to the UAF reads, writes, and virtual calls detailed above.

Suggested Fixes

To prevent execution on freed pointers, lifetime tracking must be introduced at each layer of the call chain:

  1. In WindowTreeHost::UnlockMouse: Utilize aura::WindowTracker to track both window and root_window before entering the release capture sink, and only proceed if they remain valid.

    void WindowTreeHost::UnlockMouse(Window* window) {
      Window* root_window = window->GetRootWindow();
      DCHECK(root_window);
    
      aura::WindowTracker tracker;
      tracker.Add(window);
      tracker.Add(root_window);
    
      if (window->HasCapture())
        window->ReleaseCapture();
    
      if (!tracker.Contains(root_window))
        return;
    
      auto* cursor_client = client::GetCursorClient(root_window);
      if (cursor_client) {
        cursor_client->UnlockCursor();
        cursor_client->ShowCursor();
      }
    }
    
  2. In DesktopWindowTreeHostLinux::UnlockMouse: Guard the platform calls with the class’s existing weak_factory_:

    void DesktopWindowTreeHostLinux::UnlockMouse(aura::Window* window) {
      auto weak_this = weak_factory_.GetWeakPtr();
      DesktopWindowTreeHostPlatform::UnlockMouse(window);
    
      if (!weak_this || !SupportsMouseLock())
        return;
    
      auto* wayland_extension =
          ui::GetWaylandToplevelExtension(*platform_window());
      wayland_extension->LockPointer(false /*enabled*/);
    }
    
  3. In RenderWidgetHostViewEventHandler::UnlockPointer: Use a base::WeakPtr<RenderWidgetHostViewBase> to verify the view’s lifetime before accessing members:

    void RenderWidgetHostViewEventHandler::UnlockPointer() {
      delegate_->SetTooltipsEnabled(true);
    
      aura::Window* root_window = window_->GetRootWindow();
      if (!mouse_locked_ || !root_window)
        return;
    
      mouse_locked_ = false;
      mouse_locked_unadjusted_movement_.reset();
    
      base::WeakPtr<RenderWidgetHostViewBase> weak_view = host_view_->GetWeakPtr();
      window_->GetHost()->UnlockMouse(window_);
    
      if (!weak_view)
        return;
    
      window_->MoveCursorTo(gfx::ToFlooredPoint(unlocked_mouse_position_));
      synthetic_move_position_ =
          gfx::ToFlooredPoint(unlocked_global_mouse_position_);
    
      host_->LostPointerLock();
    }
    

Evaluated with Chrome root at commit: b5b015ea5f690560237d1f0cff1405844cd12b8d


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