High watch CVSS 8.3 chrome UAF 📄 Reporter bug report 🔧 Commit mapped

Overview

High
Severity
8.3
CVSS
No
Exploited ITW
Fixed
Fix Status
ImpactUse after free in Core
DescriptionUse after free in Core
ComponentCore
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.
Tracker497066659
Fix commit73cfb55d92a0 (chromium/src) +21/-0
CWECWE-416 (Use-after-free)
CVSS vectorCVSS:3.1/AV:N/AC:H/PR:N/UI:R/S:C/C:H/I:H/A:H
CISA KEVNot listed
CreditedGoogle
Reported2026-03-28 (per vendor advisory)
Fix landed2026-05-06 (public git)
Disclosed2026-05-12
Patch gap6 days — fix public in source before the advisory

Background

`LegacyRenderWidgetHostHWND`
A legacy Win32 HWND host object owned by the browser process that bridges accessibility/input for a RenderWidgetHostViewAura.
Re-entrant message pumping
Synchronous Win32/COM calls (::SetParent, COM activation) can internally dispatch queued window messages, running arbitrary other code before they return.
`base::WeakPtr` guard
A weak reference whose validity is checked after a potentially re-entrant call to detect that the owning object was destroyed mid-method.
`msg_handler_weak_factory_`
The weak-pointer factory on LegacyRenderWidgetHostHWND used to mint guards against the object being freed.

Root Cause Analysis

In LegacyRenderWidgetHostHWND::UpdateParent, the method called ::SetParent(hwnd(), new_parent) and CreateDirectManipulationHelper() and then continued to touch instance state (this) afterward. Those calls are synchronous but can pump the message loop, and per the added comment a re-entrant dispatch can reach RenderWidgetHostViewAura::OnWindowDestroying → LegacyRenderWidgetHostHWND::Destroy() → ::DestroyWindow() → WM_NCDESTROY → OnNCDestroy → delete this, freeing the object while UpdateParent is still on the stack. The violated invariant is that this remains valid for the remainder of the method; when destruction happened re-entrantly, subsequent member access was a use-after-free.

The fix takes a base::WeakPtr<LegacyRenderWidgetHostHWND> from msg_handler_weak_factory_ before each dangerous call and returns immediately if the weak ref is cleared afterward, so the method stops touching freed memory.

Key insight
The core mistake was assuming synchronous ::SetParent/CreateDirectManipulationHelper calls could not destroy this, when their re-entrant message pumping can; the fix adds base::WeakPtr checks after each such call and bails out when the object has been freed.

Attack Path

  1. Trigger a parent change An action that causes UpdateParent to run with a different new_parent enters the current_parent != new_parent branch and calls ::SetParent. (the precise web-reachable trigger is not shown in the diff — inferred)
  2. Re-entrant destruction The ::SetParent (or the subsequent CreateDirectManipulationHelper) pumps the message loop, dispatching a window-destroy path that runs OnWindowDestroying → Destroy() → WM_NCDESTROY → delete this.
  3. Stale `this` continues Control returns into UpdateParent, which pre-patch kept dereferencing freed member state (e.g. tooltip reset, direct_manipulation_helper_), a use-after-free in the browser process.
  4. Guarded exit (post-fix) With the patch, the if (!ref) return; checks after each call abort the method once the object is gone, preventing the UAF.

Impact Assessment

Per the vendor advisory this is a high-severity use-after-free in Core: a use-after-free of a LegacyRenderWidgetHostHWND in the browser process triggered by re-entrant message pumping during a parent-window update, on Windows. Exploitation into controlled corruption or code execution is not demonstrated by the diff and is inferred; the advisory does not report in-the-wild exploitation.

Changed Functions

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

Files Changed

  • content/browser/renderer_host/legacy_render_widget_host_win.cc

Audit Directions

  • Synchronous Win32/COM calls that pump messages
    Audit methods calling ::SetParent, ::DestroyWindow, CoCreateInstance, or COM activation that continue to touch this afterward without a WeakPtr/reentrancy guard.
  • Destruction reachable via message dispatch
    Trace object lifetimes where a window-message handler can delete this, and verify every caller on the stack re-checks validity after any call that can dispatch messages.
  • Consistency with existing guard patterns
    The comment references OnKeyboardRange et al.; confirm all peer methods in LegacyRenderWidgetHostHWND that make pumping calls use the same msg_handler_weak_factory_ guard uniformly.
