Overview

High
Severity
CVSS
No
Exploited ITW
Fixed
Fix Status
ImpactUse after free in Journeys
DescriptionUse after free in Journeys
ComponentJourneys
Bug ClassUAF
Tracker523224019
Fix commit2ac80efdce9d (chromium/src) +14/-9
CISA KEVNot listed
CreditedGoogle
Disclosed2026-06-30

Changed Functions

FunctionChangeNotes
HistoryClustersSidePanelContextMenu
chrome/browser/ui/webui/history_clusters/history_clusters_handler.cc
modified

Files Changed

  • chrome/browser/ui/webui/history_clusters/history_clusters_handler.cc
  • chrome/browser/ui/webui/history_clusters/history_clusters_handler.h
From 2ac80efdce9d237400eea2a873c8f624dd5c2060 Mon Sep 17 00:00:00 2001
From: Sophie Chang <sophiechang@chromium.org>
Date: Tue, 16 Jun 2026 08:47:22 -0700
Subject: [PATCH] Replace raw pointers with raw_ptr in HistoryClustersHandler ContextInterface.

This change updates the `ContextInterface` type alias and its usage within `HistoryClustersHandler` to use `raw_ptr` for `BrowserWindowInterface` and `tabs::TabInterface` pointers, improving memory safety.

