Chrome · File Input
CVE-2026-11228
Logic Error in File Input
Overview
Low
Severity
—
CVSS
No
Exploited ITW
Fixed
Fix Status
Changed Functions
| Function | Change | Notes |
|---|---|---|
ifchrome/browser/file_select_helper.cc |
modified | |
WebContentschrome/browser/file_select_helper.h |
modified | |
TabInterfacechrome/browser/file_select_helper.h |
modified | |
DialogModelchrome/browser/file_select_helper.h |
modified | |
ASSERT_TRUEchrome/browser/ui/views/file_system_access/file_system_access_browsertest.cc |
modified | |
FileSystemAccessBrowserTestForWebUIchrome/browser/ui/views/file_system_access/file_system_access_browsertest.cc |
modified |
Files Changed
chrome/browser/file_select_helper.ccchrome/browser/file_select_helper.hchrome/browser/ui/views/file_system_access/file_system_access_browsertest.cc
Patch
From 9c6ca0273599b9e2713247bb751a1891ad05adf4 Mon Sep 17 00:00:00 2001 From: Alison Gale <[email protected]> Date: Thu, 23 Apr 2026 16:45:57 -0700 Subject: [PATCH] [SxS] Cancel file picker if active tab changes This handles the case where a tab gets deactivated while the file picker is open. One example of this happening is in split view where the user is mid drag and drop when the file picker is opened. #top-chrome-bug-fixit Bug: 454484864,474583539 Change-Id: I91e869ea26b0884c5beb358516c602ec8178d5c9 Reviewed-on: https://chromium-review.googlesource.com/c/chromium/src/+/7154160 Commit-Queue: Alison Gale <[email protected]> Reviewed-by: Dana Fried <[email protected]> Cr-Commit-Position: refs/heads/main@{#1619839} --- diff --git a/chrome/browser/file_select_helper.cc b/chrome/browser/file_select_helper.cc index eb9e243..b8a24fda 100644 --- a/chrome/browser/file_select_helper.cc +++ b/chrome/browser/file_select_helper.cc @@ -30,6 +30,7 @@ #include "chrome/grit/generated_resources.h" #include "components/enterprise/buildflags/buildflags.h" #include "components/enterprise/common/proto/connectors.pb.h" +#include "components/tabs/public/tab_interface.h" #include "content/public/browser/browser_task_traits.h" #include "content/public/browser/browser_thread.h" #include "content/public/browser/file_select_listener.h" @@ -557,6 +558,14 @@ listener_ = std::move(listener); content::WebContentsObserver::Observe(web_contents_); + tabs::TabInterface* tab_interface = + tabs::TabInterface::MaybeGetFromContents(web_contents_); + if (tab_interface) { + tab_deactivated_subscription_ = + tab_interface->RegisterWillDeactivate(base::BindRepeating( + &FileSelectHelper::OnTabDeactivated, base::Unretained(this))); + } + #if !BUILDFLAG(IS_ANDROID) if (PictureInPictureWindowManager::GetInstance() ->ShouldFileDialogBlockPictureInPicture(web_contents_)) { @@ -676,6 +685,8 @@ scoped_tuck_picture_in_picture_.reset(); #endif // !BUILDFLAG(IS_ANDROID) + tab_deactivated_subscription_ = {}; + // If there are temporary files, then this instance needs to stick around // until web_contents_ is destroyed, so that this instance can delete the // temporary files. @@ -755,6 +766,10 @@ CleanUp(); } +void FileSelectHelper::OnTabDeactivated(tabs::TabInterface* tab) { + RunFileChooserEnd(); +} + // static bool FileSelectHelper::IsAcceptTypeValid(const std::string& accept_type) { // TODO(raymes): This only does some basic checks, extend to test more cases. diff --git a/chrome/browser/file_select_helper.h b/chrome/browser/file_select_helper.h index 58b1bfe9..37c575db 100644 --- a/chrome/browser/file_select_helper.h +++ b/chrome/browser/file_select_helper.h @@ -8,6 +8,7 @@ #include <memory> #include <vector> +#include "base/callback_list.h" #include "base/gtest_prod_util.h" #include "base/memory/raw_ptr.h" #include "base/memory/ref_counted.h" @@ -34,6 +35,10 @@ class WebContents; } +namespace tabs { +class TabInterface; +} + namespace ui { class DialogModel; struct SelectedFileInfo; @@ -143,6 +148,8 @@ void RenderFrameDeleted(content::RenderFrameHost* render_frame_host) override; void WebContentsDestroyed() override; + void OnTabDeactivated(tabs::TabInterface* tab); + void EnumerateDirectoryImpl( content::WebContents* tab, scoped_refptr<content::FileSelectListener> listener, @@ -320,6 +327,8 @@ // Set to false in unit tests since there is no WebContents. bool abort_on_missing_web_contents_in_tests_ = true; + base::CallbackListSubscription tab_deactivated_subscription_; + #if !BUILDFLAG(IS_ANDROID) // When not null, this prevents picture-in-picture windows from opening. std::unique_ptr<ScopedDisallowPictureInPicture> diff --git a/chrome/browser/ui/views/file_system_access/file_system_access_browsertest.cc b/chrome/browser/ui/views/file_system_access/file_system_access_browsertest.cc index 98be5531..2594a266 100644 --- a/chrome/browser/ui/views/file_system_access/file_system_access_browsertest.cc +++ b/chrome/browser/ui/views/file_system_access/file_system_access_browsertest.cc @@ -26,6 +26,8 @@ #include "chrome/browser/ui/actions/chrome_action_id.h" #include "chrome/browser/ui/browser.h" #include "chrome/browser/ui/browser_actions.h" +#include "chrome/browser/ui/browser_commands.h" +#include "chrome/browser/ui/tabs/split_tab_metrics.h" #include "chrome/browser/ui/ui_features.h" #include "chrome/browser/ui/views/file_system_access/file_system_access_test_utils.h" #include "chrome/browser/ui/views/frame/browser_view.h" @@ -1356,6 +1358,42 @@ ASSERT_EQ("AbortError", content::EvalJs(first_tab, "window.p")); } +// Test that creating a split view while the dialog is showing closes the +// dialog. https://crbug.com/474583539 https://crbug.com/454484864 +IN_PROC_BROWSER_TEST_P(FileSystemAccessBrowserTest, + ShowOpenFileThenHideDueToSplitView) { + ASSERT_TRUE(ui_test_utils::NavigateToURL( + browser(), embedded_test_server()->GetURL("/title1.html"))); + content::WebContents* first_tab = + browser()->tab_strip_model()->GetActiveWebContents(); + + // Open the dialog and wait until it's created. + content::SelectFileDialogRecorder recorder; + ui::SelectFileDialog::SetFactory( + std::make_unique<content::ObservableSelectFileDialogFactory>( + recorder.GetWeakPtr())); + ASSERT_EQ(42, + content::EvalJs( + first_tab, + "window.p = self.showOpenFilePicker().catch(e => e.name); 42")); + ASSERT_TRUE(base::test::RunUntil([&recorder]() { + return recorder.state != content::SelectFileDialogRecorder::kNotCreated; + })); + + // Create a split view. + const int active_index = browser()->tab_strip_model()->active_index(); + chrome::NewSplitTab(browser(), + split_tabs::SplitTabCreatedSource::kToolbarButton); + EXPECT_TRUE(content::WaitForLoadStop( + browser()->tab_strip_model()->GetWebContentsAt(active_index + 1))); + + // The first tab should not be the active tab anymore. + ASSERT_NE(first_tab, browser()->tab_strip_model()->GetActiveWebContents()); + + // Check that the dialog was closed. + ASSERT_EQ("AbortError", content::EvalJs(first_tab, "window.p")); +} + class FileSystemAccessBrowserTestForWebUI : public InProcessBrowserTest, public ::testing::WithParamInterface<bool> {
Loading diff…
Regression Test / PoC
shipped with the fix
diff --git a/chrome/browser/ui/views/file_system_access/file_system_access_browsertest.cc b/chrome/browser/ui/views/file_system_access/file_system_access_browsertest.cc
index 98be5531..2594a266 100644
--- a/chrome/browser/ui/views/file_system_access/file_system_access_browsertest.cc
+++ b/chrome/browser/ui/views/file_system_access/file_system_access_browsertest.cc
@@ -26,6 +26,8 @@
#include "chrome/browser/ui/actions/chrome_action_id.h"
#include "chrome/browser/ui/browser.h"
#include "chrome/browser/ui/browser_actions.h"
+#include "chrome/browser/ui/browser_commands.h"
+#include "chrome/browser/ui/tabs/split_tab_metrics.h"
#include "chrome/browser/ui/ui_features.h"
#include "chrome/browser/ui/views/file_system_access/file_system_access_test_utils.h"
#include "chrome/browser/ui/views/frame/browser_view.h"
@@ -1356,6 +1358,42 @@
ASSERT_EQ("AbortError", content::EvalJs(first_tab, "window.p"));
}
+// Test that creating a split view while the dialog is showing closes the
+// dialog. https://crbug.com/474583539 https://crbug.com/454484864
+IN_PROC_BROWSER_TEST_P(FileSystemAccessBrowserTest,
+ ShowOpenFileThenHideDueToSplitView) {
+ ASSERT_TRUE(ui_test_utils::NavigateToURL(
+ browser(), embedded_test_server()->GetURL("/title1.html")));
+ content::WebContents* first_tab =
+ browser()->tab_strip_model()->GetActiveWebContents();
+
+ // Open the dialog and wait until it's created.
+ content::SelectFileDialogRecorder recorder;
+ ui::SelectFileDialog::SetFactory(
+ std::make_unique<content::ObservableSelectFileDialogFactory>(
+ recorder.GetWeakPtr()));
+ ASSERT_EQ(42,
+ content::EvalJs(
+ first_tab,
+ "window.p = self.showOpenFilePicker().catch(e => e.name); 42"));
+ ASSERT_TRUE(base::test::RunUntil([&recorder]() {
+ return recorder.state != content::SelectFileDialogRecorder::kNotCreated;
+ }));
+
+ // Create a split view.
+ const int active_index = browser()->tab_strip_model()->active_index();
+ chrome::NewSplitTab(browser(),
+ split_tabs::SplitTabCreatedSource::kToolbarButton);
+ EXPECT_TRUE(content::WaitForLoadStop(
+ browser()->tab_strip_model()->GetWebContentsAt(active_index + 1)));
+
+ // The first tab should not be the active tab anymore.
+ ASSERT_NE(first_tab, browser()->tab_strip_model()->GetActiveWebContents());
+
+ // Check that the dialog was closed.
+ ASSERT_EQ("AbortError", content::EvalJs(first_tab, "window.p"));
+}
+
class FileSystemAccessBrowserTestForWebUI
: public InProcessBrowserTest,
public ::testing::WithParamInterface<bool> {
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