Chrome · Google Lens › Context-input
CVE-2026-113201
Memory Corruption in Google Lens (Context-input)
Overview
Low
Severity
—
CVSS
No
Exploited ITW
Fixed
Fix Status
Changed Functions
| Function | Change | Notes |
|---|---|---|
ifchrome/browser/ui/contextual_search/tab_contextualization_controller.cc |
modified | |
ifchrome/browser/ui/lens/lens_search_contextualization_controller.cc |
modified | |
ASSERT_TRUEchrome/browser/ui/lens/lens_overlay_controller_browsertest.cc |
modified |
Files Changed
components/pdf/browser/pdf_document_helper.hcomponents/pdf/browser/pdf_document_helper.ccchrome/browser/ui/contextual_search/tab_contextualization_controller.ccchrome/browser/ui/lens/lens_overlay_controller_browsertest.ccchrome/browser/ui/lens/lens_search_contextualization_controller.ccchrome/browser/ui/contextual_search/tab_contextualization_controller_browsertest.cc
Patch
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.
References
On This Page