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 <agale@chromium.org>
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 <agale@chromium.org>
Reviewed-by: Dana Fried <dfried@chromium.org>
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