Low chrome Memory Corruption 🔧 Commit mapped

Overview

Low
Severity
—
CVSS
No
Exploited ITW
Fixed
Fix Status
ImpactMemory corruption in Core
DescriptionMemory corruption in Core
ComponentCore
Sub-componentGlic
Bug ClassMemory Corruption
Tracker556213889
Fix commit9a739c9feb0d (chromium/src) +118/-7
CISA KEVNot listed
CreditedGoogle
Disclosed2026-10-20

Changed Functions

FunctionChangeNotes
is_paste_and_match_style_enabled_
chrome/browser/renderer_context_menu/render_view_context_menu.cc
modified
if
chrome/browser/renderer_context_menu/render_view_context_menu.cc
modified
GlicContextMenuStaleDocumentBrowserTest
chrome/browser/renderer_context_menu/render_view_context_menu_browsertest.cc
modified
GlicContextMenuStaleDocumentBrowserTest
chrome/browser/renderer_context_menu/render_view_context_menu_browsertest.cc
modified

Files Changed

  • chrome/browser/renderer_context_menu/render_view_context_menu.h
  • chrome/browser/renderer_context_menu/render_view_context_menu.cc
  • chrome/browser/renderer_context_menu/render_view_context_menu_browsertest.cc
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.