CVE-2026-11661
Overview
Changed Functions
| Function | Change | Notes |
|---|---|---|
ifui/views/win/hwnd_message_handler_headless.cc |
modified | |
switchui/views/win/hwnd_message_handler_headless.cc |
modified |
Files Changed
ui/views/win/hwnd_message_handler.hui/views/win/hwnd_message_handler_headless.cc
Patch
From dce2a77c9cca8e7078ff535c8d2f6bc83da6c297 Mon Sep 17 00:00:00 2001
From: Peter Kvitek <kvitekp@chromium.org>
Date: Tue, 26 May 2026 08:51:31 -0700
Subject: [PATCH] [headless][win] Safeguard message handler code for potential UAF
HWNDMessageHandlerHeadless code frequently calls
HWNDMessageHandlerDelegate methods which could destroy the window
invalidating 'this' pointer. This CL adds safeguards to prevent UAF
after delegate calls.
Bug: 513748868
Change-Id: If0fc3042bf330a30ab953cbdc499b31e6b102714
Reviewed-on: https://chromium-review.googlesource.com/c/chromium/src/+/7868576
Reviewed-by: David Bienvenu <davidbienvenu@chromium.org>
Commit-Queue: Peter Kvitek <kvitekp@chromium.org>
Cr-Commit-Position: refs/heads/main@{#1636225}
---
diff --git a/ui/views/win/hwnd_message_handler.h b/ui/views/win/hwnd_message_handler.h
index 49f0740..6a683fedc 100644
--- a/ui/views/win/hwnd_message_handler.h
+++ b/ui/views/win/hwnd_message_handler.h
@@ -272,6 +272,11 @@
// Returns true if IsFrameSystemDrawn() and there's actually a frame to draw.
bool HasSystemFrame() const;
+ // Allow WeakPtr use in subclasses.
+ base::WeakPtr<HWNDMessageHandler> GetWeakPtr() {
+ return msg_handler_weak_factory_.GetWeakPtr();
+ }
+
private:
friend class ::views::test::DesktopWindowTreeHostWinTestApi;
diff --git a/ui/views/win/hwnd_message_handler_headless.cc b/ui/views/win/hwnd_message_handler_headless.cc
index 3f4f33d..821a8d6 100644
--- a/ui/views/win/hwnd_message_handler_headless.cc
+++ b/ui/views/win/hwnd_message_handler_headless.cc
@@ -56,7 +56,12 @@
initial_bounds_valid_ = !bounds.IsEmpty();
+ auto weak_ptr = GetWeakPtr();
+
WindowImpl::Init(parent, bounds);
+ if (!weak_ptr) {
+ return;
+ }
// In headless mode remember the expected window bounds possibly adjusted
// according to the scale factor.
@@ -78,6 +83,10 @@
}
}
+ if (!weak_ptr) {
+ return;
+ }
+
InitExtras();
}
@@ -158,7 +167,14 @@
bool size_changed = bounds_.size() != size;
gfx::Rect bounds = bounds_;
bounds.set_size(size);
+
+ auto weak_ptr = GetWeakPtr();
+
SetHeadlessWindowBounds(bounds);
+ if (!weak_ptr) {
+ return;
+ }
+
if (size_changed) {
delegate_->HandleClientSizeChanged(GetClientAreaBounds().size());
}
@@ -206,6 +222,8 @@
bool activate = true;
+ auto weak_ptr = GetWeakPtr();
+
switch (show_state) {
case ui::mojom::WindowShowState::kMinimized:
Minimize();
@@ -229,6 +247,10 @@
break;
}
+ if (!weak_ptr) {
+ return;
+ }
+
// In headless mode the platform window is always hidden, so instead of
// showing it just maintain a local flag to track the expected headless
// window visibility state and explicitly activate window just like
@@ -236,6 +258,9 @@
if (!is_visible_) {
is_visible_ = true;
delegate_->HandleVisibilityChanged(/*visible=*/true);
+ if (!weak_ptr) {
+ return;
+ }
}
if (activate) {
@@ -262,7 +287,14 @@
window_state_ = WindowState::kMaximized;
gfx::Rect bounds = GetZoomedWindowBounds();
+
+ auto weak_ptr = GetWeakPtr();
+
SetBoundsInternal(bounds, /*force_size_changed=*/false);
+ if (!weak_ptr) {
+ return;
+ }
+
delegate_->HandleCommand(static_cast<int>(SC_MAXIMIZE));
}
@@ -273,17 +305,33 @@
window_state_ = WindowState::kMinimized;
+ auto weak_ptr = GetWeakPtr();
+
// Windows automatiaclly deactivates minimized windows, so we need to
// replicate this behavior to prevent focus not being restored, see
// https://crbug.com/358998544.
was_active_before_minimize_ = is_active_;
if (is_active_) {
Deactivate();
+ if (!weak_ptr) {
+ return;
+ }
}
delegate_->HandleWindowMinimizedOrRestored(/*restored=*/false);
+ if (!weak_ptr) {
+ return;
+ }
+
delegate_->HandleCommand(static_cast<int>(SC_MINIMIZE));
+ if (!weak_ptr) {
+ return;
+ }
+
delegate_->HandleNativeBlur(nullptr);
+ if (!weak_ptr) {
+ return;
+ }
}
void HWNDMessageHandlerHeadless::Restore() {
@@ -294,12 +342,24 @@
auto prev_state = window_state_;
window_state_ = WindowState::kNormal;
+ auto weak_ptr = GetWeakPtr();
+
RestoreBounds();
+ if (!weak_ptr) {
+ return;
+ }
if (prev_state == WindowState::kMinimized) {
delegate_->HandleWindowMinimizedOrRestored(/*restored=*/true);
+ if (!weak_ptr) {
+ return;
+ }
+
if (was_active_before_minimize_) {
Activate();
+ if (!weak_ptr) {
+ return;
+ }
}
}
@@ -445,13 +505,21 @@
bool force_size_changed) {
gfx::Rect old_bounds = GetClientAreaBounds();
+ auto weak_ptr = GetWeakPtr();
+
SetHeadlessWindowBounds(bounds_in_pixels);
+ if (!weak_ptr) {
+ return;
+ }
// In normal mode the delegate is called when the platform window receives
// WM_MOVE/WM_MOVING messages, however, in headless mode platform window is
// never moved, so call the delegate here. See http://crbug.com/401294443.
if (old_bounds.origin() != bounds_in_pixels.origin()) {
delegate_->HandleMove();
+ if (!weak_ptr) {
+ return;
+ }
Original Bug Report
Potential Browser Use-After-Free in HWNDMessageHandlerHeadless on Windows
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. Please see https://chromium.googlesource.com/chromium/src/+/main/docs/security/ai-generated-security-bugs-faq.md for more information.
Overview: The HWNDMessageHandlerHeadless class, used for Chrome’s native headless mode on Windows, performs synchronous callouts without re-entrancy guards. If an observer synchronously destroys the window during a movement callout, subsequent code in the same method attempts to access the freed object. This can result in a browser-process crash or potential arbitrary code execution.
Affected files:
ui/views/win/hwnd_message_handler_headless.ccui/views/win/hwnd_message_handler.hui/views/win/hwnd_message_handler.cc
Estimated timestamp from git blame: 2023-10-09
Summary
In Chrome’s native headless mode on Windows (--headless=new), the HWNDMessageHandlerHeadless class manages window operations. Several methods in this class issue synchronous callouts to a delegate (HWNDMessageHandlerDelegate). Because these callouts can trigger observers that synchronously destroy the Widget and its associated message handler, the omission of re-entrancy guards leads to a potential Use-After-Free (UAF) vulnerability.
In the base class HWNDMessageHandler, these operations are explicitly protected by a base::WeakPtr guard (using msg_handler_weak_factory_). However, HWNDMessageHandlerHeadless overrides these methods but fails to implement similar protections.
Technical Details
In ui/views/win/hwnd_message_handler_headless.cc, the method SetBoundsInternal (lines 443-461) performs a synchronous callout to the delegate:
void HWNDMessageHandlerHeadless::SetBoundsInternal(
const gfx::Rect& bounds_in_pixels, bool force_size_changed) {
gfx::Rect old_bounds = GetClientAreaBounds();
SetHeadlessWindowBounds(bounds_in_pixels);
if (old_bounds.origin() != bounds_in_pixels.origin()) {
delegate_->HandleMove(); // [1] Synchronous Callout
}
// [2] Potential UAF access on 'this' and 'delegate_'
if (old_bounds.size() != bounds_in_pixels.size() || force_size_changed) {
delegate_->HandleClientSizeChanged(GetClientAreaBounds().size());
}
}
If the call at [1] triggers an observer (such as a WindowTreeHostObserver) that synchronously closes the window (e.g., via Widget::CloseNow()), the HWNDMessageHandlerHeadless instance is destroyed. When execution returns to [2], the code attempts to access members of the freed object, including a virtual method call to GetClientAreaBounds().
Similar issues exist in other methods that perform callouts, including:
Maximize()Minimize()Restore()Show()SetSize()Activate()/Deactivate()
Potential Attack Vector
Note: These steps are based on static analysis and have not been verified with a functional Proof of Concept.
- A compromised renderer process initiates a window movement or resize request (e.g., calling
window.moveTo()on a popup window). - The request reaches the browser process and is routed to
HWNDMessageHandlerHeadless::SetBoundsInternal. - A UI observer (e.g., reacting to
OnHostMovedInPixels) synchronously destroys the widget during the movement callout. - The browser process attempts to continue execution in the freed
SetBoundsInternalstack frame, leading to a UAF.
Impact
This is a potential browser-process UAF. Since the browser process is unsandboxed, a successful exploit could allow for arbitrary code execution with the user’s full privileges.
Suggested Fix
HWNDMessageHandlerHeadless should implement re-entrancy guards similar to the base class. Before making any delegate callout that could result in destruction, the code should acquire a base::WeakPtr and verify the object’s validity before proceeding with subsequent member accesses.
auto weak_ptr = msg_handler_weak_factory_.GetWeakPtr();
delegate_->HandleMove();
if (!weak_ptr) return;
Evaluated with Chrome root at commit: 1a8d40fc44df2088d5945c0bf53584038aa1614a
Results so far have been promising, but there can be wrong deductions. Feel free to adjust as follows:
- If you are familiar with the severity guidelines, you may adjust the severity.
- If this is a false positive, and there’s no work to be done, please close as WAI.
- If there is work to do here but not a vulnerability, please change the issue type to Task/Bug/FR.
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.