Bug: 523224019
Change-Id: Icc5798a93af5115fa86fb0442008562d8c9847eb
Reviewed-on: https://chromium-review.googlesource.com/c/chromium/src/+/7940929
Reviewed-by: Marlon Facey <mfacey@chromium.org>
Commit-Queue: Sophie Chang <sophiechang@chromium.org>
Reviewed-by: Moe Ahmadi <mahmadi@chromium.org>
Cr-Commit-Position: refs/heads/main@{#1647602}
---

diff --git a/chrome/browser/ui/webui/history_clusters/history_clusters_handler.cc b/chrome/browser/ui/webui/history_clusters/history_clusters_handler.cc
index 961b2d7..52a4399 100644
--- a/chrome/browser/ui/webui/history_clusters/history_clusters_handler.cc
+++ b/chrome/browser/ui/webui/history_clusters/history_clusters_handler.cc
@@ -73,11 +73,13 @@
 // Returns the current browser window, regardless of whether this instance is
 // tab-scoped or window-scoped.
 BrowserWindowInterface* GetBrowserWindowInterface(
-    std::variant<BrowserWindowInterface*, tabs::TabInterface*> interface) {
-  if (std::holds_alternative<BrowserWindowInterface*>(interface)) {
-    return std::get<BrowserWindowInterface*>(interface);
+    std::variant<raw_ptr<BrowserWindowInterface>, raw_ptr<tabs::TabInterface>>
+        interface) {
+  if (std::holds_alternative<raw_ptr<BrowserWindowInterface>>(interface)) {
+    return std::get<raw_ptr<BrowserWindowInterface>>(interface);
   }
-  return std::get<tabs::TabInterface*>(interface)->GetBrowserWindowInterface();
+  return std::get<raw_ptr<tabs::TabInterface>>(interface)
+      ->GetBrowserWindowInterface();
 }
 
 class HistoryClustersSidePanelContextMenu
@@ -85,7 +87,8 @@
       public ui::SimpleMenuModel::Delegate {
  public:
   HistoryClustersSidePanelContextMenu(
-      std::variant<BrowserWindowInterface*, tabs::TabInterface*> interface,
+      std::variant<raw_ptr<BrowserWindowInterface>, raw_ptr<tabs::TabInterface>>
+          interface,
       GURL url)
       : ui::SimpleMenuModel(this), interface_(interface), url_(std::move(url)) {
     AddItemWithStringId(IDC_CONTENT_CONTEXT_OPENLINKNEWTAB,
@@ -100,7 +103,8 @@
                         IDS_HISTORY_CLUSTERS_COPY_LINK);
   }
   HistoryClustersSidePanelContextMenu(
-      std::variant<BrowserWindowInterface*, tabs::TabInterface*> interface,
+      std::variant<raw_ptr<BrowserWindowInterface>, raw_ptr<tabs::TabInterface>>
+          interface,
       std::string query)
       : ui::SimpleMenuModel(this), interface_(interface), query_(query) {
     AddItemWithStringId(IDC_CUT, IDS_HISTORY_CLUSTERS_CUT);
@@ -177,7 +181,8 @@
  private:
   // Exactly one of `browser_window_interface_` and `tab_interface_` will be
   // non-nullptr.
-  std::variant<BrowserWindowInterface*, tabs::TabInterface*> interface_;
+  std::variant<raw_ptr<BrowserWindowInterface>, raw_ptr<tabs::TabInterface>>
+      interface_;
   std::string query_;
   GURL url_;
 };
diff --git a/chrome/browser/ui/webui/history_clusters/history_clusters_handler.h b/chrome/browser/ui/webui/history_clusters/history_clusters_handler.h
index 82a1ead..12631580 100644
--- a/chrome/browser/ui/webui/history_clusters/history_clusters_handler.h
+++ b/chrome/browser/ui/webui/history_clusters/history_clusters_handler.h
@@ -90,8 +90,8 @@
   void SetSidePanelUIEmbedder(
       base::WeakPtr<TopChromeWebUIController::Embedder> side_panel_embedder);
 
-  using ContextInterface =
-      std::variant<BrowserWindowInterface*, tabs::TabInterface*>;
+  using ContextInterface = std::variant<raw_ptr<BrowserWindowInterface>,
+                                        raw_ptr<tabs::TabInterface>>;
   void SetContextInterface(ContextInterface interface);
 
   // Used to set the in-page query from the browser.
Loading diff…

Original Bug Report

reported by rj...@google.com

Potential UAF in HistoryClustersHandler via dangling TabInterface bypassing MiraclePtr

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 the browser process due to HistoryClustersHandler caching a tabs::TabInterface pointer inside a std::variant. This structure bypasses MiraclePtr protections. When a popup window is converted to a tab, the TabInterface is destroyed but the handler persists, allowing a compromised renderer to hijack control flow via Mojo IPC.

Affected files:

  • chrome/browser/ui/webui/history_clusters/history_clusters_handler.h
  • chrome/browser/ui/webui/history_clusters/history_clusters_handler.cc
  • chrome/browser/ui/webui/history/history_ui.cc

Estimated timestamp from git blame: 2024-08-30

Root Cause

In chrome/browser/ui/webui/history_clusters/history_clusters_handler.h, the interface_ field caches context information for the handler. It is defined as a std::variant holding raw pointers:

using ContextInterface = std::variant<BrowserWindowInterface*, tabs::TabInterface*>;
ContextInterface interface_;

Because standard raw pointers are used inside the std::variant, Chromium’s MiraclePtr (BackupRefPtr) rewrite tooling cannot protect them. If either pointer becomes dangling, dereferencing it leads to an immediate Use-After-Free (UAF) rather than a safe crash.

Vulnerability Details

The UAF occurs when the chrome://history WebUI is opened in a non-normal window (like a popup) and is subsequently moved to a normal tabbed browser window.

Potential Attacker Steps:

  1. Precondition: An attacker exploits a renderer bug to gain code execution in a process hosting the chrome://history WebUI.
  2. The WebUI is loaded inside a popup window. During initialization, HistoryUI::BindInterface retrieves the tabs::TabInterface* for the current WebContents and passes it to the HistoryClustersHandler constructor, which caches it in the interface_ variant.
  3. The popup window is converted to a tabbed browser (e.g., via the “Show as tab” action, ConvertPopupToTabbedBrowser).
  4. This action calls TabStripModel::DetachWebContentsAtForInsertion, which in turn calls tabs::TabModel::DestroyAndTakeWebContents.
  5. DestroyAndTakeWebContents safely moves the WebContents out of the TabModel and then explicitly destroys the TabModel (which implements tabs::TabInterface).
  6. Because the WebContents survives, the associated HistoryUI and HistoryClustersHandler also survive. However, HistoryClustersHandler does not observe TabInterface destruction, leaving interface_ as a dangling pointer to the freed TabModel.
  7. The attacker sprays the browser heap to replace the freed TabModel with controlled data, including a fake vtable.
  8. The attacker sends a Mojo IPC message over the mojom::PageHandler interface (e.g., OpenVisitUrlsInTabGroup or ShowContextMenuForURL).
  9. In history_clusters_handler.cc, these IPC handlers call GetBrowserWindowInterface(interface_).
  10. The helper function executes std::get<tabs::TabInterface*>(interface)->GetBrowserWindowInterface(). This makes a virtual function call on the freed memory, dereferencing the attacker’s fake vtable and resulting in arbitrary code execution in the browser process.

Note: These steps trace the code paths theoretically available; our tooling has not executed a working exploit.

Suggested Fix

  1. Adopt MiraclePtr: Change the ContextInterface alias to use raw_ptr to ensure MiraclePtr protection:
    using ContextInterface = std::variant<raw_ptr<BrowserWindowInterface>,
                                          raw_ptr<tabs::TabInterface>>;
    
  2. Lifetime Observation: HistoryClustersHandler should properly observe the lifetime of the TabInterface. Since it accepts a TabInterface* in its constructor, it should register for destruction notifications (e.g., using tabs::TabInterface::RegisterWillDetach) and clear the pointer or prevent further Mojo processing when the tab detaches or is destroyed.

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