CVE-2026-8542
Overview
Background
- `LegacyRenderWidgetHostHWND`
- A legacy Win32
HWNDhost object owned by the browser process that bridges accessibility/input for aRenderWidgetHostViewAura. - 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
LegacyRenderWidgetHostHWNDused 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.
::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
- Trigger a parent change
An action that causes
UpdateParentto run with a differentnew_parententers thecurrent_parent != new_parentbranch and calls::SetParent. (the precise web-reachable trigger is not shown in the diff — inferred) - Re-entrant destruction
The
::SetParent(or the subsequentCreateDirectManipulationHelper) pumps the message loop, dispatching a window-destroy path that runsOnWindowDestroying→Destroy()→WM_NCDESTROY→delete this. - 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. - 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
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
| Function | Change | Notes |
|---|---|---|
ifcontent/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 messagesAudit methods calling
::SetParent,::DestroyWindow,CoCreateInstance, or COM activation that continue to touchthisafterward without aWeakPtr/reentrancy guard. - Destruction reachable via message dispatchTrace 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 patternsThe comment references
OnKeyboardRange et al.; confirm all peer methods inLegacyRenderWidgetHostHWNDthat make pumping calls use the samemsg_handler_weak_factory_guard uniformly.
Patch
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; + } } }
Original Bug Report
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.cccontent/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.)
- 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. - Reparenting the Window:
HideImpl()attempts to reparent the legacy window to the global hidden window by callinglegacy_render_widget_host_HWND_->UpdateParent(ui::GetHiddenWindow()). - Synchronous OS Call: Inside
LegacyRenderWidgetHostHWND::UpdateParent(), the code executes the synchronous Win32 API::SetParent(hwnd(), new_parent). - Triggering the Event Hook:
::SetParentsynchronously broadcasts theEVENT_OBJECT_PARENTCHANGEevent to the OS. If an accessibility tool or UI Automation (UIA) client is running, the OS invokes its registeredSetWinEventHookcallbacks. - 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
MsgWaitForMultipleObjectsto 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. - Window Destruction: A pre-queued attacker IPC (e.g.,
WidgetHostMsg_Close) is dispatched, tearing down theRenderWidgetHostViewAura’s window. This invokesRenderWidgetHostViewAura::OnWindowDestroying(content/browser/renderer_host/render_widget_host_view_aura.cc:2309). - Extracting the Pointer: At line 2331, the code executes
legacy_render_widget_host_HWND_.ExtractAsDangling()->Destroy();. - Bypassing MiraclePtr (BRP):
ExtractAsDangling()creates a temporaryDanglingTypeobject holding the MiraclePtr reference count and clears thelegacy_render_widget_host_HWND_member. The temporaryDanglingTypeis immediately dereferenced to callLegacyRenderWidgetHostHWND::Destroy(), which sends aWM_NCDESTROYmessage and callsdelete thisinOnNCDestroy. Crucially, whenDestroy()returns, the statement ends, and the temporaryDanglingTypedrops its BRP refcount to 0. PartitionAlloc immediately unquarantines the freedLegacyRenderWidgetHostHWNDchunk and moves it to the freelist. - 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 theraw_ptr<RenderWidgetHostViewAura> host_field with a forged pointer. - The UAF and Hijack: The nested loop completes, and
::SetParentreturns. Execution resumes inUpdateParentusing the now-danglingthispointer. At line 122,UpdateParentexecuteshost_->UpdateTooltip(std::u16string());. It reads the attacker’s forgedhost_pointer and makes a virtual function call (UpdateTooltip), granting the attacker arbitrary Remote Code Execution (RCE).
Recommended Fix
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.