CVE-2026-7911
Overview
Files Changed
ui/aura/window.cc
Patch
From 54847382f578feb9ac9756600e04f9b1eb55b58d Mon Sep 17 00:00:00 2001
From: Takashi Sakamoto <tasak@google.com>
Date: Mon, 30 Mar 2026 18:01:06 -0700
Subject: [PATCH] Enable CHECK(!is_destroying_) on M149 or newer.
Bug: 497548912
Change-Id: Ie2ac006f546152668524afa8b7e489c5334703c3
Reviewed-on: https://chromium-review.googlesource.com/c/chromium/src/+/7710793
Reviewed-by: Mitsuru Oshima <oshima@chromium.org>
Commit-Queue: Takashi Sakamoto <tasak@google.com>
Cr-Commit-Position: refs/heads/main@{#1607525}
---
diff --git a/ui/aura/window.cc b/ui/aura/window.cc
index c1db2c9..832b4fc 100644
--- a/ui/aura/window.cc
+++ b/ui/aura/window.cc
@@ -187,7 +187,9 @@
Window::~Window() {
// TODO(crbug.com/461127606): Crash on re-entrant destruction.
- CHECK(!is_destroying_, base::NotFatalUntil::M149);
+ // TODO(crbug.com/497548912): Continue crashing on re-entrant destruction
+ // on Chrome M149 or newer.
+ CHECK(!is_destroying_);
is_destroying_ = true;
WindowOcclusionTracker::ScopedPause pause_occlusion_tracking;
Original Bug Report
Potential UAF and RCE via re-entrant aura::Window destruction on Windows
Project Fortify, an experimental security project, has identified the following potential security issue.
Overview: A potential Use-After-Free vulnerability exists in aura::Window on Windows due to a non-fatal re-entrancy guard in its destructor. A nested message loop during destruction can allow a second, re-entrant destruction of the same window, bypassing MiraclePtr and leading to a highly reliable UAF. An attacker could potentially exploit this to achieve Remote Code Execution in the browser process.
Affected files:
ui/aura/window.cccontent/browser/renderer_host/render_widget_host_view_aura.ccui/wm/core/transient_window_manager.cccontent/browser/renderer_host/legacy_render_widget_host_win.cc
Estimated timestamp from git blame: 2026-01-15
Vulnerability Description
A potential Use-After-Free (UAF) and double-free vulnerability exists in aura::Window::~Window() on Windows. The destructor guards against re-entrant destruction with CHECK(!is_destroying_, base::NotFatalUntil::M149). Because this check is non-fatal in builds prior to M149, it triggers a DumpWithoutCrashing but allows execution to continue.
During window destruction, aura::Window notifies its delegate. For a RenderWidgetHostViewAura (RWHVA), this notification eventually calls LegacyRenderWidgetHostHWND::Destroy(), which invokes the Windows API ::DestroyWindow(). This API synchronously pumps the thread’s message loop, which can process pending IPCs.
If the destruction of the window’s parent is triggered during this nested message loop, the parent will iterate through its transient children and call delete on the child window again. This re-enters the ~Window() destructor.
MiraclePtr (BRP) Bypass
When the inner (re-entrant) destructor executes, it successfully removes the window from its parent’s children_ list, its transient parent’s transient_children_ list, and its delegate’s window_ member. Since all of these are raw_ptrs, clearing them drops the MiraclePtr reference count to zero.
When the inner destructor finishes, operator delete genuinely frees the memory to the allocator (bypassing the BackupRefPtr quarantine). When the nested message loop unwinds, the outer destructor resumes execution using its implicit this pointer—which is an unprotected raw C++ pointer—pointing to the now-freed memory. The outer destructor then iterates over the observers_ list, providing a reliable primitive for virtual call hijacking.
Suggested Attacker Steps
Note: These are potential steps based on static analysis; our tooling does not yet have the ability to run code to produce a working Proof of Concept.
- A compromised renderer process creates a popup window, which is initialized as a transient child of the main renderer window.
- The renderer sends an IPC to close the popup, triggering
delete window_;and entering the “outer”aura::Window::~Window(). - The outer destructor notifies the delegate, leading to a call to
::DestroyWindow()on Windows, which pumps the message loop. - During this pump, the attacker triggers the destruction of the main window (e.g., by crashing the renderer or via another IPC).
- The main window tears down and calls
deleteon its transient children, including the popup. This enters the “inner”~Window()destructor. - The non-fatal
CHECKis bypassed. The inner destructor clears allraw_ptrreferences to the popup and frees the memory. - The attacker sprays the heap via IPCs processed in the still-running message loop, reallocating the freed
aura::Windowmemory with a forgedbase::ObserverList. - The nested message loop unwinds, and the outer destructor resumes. It iterates the attacker-controlled
observers_list and callsobserver.OnWindowDestroying(this). - The attacker hijacks the virtual method call to achieve Remote Code Execution (RCE) in the browser process.
Suggested Fix
The re-entrancy guard in aura::Window::~Window() must be made strictly fatal to prevent the inner destructor from proceeding.
Change:
CHECK(!is_destroying_, base::NotFatalUntil::M149);
To:
CHECK(!is_destroying_);
Alternatively, if telemetry is still required without crashing the whole browser in release builds immediately, use base::ImmediateCrash() or base::debug::Alias to halt the specific execution path once the dump is generated, preventing the destructor from continuing.
Evaluated with Chrome root at commit: a9cbf6e8b275fe4147435aa905f3b7f5a656f5f0
Results from 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.