Chrome · Core › Glic
CVE-2026-113288
Memory Corruption in Core (Glic)
Overview
Low
Severity
—
CVSS
No
Exploited ITW
Fixed
Fix Status
Changed Functions
| Function | Change | Notes |
|---|---|---|
is_paste_and_match_style_enabled_chrome/browser/renderer_context_menu/render_view_context_menu.cc |
modified | |
ifchrome/browser/renderer_context_menu/render_view_context_menu.cc |
modified | |
GlicContextMenuStaleDocumentBrowserTestchrome/browser/renderer_context_menu/render_view_context_menu_browsertest.cc |
modified | |
GlicContextMenuStaleDocumentBrowserTestchrome/browser/renderer_context_menu/render_view_context_menu_browsertest.cc |
modified |
Files Changed
chrome/browser/renderer_context_menu/render_view_context_menu.hchrome/browser/renderer_context_menu/render_view_context_menu.ccchrome/browser/renderer_context_menu/render_view_context_menu_browsertest.cc
Patch
From 9a739c9feb0d55194cfe2929fd7fcdd09bfa7e13 Mon Sep 17 00:00:00 2001 From: Sana Akbani <[email protected]> Date: Mon, 21 Sep 2026 15:11:04 -0700 Subject: [PATCH] [glic] Validate source document before shipping context menu selection On Views platforms, context menus are not dismissed when the underlying tab navigates. Snapshot a WeakDocumentPtr when the menu opens and bail out in ExecGlic() if the originating document is no longer the active primary page. This prevents cross-navigation selection leaks, eligibility bypasses, and null-deref crashes on destroyed frames. TAG=agy Bug: 556213889 Change-Id: I90e94c301697715c69fb951c8d1ecb406a6a6964 Reviewed-on: https://chromium-review.googlesource.com/c/chromium/src/+/8421867 Reviewed-by: Ian Vollick <[email protected]> Reviewed-by: Avi Drissman <[email protected]> Commit-Queue: Sana Akbani <[email protected]> Cr-Commit-Position: refs/heads/main@{#1702093} --- diff --git a/chrome/browser/renderer_context_menu/render_view_context_menu.cc b/chrome/browser/renderer_context_menu/render_view_context_menu.cc index e5544db..a7ad5785 100644 --- a/chrome/browser/renderer_context_menu/render_view_context_menu.cc +++ b/chrome/browser/renderer_context_menu/render_view_context_menu.cc @@ -228,6 +228,7 @@ #include "content/public/browser/navigation_controller.h" #include "content/public/browser/navigation_details.h" #include "content/public/browser/navigation_entry.h" +#include "content/public/browser/page.h" #include "content/public/browser/picture_in_picture_window_controller.h" #include "content/public/browser/render_frame_host.h" #include "content/public/browser/render_process_host.h" @@ -1068,14 +1069,11 @@ RenderViewContextMenu::RenderViewContextMenu( content::RenderFrameHost& render_frame_host, - const content::ContextMenuParams& params, - bool is_paste_enabled, + const content::ContextMenuParams& params, bool is_paste_enabled, bool is_paste_and_match_style_enabled) : RenderViewContextMenuBase(render_frame_host, params), extension_items_( - browser_context_, - this, - &menu_model_, + browser_context_, this, &menu_model_, base::BindRepeating( extensions::context_menu_helpers::MenuItemMatchesParams, params_)), @@ -1092,7 +1090,8 @@ embedder_web_contents_(GetWebContentsToUse(&render_frame_host)), autofill_context_menu_manager_(this, &menu_model_), is_paste_enabled_(is_paste_enabled), - is_paste_and_match_style_enabled_(is_paste_and_match_style_enabled) { + is_paste_and_match_style_enabled_(is_paste_and_match_style_enabled), + source_document_at_menu_open_(render_frame_host.GetWeakDocumentPtr()) { if (!g_custom_id_ranges_initialized) { g_custom_id_ranges_initialized = true; SetContentCustomCommandIdRange(IDC_CONTENT_CONTEXT_CUSTOM_FIRST, @@ -5164,12 +5163,22 @@ } void RenderViewContextMenu::ExecGlic() { + // Context menus remain open across navigations on Views platforms. Bail out + // if the originating document is no longer the active primary page to avoid + // cross-navigation selection leaks, eligibility bypasses, or null-derefs. + content::RenderFrameHost* source_frame = + source_document_at_menu_open_.AsRenderFrameHostIfValid(); + if (!source_frame || + &source_frame->GetPage() != &source_web_contents_->GetPrimaryPage()) { + return; + } + if (glic::GlicEnabling::IsContextualMenuItemEnabled(GetProfile(), params_.selection_text)) { glic_item_executed_ = true; glic::GlicContextMenuInvocationHelper::HandleContextualMenuClick( tabs::TabInterface::MaybeGetFromContents(source_web_contents_), - params_.selection_text, GetRenderFrameHost()->GetGlobalId()); + params_.selection_text, source_frame->GetGlobalId()); } } diff --git a/chrome/browser/renderer_context_menu/render_view_context_menu.h b/chrome/browser/renderer_context_menu/render_view_context_menu.h index cbf156d..6b93bf30 100644 --- a/chrome/browser/renderer_context_menu/render_view_context_menu.h +++ b/chrome/browser/renderer_context_menu/render_view_context_menu.h @@ -28,6 +28,7 @@ #include "components/renderer_context_menu/render_view_context_menu_proxy.h" #include "components/supervised_user/core/browser/supervised_user_url_filtering_service.h" #include "content/public/browser/context_menu_params.h" +#include "content/public/browser/weak_document_ptr.h" #include "content/public/common/buildflags.h" #include "extensions/buildflags/buildflags.h" #include "mojo/public/cpp/bindings/associated_remote.h" @@ -635,6 +636,10 @@ bool glic_item_shown_ = false; bool glic_item_executed_ = false; + // Tracks the originating document at menu-open time so Glic actions can bail + // out if the tab navigated while the menu was open. + const content::WeakDocumentPtr source_document_at_menu_open_; + base::WeakPtrFactory<RenderViewContextMenu> weak_pointer_factory_{this}; }; diff --git a/chrome/browser/renderer_context_menu/render_view_context_menu_browsertest.cc b/chrome/browser/renderer_context_menu/render_view_context_menu_browsertest.cc index 2dcf1bd..0504f0a 100644 --- a/chrome/browser/renderer_context_menu/render_view_context_menu_browsertest.cc +++ b/chrome/browser/renderer_context_menu/render_view_context_menu_browsertest.cc @@ -856,6 +856,103 @@ histogram_tester.ExpectBucketCount("Glic.WebContentsContextMenu.Page", 0, 1); } +// Regression tests for crbug.com/556213889: the context menu is not dismissed +// when the underlying tab navigates, so ExecGlic() must not ship a selection +// snapshotted from a document that is no longer the tab's primary page. +class GlicContextMenuStaleDocumentBrowserTest + : public ContextMenuBrowserTestBase { + protected: + GlicContextMenuStaleDocumentBrowserTest() { + scoped_feature_list_.InitWithFeatures( + { + features::kGlic, + features::kGlicContextMenu, + }, + {}); + } + + static constexpr char kTextSelectionHistogram[] = + "Glic.WebContentsContextMenu.TextSelection"; + + void SummonMenuNavigateAndActivateGlic(const std::u16string& selection_text, + const GURL& navigate_to) { + std::unique_ptr<TestRenderViewContextMenu> menu = + CreateContextMenuForTextInWebContents(selection_text); + menu->OnMenuWillShow(const_cast<ui::SimpleMenuModel*>(&menu->menu_model())); + + if (!navigate_to.is_empty()) { + ASSERT_TRUE(ui_test_utils::NavigateToURL(browser(), navigate_to)); + } + + menu->ExecuteCommand(IDC_CONTENT_CONTEXT_GLIC, 0); + menu->MenuClosed(const_cast<ui::SimpleMenuModel*>(&menu->menu_model())); + } + + private: + base::test::ScopedFeatureList scoped_feature_list_; +}; + +IN_PROC_BROWSER_TEST_F(GlicContextMenuStaleDocumentBrowserTest, + SelectionNotSentAfterNavigationWithDocumentCached) { + glic::GlicEnabling::ScopedBypassEnablementChecksForTesting scoped_glic_bypass; + ASSERT_TRUE(embedded_test_server()->Start()); + base::HistogramTester histogram_tester; + + ASSERT_TRUE(ui_test_utils::NavigateToURL( + browser(), embedded_test_server()->GetURL("a.test", "/title1.html"))); + + ASSERT_NO_FATAL_FAILURE(SummonMenuNavigateAndActivateGlic( + u"secret selection from page A", + embedded_test_server()->GetURL("b.test", "/title2.html"))); + + histogram_tester.ExpectUniqueSample(kTextSelectionHistogram, 0, 1); +} + +IN_PROC_BROWSER_TEST_F(GlicContextMenuStaleDocumentBrowserTest, + SelectionNotSentAfterNavigationWithDocumentDestroyed) { + glic::GlicEnabling::ScopedBypassEnablementChecksForTesting scoped_glic_bypass; + ASSERT_TRUE(embedded_test_server()->Start()); + base::HistogramTester histogram_tester; + + ASSERT_TRUE(ui_test_utils::NavigateToURL( + browser(), embedded_test_server()->GetURL("a.test", "/title1.html"))); + + content::WebContents* web_contents = + browser()->GetTabStripModel()->GetActiveWebContents(); + content::RenderFrameHostWrapper source_frame( + web_contents->GetPrimaryMainFrame()); + + std::unique_ptr<TestRenderViewContextMenu> menu = + CreateContextMenuForTextInWebContents(u"secret selection from page A"); + menu->OnMenuWillShow(const_cast<ui::SimpleMenuModel*>(&menu->menu_model())); + + ASSERT_TRUE(ui_test_utils::NavigateToURL( + browser(), embedded_test_server()->GetURL("b.test", "/title2.html"))); + + web_contents->GetController().GetBackForwardCache().Flush(); + ASSERT_TRUE(source_frame.WaitUntilRenderFrameDeleted()); + + menu->ExecuteCommand(IDC_CONTENT_CONTEXT_GLIC, 0); + menu->MenuClosed(const_cast<ui::SimpleMenuModel*>(&menu->menu_model())); + + histogram_tester.ExpectUniqueSample(kTextSelectionHistogram, 0, 1); +} + +IN_PROC_BROWSER_TEST_F(GlicContextMenuStaleDocumentBrowserTest, + SelectionSentWhenDocumentUnchanged) { + glic::GlicEnabling::ScopedBypassEnablementChecksForTesting scoped_glic_bypass; + ASSERT_TRUE(embedded_test_server()->Start()); + base::HistogramTester histogram_tester; + + ASSERT_TRUE(ui_test_utils::NavigateToURL( + browser(), embedded_test_server()->GetURL("a.test", "/title1.html"))); + + ASSERT_NO_FATAL_FAILURE( + SummonMenuNavigateAndActivateGlic(u"selection", GURL())); + + histogram_tester.ExpectUniqueSample(kTextSelectionHistogram, 1, 1); +} + // TODO(crbug.com/455524503): De-flake and re-enable on ChromeOS. #if BUILDFLAG(IS_CHROMEOS) #define MAYBE_SaveLinkAsEntryIsDisabledForBlockedUrls \
Loading diff…
Regression Test / PoC
shipped with the fix
diff --git a/chrome/browser/renderer_context_menu/render_view_context_menu_browsertest.cc b/chrome/browser/renderer_context_menu/render_view_context_menu_browsertest.cc
index 2dcf1bd..0504f0a 100644
--- a/chrome/browser/renderer_context_menu/render_view_context_menu_browsertest.cc
+++ b/chrome/browser/renderer_context_menu/render_view_context_menu_browsertest.cc
@@ -856,6 +856,103 @@
histogram_tester.ExpectBucketCount("Glic.WebContentsContextMenu.Page", 0, 1);
}
+// Regression tests for crbug.com/556213889: the context menu is not dismissed
+// when the underlying tab navigates, so ExecGlic() must not ship a selection
+// snapshotted from a document that is no longer the tab's primary page.
+class GlicContextMenuStaleDocumentBrowserTest
+ : public ContextMenuBrowserTestBase {
+ protected:
+ GlicContextMenuStaleDocumentBrowserTest() {
+ scoped_feature_list_.InitWithFeatures(
+ {
+ features::kGlic,
+ features::kGlicContextMenu,
+ },
+ {});
+ }
+
+ static constexpr char kTextSelectionHistogram[] =
+ "Glic.WebContentsContextMenu.TextSelection";
+
+ void SummonMenuNavigateAndActivateGlic(const std::u16string& selection_text,
+ const GURL& navigate_to) {
+ std::unique_ptr<TestRenderViewContextMenu> menu =
+ CreateContextMenuForTextInWebContents(selection_text);
+ menu->OnMenuWillShow(const_cast<ui::SimpleMenuModel*>(&menu->menu_model()));
+
+ if (!navigate_to.is_empty()) {
+ ASSERT_TRUE(ui_test_utils::NavigateToURL(browser(), navigate_to));
+ }
+
+ menu->ExecuteCommand(IDC_CONTENT_CONTEXT_GLIC, 0);
+ menu->MenuClosed(const_cast<ui::SimpleMenuModel*>(&menu->menu_model()));
+ }
+
+ private:
+ base::test::ScopedFeatureList scoped_feature_list_;
+};
+
+IN_PROC_BROWSER_TEST_F(GlicContextMenuStaleDocumentBrowserTest,
+ SelectionNotSentAfterNavigationWithDocumentCached) {
+ glic::GlicEnabling::ScopedBypassEnablementChecksForTesting scoped_glic_bypass;
+ ASSERT_TRUE(embedded_test_server()->Start());
+ base::HistogramTester histogram_tester;
+
+ ASSERT_TRUE(ui_test_utils::NavigateToURL(
+ browser(), embedded_test_server()->GetURL("a.test", "/title1.html")));
+
+ ASSERT_NO_FATAL_FAILURE(SummonMenuNavigateAndActivateGlic(
+ u"secret selection from page A",
+ embedded_test_server()->GetURL("b.test", "/title2.html")));
+
+ histogram_tester.ExpectUniqueSample(kTextSelectionHistogram, 0, 1);
+}
+
+IN_PROC_BROWSER_TEST_F(GlicContextMenuStaleDocumentBrowserTest,
+ SelectionNotSentAfterNavigationWithDocumentDestroyed) {
+ glic::GlicEnabling::ScopedBypassEnablementChecksForTesting scoped_glic_bypass;
+ ASSERT_TRUE(embedded_test_server()->Start());
+ base::HistogramTester histogram_tester;
+
+ ASSERT_TRUE(ui_test_utils::NavigateToURL(
+ browser(), embedded_test_server()->GetURL("a.test", "/title1.html")));
+
+ content::WebContents* web_contents =
+ browser()->GetTabStripModel()->GetActiveWebContents();
+ content::RenderFrameHostWrapper source_frame(
+ web_contents->GetPrimaryMainFrame());
+
+ std::unique_ptr<TestRenderViewContextMenu> menu =
+ CreateContextMenuForTextInWebContents(u"secret selection from page A");
+ menu->OnMenuWillShow(const_cast<ui::SimpleMenuModel*>(&menu->menu_model()));
+
+ ASSERT_TRUE(ui_test_utils::NavigateToURL(
+ browser(), embedded_test_server()->GetURL("b.test", "/title2.html")));
+
+ web_contents->GetController().GetBackForwardCache().Flush();
+ ASSERT_TRUE(source_frame.WaitUntilRenderFrameDeleted());
+
+ menu->ExecuteCommand(IDC_CONTENT_CONTEXT_GLIC, 0);
+ menu->MenuClosed(const_cast<ui::SimpleMenuModel*>(&menu->menu_model()));
+
+ histogram_tester.ExpectUniqueSample(kTextSelectionHistogram, 0, 1);
+}
+
+IN_PROC_BROWSER_TEST_F(GlicContextMenuStaleDocumentBrowserTest,
+ SelectionSentWhenDocumentUnchanged) {
+ glic::GlicEnabling::ScopedBypassEnablementChecksForTesting scoped_glic_bypass;
+ ASSERT_TRUE(embedded_test_server()->Start());
+ base::HistogramTester histogram_tester;
+
+ ASSERT_TRUE(ui_test_utils::NavigateToURL(
+ browser(), embedded_test_server()->GetURL("a.test", "/title1.html")));
+
+ ASSERT_NO_FATAL_FAILURE(
+ SummonMenuNavigateAndActivateGlic(u"selection", GURL()));
+
+ histogram_tester.ExpectUniqueSample(kTextSelectionHistogram, 1, 1);
+}
+
// TODO(crbug.com/455524503): De-flake and re-enable on ChromeOS.
#if BUILDFLAG(IS_CHROMEOS)
#define MAYBE_SaveLinkAsEntryIsDisabledForBlockedUrls \
Loading diff…
Original Bug Report
The reporter's bug is still restricted on the tracker. Chrome de-restricts security bugs ~30–90 days after the fix ships; a later run will backfill it here.
References
On This Page