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 <paugomez@google.com>
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 <thestig@chromium.org>
Reviewed-by: Katie Lu <ktlu@google.com>
Commit-Queue: Katie Lu <ktlu@google.com>
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