Overview

High
Severity
8.3
CVSS
No
Exploited ITW
Fixed
Fix Status
ImpactUse after free in Core
DescriptionUse after free in Core
ComponentCore
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.
Tracker522546457
Fix commit590ec980cd07 (chromium/src) +4/-6
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-06-11 (per vendor advisory)
Fix landed2026-09-01 (public git)
Disclosed2026-09-08
Patch gap7 days — fix public in source before the advisory

Background

`LegacyRenderWidgetHostHWND`
the browser-process object (this in the patch) that owns a legacy Win32 child window (hwnd()) for a render widget on Windows.
`ui::AXSystemCaretWin`
a Windows accessibility object representing the system caret, constructed for the widget’s HWND regardless of accessibility mode.
`WM_NCDESTROY`
the final Win32 message delivered when a window is being torn down; per the commit message it can arrive re-entrantly and destroy the window (and its owner) mid-operation.
Re-entrant message pumping
constructing ui::AXSystemCaretWin can pump native messages, so unrelated window-destruction work can run before the constructor returns.

Root Cause Analysis

The removed code assigned std::make_unique<ui::AXSystemCaretWin>(hwnd()) directly into the member ax_system_caret_. Per the commit message, constructing ui::AXSystemCaretWin pumps native messages, so a WM_NCDESTROY can arrive during construction and destroy the window — deleting the enclosing LegacyRenderWidgetHostHWND (this) — before the constructor returns. Control then returns to code that writes the result into ax_system_caret_ on the already-freed this, calls hwnd() inside the old if (!::IsWindow(hwnd())) check, and — if that check fires — runs delete this on memory that was already freed, i.e. a use-after-free and potential double-free. The removed comment (“If we failed to create the child, then return false”) shows the old check was meant to detect a failed child creation, not re-entrant destruction of this, so it dereferenced freed state to make that decision.

The fix constructs the caret into a stack-local ax_system_caret, checks a survival guard if (!ref) and returns early — without touching any member and without delete this — when destruction occurred, and only std::moves into ax_system_caret_ when this is still alive. This works because no member of this is written and no delete this runs on the path where this was already destroyed.

Key insight
The single core mistake was writing the just-constructed caret into the object’s own member (ax_system_caret_) and re-deleting itself via a this-dependent check (::IsWindow(hwnd())) after a construction step that could already have freed this; the fix defers the member write behind a survival guard (ref) that does not touch freed memory and drops the now-unsafe delete this.

Attack Path

  1. Trigger caret creation Cause the browser to reach the code that constructs a ui::AXSystemCaretWin for the widget’s HWND (the commit names this LegacyRenderWidgetHostHWND::InitOrDeleteSelf (inferred) — not shown in the @@ header).
  2. Pump a destroy message during construction During std::make_unique<ui::AXSystemCaretWin>(hwnd()), the native message pump delivers a WM_NCDESTROY that destroys the window and deletes the enclosing LegacyRenderWidgetHostHWND (this).
  3. Return into the freed object Control returns to the old code, which writes the constructed pointer into ax_system_caret_ on the freed this and evaluates !::IsWindow(hwnd()), then executes delete this on already-freed memory — a use-after-free / double-free in the browser process.
  4. Escalation Turning that browser-process use-after-free into a controlled read/write or code execution is not demonstrated by the diff (inferred).

Impact Assessment

An attacker able to drive the re-entrant WM_NCDESTROY during caret creation obtains a use-after-free write (and a likely double-free via delete this) on the freed LegacyRenderWidgetHostHWND in the privileged browser process on Windows. Preconditions are the Windows caret-creation path being exercised while a window-destruction message can be pumped mid-construction. The vendor advisory rates this high severity and does not report in-the-wild exploitation; NVD scores it CVSS 8.3. Any escalation to memory corruption primitives or code execution is (inferred).

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

  • Construction that pumps messages
    Audit sites that assign the result of a native-object constructor (that may pump messages, show UI, or run nested loops) directly into a member of this, since this may be freed before the assignment lands.
  • Liveness checks that touch freed state
    Flag re-entrancy guards that test survival via a this-dependent call such as ::IsWindow(hwnd()) instead of a self-independent weak/scoped reference (ref), because the check itself dereferences possibly-freed memory.
  • `delete this` on error/reentrancy paths
    Review delete this on failure branches reached after a re-entrant callback, which can double-free when the object was already destroyed during that callback.
From 590ec980cd0778861da360e5f343dc3816a69b9c Mon Sep 17 00:00:00 2001
From: Greg Thompson <grt@chromium.org>
Date: Mon, 31 Aug 2026 23:17:16 -0700
Subject: [PATCH] [Windows] Handle re-entrant destruction when creating the caret

Creation of an AXSystemCaretWin can pump native messages, which means
that an WM_NCDESTROY could arrive and destroy the window for which the
caret is being created. Handle this by returning early from
LegacyRenderWidgetHostHWND::InitOrDeleteSelf.

Fixed: 522546457
Change-Id: I10b97cb7e1d067edf7b74768ea0598fa0bc65f76
Reviewed-on: https://chromium-review.googlesource.com/c/chromium/src/+/8320427
Commit-Queue: Greg Thompson <grt@chromium.org>
Auto-Submit: Greg Thompson <grt@chromium.org>
Reviewed-by: Dave Tapuska <dtapuska@chromium.org>
Cr-Commit-Position: refs/heads/main@{#1689642}
---
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 88aead6a..ec0f5c93 100644
--- a/content/browser/renderer_host/legacy_render_widget_host_win.cc
+++ b/content/browser/renderer_host/legacy_render_widget_host_win.cc
@@ -254,16 +254,14 @@
     return false;
   }
 
