CVE-2026-7343
Overview
Background
- `HWNDMessageHandler`
- the Windows
viewsclass that owns a top-levelHWNDand translates Win32 window messages into Chromium window-management callbacks. - `msg_handler_weak_factory_`
- a
base::WeakPtrFactorywhoseGetWeakPtr()yields a pointer that becomes null the instant theHWNDMessageHandleris destroyed, letting a method detect its own deletion. - Reentrant Win32 call
- a call such as
::SetWindowPos,GetMonitorInfo-driven resizing, orGetScreenWin()->UpdateDisplayInfos()that can synchronously pump messages and run code which tears down the window (and itsHWNDMessageHandler). - `SetBoundsInternal`
HWNDMessageHandler::SetBoundsInternal, which calls::SetWindowPosand then touches member state anddelegate_, making it a classic point wherethiscan die mid-method.
Root Cause Analysis
Several HWNDMessageHandler methods invoked a Win32 operation that can synchronously spin a nested message loop — SetBoundsInternal/::SetWindowPos, GetScreenWin()->UpdateDisplayInfos(), and monitor-driven resizing during fullscreen transitions — and then continued to touch this (calling SetBoundsInternal, delegate_->HandleWindowScaleFactorChanged, or member fields) after the call returned. If that reentrant pump destroyed the window, the surrounding method resumed on a freed HWNDMessageHandler, a use-after-free. The pre-patch code already knew about this hazard in one place (it captured auto ref = msg_handler_weak_factory_.GetWeakPtr() only around the delegate_->HandleClientSizeChanged call), but the earlier reentrant ::SetWindowPos and the other call sites had no such guard.
The fix hoists/adds a GetWeakPtr() capture before each reentrant call and returns early (return; or return 0;) when !ref, so no post-free member access occurs. This closes the gap without changing any window-management behavior on the non-destruction path.
this survived calls that can reentrantly pump the Windows message loop and destroy the window; the fix takes a WeakPtr before each such call and bails out immediately if the object was freed during it.Attack Path
- Trigger a window geometry transition
Cause the browser/window to undergo a bounds change, DPI/scale-factor change, or fullscreen enter/exit that routes into the affected
HWNDMessageHandlermethod on Windows. - Reenter via the synchronous Win32 call
The method invokes a reentrant operation (
::SetWindowPos, monitor resizing, orUpdateDisplayInfos()) that pumps a nested message loop. (inferred) - Destroy the window during the pump
Code running in that nested pump closes/replaces the top-level window, freeing the
HWNDMessageHandler. (inferred) - Resume on freed memory
Control returns to the original method, which (pre-patch) dereferences the freed
thisviaSetBoundsInternal,delegate_, or member fields — the use-after-free.
Impact Assessment
Views, reachable only on Windows and only when a window-geometry/DPI/fullscreen transition reentrantly pumps messages and the window is torn down during that pump — a timing-sensitive condition. Successful exploitation would let an attacker operate on a freed HWNDMessageHandler, potentially yielding memory corruption (inferred); the advisory does not state in-the-wild exploitation, and any escalation beyond the freed-object access is (inferred).Changed Functions
| Function | Change | Notes |
|---|---|---|
ifui/views/win/hwnd_message_handler.cc |
modified |
Files Changed
ui/views/win/hwnd_message_handler.cc
Audit Directions
- Post-reentrancy member accessAudit any
HWNDMessageHandler/viewsmethod that touchesthis,delegate_, or members after::SetWindowPos,GetMonitorInfo-driven resizing, DWM calls, or other Win32 APIs that can pump messages; each needs aWeakPtrguard. - Partial guardsFlag functions that capture a
WeakPtrfor some but not all reentrant calls in the same body (the pre-patch bug here), since a single unguarded reentrant call reintroduces the UAF. - WeakPtrFactory call-site orderingVerify
GetWeakPtr()is taken before the reentrant call, not after, and that every subsequent member dereference on that path is gated byif (!ref) return;.
Patch
From 1063c5579ec89566c997e9cba01c32f5d0a4bd38 Mon Sep 17 00:00:00 2001 From: David Bienvenu <[email protected]> Date: Mon, 20 Apr 2026 09:19:56 -0700 Subject: [PATCH] win: Add WeakPtr checks in HWNDMessageHandler. Bug: 503645680 Change-Id: I65089bc28d23a89f75d6982d1055bbba1aa10c1a Reviewed-on: https://chromium-review.googlesource.com/c/chromium/src/+/7774439 Reviewed-by: Elly <[email protected]> Commit-Queue: David Bienvenu <[email protected]> Cr-Commit-Position: refs/heads/main@{#1617544} --- diff --git a/ui/views/win/hwnd_message_handler.cc b/ui/views/win/hwnd_message_handler.cc index e00c2c13..d6546c2 100644 --- a/ui/views/win/hwnd_message_handler.cc +++ b/ui/views/win/hwnd_message_handler.cc @@ -1570,7 +1570,11 @@ MONITORINFO monitor_info = {sizeof(monitor_info)}; ::GetMonitorInfo(::MonitorFromWindow(hwnd(), MONITOR_DEFAULTTOPRIMARY), &monitor_info); + auto ref = msg_handler_weak_factory_.GetWeakPtr(); SetBoundsInternal(gfx::Rect(monitor_info.rcMonitor), false); + if (!ref) { + return; + } // Inform the taskbar that this window is now a fullscreen window so it go // behind the window in the Z-Order. The taskbar heuristics to detect // fullscreen windows are not reliable. Marking it explicitly seems to work @@ -2012,8 +2016,15 @@ // in which the display a window is on has a different scale factor than the // window, when the window handles the scale factor change. // See https://crbug.com/1368455 for more info. + auto ref = msg_handler_weak_factory_.GetWeakPtr(); display::win::GetScreenWin()->UpdateDisplayInfos(); + if (!ref) { + return 0; + } SetBoundsInternal(gfx::Rect(*reinterpret_cast<RECT*>(l_param)), false); + if (!ref) { + return 0; + } delegate_->HandleWindowScaleFactorChanged(scaling_factor); return 0; } @@ -3805,16 +3816,19 @@ bool force_size_changed) { gfx::Size old_size = GetClientAreaBounds().size(); + auto ref = msg_handler_weak_factory_.GetWeakPtr(); ::SetWindowPos(hwnd(), nullptr, bounds_in_pixels.x(), bounds_in_pixels.y(), bounds_in_pixels.width(), bounds_in_pixels.height(), SWP_NOACTIVATE | SWP_NOZORDER); + if (!ref) { + return; + } // If HWND size is not changed, we will not receive standard size change // notifications. If |force_size_changed| is |true|, we should pretend size is // changed. if (old_size == bounds_in_pixels.size() && force_size_changed && !background_fullscreen_hack_) { - auto ref = msg_handler_weak_factory_.GetWeakPtr(); delegate_->HandleClientSizeChanged(GetClientAreaBounds().size()); if (!ref) { return; @@ -3847,7 +3861,11 @@ gfx::Rect shrunk_rect(monitor_info.rcMonitor); shrunk_rect.set_height(shrunk_rect.height() - 1); background_fullscreen_hack_ = true; + auto ref = msg_handler_weak_factory_.GetWeakPtr(); SetBoundsInternal(shrunk_rect, false); + if (!ref) { + return; + } // Inform the taskbar that this window is no longer a fullscreen window so it // can bring itself to the top of the Z-Order. The taskbar heuristics to // detect fullscreen windows are not reliable. Marking it explicitly seems to
Original Bug Report
Potential Use-after-free in HWNDMessageHandler::OnDpiChanged via nested message loops
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 without the Chrome Security team. Please see go/chrome-ai-generated-security-bugs-faq for more information.
Overview: A potential use-after-free vulnerability exists in HWNDMessageHandler::OnDpiChanged on Windows. Synchronous window operations (like SetWindowPos) can enter a nested message loop, allowing an attacker to queue and execute a window close task. This destroys the HWNDMessageHandler while it is still on the call stack, leading to a UAF upon return.
Affected files:
ui/views/win/hwnd_message_handler.ccui/views/widget/desktop_aura/desktop_native_widget_aura.ccui/views/widget/desktop_aura/desktop_window_tree_host_win.ccui/views/win/hwnd_message_handler.h
Estimated timestamp from git blame: 2026-02-28
Summary
A potential Use-After-Free (UAF) vulnerability has been identified in HWNDMessageHandler::OnDpiChanged in the Chrome browser process on Windows. The issue occurs when the handler performs synchronous operations that trigger a native nested message loop. If application tasks are permitted in this loop, an attacker can destroy the handler while OnDpiChanged is still executing, leading to memory corruption.
Vulnerability Details
In ui/views/win/hwnd_message_handler.cc, the HWNDMessageHandler::OnDpiChanged function handles WM_DPICHANGED messages. During its execution, it calls SetBoundsInternal, which calls the Windows API ::SetWindowPos:
2015: display::win::GetScreenWin()->UpdateDisplayInfos();
2016: SetBoundsInternal(gfx::Rect(*reinterpret_cast<RECT*>(l_param)), false);
2017: delegate_->HandleWindowScaleFactorChanged(scaling_factor);
As noted in Chromium codebase comments (ui/views/win/hwnd_message_handler.cc:691), ::SetWindowPos can trigger a native nested message loop, especially during complex DPI transitions.
If the browser is already in a state that allows application tasks to run during native nested loops (e.g., during a drag-and-drop operation where base::CurrentThread::ScopedAllowApplicationTasksInNativeNestedLoop is active), the message pump will process incoming tasks while blocked inside ::SetWindowPos.
If an IPC task to close the window is processed during this nested loop, it posts a task to HWNDMessageHandler::CloseNow. This task is immediately picked up by the message pump and executed. CloseNow calls ::DestroyWindow, which synchronously destroys the HWNDMessageHandler instance (via WM_NCDESTROY and DesktopNativeWidgetAura::OnHostClosed).
When the nested loop finishes and ::SetWindowPos returns, execution continues in SetBoundsInternal (accessing background_fullscreen_hack_ at line 3816) and subsequently OnDpiChanged (accessing delegate_ at line 2017). Both accesses dereference the freed this pointer, resulting in a use-after-free. Because HandleWindowScaleFactorChanged is a virtual method, this can potentially lead to a controlled vtable indirect call and arbitrary code execution in the browser process.
Potential Attack Scenario
While we have not verified this with a working Proof of Concept, an attacker might trigger this path via the following steps:
- A compromised renderer opens a popup window.
- The attacker’s script initiates an operation that enters a native nested loop with application tasks allowed (e.g., initiating a drag-and-drop).
- While the drag-and-drop loop is active, the attacker sends a
window.moveTo()IPC to move the popup across a DPI boundary. - This synchronously triggers
OnDpiChangedand the::SetWindowPoscall, which enters a second nested loop. - The attacker sends a
window.close()IPC targeting the popup. - The close task is processed by the second nested loop, destroying the window and the
HWNDMessageHandler. ::SetWindowPosreturns, and the freed handler is dereferenced.
Lack of Mitigations
- Missing WeakPtr Guards: While
SetBoundsInternaluses aWeakPtrafter notifying its delegate (line 3817), it does not protect the initial accesses immediately afterSetWindowPos.OnDpiChangedentirely lacks aWeakPtrguard. - MiraclePtr (BRP): The
raw_ptrprotection does not prevent this UAF because the implicitly capturedthispointer is a native C++ pointer and noraw_ptron the stack holds the object alive during this specific sequence to trigger quarantine.
Suggested Fix
Add base::WeakPtr guards in HWNDMessageHandler::OnDpiChanged and SetBoundsInternal to detect if the object has been destroyed during the SetWindowPos or UpdateDisplayInfos calls. For example, in OnDpiChanged:
base::WeakPtr<HWNDMessageHandler> ref(msg_handler_weak_factory_.GetWeakPtr());
display::win::GetScreenWin()->UpdateDisplayInfos();
SetBoundsInternal(gfx::Rect(*reinterpret_cast<RECT*>(l_param)), false);
if (!ref) return 0;
delegate_->HandleWindowScaleFactorChanged(scaling_factor);
Similar checks should be placed immediately after ::SetWindowPos in SetBoundsInternal.
Evaluated with Chrome root at commit: 661452647ddb2827305122ff3273bd5dea403f09
Results 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.