Low chrome Memory Corruption 🔧 Commit mapped

Overview

Low
Severity
—
CVSS
No
Exploited ITW
Fixed
Fix Status
ImpactMemory corruption in Google Lens
DescriptionMemory corruption in Google Lens
ComponentGoogle Lens
Sub-componentContext-input
Bug ClassMemory Corruption
Tracker503665813
Fix commit4f3d2b438891 (chromium/src) +84/-7
CISA KEVNot listed
CreditedGoogle
Disclosed2026-10-20

Changed Functions

FunctionChangeNotes
if
chrome/browser/ui/contextual_search/tab_contextualization_controller.cc
modified
if
chrome/browser/ui/lens/lens_search_contextualization_controller.cc
modified
ASSERT_TRUE
chrome/browser/ui/lens/lens_overlay_controller_browsertest.cc
modified

Files Changed

  • components/pdf/browser/pdf_document_helper.h
  • components/pdf/browser/pdf_document_helper.cc
  • chrome/browser/ui/contextual_search/tab_contextualization_controller.cc
  • chrome/browser/ui/lens/lens_overlay_controller_browsertest.cc
  • chrome/browser/ui/lens/lens_search_contextualization_controller.cc
  • chrome/browser/ui/contextual_search/tab_contextualization_controller_browsertest.cc
From 4f3d2b438891cc937bed2d69d531173f59197a52 Mon Sep 17 00:00:00 2001
From: Jason Hu <hujasonx@google.com>
Date: Mon, 21 Sep 2026 16:11:56 -0700
Subject: [PATCH] [context-input] Verify main frame MIME type before PDF extraction

LensSearchContextualizationController and TabContextualizationController
previously called PDFDocumentHelper::MaybeGetForWebContents to retrieve
PDF bytes, which searches across all subframes without verifying whether
the top-level document is actually a PDF. If a malicious HTML page
embedded a cross-origin PDF in an iframe, the controllers would extract
and upload the PDF bytes as part of page contextualization.

This CL introduces PDFDocumentHelper::MaybeGetForFullPagePdf to check
that the WebContents MIME type is application/pdf (pdf::kPDFMimeType)
before retrieving PDFDocumentHelper, and migrates
LensSearchContextualizationController and TabContextualizationController
to use it.

