Chrome · Translate
CVE-2026-106369
Authorization Error in Translate
Overview
High
Severity
—
CVSS
No
Exploited ITW
Fixed
Fix Status
Changed Functions
| Function | Change | Notes |
|---|---|---|
ifchrome/browser/ui/pdf/chrome_pdf_document_helper_client.cc |
modified | |
TranslateManagerPdfBrowserTestchrome/browser/translate/translate_manager_browsertest.cc |
modified | |
EXPECT_TRUEchrome/browser/translate/translate_manager_browsertest.cc |
modified | |
ASSERT_TRUEchrome/browser/translate/translate_manager_browsertest.cc |
modified |
Files Changed
chrome/browser/ui/pdf/BUILD.gnchrome/browser/ui/pdf/chrome_pdf_document_helper_client.ccchrome/browser/translate/translate_manager_browsertest.cc
Patch
From 35a08ea9ad2b47993333ad759df13468f3b5ae00 Mon Sep 17 00:00:00 2001 From: Paula Gomez <[email protected]> Date: Wed, 16 Sep 2026 10:30:29 -0700 Subject: [PATCH] Verify that PDF embedder is the active primary main frame OnDocumentLoadComplete and OnPdfTextExtracted now verify that the PDF RenderFrameHost is active, the tab's MIME type is application/pdf, and the direct embedder of the PDF viewer is the WebContents's primary main frame, to prevent leaking text to any embedding or cross-origin frame. This change is meant to replace https://crrev.com/1695715 as a fix for crbug.com/539043243, since the original change broke PDF translation functionality. Bug: 489787451, 539043243 Change-Id: Ie005ad8d15bfcc5792b71df986f5ddc714850253 Reviewed-on: https://chromium-review.googlesource.com/c/chromium/src/+/8408786 Reviewed-by: Lei Zhang <[email protected]> Reviewed-by: Katie Lu <[email protected]> Commit-Queue: Katie Lu <[email protected]> Cr-Commit-Position: refs/heads/main@{#1699026} --- diff --git a/chrome/browser/ui/pdf/chrome_pdf_document_helper_client.cc b/chrome/browser/ui/pdf/chrome_pdf_document_helper_client.cc index 69a0508a..7f05e176 100644 --- a/chrome/browser/ui/pdf/chrome_pdf_document_helper_client.cc +++ b/chrome/browser/ui/pdf/chrome_pdf_document_helper_client.cc @@ -20,6 +20,7 @@ #include "components/feature_engagement/public/feature_constants.h" #include "components/pdf/browser/pdf_document_helper.h" #include "components/pdf/browser/pdf_frame_util.h" +#include "components/pdf/common/constants.h" #include "components/tabs/public/tab_interface.h" #include "components/translate/core/common/translate_features.h" #include "components/user_education/common/feature_promo/feature_promo_controller.h" @@ -43,6 +44,38 @@ : content::WebContents::FromRenderFrameHost(&render_frame_host); } +content::RenderFrameHost* GetEmbedderHost( + content::RenderFrameHost& render_frame_host) { + if (chrome_pdf::features::IsOopifPdfEnabled()) { + return pdf_frame_util::GetEmbedderHost(&render_frame_host); + } + auto* guest_view = + extensions::MimeHandlerViewGuest::FromRenderFrameHost(&render_frame_host); + return guest_view ? guest_view->embedder_rfh() : nullptr; +} + +// Returns the primary main frame if `render_frame_host` belongs to an active, +// full-page PDF, or nullptr otherwise. +content::RenderFrameHost* GetFullPagePdfMainFrame( + content::RenderFrameHost& render_frame_host) { + if (!render_frame_host.IsActive()) { + return nullptr; + } + + content::WebContents* web_contents = GetWebContentsToUse(render_frame_host); + if (!web_contents || + web_contents->GetContentsMimeType() != pdf::kPDFMimeType) { + return nullptr; + } + + content::RenderFrameHost* main_frame = web_contents->GetPrimaryMainFrame(); + if (!main_frame || GetEmbedderHost(render_frame_host) != main_frame) { + return nullptr; + } + + return main_frame; +} + bool MaybeShowFeaturePromo(const base::Feature& feature, content::WebContents* contents) { auto* user_education_interface = @@ -109,11 +142,12 @@ } } - if (base::FeatureList::IsEnabled(translate::kEnableTranslatePdf)) { - auto* pdf_helper = - pdf::PDFDocumentHelper::GetForCurrentDocument(&render_frame_host); - if (pdf_helper) { - // Get the text of the first page and send it to the render frame for + // Only full-page PDFs are eligible for translation. + if (base::FeatureList::IsEnabled(translate::kEnableTranslatePdf) && + GetFullPagePdfMainFrame(render_frame_host)) { + if (auto* pdf_helper = + pdf::PDFDocumentHelper::GetForCurrentDocument(&render_frame_host)) { + // Get the text of the first page and send it to the main frame for // language detection. pdf_helper->GetPageText( 0, base::BindOnce(&ChromePDFDocumentHelperClient::OnPdfTextExtracted, @@ -132,12 +166,18 @@ return; } + content::RenderFrameHost* main_frame = + GetFullPagePdfMainFrame(*render_frame_host); + if (!main_frame) { + return; + } + mojo::AssociatedRemote<chrome::mojom::ChromeRenderFrame> chrome_render_frame; - render_frame_host->GetRemoteAssociatedInterfaces()->GetInterface( + main_frame->GetRemoteAssociatedInterfaces()->GetInterface( &chrome_render_frame); - // TODO(b/502015383): Use the actual PDF language tag. + // TODO(crbug.com/502015383): Use the actual PDF language tag. chrome_render_frame->PdfPageCaptured(text, /*pdf_lang=*/"", - render_frame_host->GetLastCommittedURL()); + main_frame->GetLastCommittedURL()); } void ChromePDFDocumentHelperClient::UpdateContentRestrictions( diff --git a/chrome/browser/translate/translate_manager_browsertest.cc b/chrome/browser/translate/translate_manager_browsertest.cc index 34d721c7..8613314 100644 --- a/chrome/browser/translate/translate_manager_browsertest.cc +++ b/chrome/browser/translate/translate_manager_browsertest.cc @@ -56,6 +56,7 @@ #include "net/dns/mock_host_resolver.h" #include "net/test/embedded_test_server/http_request.h" #include "net/test/embedded_test_server/http_response.h" +#include "pdf/buildflags.h" #include "ui/accessibility/accessibility_features.h" #include "ui/base/page_transition_types.h" #include "ui/events/base_event_utils.h" @@ -64,6 +65,15 @@ #include "ui/views/test/button_test_api.h" #include "url/gurl.h" +#if BUILDFLAG(ENABLE_PDF) +#include "base/scoped_observation.h" +#include "base/test/test_future.h" +#include "components/pdf/browser/pdf_document_helper.h" +#include "components/translate/content/browser/pdf_translation_coordinator.h" +#include "components/translate/core/browser/language_state.h" +#include "components/translate/core/browser/translate_driver.h" +#endif // BUILDFLAG(ENABLE_PDF) + namespace translate { namespace { @@ -1555,5 +1565,97 @@ } #endif +#if BUILDFLAG(ENABLE_PDF) +class TranslateManagerPdfBrowserTest : public TranslateManagerBrowserTest { + protected: + void InitFeatures() override { + scoped_feature_list_.InitWithFeatures( + {kEnableTranslatePdf, features::kReadAnythingTranslateEntryPoint}, {}); + } +}; + +// Test that loading a full-page PDF initiates language detection and executes +// `PDFTranslationCoordinator` to determine PDF translatability. +IN_PROC_BROWSER_TEST_F(TranslateManagerPdfBrowserTest, + FullPagePdfTranslatabilityDetermined) { + ASSERT_TRUE(ui_test_utils::NavigateToURL( + browser(), + embedded_test_server()->GetURL( + "/pdf/accessibility/paragraphs-and-heading-untagged.pdf"))); + + content::WebContents* web_contents = + browser()->tab_strip_model()->GetActiveWebContents(); + ASSERT_TRUE(web_contents); + + ChromeTranslateClient* client = GetChromeTranslateClient(); + ASSERT_TRUE(client); + + EXPECT_TRUE(base::test::RunUntil([&]() { + return client->GetLanguageState().pdf_translatability_status() != + LanguageState::PdfTranslatabilityStatus::kNotChecked; + })); + + EXPECT_EQ(client->GetLanguageState().pdf_translatability_status(), + LanguageState::PdfTranslatabilityStatus::kTranslatable); + + auto* coordinator = PDFTranslationCoordinator::GetForCurrentDocument( + web_contents->GetPrimaryMainFrame()); + ASSERT_TRUE(coordinator); + EXPECT_EQ(coordinator->status(), + PDFTranslationCoordinator::TranslatabilityStatus::kTranslatable); +} + +// Test that loading an HTML page with an embedded PDF drops PDF translation +// processing (it is not a full-page PDF), so PDF text is never sent to the main +// frame for language detection and PDF translatability is never checked. +IN_PROC_BROWSER_TEST_F(TranslateManagerPdfBrowserTest, + EmbeddedPdfDroppedForTranslation) { + ChromeTranslateClient* client = GetChromeTranslateClient(); + ASSERT_TRUE(client); + + struct : TranslateDriver::LanguageDetectionObserver { + void OnLanguageDetermined( + const LanguageDetectionDetails& details) override { + last_contents = details.contents; + } + std::u16string last_contents; + } observer; + base::ScopedObservation<TranslateDriver, + TranslateDriver::LanguageDetectionObserver> + observation(&observer); + observation.Observe(client->GetTranslateDriver()); + + ASSERT_TRUE(ui_test_utils::NavigateToURL( + browser(), embedded_test_server()->GetURL("/pdf/pdf_embed.html"))); + + content::WebContents* web_contents = + browser()->tab_strip_model()->GetActiveWebContents(); + ASSERT_TRUE(web_contents); + + pdf::PDFDocumentHelper* pdf_helper = nullptr; + ASSERT_TRUE(base::test::RunUntil([&]() { + pdf_helper = pdf::PDFDocumentHelper::MaybeGetForWebContents(*web_contents); + return pdf_helper != nullptr; + })); + + base::test::TestFuture<void> document_loaded; + pdf_helper->RegisterForDocumentLoadComplete(document_loaded.GetCallback()); + ASSERT_TRUE(document_loaded.Wait()); + + // Flush the PDF and renderer Mojo pipes to ensure any PdfPageCaptured() IPC + // triggered by OnDocumentLoadComplete() would have completed. + base::test::TestFuture<const std::u16string&> page_text; + pdf_helper->GetPageText(0, page_text.GetCallback()); + ASSERT_FALSE(page_text.Get().empty()); + ASSERT_TRUE(content::ExecJs(web_contents->GetPrimaryMainFrame(), "")); + + EXPECT_NE(observer.last_contents, page_text.Get()); + EXPECT_EQ(client->GetLanguageState().pdf_translatability_status(), + LanguageState::PdfTranslatabilityStatus::kNotChecked); + EXPECT_FALSE(PDFTranslationCoordinator::GetForCurrentDocument( + web_contents->GetPrimaryMainFrame())); +} +#endif // BUILDFLAG(ENABLE_PDF) + } // namespace } // namespace translate diff --git a/chrome/browser/ui/pdf/BUILD.gn b/chrome/browser/ui/pdf/BUILD.gn index 15be482..6543e88e 100644 --- a/chrome/browser/ui/pdf/BUILD.gn +++ b/chrome/browser/ui/pdf/BUILD.gn @@ -22,6 +22,7 @@ "//chrome/browser/ui/user_education", "//chrome/common", "//components/pdf/browser", + "//components/pdf/common:constants", "//components/tabs:public", "//extensions/browser", "//extensions/browser/mime_handler",
Loading diff…
Regression Test / PoC
shipped with the fix
diff --git a/chrome/browser/translate/translate_manager_browsertest.cc b/chrome/browser/translate/translate_manager_browsertest.cc
index 34d721c7..8613314 100644
--- a/chrome/browser/translate/translate_manager_browsertest.cc
+++ b/chrome/browser/translate/translate_manager_browsertest.cc
@@ -56,6 +56,7 @@
#include "net/dns/mock_host_resolver.h"
#include "net/test/embedded_test_server/http_request.h"
#include "net/test/embedded_test_server/http_response.h"
+#include "pdf/buildflags.h"
#include "ui/accessibility/accessibility_features.h"
#include "ui/base/page_transition_types.h"
#include "ui/events/base_event_utils.h"
@@ -64,6 +65,15 @@
#include "ui/views/test/button_test_api.h"
#include "url/gurl.h"
+#if BUILDFLAG(ENABLE_PDF)
+#include "base/scoped_observation.h"
+#include "base/test/test_future.h"
+#include "components/pdf/browser/pdf_document_helper.h"
+#include "components/translate/content/browser/pdf_translation_coordinator.h"
+#include "components/translate/core/browser/language_state.h"
+#include "components/translate/core/browser/translate_driver.h"
+#endif // BUILDFLAG(ENABLE_PDF)
+
namespace translate {
namespace {
@@ -1555,5 +1565,97 @@
}
#endif
+#if BUILDFLAG(ENABLE_PDF)
+class TranslateManagerPdfBrowserTest : public TranslateManagerBrowserTest {
+ protected:
+ void InitFeatures() override {
+ scoped_feature_list_.InitWithFeatures(
+ {kEnableTranslatePdf, features::kReadAnythingTranslateEntryPoint}, {});
+ }
+};
+
+// Test that loading a full-page PDF initiates language detection and executes
+// `PDFTranslationCoordinator` to determine PDF translatability.
+IN_PROC_BROWSER_TEST_F(TranslateManagerPdfBrowserTest,
+ FullPagePdfTranslatabilityDetermined) {
+ ASSERT_TRUE(ui_test_utils::NavigateToURL(
+ browser(),
+ embedded_test_server()->GetURL(
+ "/pdf/accessibility/paragraphs-and-heading-untagged.pdf")));
+
+ content::WebContents* web_contents =
+ browser()->tab_strip_model()->GetActiveWebContents();
+ ASSERT_TRUE(web_contents);
+
+ ChromeTranslateClient* client = GetChromeTranslateClient();
+ ASSERT_TRUE(client);
+
+ EXPECT_TRUE(base::test::RunUntil([&]() {
+ return client->GetLanguageState().pdf_translatability_status() !=
+ LanguageState::PdfTranslatabilityStatus::kNotChecked;
+ }));
+
+ EXPECT_EQ(client->GetLanguageState().pdf_translatability_status(),
+ LanguageState::PdfTranslatabilityStatus::kTranslatable);
+
+ auto* coordinator = PDFTranslationCoordinator::GetForCurrentDocument(
+ web_contents->GetPrimaryMainFrame());
+ ASSERT_TRUE(coordinator);
+ EXPECT_EQ(coordinator->status(),
+ PDFTranslationCoordinator::TranslatabilityStatus::kTranslatable);
+}
+
+// Test that loading an HTML page with an embedded PDF drops PDF translation
+// processing (it is not a full-page PDF), so PDF text is never sent to the main
+// frame for language detection and PDF translatability is never checked.
+IN_PROC_BROWSER_TEST_F(TranslateManagerPdfBrowserTest,
+ EmbeddedPdfDroppedForTranslation) {
+ ChromeTranslateClient* client = GetChromeTranslateClient();
+ ASSERT_TRUE(client);
+
+ struct : TranslateDriver::LanguageDetectionObserver {
+ void OnLanguageDetermined(
+ const LanguageDetectionDetails& details) override {
+ last_contents = details.contents;
+ }
+ std::u16string last_contents;
+ } observer;
+ base::ScopedObservation<TranslateDriver,
+ TranslateDriver::LanguageDetectionObserver>
+ observation(&observer);
+ observation.Observe(client->GetTranslateDriver());
+
+ ASSERT_TRUE(ui_test_utils::NavigateToURL(
+ browser(), embedded_test_server()->GetURL("/pdf/pdf_embed.html")));
+
+ content::WebContents* web_contents =
+ browser()->tab_strip_model()->GetActiveWebContents();
+ ASSERT_TRUE(web_contents);
+
+ pdf::PDFDocumentHelper* pdf_helper = nullptr;
+ ASSERT_TRUE(base::test::RunUntil([&]() {
+ pdf_helper = pdf::PDFDocumentHelper::MaybeGetForWebContents(*web_contents);
+ return pdf_helper != nullptr;
+ }));
+
+ base::test::TestFuture<void> document_loaded;
+ pdf_helper->RegisterForDocumentLoadComplete(document_loaded.GetCallback());
+ ASSERT_TRUE(document_loaded.Wait());
+
+ // Flush the PDF and renderer Mojo pipes to ensure any PdfPageCaptured() IPC
+ // triggered by OnDocumentLoadComplete() would have completed.
+ base::test::TestFuture<const std::u16string&> page_text;
+ pdf_helper->GetPageText(0, page_text.GetCallback());
+ ASSERT_FALSE(page_text.Get().empty());
+ ASSERT_TRUE(content::ExecJs(web_contents->GetPrimaryMainFrame(), ""));
+
+ EXPECT_NE(observer.last_contents, page_text.Get());
+ EXPECT_EQ(client->GetLanguageState().pdf_translatability_status(),
+ LanguageState::PdfTranslatabilityStatus::kNotChecked);
+ EXPECT_FALSE(PDFTranslationCoordinator::GetForCurrentDocument(
+ web_contents->GetPrimaryMainFrame()));
+}
+#endif // BUILDFLAG(ENABLE_PDF)
+
} // namespace
} // namespace translate
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