From 73cfb55d92a0eede38f859e26efecfc458253524 Mon Sep 17 00:00:00 2001
From: Andrew Paseltiner <[email protected]>
Date: Wed, 06 May 2026 10:40:45 -0700
Subject: [PATCH] Prevent UAF in LegacyRenderWidgetHostHWND::UpdateParent

Synchronous Win32 and COM calls (::SetParent,
CreateDirectManipulationHelper) can pump the message loop, leading to
the destruction of the object. This patch adds base::WeakPtr guards to
detect when |this| has been freed mid-method.

Fixed: 497066659
Change-Id: Ieaaf17bb587912a5091d45b65d84463e0ddcbbcb
Reviewed-on: https://chromium-review.googlesource.com/c/chromium/src/+/7822519
Commit-Queue: Andrew Paseltiner <[email protected]>
Reviewed-by: Alex Moshchuk <[email protected]>
Cr-Commit-Position: refs/heads/main@{#1626308}
---

diff --git a/content/browser/renderer_host/legacy_render_widget_host_win.cc b/content/browser/renderer_host/legacy_render_widget_host_win.cc
index 1df9615..3b28ceeb 100644
--- a/content/browser/renderer_host/legacy_render_widget_host_win.cc
+++ b/content/browser/renderer_host/legacy_render_widget_host_win.cc
@@ -114,10 +114,26 @@
   // call altogether.
   const HWND current_parent = GetParent();
   if (current_parent != new_parent) {
+    // ::SetParent and CreateDirectManipulationHelper (CoCreateInstance /
+    // IDirectManipulationManager::Activate / Enable) can synchronously dispatch
+    // window messages. Re-entrant dispatch may reach
+    // RenderWidgetHostViewAura::OnWindowDestroying ->
+    // LegacyRenderWidgetHostHWND::Destroy() -> ::DestroyWindow() ->
+    // WM_NCDESTROY -> OnNCDestroy -> delete this. Guard against |this| being
+    // freed mid-method, matching the pattern used in OnKeyboardRange et al.
+    base::WeakPtr<LegacyRenderWidgetHostHWND> ref(
+        msg_handler_weak_factory_.GetWeakPtr());
+
     ::SetParent(hwnd(), new_parent);
+    if (!ref) {
+      return;
+    }
 
     if (!only_update_direct_manipulation_helper) {
       CreateDirectManipulationHelper();
+      if (!ref) {
+        return;
+      }
     }
 
     // Reset tooltips when parent changed; otherwise tooltips could stay open as
@@ -141,7 +157,12 @@
     // subsequently changes.
     if (!only_update_direct_manipulation_helper &&
         !direct_manipulation_helper_) {
+      base::WeakPtr<LegacyRenderWidgetHostHWND> ref(
+          msg_handler_weak_factory_.GetWeakPtr());
       CreateDirectManipulationHelper();
+      if (!ref) {
+        return;
+      }
     }
   }
 
Loading diff…

Original Bug Report

reported by [email protected]

Potential Use-After-Free in LegacyRenderWidgetHostHWND::UpdateParent via nested message loop

Project Fortify, an experimental security project, has identified the following potential security issue.

Overview: A Use-After-Free (UAF) vulnerability exists in LegacyRenderWidgetHostHWND::UpdateParent because it lacks re-entrancy guards during synchronous Win32 API calls. If the object is destroyed during a nested message loop triggered by these calls, subsequent member accesses lead to a UAF, potentially allowing for a browser-process sandbox escape.

Affected files:

  • content/browser/renderer_host/legacy_render_widget_host_win.cc
  • content/browser/renderer_host/render_widget_host_view_aura.cc

Estimated timestamp from git blame: 2025-07-09

Summary

A potential Use-After-Free (UAF) vulnerability has been identified in LegacyRenderWidgetHostHWND::UpdateParent (content/browser/renderer_host/legacy_render_widget_host_win.cc). The method invokes the synchronous Win32 API ::SetParent and potentially CreateDirectManipulationHelper(), both of which can broadcast events or make COM calls that pump the UI thread’s message queue. If a pending IPC task or event handler destroys the window during this nested message loop, the LegacyRenderWidgetHostHWND object is freed. When the synchronous call returns, the method proceeds to access members of the freed object, potentially leading to a sandbox escape via a vtable hijack in the highly privileged Browser Process.

Potential Exploit Steps

(Note: These are suggested steps; our tooling agent does not have the ability to run code or provide a working proof of concept.)

  1. Triggering the State Change: An attacker initiates an action in a compromised renderer (e.g., hiding a WebContents by switching tabs) that causes RenderWidgetHostViewAura::HideImpl() to be called in the Browser Process.
  2. Reparenting the Window: HideImpl() attempts to reparent the legacy window to the global hidden window by calling legacy_render_widget_host_HWND_->UpdateParent(ui::GetHiddenWindow()).
  3. Synchronous OS Call: Inside LegacyRenderWidgetHostHWND::UpdateParent(), the code executes the synchronous Win32 API ::SetParent(hwnd(), new_parent).
  4. Triggering the Event Hook: ::SetParent synchronously broadcasts the EVENT_OBJECT_PARENTCHANGE event to the OS. If an accessibility tool or UI Automation (UIA) client is running, the OS invokes its registered SetWinEventHook callbacks.
  5. The Nested Message Loop: UIA hooks frequently make cross-apartment COM calls. Because the UI thread is a COM Single-Threaded Apartment (STA), COM blocks waiting for the RPC reply but uses MsgWaitForMultipleObjects to pump the UI thread’s message queue to prevent deadlocks. This creates a nested message loop that allows pending IPC tasks to execute mid-function.
  6. Window Destruction: A pre-queued attacker IPC (e.g., WidgetHostMsg_Close) is dispatched, tearing down the RenderWidgetHostViewAura’s window. This invokes RenderWidgetHostViewAura::OnWindowDestroying (content/browser/renderer_host/render_widget_host_view_aura.cc:2309).
  7. Extracting the Pointer: At line 2331, the code executes legacy_render_widget_host_HWND_.ExtractAsDangling()->Destroy();.
  8. Bypassing MiraclePtr (BRP): ExtractAsDangling() creates a temporary DanglingType object holding the MiraclePtr reference count and clears the legacy_render_widget_host_HWND_ member. The temporary DanglingType is immediately dereferenced to call LegacyRenderWidgetHostHWND::Destroy(), which sends a WM_NCDESTROY message and calls delete this in OnNCDestroy. Crucially, when Destroy() returns, the statement ends, and the temporary DanglingType drops its BRP refcount to 0. PartitionAlloc immediately unquarantines the freed LegacyRenderWidgetHostHWND chunk and moves it to the freelist.
  9. Reclaiming the Memory: The nested message loop continues pumping. Subsequent pre-queued attacker IPCs (e.g., Blob allocations) allocate memory matching sizeof(LegacyRenderWidgetHostHWND). PartitionAlloc assigns the newly freed chunk to the attacker’s object, allowing them to overwrite the memory backing the raw_ptr<RenderWidgetHostViewAura> host_ field with a forged pointer.
  10. The UAF and Hijack: The nested loop completes, and ::SetParent returns. Execution resumes in UpdateParent using the now-dangling this pointer. At line 122, UpdateParent executes host_->UpdateTooltip(std::u16string());. It reads the attacker’s forged host_ pointer and makes a virtual function call (UpdateTooltip), granting the attacker arbitrary Remote Code Execution (RCE).

Other methods in LegacyRenderWidgetHostHWND (such as OnKeyboardRange, OnMouseMessage, and OnTouchMessage) correctly use a base::WeakPtr guard to detect if the object was destroyed during synchronous processing. The same pattern should be applied to UpdateParent.

base::WeakPtr<LegacyRenderWidgetHostHWND> ref(msg_handler_weak_factory_.GetWeakPtr());
::SetParent(hwnd(), new_parent);
if (!ref) {
  return;
}

Evaluated with Chrome root at commit: a9cbf6e8b275fe4147435aa905f3b7f5a656f5f0


Results from so far have been promising, but there can be wrong deductions. If this proves to be a false positive, please close as WAI; 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