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 <[email protected]> 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 <[email protected]> Commit-Queue: Jason Hu <[email protected]> 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