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 <sanaakbani@google.com>
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 <vollick@chromium.org>
Reviewed-by: Avi Drissman <avi@chromium.org>
Commit-Queue: Sana Akbani <sanaakbani@google.com>
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