-  // We create a system caret regardless of accessibility mode since not all
+  // Create a system caret regardless of accessibility mode since not all
   // assistive software that makes use of a caret is classified as a screen
   // reader, e.g. the built-in Windows Magnifier.
-  ax_system_caret_ = std::make_unique<ui::AXSystemCaretWin>(hwnd());
-
-  // If we failed to create the child, then return false.
-  if (!::IsWindow(hwnd())) {
-    delete this;
+  auto ax_system_caret = std::make_unique<ui::AXSystemCaretWin>(hwnd());
+  if (!ref) {
     return false;
   }
+  ax_system_caret_ = std::move(ax_system_caret);
 
   // Ignore failure from this call. Some SKUs of Windows such as Hololens do not
   // support MSAA, and this call failing should not stop us from initializing
Loading diff…

Original Bug Report

reported by rj...@google.com

Use-After-Free in LegacyRenderWidgetHostHWND::InitOrDeleteSelf

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. 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 vulnerability exists in LegacyRenderWidgetHostHWND::InitOrDeleteSelf when creating an AXSystemCaretWin object. The constructor calls ::NotifyWinEvent, which can synchronously invoke a nested message loop that processes messages leading to the destruction of the window. When the constructor returns, the freed object is accessed, potentially allowing arbitrary code execution in the browser process.

Affected files:

  • content/browser/renderer_host/legacy_render_widget_host_win.cc

Estimated timestamp from git blame: 2017-12-09

Summary

A Use-After-Free (UAF) vulnerability has been identified in LegacyRenderWidgetHostHWND::InitOrDeleteSelf within the browser process. This issue occurs due to a lack of re-entrancy protection when initializing the system caret (ui::AXSystemCaretWin), allowing the potential destruction of the LegacyRenderWidgetHostHWND object while the initialization method is still active on the stack.

Technical Details

In content/browser/renderer_host/legacy_render_widget_host_win.cc, InitOrDeleteSelf is responsible for setting up the legacy render widget host window. This method uses base::WeakPtr guards to protect against re-entrancy during synchronous Windows API calls that could result in the object’s destruction. However, the initialization of ax_system_caret_ lacks this protection:

// Line 260
ax_system_caret_ = std::make_unique<ui::AXSystemCaretWin>(hwnd());

// Line 263
if (!::IsWindow(hwnd())) {
  delete this;
  return false;
}

The constructor of ui::AXSystemCaretWin (located in ui/accessibility/platform/ax_system_caret_win.cc) calls ::NotifyWinEvent synchronously:

if (event_target_) {
  NotifyWinEventAndTrace(EVENT_OBJECT_CREATE, event_target_, OBJID_CARET, -caret_->GetUniqueId());
}

The call to ::NotifyWinEvent can spin a nested message loop if system hooks or accessibility tools are present. During this loop, pending IPC messages can be processed.

Potential Exploitation Path

While our automated tooling cannot run arbitrary code to confirm exploitation, the following theoretical steps describe how an attacker compromising a renderer process might trigger this:

  1. A compromised renderer process sends an IPC to the browser process to create a new window or popup.
  2. The browser process creates an aura window, which invokes LegacyRenderWidgetHostHWND::Create.
  3. InitOrDeleteSelf executes, creating the legacy window and reaching line 260.
  4. hwnd() safely evaluates, and the AXSystemCaretWin constructor runs, invoking ::NotifyWinEvent.
  5. While ::NotifyWinEvent spins a message loop, the compromised renderer immediately sends a ‘close window’ IPC.
  6. The browser processes the ‘close window’ IPC, which calls RenderWidgetHostViewAura::OnWindowDestroying.
  7. OnWindowDestroying executes legacy_render_widget_host_HWND_.ExtractAsDangling()->Destroy();. By using ExtractAsDangling(), the raw_ptr reference is removed without being quarantined by MiraclePtr.
  8. Destroy() leads to OnNCDestroy, which executes delete this;, freeing the LegacyRenderWidgetHostHWND memory.
  9. The compromised renderer sprays the browser’s heap to reclaim the freed memory with a fake object containing an attacker-controlled vtable and a fake hwnd_ set to 0.
  10. The nested message loop concludes, and the constructor returns.
  11. InitOrDeleteSelf resumes. It assigns the returned unique pointer to ax_system_caret_ (a Write-After-Free) and evaluates if (!::IsWindow(hwnd())) (a Read-After-Free).
  12. Because hwnd() returns the attacker-controlled value 0, the branch is taken, executing delete this;.
  13. delete this; triggers the virtual destructor via the attacker-controlled vtable, leading to arbitrary code execution (RCE) and a sandbox escape.

Suggested Fix

Introduce a base::WeakPtr check after the creation of the AXSystemCaretWin object to ensure this has not been deleted:

  auto system_caret = std::make_unique<ui::AXSystemCaretWin>(hwnd());
  if (!ref) {
    return false;
  }
  ax_system_caret_ = std::move(system_caret);

  // If we failed to create the child, then return false.
  if (!::IsWindow(hwnd())) {
    delete this;
    return false;
  }

Evaluated with Chrome root at commit: 2155cb00003ec35716a76ed3246eae995f87b7ff


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.

View on issue tracker