CVE-2026-13853
Overview
Changed Functions
| Function | Change | Notes |
|---|---|---|
HistoryClustersSidePanelContextMenuchrome/browser/ui/webui/history_clusters/history_clusters_handler.cc |
modified |
Files Changed
chrome/browser/ui/webui/history_clusters/history_clusters_handler.ccchrome/browser/ui/webui/history_clusters/history_clusters_handler.h
Patch
From 2ac80efdce9d237400eea2a873c8f624dd5c2060 Mon Sep 17 00:00:00 2001 From: Sophie Chang <[email protected]> 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 <[email protected]> Commit-Queue: Sophie Chang <[email protected]> Reviewed-by: Moe Ahmadi <[email protected]> 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.
Original Bug Report
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.hchrome/browser/ui/webui/history_clusters/history_clusters_handler.ccchrome/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:
- Precondition: An attacker exploits a renderer bug to gain code execution in a process hosting the
chrome://historyWebUI. - The WebUI is loaded inside a popup window. During initialization,
HistoryUI::BindInterfaceretrieves thetabs::TabInterface*for the currentWebContentsand passes it to theHistoryClustersHandlerconstructor, which caches it in theinterface_variant. - The popup window is converted to a tabbed browser (e.g., via the “Show as tab” action,
ConvertPopupToTabbedBrowser). - This action calls
TabStripModel::DetachWebContentsAtForInsertion, which in turn callstabs::TabModel::DestroyAndTakeWebContents. DestroyAndTakeWebContentssafely moves theWebContentsout of theTabModeland then explicitly destroys theTabModel(which implementstabs::TabInterface).- Because the
WebContentssurvives, the associatedHistoryUIandHistoryClustersHandleralso survive. However,HistoryClustersHandlerdoes not observeTabInterfacedestruction, leavinginterface_as a dangling pointer to the freedTabModel. - The attacker sprays the browser heap to replace the freed
TabModelwith controlled data, including a fake vtable. - The attacker sends a Mojo IPC message over the
mojom::PageHandlerinterface (e.g.,OpenVisitUrlsInTabGrouporShowContextMenuForURL). - In
history_clusters_handler.cc, these IPC handlers callGetBrowserWindowInterface(interface_). - 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
- Adopt MiraclePtr: Change the
ContextInterfacealias to useraw_ptrto ensure MiraclePtr protection:using ContextInterface = std::variant<raw_ptr<BrowserWindowInterface>, raw_ptr<tabs::TabInterface>>; - Lifetime Observation:
HistoryClustersHandlershould properly observe the lifetime of theTabInterface. Since it accepts aTabInterface*in its constructor, it should register for destruction notifications (e.g., usingtabs::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.