High CVSS 8.3 chrome UAF 📄 Reporter bug report 🔧 Commit mapped

Overview

High
Severity
8.3
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
CWECWE-416 (Use-after-free)
CVSS vectorCVSS:3.1/AV:N/AC:H/PR:N/UI:R/S:C/C:H/I:H/A:H
CISA KEVNot listed
CreditedGoogle
Reported2026-04-17 (per vendor advisory)
Fix landed2026-04-22 (public git)
Disclosed2026-05-27
Patch gap35 days — fix public in source before the advisory

Background

`OnMouseRange`
The message handler for mouse messages on this window. For unhandled non-client mouse messages (WM_NCMOUSEMOVE … WM_NCXBUTTONDBLCLK) it forwards to the parent via ::DefWindowProc(GetParent(), …) and then calls SetMsgHandled(TRUE).
`::DefWindowProc` modal loop
For a non-client drag (e.g. -webkit-app-region: drag), DefWindowProc enters a nested native modal message loop to run the drag. Chromium’s MessagePumpForUI keeps pumping IPC and tasks inside that loop, so arbitrary browser-side work can run before DefWindowProc returns.
`SetMsgHandled(TRUE)`
Expands to this->msg_handled_ = 1;, a member write on the LegacyRenderWidgetHostHWND instance.
`ref` (WeakPtr)
A base::WeakPtr to this already created earlier in OnMouseRange; after the object is destroyed it evaluates false.

Root Cause Analysis

The violated invariant is that this must still be alive when OnMouseRange writes msg_handled_ after ::DefWindowProc returns. DefWindowProc for a draggable non-client area runs a nested modal loop; while it spins, the browser UI thread keeps processing tasks and IPC, and a renderer-triggered teardown (tab close or renderer crash) can destroy the LegacyRenderWidgetHostHWND. Destruction runs through RenderWidgetHostViewAura, which uses ExtractAsDangling() on its raw_ptr before Destroy() → ::DestroyWindow → OnNCDestroy → delete this. When DefWindowProc finally returns, control resumes inside the freed object and executes SetMsgHandled(TRUE), i.e. this->msg_handled_ = 1; — a use-after-free write of the 4-byte value 1 at the msg_handled_ offset into the (possibly reclaimed) memory.

The patch guards the post-DefWindowProc write with the already-existing weak pointer: it wraps SetMsgHandled(TRUE); in if (ref) { SetMsgHandled(TRUE); }, with a comment that DefWindowProc() may delete this via a nested modal loop. If the object was destroyed during the modal loop, ref is invalidated and the member write is skipped, eliminating the UAF write. The advisory’s UAF class is substantiated; notably the diff shows the sink is a write to a controlled offset, not merely a read — a stronger primitive than a typical read UAF. The advisory also details how the ExtractAsDangling() temporary keeps the BackupRefPtr refcount at 1 during delete this and defers quarantine until Destroy() returns, after which the memory enters SchedulerLoopQuarantine; those allocator interactions are advisory analysis and are not themselves in the one-hunk diff.

Key insight
Forwarding a non-client mouse message through ::DefWindowProc can spin a nested modal loop that lets the renderer tear down the window, so the single statement after the call — SetMsgHandled(TRUE), a member write — ran on a freed this; the fix simply refuses that write when the object’s WeakPtr is no longer valid.

Attack Path

  1. Compromised renderer Attacker has code execution in a renderer and can drive the browser via IPC (precondition per advisory; IPC surface not in the diff — (inferred)).
  2. Trigger a non-client drag Spoof input so a WM_NCLBUTTONDOWN in a draggable region (-webkit-app-region: drag) reaches LegacyRenderWidgetHostHWND::OnMouseRange, which calls ::DefWindowProc(GetParent(), …) and enters a native modal loop.
  3. Destroy the window While the modal loop pumps tasks/IPC, send a tab-close IPC or crash the renderer so a browser-UI-thread teardown runs delete this on the legacy window.
  4. Reclaim the memory (inferred) Churn other ADVANCED_MEMORY_SAFETY_CHECKS() objects to flush the SchedulerLoopQuarantine, returning the block to the free list, then heap-spray controlled contents into it.
  5. Trigger the UAF write End the drag so DefWindowProc returns; SetMsgHandled(TRUE) writes 0x00000001 at the msg_handled_ offset into the reclaimed, attacker-controlled block.
  6. Primitive A constrained 4-byte-write-of-1 at a fixed offset into controlled memory; per the advisory this corrupts object state toward a browser-process sandbox escape (escalation inferred; the diff proves only the UAF write).

Impact Assessment

The bug is in the browser process on Windows, reachable from a compromised renderer, and requires winning a race during a nested modal loop (AC:H, scope-changed, per the advisory’s CVSS). Confirmed by the diff: a use-after-free write (msg_handled_ = 1) on a LegacyRenderWidgetHostHWND deleted during ::DefWindowProc. The write is a narrow primitive — a fixed value (1) at a fixed member offset — so exploitability depends on grooming the freed block and on what state that offset overlaps; quarantine-flush and heap-spray are (inferred) and not shown by the diff. Escalation to sandbox escape / RCE is inferred; the realized, proven effect is a controlled 4-byte UAF write.

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

Audit Directions

  • Member access after reentrant Win32 calls
    Audit every this-member access that follows ::DefWindowProc, ::SendMessage, ::TrackPopupMenu, ::DoDragDrop, or any API that can pump a nested message loop, in message handlers of self-deleting HWND objects.
  • Self-deleting HWND lifetimes
    Review classes that delete this in OnNCDestroy/WM_NCDESTROY and are driven by pumped message loops; confirm each reentrant call site re-checks liveness.
  • `ExtractAsDangling()` teardown timing
    Examine other RenderWidgetHostViewAura/view teardown paths using ExtractAsDangling() where the temporary raw_ptr defers BRP quarantine, extending the exploitable window.
  • MessagePumpForUI reentrancy
    Look for other browser-UI-thread code assuming no task/IPC runs across a blocking Win32 call; the pump’s continued servicing inside modal loops breaks that assumption generally.
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