Overview

Critical
Severity
CVSS
No
Exploited ITW
Fixed
Fix Status
ImpactUse after free in Views
DescriptionUse after free in Views
ComponentViews
Bug ClassUAF
Tracker503645680
Fix commit1063c5579ec8 (chromium/src) +19/-1
CISA KEVNot listed
CreditedGoogle
Disclosed2026-04-28

Changed Functions

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

Files Changed

  • ui/views/win/hwnd_message_handler.cc
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
Loading diff…

Original Bug Report

reported by [email protected]

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