CVE-2025-11210
Overview
Changed Functions
| Function | Change | Notes |
|---|---|---|
ifcontent/browser/web_contents/web_contents_impl.cc |
modified | |
InactiveContentsDelegatecontent/browser/web_contents/web_contents_impl_browsertest.cc |
modified |
Files Changed
chrome/browser/ui/browser.ccchrome/browser/ui/browser.hcontent/browser/web_contents/web_contents_impl.cccontent/browser/web_contents/web_contents_impl_browsertest.cccontent/public/browser/web_contents_delegate.cccontent/public/browser/web_contents_delegate.h
Patch
From d4c4449f8891183a1afc71a15b8b9b6596a5aad3 Mon Sep 17 00:00:00 2001 From: Alison Gale <[email protected]> Date: Mon, 08 Sep 2025 16:21:45 -0700 Subject: [PATCH] [SxS] Prevent opening file picker from inactive tab in split For security purposes, only the active tab should be able to open a filed picker so the user knows which site is receiving the files. Change-Id: If73e028c4041b40d2cfc935cb37f2b61b32416fd Bug: 441139337,440523110 Reviewed-on: https://chromium-review.googlesource.com/c/chromium/src/+/6916005 Reviewed-by: Eshwar Stalin <[email protected]> Commit-Queue: Alison Gale <[email protected]> Reviewed-by: Nasko Oskov <[email protected]> Cr-Commit-Position: refs/heads/main@{#1512743} --- diff --git a/chrome/browser/ui/browser.cc b/chrome/browser/ui/browser.cc index 96f4531..b48603f9 100644 --- a/chrome/browser/ui/browser.cc +++ b/chrome/browser/ui/browser.cc @@ -2222,6 +2222,10 @@ window_->Activate(); } +bool Browser::IsContentsActive(content::WebContents* contents) { + return tab_strip_model_->GetActiveWebContents() == contents; +} + void Browser::LoadingStateChanged(WebContents* source, bool should_show_loading_ui) { ScheduleUIUpdate(source, content::INVALIDATE_TYPE_LOAD); diff --git a/chrome/browser/ui/browser.h b/chrome/browser/ui/browser.h index cb5164b..e7c3c7ca 100644 --- a/chrome/browser/ui/browser.h +++ b/chrome/browser/ui/browser.h @@ -696,6 +696,7 @@ // Overridden from content::WebContentsDelegate: void ActivateContents(content::WebContents* contents) override; + bool IsContentsActive(content::WebContents* contents) override; void SetTopControlsShownRatio(content::WebContents* web_contents, float ratio) override; int GetTopControlsHeight() override; diff --git a/content/browser/web_contents/web_contents_impl.cc b/content/browser/web_contents/web_contents_impl.cc index 6dc6fedc..794678d 100644 --- a/content/browser/web_contents/web_contents_impl.cc +++ b/content/browser/web_contents/web_contents_impl.cc @@ -8942,6 +8942,10 @@ // Do not allow background tab to open file chooser. return; } + if (!delegate_->IsContentsActive(this)) { + // Do not allow inactive tabs to open file chooser. + return; + } if (active_file_chooser_) { // Only allow one active file chooser at one time. return; diff --git a/content/browser/web_contents/web_contents_impl_browsertest.cc b/content/browser/web_contents/web_contents_impl_browsertest.cc index 36fa97c..9a54a68 100644 --- a/content/browser/web_contents/web_contents_impl_browsertest.cc +++ b/content/browser/web_contents/web_contents_impl_browsertest.cc @@ -3152,6 +3152,37 @@ EXPECT_EQ(shell()->run_file_chooser_count(), 0u); } +class InactiveContentsDelegate : public WebContentsDelegate { + public: + explicit InactiveContentsDelegate(WebContents* contents) { + contents->SetDelegate(this); + } + + bool IsContentsActive(content::WebContents* web_contents) override { + return false; + } +}; + +IN_PROC_BROWSER_TEST_F(WebContentsImplBrowserTest, + FileChooserBlockedFromInactiveButVisibleWebContents) { + ASSERT_TRUE(embedded_test_server()->Start()); + shell()->set_hold_file_chooser(); + + EXPECT_TRUE(NavigateToURL(shell(), GURL("about:blank"))); + + WebContentsImpl* wc = static_cast<WebContentsImpl*>(shell()->web_contents()); + EXPECT_EQ(shell()->web_contents()->GetVisibility(), Visibility::VISIBLE); + InactiveContentsDelegate delegate(wc); + + auto [chooser, remote] = + FileChooserImpl::CreateForTesting(wc->GetPrimaryMainFrame()); + auto file_select_listener = base::MakeRefCounted<MockFileSelectListener>(); + wc->RunFileChooser(chooser->GetWeakPtr(), wc->GetPrimaryMainFrame(), + file_select_listener, blink::mojom::FileChooserParams()); + EXPECT_TRUE(file_select_listener->cancelled()); + EXPECT_EQ(shell()->run_file_chooser_count(), 0u); +} + IN_PROC_BROWSER_TEST_F(WebContentsImplBrowserTest, EnumerateDirectoryBlockedFromHiddenWebContents) { ASSERT_TRUE(embedded_test_server()->Start()); diff --git a/content/public/browser/web_contents_delegate.cc b/content/public/browser/web_contents_delegate.cc index e4d6967..87e310a5 100644 --- a/content/public/browser/web_contents_delegate.cc +++ b/content/public/browser/web_contents_delegate.cc @@ -57,6 +57,10 @@ return nullptr; } +bool WebContentsDelegate::IsContentsActive(WebContents* contents) { + return true; +} + bool WebContentsDelegate::CanOverscrollContent() { return false; } diff --git a/content/public/browser/web_contents_delegate.h b/content/public/browser/web_contents_delegate.h index 726ae29..e02c45b 100644 --- a/content/public/browser/web_contents_delegate.h +++ b/content/public/browser/web_contents_delegate.h @@ -194,6 +194,14 @@ // Selects the specified contents, bringing its container to the front. virtual void ActivateContents(WebContents* contents) {} + // A WebContents within a browser is considered active if it is the "active" + // tab in the browser's tab strip. A non-active WebContents cannot have focus, + // but it is possible for an active WebContents to not be focused if focus is + // elsewhere in the browser.Just because a WebContents is visible, doesn't + // mean it is active due to the SplitView feature. WebContents outside a + // browser are always considered active. + virtual bool IsContentsActive(WebContents* contents); + // Notifies the delegate that this contents is starting or is done loading // some resource. The delegate should use this notification to represent // loading feedback. See WebContents::IsLoading()
Regression Test / PoC
diff --git a/content/browser/web_contents/web_contents_impl_browsertest.cc b/content/browser/web_contents/web_contents_impl_browsertest.cc
index 36fa97c..9a54a68 100644
--- a/content/browser/web_contents/web_contents_impl_browsertest.cc
+++ b/content/browser/web_contents/web_contents_impl_browsertest.cc
@@ -3152,6 +3152,37 @@
EXPECT_EQ(shell()->run_file_chooser_count(), 0u);
}
+class InactiveContentsDelegate : public WebContentsDelegate {
+ public:
+ explicit InactiveContentsDelegate(WebContents* contents) {
+ contents->SetDelegate(this);
+ }
+
+ bool IsContentsActive(content::WebContents* web_contents) override {
+ return false;
+ }
+};
+
+IN_PROC_BROWSER_TEST_F(WebContentsImplBrowserTest,
+ FileChooserBlockedFromInactiveButVisibleWebContents) {
+ ASSERT_TRUE(embedded_test_server()->Start());
+ shell()->set_hold_file_chooser();
+
+ EXPECT_TRUE(NavigateToURL(shell(), GURL("about:blank")));
+
+ WebContentsImpl* wc = static_cast<WebContentsImpl*>(shell()->web_contents());
+ EXPECT_EQ(shell()->web_contents()->GetVisibility(), Visibility::VISIBLE);
+ InactiveContentsDelegate delegate(wc);
+
+ auto [chooser, remote] =
+ FileChooserImpl::CreateForTesting(wc->GetPrimaryMainFrame());
+ auto file_select_listener = base::MakeRefCounted<MockFileSelectListener>();
+ wc->RunFileChooser(chooser->GetWeakPtr(), wc->GetPrimaryMainFrame(),
+ file_select_listener, blink::mojom::FileChooserParams());
+ EXPECT_TRUE(file_select_listener->cancelled());
+ EXPECT_EQ(shell()->run_file_chooser_count(), 0u);
+}
+
IN_PROC_BROWSER_TEST_F(WebContentsImplBrowserTest,
EnumerateDirectoryBlockedFromHiddenWebContents) {
ASSERT_TRUE(embedded_test_server()->Start());
Original Bug Report
(Split View) UI spoofing Upload leads leaking confidential files, Photos to Attacker
Steps to reproduce the problem
- Enable the #side-by-side flag
- Open poc.html in Chrome Browser
- Right click and open in Split view
- This causes the Attacker file dialog to on omnibox e.g.: drive.google.com and then Select any file or photo
- Successfully Uploaded in Attacker site
Problem Description
When the user performs a right-click, this malicious listener is activated, and it displays the attacker’s fabricated file upload dialog with Omnibox spoof, The file dialog itself isn’t coming from Google’s official website, but rather from an Attacker site
This is the primary goal. By convincing a user that they are on a legitimate website using Omnibox spoofing
This causes the Attacker page file dialog to open in the middle of the screen and above omnibox showing legitimate website Eg: https://drive.google.com/drive/ that was just opened and might confuse the user into leaking sensitive information (their Photos, confidential files) to an attacker. This can make user into Believing it was opened/requested by the “drive.google.com” tab.
the user’s most critical security checkpoint: the address bar. The user believes they are on a safe, legitimate domain, which dramatically increases the likelihood of a successful attack
Summary
(Split View) UI spoofing Upload leads leaking confidential files, Photos to Attacker
Additional Data
Category: Security
Chrome Channel: Beta
Regression: N/A \