Bug: 503665813
Change-Id: Ia006bf7c03ae9ac4d45c96f8e7ff3938b83941a8
Reviewed-on: https://chromium-review.googlesource.com/c/chromium/src/+/8436455
Reviewed-by: Lei Zhang <thestig@chromium.org>
Commit-Queue: Jason Hu <hujasonx@google.com>
Cr-Commit-Position: refs/heads/main@{#1702164}
---
diff --git a/chrome/browser/ui/contextual_search/tab_contextualization_controller.cc b/chrome/browser/ui/contextual_search/tab_contextualization_controller.cc
index 1829c073..c33e436 100644
--- a/chrome/browser/ui/contextual_search/tab_contextualization_controller.cc
+++ b/chrome/browser/ui/contextual_search/tab_contextualization_controller.cc
@@ -379,7 +379,7 @@
 #if BUILDFLAG(ENABLE_PDF)
   // Capture the PDF bytes if the PDF helper exists.
   pdf::PDFDocumentHelper* pdf_helper =
-      pdf::PDFDocumentHelper::MaybeGetForWebContents(*web_contents);
+      pdf::PDFDocumentHelper::MaybeGetForFullPagePdf(*web_contents);
   if (pdf_helper) {
     // Fetch the PDF bytes then run the callback.
     pdf_helper->GetPdfBytes(
@@ -443,7 +443,7 @@
 
   // Get the most visible page index if the PDF helper exists.
   pdf::PDFDocumentHelper* pdf_helper =
-      pdf::PDFDocumentHelper::MaybeGetForWebContents(*web_contents);
+      pdf::PDFDocumentHelper::MaybeGetForFullPagePdf(*web_contents);
   if (pdf_helper) {
     // TODO(crbug.com/443743308): Parallelize the PDF page index fetch with the
     // PDF bytes fetch.
diff --git a/chrome/browser/ui/lens/lens_search_contextualization_controller.cc b/chrome/browser/ui/lens/lens_search_contextualization_controller.cc
index 4f539de8..4991e94 100644
--- a/chrome/browser/ui/lens/lens_search_contextualization_controller.cc
+++ b/chrome/browser/ui/lens/lens_search_contextualization_controller.cc
@@ -207,7 +207,7 @@
   // calling MaybeGetPdfBytes or else the `callback` will have been moved but
   // not called.
   pdf::PDFDocumentHelper* pdf_helper =
-      pdf::PDFDocumentHelper::MaybeGetForWebContents(
+      pdf::PDFDocumentHelper::MaybeGetForFullPagePdf(
           *lens_search_controller_->GetTabInterface()->GetContents());
   if (pdf_helper) {
     // Fetch the PDF bytes then run the callback.
@@ -273,7 +273,7 @@
         uint32_t page_count,
         PdfPartialPageTextRetrievedCallback callback) {
   pdf::PDFDocumentHelper* pdf_helper =
-      pdf::PDFDocumentHelper::MaybeGetForWebContents(
+      pdf::PDFDocumentHelper::MaybeGetForFullPagePdf(
           *lens_search_controller_->GetTabInterface()->GetContents());
   if (!pdf_helper ||
       lens::features::GetLensOverlayPdfSuggestCharacterTarget() == 0 ||
@@ -455,7 +455,7 @@
 
 #if BUILDFLAG(ENABLE_PDF)
   pdf::PDFDocumentHelper* pdf_helper =
-      pdf::PDFDocumentHelper::MaybeGetForWebContents(
+      pdf::PDFDocumentHelper::MaybeGetForFullPagePdf(
           *lens_search_controller_->GetTabInterface()->GetContents());
   if (pdf_helper) {
     pdf_helper->GetMostVisiblePageIndex(base::BindOnce(
@@ -816,7 +816,7 @@
       std::numeric_limits<uint32_t>::max());
 
   pdf::PDFDocumentHelper* pdf_helper =
-      pdf::PDFDocumentHelper::MaybeGetForWebContents(
+      pdf::PDFDocumentHelper::MaybeGetForFullPagePdf(
           *lens_search_controller_->GetTabInterface()->GetContents());
 
   // Stop the loop if the character limit is reached or if the page index is
@@ -1195,7 +1195,7 @@
 
 #if BUILDFLAG(ENABLE_PDF)
   pdf::PDFDocumentHelper* pdf_helper =
-      pdf::PDFDocumentHelper::MaybeGetForWebContents(
+      pdf::PDFDocumentHelper::MaybeGetForFullPagePdf(
           *lens_search_controller_->GetTabInterface()->GetContents());
   if (pdf_helper) {
     pdf_helper->GetMostVisiblePageIndex(base::BindOnce(
diff --git a/components/pdf/browser/pdf_document_helper.cc b/components/pdf/browser/pdf_document_helper.cc
index 75c1cd9..fafb5896 100644
--- a/components/pdf/browser/pdf_document_helper.cc
+++ b/components/pdf/browser/pdf_document_helper.cc
@@ -12,6 +12,7 @@
 #include "components/pdf/browser/pdf_document_helper_client.h"
 #include "components/pdf/browser/pdf_first_content_paint_registry.h"
 #include "components/pdf/browser/pdf_frame_util.h"
+#include "components/pdf/common/constants.h"
 #include "content/public/browser/document_user_data.h"
 #include "content/public/browser/render_widget_host.h"
 #include "content/public/browser/render_widget_host_view.h"
@@ -114,6 +115,15 @@
   return pdf_helper;
 }
 
+// static
+PDFDocumentHelper* PDFDocumentHelper::MaybeGetForFullPagePdf(
+    content::WebContents& contents) {
+  if (contents.GetContentsMimeType() != pdf::kPDFMimeType) {
+    return nullptr;
+  }
+  return MaybeGetForWebContents(contents);
+}
+
 PDFDocumentHelper::PDFDocumentHelper(
     content::RenderFrameHost* rfh,
     std::unique_ptr<PDFDocumentHelperClient> client)
diff --git a/components/pdf/browser/pdf_document_helper.h b/components/pdf/browser/pdf_document_helper.h
index b511170..72c9484b 100644
--- a/components/pdf/browser/pdf_document_helper.h
+++ b/components/pdf/browser/pdf_document_helper.h
@@ -64,9 +64,19 @@
       content::RenderFrameHost* rfh,
       std::unique_ptr<PDFDocumentHelperClient> client);
 
+  // Returns the PDFDocumentHelper associated with any frame in `contents`.
+  // WARNING: This searches across all frames, including subframes/iframes. If
+  // the caller only intends to operate on top-level full-page PDF documents,
+  // use `MaybeGetForFullPagePdf()` instead to avoid cross-origin data leaks.
   static PDFDocumentHelper* MaybeGetForWebContents(
       content::WebContents& contents);
 
+  // Returns the PDFDocumentHelper associated with `contents` for a full-page
+  // PDF. Returns nullptr if the top-level document is not a PDF, or if no
+  // PDFDocumentHelper exists.
+  static PDFDocumentHelper* MaybeGetForFullPagePdf(
+      content::WebContents& contents);
+
   // content::RenderWidgetHostObserver:
   void RenderWidgetHostDestroyed(
       content::RenderWidgetHost* widget_host) override;
diff --git a/chrome/browser/ui/contextual_search/tab_contextualization_controller_browsertest.cc b/chrome/browser/ui/contextual_search/tab_contextualization_controller_browsertest.cc
index 5db0ef1..6dba3a1a 100644
--- a/chrome/browser/ui/contextual_search/tab_contextualization_controller_browsertest.cc
+++ b/chrome/browser/ui/contextual_search/tab_contextualization_controller_browsertest.cc
@@ -203,6 +203,27 @@
   EXPECT_TRUE(data->is_page_context_eligible.has_value());
   EXPECT_FALSE(data->is_page_context_eligible.value());
 }
+
+IN_PROC_BROWSER_TEST_F(TabContextualizationControllerBrowserTest,
+                       GetPageContextForEmbeddedPdfIframe) {
+  auto* controller = GetTabContextualizationController();
+
+  GURL url(embedded_test_server()->GetURL("/pdf/test-iframe.html"));
+  ASSERT_TRUE(ui_test_utils::NavigateToURL(browser(), url));
+
+  base::test::TestFuture<std::unique_ptr<lens::ContextualInputData>> future;
+  controller->GetPageContext(future.GetCallback());
+  auto data = future.Take();
+
+  ASSERT_TRUE(data);
+  EXPECT_TRUE(data->tab_session_id.has_value());
+  EXPECT_EQ(data->page_url, url);
+  EXPECT_TRUE(data->page_title.has_value());
+  EXPECT_EQ(data->primary_content_type, lens::MimeType::kAnnotatedPageContent);
+  ASSERT_EQ(data->context_input->size(), 1u);
+  EXPECT_EQ((*data->context_input)[0].content_type_,
+            lens::MimeType::kAnnotatedPageContent);
+}
 #endif  // BUILDFLAG(ENABLE_PDF)
 
 IN_PROC_BROWSER_TEST_F(TabContextualizationControllerBrowserTest,
diff --git a/chrome/browser/ui/lens/lens_overlay_controller_browsertest.cc b/chrome/browser/ui/lens/lens_overlay_controller_browsertest.cc
index 171ced1..1cd63038 100644
--- a/chrome/browser/ui/lens/lens_overlay_controller_browsertest.cc
+++ b/chrome/browser/ui/lens/lens_overlay_controller_browsertest.cc
@@ -6209,6 +6209,42 @@
 }
 
 IN_PROC_BROWSER_TEST_P(LensOverlayControllerBrowserPDFContextualizationTest,
+                       PdfBytesNotExtractedForEmbeddedPdfIframe) {
+  // Open an HTML page embedding a PDF in an iframe.
+  const GURL url = embedded_test_server()->GetURL("/pdf/test-iframe.html");
+  ASSERT_TRUE(ui_test_utils::NavigateToURL(browser(), url));
+
+  // State should start in off.
+  auto* controller = GetLensOverlayController();
+  ASSERT_TRUE(controller);
+  ASSERT_EQ(controller->state(), State::kOff);
+
+  // Open the overlay.
+  OpenLensOverlay(LensOverlayInvocationSource::kAppMenu);
+  ASSERT_EQ(controller->state(), State::kScreenshot);
+  ASSERT_TRUE(base::test::RunUntil(
+      [&]() { return controller->state() == State::kOverlay; }));
+
+  auto* fake_query_controller =
+      static_cast<lens::TestLensOverlayQueryController*>(
+          GetLensOverlayQueryController());
+
+  // Wait until page content payload is sent.
+  ASSERT_TRUE(base::test::RunUntil([&]() {
+    return fake_query_controller->last_sent_page_content_payload()
+               .content()
+               .content_data()
+               .size() >= 1;
+  }));
+  for (const auto& content_data :
+       fake_query_controller->last_sent_page_content_payload()
+           .content()
+           .content_data()) {
+    EXPECT_NE(content_data.content_type(), lens::ContentData::CONTENT_TYPE_PDF);
+  }
+}
+
+IN_PROC_BROWSER_TEST_P(LensOverlayControllerBrowserPDFContextualizationTest,
                        PartialPdfIncludedInRequest) {
   // Open the PDF document and wait for it to finish loading.
   const GURL url = embedded_test_server()->GetURL(kPdfDocument);
Loading diff…

Regression Test / PoC

shipped with the fix
diff --git a/chrome/browser/ui/contextual_search/tab_contextualization_controller_browsertest.cc b/chrome/browser/ui/contextual_search/tab_contextualization_controller_browsertest.cc
index 5db0ef1..6dba3a1a 100644
--- a/chrome/browser/ui/contextual_search/tab_contextualization_controller_browsertest.cc
+++ b/chrome/browser/ui/contextual_search/tab_contextualization_controller_browsertest.cc
@@ -203,6 +203,27 @@
   EXPECT_TRUE(data->is_page_context_eligible.has_value());
   EXPECT_FALSE(data->is_page_context_eligible.value());
 }
+
+IN_PROC_BROWSER_TEST_F(TabContextualizationControllerBrowserTest,
+                       GetPageContextForEmbeddedPdfIframe) {
+  auto* controller = GetTabContextualizationController();
+
+  GURL url(embedded_test_server()->GetURL("/pdf/test-iframe.html"));
+  ASSERT_TRUE(ui_test_utils::NavigateToURL(browser(), url));
+
+  base::test::TestFuture<std::unique_ptr<lens::ContextualInputData>> future;
+  controller->GetPageContext(future.GetCallback());
+  auto data = future.Take();
+
+  ASSERT_TRUE(data);
+  EXPECT_TRUE(data->tab_session_id.has_value());
+  EXPECT_EQ(data->page_url, url);
+  EXPECT_TRUE(data->page_title.has_value());
+  EXPECT_EQ(data->primary_content_type, lens::MimeType::kAnnotatedPageContent);
+  ASSERT_EQ(data->context_input->size(), 1u);
+  EXPECT_EQ((*data->context_input)[0].content_type_,
+            lens::MimeType::kAnnotatedPageContent);
+}
 #endif  // BUILDFLAG(ENABLE_PDF)
 
 IN_PROC_BROWSER_TEST_F(TabContextualizationControllerBrowserTest,
diff --git a/chrome/browser/ui/lens/lens_overlay_controller_browsertest.cc b/chrome/browser/ui/lens/lens_overlay_controller_browsertest.cc
index 171ced1..1cd63038 100644
--- a/chrome/browser/ui/lens/lens_overlay_controller_browsertest.cc
+++ b/chrome/browser/ui/lens/lens_overlay_controller_browsertest.cc
@@ -6209,6 +6209,42 @@
 }
 
 IN_PROC_BROWSER_TEST_P(LensOverlayControllerBrowserPDFContextualizationTest,
+                       PdfBytesNotExtractedForEmbeddedPdfIframe) {
+  // Open an HTML page embedding a PDF in an iframe.
+  const GURL url = embedded_test_server()->GetURL("/pdf/test-iframe.html");
+  ASSERT_TRUE(ui_test_utils::NavigateToURL(browser(), url));
+
+  // State should start in off.
+  auto* controller = GetLensOverlayController();
+  ASSERT_TRUE(controller);
+  ASSERT_EQ(controller->state(), State::kOff);
+
+  // Open the overlay.
+  OpenLensOverlay(LensOverlayInvocationSource::kAppMenu);
+  ASSERT_EQ(controller->state(), State::kScreenshot);
+  ASSERT_TRUE(base::test::RunUntil(
+      [&]() { return controller->state() == State::kOverlay; }));
+
+  auto* fake_query_controller =
+      static_cast<lens::TestLensOverlayQueryController*>(
+          GetLensOverlayQueryController());
+
+  // Wait until page content payload is sent.
+  ASSERT_TRUE(base::test::RunUntil([&]() {
+    return fake_query_controller->last_sent_page_content_payload()
+               .content()
+               .content_data()
+               .size() >= 1;
+  }));
+  for (const auto& content_data :
+       fake_query_controller->last_sent_page_content_payload()
+           .content()
+           .content_data()) {
+    EXPECT_NE(content_data.content_type(), lens::ContentData::CONTENT_TYPE_PDF);
+  }
+}
+
+IN_PROC_BROWSER_TEST_P(LensOverlayControllerBrowserPDFContextualizationTest,
                        PartialPdfIncludedInRequest) {
   // Open the PDF document and wait for it to finish loading.
   const GURL url = embedded_test_server()->GetURL(kPdfDocument);
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.