Critical watch CVSS 7.5 chrome UAF 📄 Reporter bug report 🔧 Commit mapped

Overview

Critical
Severity
7.5
CVSS
No
Exploited ITW
Fixed
Fix Status
ImpactUse after free in Views
DescriptionUse after free in Views
ComponentViews
Sub-componentWindows
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.
Tracker503645680
Fix commit1063c5579ec8 (chromium/src) +19/-1
CWECWE-416 (Use-after-free)
CVSS vectorCVSS:3.1/AV:N/AC:H/PR:N/UI:R/S:U/C:H/I:H/A:H
CISA KEVNot listed
CreditedGoogle
Reported2026-04-17 (per vendor advisory)
Fix landed2026-04-20 (public git)
Disclosed2026-04-28
Patch gap8 days — fix public in source before the advisory

Background

`HWNDMessageHandler`
the Windows views class that owns a top-level HWND and translates Win32 window messages into Chromium window-management callbacks.
`msg_handler_weak_factory_`
a base::WeakPtrFactory whose GetWeakPtr() yields a pointer that becomes null the instant the HWNDMessageHandler is destroyed, letting a method detect its own deletion.
Reentrant Win32 call
a call such as ::SetWindowPos, GetMonitorInfo-driven resizing, or GetScreenWin()->UpdateDisplayInfos() that can synchronously pump messages and run code which tears down the window (and its HWNDMessageHandler).
`SetBoundsInternal`
HWNDMessageHandler::SetBoundsInternal, which calls ::SetWindowPos and then touches member state and delegate_, making it a classic point where this can 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.

Key insight
The methods assumed 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

  1. 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 HWNDMessageHandler method on Windows.
  2. Reenter via the synchronous Win32 call The method invokes a reentrant operation (::SetWindowPos, monitor resizing, or UpdateDisplayInfos()) that pumps a nested message loop. (inferred)
  3. Destroy the window during the pump Code running in that nested pump closes/replaces the top-level window, freeing the HWNDMessageHandler. (inferred)
  4. Resume on freed memory Control returns to the original method, which (pre-patch) dereferences the freed this via SetBoundsInternal, delegate_, or member fields — the use-after-free.

Impact Assessment

The advisory rates this critical and classifies it as a use-after-free in 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

FunctionChangeNotes
if
ui/views/win/hwnd_message_handler.cc
modified

Files Changed

  • ui/views/win/hwnd_message_handler.cc

Audit Directions

  • Post-reentrancy member access
    Audit any HWNDMessageHandler/views method that touches this, delegate_, or members after ::SetWindowPos, GetMonitorInfo-driven resizing, DWM calls, or other Win32 APIs that can pump messages; each needs a WeakPtr guard.
  • Partial guards
    Flag functions that capture a WeakPtr for 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 ordering
    Verify GetWeakPtr() is taken before the reentrant call, not after, and that every subsequent member dereference on that path is gated by if (!ref) return;.
From 1063c5579ec89566c997e9cba01c32f5d0a4bd38 Mon Sep 17 00:00:00 2001
From: David Bienvenu <davidbienvenu@chromium.org>
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 <ellyjones@chromium.org>
Commit-Queue: David Bienvenu <davidbienvenu@chromium.org>
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
Loading diff…

Original Bug Report

reported by vm...@google.com

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.cc
  • ui/views/widget/desktop_aura/desktop_native_widget_aura.cc
  • ui/views/widget/desktop_aura/desktop_window_tree_host_win.cc
  • ui/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:

  1. A compromised renderer opens a popup window.
  2. The attacker’s script initiates an operation that enters a native nested loop with application tasks allowed (e.g., initiating a drag-and-drop).
  3. While the drag-and-drop loop is active, the attacker sends a window.moveTo() IPC to move the popup across a DPI boundary.
  4. This synchronously triggers OnDpiChanged and the ::SetWindowPos call, which enters a second nested loop.
  5. The attacker sends a window.close() IPC targeting the popup.
  6. The close task is processed by the second nested loop, destroying the window and the HWNDMessageHandler.
  7. ::SetWindowPos returns, and the freed handler is dereferenced.

Lack of Mitigations

  • Missing WeakPtr Guards: While SetBoundsInternal uses a WeakPtr after notifying its delegate (line 3817), it does not protect the initial accesses immediately after SetWindowPos. OnDpiChanged entirely lacks a WeakPtr guard.
  • MiraclePtr (BRP): The raw_ptr protection does not prevent this UAF because the implicitly captured this pointer is a native C++ pointer and no raw_ptr on 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.

View on issue tracker