Overview

High
Severity
CVSS
No
Exploited ITW
Fixed
Fix Status
ImpactUse after free in Core
DescriptionUse after free in Core
ComponentCore
Bug ClassUAF
Tracker503793153
Fix commit289ebec69073 (chromium/src) +5/-1
CISA KEVNot listed
CreditedGoogle
Disclosed2026-05-27

Changed Functions

FunctionChangeNotes
if
content/browser/renderer_host/legacy_render_widget_host_win.cc
modified

Files Changed

  • content/browser/renderer_host/legacy_render_widget_host_win.cc
From 289ebec6907316bff8bfd3486a6fda81954ffe06 Mon Sep 17 00:00:00 2001
From: Andrew Paseltiner <apaseltiner@chromium.org>
Date: Wed, 22 Apr 2026 11:11:49 -0700
Subject: [PATCH] Fix UAF in LegacyRenderWidgetHostHWND::OnMouseRange

When handling non-client mouse messages, LegacyRenderWidgetHostHWND
calls ::DefWindowProc on the parent window. This call can enter a nested
modal loop (e.g. for window dragging), during which the
LegacyRenderWidgetHostHWND object can be destroyed.

This CL adds a liveness check using a WeakPtr after the ::DefWindowProc
call to prevent a Use-After-Free write when calling SetMsgHandled.

Fixed: 503793153
Change-Id: I3b9bfb997ca2fe9a448ec92772711a2d7562b5d8
Reviewed-on: https://chromium-review.googlesource.com/c/chromium/src/+/7780978
Reviewed-by: Charlie Reis <creis@chromium.org>
Commit-Queue: Andrew Paseltiner <apaseltiner@chromium.org>
Cr-Commit-Position: refs/heads/main@{#1618972}
---

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 0ae2e3d4..d2c43cece 100644
--- a/content/browser/renderer_host/legacy_render_widget_host_win.cc
+++ b/content/browser/renderer_host/legacy_render_widget_host_win.cc
@@ -432,7 +432,11 @@
   if (!msg_handled &&
       (message >= WM_NCMOUSEMOVE && message <= WM_NCXBUTTONDBLCLK)) {
     ret = ::DefWindowProc(GetParent(), message, w_param, l_param);
-    SetMsgHandled(TRUE);
+    // DefWindowProc() may result in |this| being deleted (e.g. if a nested
+    // modal loop is entered and the tab is closed). See crbug.com/503793153.
+    if (ref) {
+      SetMsgHandled(TRUE);
+    }
   }
   return ret;
 }
Loading diff…

Original Bug Report

reported by rj...@google.com

Potential Use-After-Free Write in LegacyRenderWidgetHostHWND via Modal Loop

Flapjack, 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 https://chromium.googlesource.com/chromium/src/+/main/docs/security/ai-generated-security-bugs-faq.md for more information.

Overview: A Use-After-Free write vulnerability exists in LegacyRenderWidgetHostHWND on Windows. A nested modal message loop triggered by ::DefWindowProc allows the object to be destroyed and its memory reclaimed before a member variable is updated. This results in an arbitrary 4-byte write of 1, which could lead to a sandbox escape.

Affected files:

  • content/browser/renderer_host/legacy_render_widget_host_win.cc

Estimated timestamp from git blame: 2025-09-03

Description

A potential Use-After-Free (UAF) vulnerability exists in LegacyRenderWidgetHostHWND::OnMouseRange within content/browser/renderer_host/legacy_render_widget_host_win.cc. This class manages a child HWND used for accessibility and legacy driver support in the Chrome browser process on Windows.

Vulnerability Details

When LegacyRenderWidgetHostHWND receives non-client mouse messages (such as WM_NCLBUTTONDOWN) that are not handled by the WindowEventTarget, it forwards the message to the parent window using ::DefWindowProc:

  if (!msg_handled &&
      (message >= WM_NCMOUSEMOVE && message <= WM_NCXBUTTONDBLCLK)) {
    ret = ::DefWindowProc(GetParent(), message, w_param, l_param);
    SetMsgHandled(TRUE);
  }

