CVE-2026-118343
Overview
Background
- Reading List side panel
- a WebUI surface (
chrome/browser/ui/webui/side_panel/reading_list/) backed byReadingListPageHandler, which handles browser-side actions for the panel (mark as read, add tab, show context menu). - `views::MenuRunner` / `ui::MenuModel`
- a
MenuRunnerruns a context menu and holds araw_ptrto theMenuModelit is showing; the model must outlive the runner, and the runner must not keep pointing at a model that is being replaced/freed. - `SidePanelWebUIView`
- the view hosting the WebUI; it owns both
context_menu_model_andcontext_menu_runner_and builds a new runner each time a context menu is shown. - `GetLastActiveBrowser()`
GlobalBrowserCollection::GetInstance()->GetLastActiveBrowser()returns whatever browser was last active globally, which may be a different window than the one hosting this panel (or none).- `webui::GetBrowserWindowInterface(web_contents())`
- resolves the
BrowserWindowInterfacethat actually hosts this WebUI’sweb_contents().
Root Cause Analysis
The advisory labels this “Improper input validation in ReadingList” (InputValidation), but the commit title and diff show a use-after-free (plus a related stale/wrong-browser dereference) — no input validation is involved. There are two coupled defects. First, in SidePanelWebUIView::ShowContextMenu(), the code assigned context_menu_model_ = std::move(menu_model) and then rebuilt context_menu_runner_ pointing at the new model. Because views::MenuRunner stores a raw_ptr to the MenuModel, replacing context_menu_model_ while an existing context_menu_runner_ still held a raw_ptr to the old model left that runner dangling; the member declaration order (context_menu_runner_ before context_menu_model_) also made destruction order wrong — the model could be destroyed before the runner that references it. The fix reorders the members so context_menu_model_ is declared before context_menu_runner_ (ensuring the runner is destroyed first) and calls context_menu_runner_.reset() before reassigning context_menu_model_, so no runner ever points at a freed model.
Second, in ReadingListPageHandler, MarkCurrentTabAsRead(), AddCurrentTab(), and the context-menu handler obtained the browser via GlobalBrowserCollection::GetInstance()->GetLastActiveBrowser(). That returns a globally-last-active browser that need not be the one hosting the panel and whose lifetime is not tied to this handler’s web_contents(), so operating through it risks acting on a stale/destroyed browser. The patch replaces those calls with webui::GetBrowserWindowInterface(web_contents()), which resolves the correct hosting browser, and adds a missing if (!browser) return; null-guard in the context-menu path. Together these remove the dangling raw_ptr to a freed MenuModel and the unsafe reliance on a possibly-stale global browser pointer.
views::MenuRunner kept a raw_ptr to the previous MenuModel, but ShowContextMenu() replaced the model without resetting the runner (and declared them in an order that destroyed the model first), leaving a dangling pointer — a use-after-free; the fix resets the runner before swapping the model and fixes member order, while also resolving the browser from the panel’s own web_contents() instead of the global last-active browser.Attack Path
- Web/UI attacker drives the Reading List side panel (WebUI) to open context menus; the panel is user-reachable UI, and the renderer side can send the triggering Mojo calls (inferred: reachability via a compromised/misbehaving WebUI renderer or rapid user action).
- Open a context menu
ShowContextMenu()buildscontext_menu_model_and aMenuRunnerreferencing it. - Re-open before teardown
trigger another
ShowContextMenu()socontext_menu_model_is reassigned while the priorcontext_menu_runner_still holds araw_ptrto the old model. - Dangling model pointer the old model is freed while a runner (or destruction-ordered member) still references it.
- Primitive
subsequent use of the
MenuRunner’s danglingraw_ptrto the freedMenuModelis a use-after-free in the browser process (freed-memory access; controlled reuse is (inferred)).
Impact Assessment
raw_ptr from MenuRunner to a freed MenuModel; raw_ptr under MiraclePtr/BackupRefPtr may convert many such accesses into safe crashes, so whether this yields exploitable corruption versus a guarded crash is (inferred). The companion GetLastActiveBrowser() → GetBrowserWindowInterface() change additionally removes a wrong/stale-browser dereference. This is a lifetime bug, not input validation.Changed Functions
| Function | Change | Notes |
|---|---|---|
ifchrome/browser/ui/webui/side_panel/reading_list/reading_list_page_handler.cc |
modified |
Files Changed
chrome/browser/ui/views/side_panel/side_panel_web_ui_view.ccchrome/browser/ui/views/side_panel/side_panel_web_ui_view.hchrome/browser/ui/webui/side_panel/reading_list/reading_list_page_handler.cc
Audit Directions
- `MenuRunner` / `MenuModel` lifetimesaudit other views that own a
MenuRunnerand itsMenuModel, verifying the runner is reset before the model is replaced and that declaration/destruction order keeps the model alive longer than the runner. - `raw_ptr` holders across reassignmentsearch for members reassigned while another object holds a
raw_ptrto the old value (menus, delegates, observers). - `GetLastActiveBrowser()` misusereview other WebUI handlers that resolve a browser via the global last-active collection instead of their own
web_contents(); these can act on the wrong or destroyed window. - Advisory mislabel“Improper input validation” misclassifies this UAF; check sibling ReadingList/WebUI CVEs for the same generic tag.
Patch
From 3f44a926056f963a4e1b4c3ce51b8347008da4e1 Mon Sep 17 00:00:00 2001 From: Caroline Rising <[email protected]> Date: Mon, 19 Oct 2026 13:26:15 -0700 Subject: [PATCH] Fix UAF in reading list side panel context menu Replace GetLastActiveBrowser() calls with webui::GetBrowserWindowInterface() in ReadingListPageHandler. Move context_menu_model_ declaration before context_menu_runner_ (and reset the runner before reassigning the model) because MenuRunner holds a raw_ptr to MenuModel. Bug: 573389782 Change-Id: I0a05657421a9a801af8c04bf57048f2375bcfb32 Reviewed-on: https://chromium-review.googlesource.com/c/chromium/src/+/8611676 Commit-Queue: Caroline Rising <[email protected]> Reviewed-by: Alison Gale <[email protected]> Cr-Commit-Position: refs/heads/main@{#1722276} --- diff --git a/chrome/browser/ui/views/side_panel/side_panel_web_ui_view.cc b/chrome/browser/ui/views/side_panel/side_panel_web_ui_view.cc index c53051c..8426071 100644 --- a/chrome/browser/ui/views/side_panel/side_panel_web_ui_view.cc +++ b/chrome/browser/ui/views/side_panel/side_panel_web_ui_view.cc @@ -116,6 +116,7 @@ gfx::Point point, std::unique_ptr<ui::MenuModel> menu_model) { ConvertPointToScreen(this, &point); + context_menu_runner_.reset(); context_menu_model_ = std::move(menu_model); context_menu_runner_ = std::make_unique<views::MenuRunner>( context_menu_model_.get(), diff --git a/chrome/browser/ui/views/side_panel/side_panel_web_ui_view.h b/chrome/browser/ui/views/side_panel/side_panel_web_ui_view.h index fb6bab8..a634928d 100644 --- a/chrome/browser/ui/views/side_panel/side_panel_web_ui_view.h +++ b/chrome/browser/ui/views/side_panel/side_panel_web_ui_view.h @@ -65,8 +65,8 @@ std::unique_ptr< performance_manager::PageLiveStateDecorator::ScopedWithheldFromView> withheld_from_view_; - std::unique_ptr<views::MenuRunner> context_menu_runner_; std::unique_ptr<ui::MenuModel> context_menu_model_; + std::unique_ptr<views::MenuRunner> context_menu_runner_; // A handler to handle unhandled keyboard messages coming back from the // renderer process. views::UnhandledKeyboardEventHandler unhandled_keyboard_event_handler_; diff --git a/chrome/browser/ui/webui/side_panel/reading_list/reading_list_page_handler.cc b/chrome/browser/ui/webui/side_panel/reading_list/reading_list_page_handler.cc index 8dada41..f409b118 100644 --- a/chrome/browser/ui/webui/side_panel/reading_list/reading_list_page_handler.cc +++ b/chrome/browser/ui/webui/side_panel/reading_list/reading_list_page_handler.cc @@ -228,7 +228,7 @@ void ReadingListPageHandler::MarkCurrentTabAsRead() { BrowserWindowInterface* browser = - GlobalBrowserCollection::GetInstance()->GetLastActiveBrowser(); + webui::GetBrowserWindowInterface(web_contents()); if (!browser) { return; } @@ -239,7 +239,7 @@ void ReadingListPageHandler::AddCurrentTab() { BrowserWindowInterface* browser = - GlobalBrowserCollection::GetInstance()->GetLastActiveBrowser(); + webui::GetBrowserWindowInterface(web_contents()); if (!browser) { return; } @@ -261,7 +261,10 @@ int32_t y) { auto embedder = reading_list_ui_->embedder(); BrowserWindowInterface* browser = - GlobalBrowserCollection::GetInstance()->GetLastActiveBrowser(); + webui::GetBrowserWindowInterface(web_contents()); + if (!browser) { + return; + } if (embedder) { embedder->ShowContextMenu(gfx::Point(x, y), std::make_unique<ReadLaterItemContextMenu>(