If the mouse click occurs in a draggable area (e.g., defined by -webkit-app-region: drag), ::DefWindowProc enters a modal message loop to handle the window dragging operation. While this native modal loop is active, Chromium’s MessagePumpForUI continues to pump asynchronous IPC messages and tasks.

A compromised renderer process can trigger the destruction of the LegacyRenderWidgetHostHWND object during this modal loop (e.g., by crashing or sending a tab close IPC). The destruction sequence in RenderWidgetHostViewAura uses ExtractAsDangling() to clear its raw_ptr before calling Destroy(), which invokes ::DestroyWindow and leads to delete this in OnNCDestroy.

LRESULT LegacyRenderWidgetHostHWND::OnNCDestroy(UINT message, WPARAM w_param, LPARAM l_param) {
  may_service_accessibility_requests_ = false;
  // host_ is cleared...
  delete this;
  return 0;
}

Interaction with BackupRefPtr (BRP) and Quarantine

Although LegacyRenderWidgetHostHWND utilizes ADVANCED_MEMORY_SAFETY_CHECKS(), which places freed objects into the SchedulerLoopQuarantine, an attacker can bypass these protections:

  1. During delete this, a temporary raw_ptr generated by ExtractAsDangling() is still alive on the stack in RenderWidgetHostViewAura. This keeps the BRP refcount at 1, so PartitionAlloc zaps the memory but defers the quarantine.
  2. Once Destroy() returns, the temporary raw_ptr is destroyed, dropping the BRP refcount to 0. PartitionAlloc then moves the memory to the SchedulerLoopQuarantineBranch.
  3. The attacker, still executing tasks during the modal loop, can flush the finite-capacity quarantine by rapidly allocating and freeing other objects that use ADVANCED_MEMORY_SAFETY_CHECKS() (e.g., RenderFrameHost via iframe creation/destruction).
  4. Once the quarantine is flushed, the memory is returned to the PartitionAlloc free list and can be reclaimed via heap spraying.

Exploitation (Suggested Steps)

(Note: These are suggested steps based on static analysis; a working PoC has not been executed.)

  1. An attacker in a compromised renderer spoofs an IPC to simulate a non-client drag action, causing WM_NCLBUTTONDOWN to reach LegacyRenderWidgetHostHWND.
  2. OnMouseRange calls ::DefWindowProc, entering a native modal loop.
  3. The attacker sends an IPC to close the tab or deliberately crashes the renderer.
  4. A teardown task runs on the browser UI thread during the modal loop, triggering delete this on the legacy window.
  5. The BRP refcount drops to 0 shortly after, sending the object to the SchedulerLoopQuarantine.
  6. The attacker churns other memory-safety-checked objects via IPCs to overflow the quarantine, returning the legacy window’s memory to the free list.
  7. The attacker heap sprays the freed block with controlled data.
  8. The drag operation ends, and DefWindowProc returns.
  9. The next instruction, SetMsgHandled(TRUE);, expands to this->msg_handled_ = 1;.
  10. This performs a 4-byte write of 0x00000001 at the offset of msg_handled_ into the attacker-controlled memory, providing a powerful primitive to corrupt object state and achieve sandbox escape.

Recommendation

Check the liveness of the object after ::DefWindowProc returns using the WeakPtr (ref) that is already created earlier in the OnMouseRange function:

  if (!msg_handled &&
      (message >= WM_NCMOUSEMOVE && message <= WM_NCXBUTTONDBLCLK)) {
    ret = ::DefWindowProc(GetParent(), message, w_param, l_param);
    if (ref)
      SetMsgHandled(TRUE);
  }

Evaluated with Chrome root at commit: c0eb5541aebfa4ea08806eaf6e94bcc69f87ab2f


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