CVE-2025-11216
Overview
Background
- File System Access pickers
showOpenFilePicker(),showSaveFilePicker()andshowDirectoryPicker()let a page ask the user to choose local files or directories. The request reachesFileSystemAccessManagerImplin the browser process, which shows a nativeui::SelectFileDialogthroughFileSystemChooser.- `WebContents` visibility
- each tab’s
WebContentshas aVisibility(VISIBLE,HIDDEN,OCCLUDED). A background tab is notVISIBLE, so a dialog it raises appears while the user is looking at a different page. - `WebContentsObserver`
- a browser-side interface that gets callbacks such as visibility changes for a
WebContents. The commit makesFileSystemChooserone, so it can react after the dialog is shown. - TOCTOU (time-of-check to time-of-use)
- a race in which a condition is checked at one point and then relied on later, after it may have changed.
Root Cause Analysis
A previous fix (crrev.com/c/659915) put a one-time web_contents->GetVisibility() != Visibility::VISIBLE check in file_system_access_manager_impl.cc. It rejected the picker request with kPermissionDenied (“Tab must be visible in order to show a file picker.”). The hunk does not name the enclosing function, but the removed unittest ChooseEntries_HiddenTab exercised it through ChooseEntries. The commit message says this check ran “many steps away from creating the chooser”, which left two gaps. The tab could become hidden between the check and the dialog’s creation (a race). And nothing reacted if the tab was hidden while the native dialog was already open. In both cases a file picker started by one page could stay up while the user looked at a different tab, so the user might pick or save files without knowing which site would get them.
The fix removes the early check and, per the commit message, makes FileSystemChooser a WebContentsObserver. It checks visibility right before creating the dialog, with the observer registered immediately after so there is no gap, and then closes the dialog if the tab later becomes invisible. Both cases now reject the JS promise with AbortError, as the updated and new browser tests expect. The diff is cut off at the start of the new file_system_chooser.cc code (just after namespace { and an “aborted” helper comment), so the actual observer methods and visibility check are not visible and are described here from the commit message and tests.
FileSystemChooser itself: it checks visibility at creation and observes the WebContents, so the dialog is aborted whenever the tab stops being visible.Attack Path
- Request a picker with user activation
A page calls
showOpenFilePicker()or a related API. The browser consumes the user activation (as the context lines around the removed check show) and starts the picker flow. - Get the tab hidden
The requesting tab stops being visible, either before the dialog is created (racing the old early check) or while the dialog is open (which nothing handled before). The new
ShowOpenFileThenHidebrowser test does this by opening a new foreground tab. - Dialog persists over another page
Before the fix, the native
SelectFileDialogstayed open, or was still created, even though the requesting tab was in the background. The user sees a file picker while viewing an unrelated site. - User grants access to the wrong origin The user picks files or directories thinking the visible page asked for them, which gives the hidden page read or write handles (inferred from the picker’s purpose; the patch shows only the dialog-lifetime fix).
Impact Assessment
Changed Functions
| Function | Change | Notes |
|---|---|---|
IN_PROC_BROWSER_TEST_Pchrome/browser/ui/views/file_system_access/file_system_access_browsertest.cc |
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 | |
TEST_Fcontent/browser/file_system_access/file_system_access_manager_impl_unittest.cc |
modified |
Files Changed
chrome/browser/ui/views/file_system_access/file_system_access_browsertest.cccontent/browser/file_system_access/file_system_access_manager_impl.cccontent/browser/file_system_access/file_system_access_manager_impl_unittest.cccontent/browser/file_system_access/file_system_chooser.cc
Audit Directions
- One-time checks on long-lived UILook at other browser-process dialogs and prompts (permission bubbles, WebAuthn, WebUSB/HID/Serial/Bluetooth choosers, payment sheets, download prompts) that check
GetVisibility()or focus only when the request arrives. Check that they observe theWebContentsand dismiss themselves on hide, navigation or tab switch. - Check-to-use distance in Mojo request handlersWhere a security precondition is checked in a manager such as
FileSystemAccessManagerImpland acted on several asynchronous hops later (permission-context queries, policy checks, dialog factories), check whether the precondition can change in between. - Background-tab prompt originationTest whether a page can start a prompt and then hide itself (new foreground tab, popup, tab switch) so the prompt shows over another origin, especially with native OS dialogs that are not tied to the tab.
Patch
From e46ff6303d1aa0c1b5d1f11eb91cba36eda84a06 Mon Sep 17 00:00:00 2001 From: Fergal Daly <[email protected]> Date: Wed, 24 Sep 2025 21:56:58 -0700 Subject: [PATCH] Close file picker dialog if the tab becomes invisible. A previous fix for a similar bug (https://crrev.com/c/659915) added code to not show the dialog if the tab is already invisible however there's lots of opportunity for races. This fixes that by making the FileSystemChooser a WebContentsObserver to catch later changes. The observer is created immediately after checking the visibility so there is no race. The old code is removed as it does the checking at a point many steps away from creating the chooser. The old unittest is removed rather than updated. The relevant code has moved and rather write a new unittest, I'm relying on coverage in content_browsertests and browser_tests. The changes to file_system_chooser_unittest.cc are basically a no-op but all of the tests need a WebContents instance now. Bug: 419721056,337356054 Change-Id: I6d3fb55c4fc7c4468b6172cd505bef081060f722 Reviewed-on: https://chromium-review.googlesource.com/c/chromium/src/+/6966639 Reviewed-by: Joey Arhar <[email protected]> Commit-Queue: Fergal Daly <[email protected]> Reviewed-by: Mingyu Lei <[email protected]> Cr-Commit-Position: refs/heads/main@{#1520251} --- 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 d8073d33..3f97c77 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 @@ -10,6 +10,7 @@ #include "base/path_service.h" #include "base/strings/strcat.h" #include "base/strings/string_number_conversions.h" +#include "base/task/current_thread.h" #include "base/test/bind.h" #include "base/test/scoped_path_override.h" #include "base/test/test_file_util.h" @@ -46,6 +47,7 @@ #include "content/public/test/browser_test.h" #include "content/public/test/browser_test_utils.h" #include "content/public/test/fenced_frame_test_util.h" +#include "content/public/test/file_system_chooser_test_helpers.h" #include "content/public/test/prerender_test_util.h" #include "content/public/test/scoped_web_ui_controller_factory_registration.h" #include "content/public/test/test_navigation_observer.h" @@ -1322,11 +1324,44 @@ // Try to show a file picker in the background tab. // This should be blocked. - EXPECT_EQ("NotAllowedError", + EXPECT_EQ("AbortError", content::EvalJs(second_tab, "self.showOpenFilePicker().catch(e => e.name)")); } +// Test that opening another tab while the dialog is showing closes the dialog. +// https://crbug.com/419721056 +IN_PROC_BROWSER_TEST_P(FileSystemAccessBrowserTest, ShowOpenFileThenHide) { + 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)); + 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 second tab. + ui_test_utils::NavigateToURLWithDisposition( + browser(), embedded_test_server()->GetURL("/title2.html"), + WindowOpenDisposition::NEW_FOREGROUND_TAB, + ui_test_utils::BROWSER_TEST_WAIT_FOR_LOAD_STOP); + + // 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> { diff --git a/content/browser/file_system_access/file_system_access_manager_impl.cc b/content/browser/file_system_access/file_system_access_manager_impl.cc index 8debb42..39aa8aa 100644 --- a/content/browser/file_system_access/file_system_access_manager_impl.cc +++ b/content/browser/file_system_access/file_system_access_manager_impl.cc @@ -587,15 +587,6 @@ return; } - if (web_contents->GetVisibility() != Visibility::VISIBLE) { - std::move(callback).Run( - file_system_access_error::FromStatus( - FileSystemAccessStatus::kPermissionDenied, - "Tab must be visible in order to show a file picker."), - std::vector<blink::mojom::FileSystemAccessEntryPtr>()); - return; - } - // Consume user activation to address this issue: crbug.com/40059071 // TODO(crbug.com/411125804): Consider moving this user activation check to // the renderer process or informing the renderer that it lost user diff --git a/content/browser/file_system_access/file_system_access_manager_impl_unittest.cc b/content/browser/file_system_access/file_system_access_manager_impl_unittest.cc index 045041db..c2d849d2 100644 --- a/content/browser/file_system_access/file_system_access_manager_impl_unittest.cc +++ b/content/browser/file_system_access/file_system_access_manager_impl_unittest.cc @@ -2109,61 +2109,6 @@ bad_message_observer.WaitForBadMessage()); } -TEST_F(FileSystemAccessManagerImplTest, ChooseEntries_HiddenTab) { - base::FilePath test_file = dir_.GetPath().AppendASCII("asdf"); - ASSERT_TRUE(base::CreateTemporaryFile(&test_file)); - PathInfo test_file_info(test_file); - - manager_->SetFilePickerResultForTesting(test_file_info); - - static_cast<TestRenderFrameHost*>(web_contents_->GetPrimaryMainFrame()) - ->SimulateUserActivation(); - - mojo::Remote<blink::mojom::FileSystemAccessManager> manager_remote; - FileSystemAccessManagerImpl::BindingContext binding_context = { - kTestStorageKey, kTestURL, - web_contents_->GetPrimaryMainFrame()->GetGlobalId()}; - manager_->BindReceiver(binding_context, - manager_remote.BindNewPipeAndPassReceiver()); - - EXPECT_CALL(permission_context_, - CanObtainReadPermission(kTestStorageKey.origin())) - .WillOnce(testing::Return(true)); - EXPECT_CALL(permission_context_, - CanObtainWritePermission(kTestStorageKey.origin())) - .WillOnce(testing::Return(true)); - EXPECT_CALL(permission_context_, CheckPathsAgainstEnterprisePolicy( - testing::_, testing::_, testing::_)) - .Times(0); - - // Hiding the web contents should make ChooseEntries return an error. - web_contents_->WasHidden(); - - auto save_file_picker_options = blink::mojom::SaveFilePickerOptions::New( - blink::mojom::AcceptsTypesInfo::New( - std::vector<blink::mojom::ChooseFileSystemEntryAcceptsOptionPtr>(), - /*include_accepts_all=*/true), - /*suggested_name=*/std::string()); - auto picker_options = blink::mojom::FilePickerOptions::New( - blink::mojom::TypeSpecificFilePickerOptionsUnion:: - NewSaveFilePickerOptions(std::move(save_file_picker_options)), - /*starting_directory_id=*/std::string(), - blink::mojom::FilePickerStartInOptionsUnionPtr()); - - base::test::TestFuture<blink::mojom::FileSystemAccessErrorPtr, - std::vector<blink::mojom::FileSystemAccessEntryPtr>> - future; - manager_remote->ChooseEntries(std::move(picker_options), - future.GetCallback()); - ASSERT_TRUE(future.Wait()); - - EXPECT_EQ(future.Get<0>()->status, - blink::mojom::FileSystemAccessStatus::kPermissionDenied); - EXPECT_EQ(future.Get<0>()->message, - "Tab must be visible in order to show a file picker."); - EXPECT_EQ(future.Get<1>().size(), 0); -} - TEST_F(FileSystemAccessManagerImplTest, GetUniqueId) { const PathInfo kTestPathInfo(dir_.GetPath().AppendASCII("foo")); ASSERT_OK_AND_ASSIGN(auto default_bucket, diff --git a/content/browser/file_system_access/file_system_chooser.cc b/content/browser/file_system_access/file_system_chooser.cc index d510a87..e240d013 100644 --- a/content/browser/file_system_access/file_system_chooser.cc +++ b/content/browser/file_system_access/file_system_chooser.cc @@ -19,6 +19,7 @@ #include "content/browser/file_system_access/file_system_access_error.h" #include "content/public/browser/browser_thread.h" #include "content/public/browser/content_browser_client.h" +#include "content/public/browser/visibility.h" #include "content/public/browser/web_contents.h" #include "content/public/common/content_client.h" #include "net/base/mime_util.h" @@ -293,6 +294,16 @@ : fullscreen_block(std::move(fullscreen_block)), pip_tucker(std::move(pip_tucker)) {} +namespace { +// Called when no file is selected due to being aborted.
Regression Test / PoC
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 d8073d33..3f97c77 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
@@ -10,6 +10,7 @@
#include "base/path_service.h"
#include "base/strings/strcat.h"
#include "base/strings/string_number_conversions.h"
+#include "base/task/current_thread.h"
#include "base/test/bind.h"
#include "base/test/scoped_path_override.h"
#include "base/test/test_file_util.h"
@@ -46,6 +47,7 @@
#include "content/public/test/browser_test.h"
#include "content/public/test/browser_test_utils.h"
#include "content/public/test/fenced_frame_test_util.h"
+#include "content/public/test/file_system_chooser_test_helpers.h"
#include "content/public/test/prerender_test_util.h"
#include "content/public/test/scoped_web_ui_controller_factory_registration.h"
#include "content/public/test/test_navigation_observer.h"
@@ -1322,11 +1324,44 @@
// Try to show a file picker in the background tab.
// This should be blocked.
- EXPECT_EQ("NotAllowedError",
+ EXPECT_EQ("AbortError",
content::EvalJs(second_tab,
"self.showOpenFilePicker().catch(e => e.name)"));
}
+// Test that opening another tab while the dialog is showing closes the dialog.
+// https://crbug.com/419721056
+IN_PROC_BROWSER_TEST_P(FileSystemAccessBrowserTest, ShowOpenFileThenHide) {
+ 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));
+ 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 second tab.
+ ui_test_utils::NavigateToURLWithDisposition(
+ browser(), embedded_test_server()->GetURL("/title2.html"),
+ WindowOpenDisposition::NEW_FOREGROUND_TAB,
+ ui_test_utils::BROWSER_TEST_WAIT_FOR_LOAD_STOP);
+
+ // 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> {
diff --git a/content/browser/file_system_access/file_system_access_manager_impl_unittest.cc b/content/browser/file_system_access/file_system_access_manager_impl_unittest.cc
index 045041db..c2d849d2 100644
--- a/content/browser/file_system_access/file_system_access_manager_impl_unittest.cc
+++ b/content/browser/file_system_access/file_system_access_manager_impl_unittest.cc
@@ -2109,61 +2109,6 @@
bad_message_observer.WaitForBadMessage());
}
-TEST_F(FileSystemAccessManagerImplTest, ChooseEntries_HiddenTab) {
- base::FilePath test_file = dir_.GetPath().AppendASCII("asdf");
- ASSERT_TRUE(base::CreateTemporaryFile(&test_file));
- PathInfo test_file_info(test_file);
-
- manager_->SetFilePickerResultForTesting(test_file_info);
-
- static_cast<TestRenderFrameHost*>(web_contents_->GetPrimaryMainFrame())
- ->SimulateUserActivation();
-
- mojo::Remote<blink::mojom::FileSystemAccessManager> manager_remote;
- FileSystemAccessManagerImpl::BindingContext binding_context = {
- kTestStorageKey, kTestURL,
- web_contents_->GetPrimaryMainFrame()->GetGlobalId()};
- manager_->BindReceiver(binding_context,
- manager_remote.BindNewPipeAndPassReceiver());
-
- EXPECT_CALL(permission_context_,
- CanObtainReadPermission(kTestStorageKey.origin()))
- .WillOnce(testing::Return(true));
- EXPECT_CALL(permission_context_,
- CanObtainWritePermission(kTestStorageKey.origin()))
- .WillOnce(testing::Return(true));
- EXPECT_CALL(permission_context_, CheckPathsAgainstEnterprisePolicy(
- testing::_, testing::_, testing::_))
- .Times(0);
-
- // Hiding the web contents should make ChooseEntries return an error.
- web_contents_->WasHidden();
-
- auto save_file_picker_options = blink::mojom::SaveFilePickerOptions::New(
- blink::mojom::AcceptsTypesInfo::New(
- std::vector<blink::mojom::ChooseFileSystemEntryAcceptsOptionPtr>(),
- /*include_accepts_all=*/true),
- /*suggested_name=*/std::string());
- auto picker_options = blink::mojom::FilePickerOptions::New(
- blink::mojom::TypeSpecificFilePickerOptionsUnion::
- NewSaveFilePickerOptions(std::move(save_file_picker_options)),
- /*starting_directory_id=*/std::string(),
- blink::mojom::FilePickerStartInOptionsUnionPtr());
-
- base::test::TestFuture<blink::mojom::FileSystemAccessErrorPtr,
- std::vector<blink::mojom::FileSystemAccessEntryPtr>>
- future;
- manager_remote->ChooseEntries(std::move(picker_options),
- future.GetCallback());
- ASSERT_TRUE(future.Wait());
-
- EXPECT_EQ(future.Get<0>()->status,
- blink::mojom::FileSystemAccessStatus::kPermissionDenied);
- EXPECT_EQ(future.Get<0>()->message,
- "Tab must be visible in order to show a file picker.");
- EXPECT_EQ(future.Get<1>().size(), 0);
-}
-
TEST_F(FileSystemAccessManagerImplTest, GetUniqueId) {
const PathInfo kTestPathInfo(dir_.GetPath().AppendASCII("foo"));
ASSERT_OK_AND_ASSIGN(auto default_bucket,
diff --git a/content/browser/file_system_access/file_system_chooser_browsertest.cc b/content/browser/file_system_access/file_system_chooser_browsertest.cc
index 4248b0a3..1178ae5 100644
--- a/content/browser/file_system_access/file_system_chooser_browsertest.cc
+++ b/content/browser/file_system_access/file_system_chooser_browsertest.cc
@@ -11,6 +11,7 @@
#include "base/files/scoped_temp_dir.h"
#include "base/strings/utf_string_conversions.h"
#include "base/test/gmock_callback_support.h"
+#include "base/test/run_until.h"
#include "base/test/scoped_feature_list.h"
#include "base/threading/thread_restrictions.h"
#include "build/build_config.h"
@@ -2159,6 +2160,69 @@
dialog_params_.title);
}
+// Hide the WebContents and ensure the dialog is not shown.
+IN_PROC_BROWSER_TEST_F(FileSystemChooserBrowserTest, DontShowWhileHidden) {
+ FakeFileSystemAccessPermissionContext permission_context;
+ static_cast<FileSystemAccessManagerImpl*>(
+ shell()
+ ->web_contents()
+ ->GetBrowserContext()
+ ->GetStoragePartition(shell()->web_contents()->GetSiteInstance())
+ ->GetFileSystemAccessEntryFactory())
+ ->SetPermissionContextForTesting(&permission_context);
+
+ GURL url = embedded_test_server()->GetURL("/title1.html");
+ ASSERT_TRUE(NavigateToURL(shell(), url));
+
+ // Record the state of the dialog.
+ SelectFileDialogRecorder recorder;
+ ui::SelectFileDialog::SetFactory(
+ std::make_unique<ObservableSelectFileDialogFactory>(&recorder));
+
+ // Hide the WebContents.
+ WebContents* wc = shell()->web_contents();
+ wc->UpdateWebContentsVisibility(content::Visibility::HIDDEN);
+
+ // JS should see the dialog as aborted.
+ EXPECT_EQ(
+ "AbortError",
+ content::EvalJs(wc, "window.showOpenFilePicker().catch(e => e.name)"));
+ // The dialog should not have been created.
+ EXPECT_EQ(recorder.state, SelectFileDialogRecorder::kNotCreated);
+}
+
+// Show the dialog then hide the WebContents and ensure the dialog is dismissed.
+IN_PROC_BROWSER_TEST_F(FileSystemChooserBrowserTest, ShowThenHide) {
+ GURL url = embedded_test_server()->GetURL("/title1.html");
+ ASSERT_TRUE(NavigateToURL(shell(), url));
+
+ WebContents* wc = shell()->web_contents();
+
+ // Record the state of the dialog.
+ SelectFileDialogRecorder recorder;
+ ui::SelectFileDialog::SetFactory(
+ std::make_unique<ObservableSelectFileDialogFactory>(&recorder));
+
+ // Open the dialog and wait until it's created.
+ ASSERT_EQ(
+ 42,
+ content::EvalJs(
+ wc, "window.p = self.showOpenFilePicker().catch(e => e.name); 42"));
+ ASSERT_TRUE(base::test::RunUntil([&recorder]() {
+ return recorder.state != SelectFileDialogRecorder::kNotCreated;
+ }));
+
+ // Hide the WebContents.
+ wc->UpdateWebContentsVisibility(
+ content::Visibility::HIDDEN); // Hide the web contents.
+ ASSERT_TRUE(base::test::RunUntil([&recorder]() {
+ return recorder.state == SelectFileDialogRecorder::kDestroyed;
+ }));
+
+ // JS should see the dialog as aborted.
+ EXPECT_EQ("AbortError", content::EvalJs(wc, "p"));
+}
+
class FileSystemChooserBackForwardCacheBrowserTest
: public FileSystemChooserBrowserTest {
public:
diff --git a/content/browser/file_system_access/file_system_chooser_unittest.cc b/content/browser/file_system_access/file_system_chooser_unittest.cc
index 1fa969e..a360293 100644
--- a/content/browser/file_system_access/file_system_chooser_unittest.cc
+++ b/content/browser/file_system_access/file_system_chooser_unittest.cc
@@ -30,13 +30,20 @@
class FileSystemChooserTest : public RenderViewHostImplTestHarness {
public:
- void TearDown() override {
- RenderViewHostImplTestHarness::TearDown();
- ui::SelectFileDialog::SetFactory(nullptr);
+ void SetUp() override {
+ RenderViewHostImplTestHarness::SetUp();
+ web_contents_ = CreateTestWebContents(GetBrowserContext());
}
+ void TearDown() override {
+ ui::SelectFileDialog::SetFactory(nullptr);
+ web_contents_.reset();
+ RenderViewHostImplTestHarness::TearDown();
+ }
+
+ WebContents* web_contents() { return web_contents_.get(); }
+
std::vector<PathInfo> SyncShowDialog(
- WebContents* web_contents,
std::vector<blink::mojom::ChooseFileSystemEntryAcceptsOptionPtr> accepts,
bool include_accepts_all,
base::FilePath default_directory = base::FilePath(),
@@ -45,7 +52,7 @@
std::vector<PathInfo>>
future;
FileSystemChooser::CreateAndShow(
- web_contents,
+ web_contents(),
FileSystemChooser::Options(ui::SelectFileDialog::SELECT_OPEN_FILE,
blink::mojom::AcceptsTypesInfo::New(
std::move(accepts), include_accepts_all),
@@ -63,6 +70,7 @@
browser_context, std::move(site_instance));
}
+ std::unique_ptr<content::WebContents> web_contents_;
// Must persist throughout TearDown().
SelectFileDialogParams dialog_params_;
};
@@ -70,7 +78,7 @@
TEST_F(FileSystemChooserTest, EmptyAccepts) {
ui::SelectFileDialog::SetFactory(
std::make_unique<CancellingSelectFileDialogFactory>(&dialog_params_));
- SyncShowDialog(/*web_contents=*/nullptr, {}, /*include_accepts_all=*/true);
+ SyncShowDialog({}, /*include_accepts_all=*/true);
ASSERT_TRUE(dialog_params_.file_types);
EXPECT_TRUE(dialog_params_.file_types->include_all_files);
@@ -86,7 +94,7 @@
TEST_F(FileSystemChooserTest, EmptyAcceptsIgnoresIncludeAcceptsAll) {
ui::SelectFileDialog::SetFactory(
std::make_unique<CancellingSelectFileDialogFactory>(&dialog_params_));
- SyncShowDialog(/*web_contents=*/nullptr, {}, /*include_accepts_all=*/false);
+ SyncShowDialog({}, /*include_accepts_all=*/false);
// Should still include_all_files, even though include_accepts_all was false.
ASSERT_TRUE(dialog_params_.file_types);
@@ -110,7 +118,7 @@
accepts.emplace_back(blink::mojom::ChooseFileSystemEntryAcceptsOption::New(
u"Images", std::vector<std::string>({"image/*"}),
std::vector<std::string>({})));
- SyncShowDialog(/*web_contents=*/nullptr, std::move(accepts),
+ SyncShowDialog(std::move(accepts),
/*include_accepts_all=*/true);
ASSERT_TRUE(dialog_params_.file_types);
@@ -152,7 +160,7 @@
accepts.emplace_back(blink::mojom::ChooseFileSystemEntryAcceptsOption::New(
u"", std::vector<std::string>({}),
std::vector<std::string>({"text", "js", "text"})));
- SyncShowDialog(/*web_contents=*/nullptr, std::move(accepts),
+ SyncShowDialog(std::move(accepts),
/*include_accepts_all=*/true);
ASSERT_TRUE(dialog_params_.file_types);
@@ -182,7 +190,7 @@
accepts.emplace_back(blink::mojom::ChooseFileSystemEntryAcceptsOption::New(
u"", std::vector<std::string>({"image/*"}),
std::vector<std::string>({"text", "jpg"})));
... (truncated)
Original Bug Report
`showSaveFilePicker()` DIalog can Overlaid on Other Origin lead to Origin Spoofing
Steps to reproduce the problem
- Access poc.html
- Click anywhere at the poc.html
- Browse any sites and after 3sec the dialog will appear on other origin
Problem Description
This vulnerability occurs when the showSaveFilePicker() dialog can be overlaid on another origin . By default, if a page is inactive or not actively accessed by the user, any dialogs should remain inactive, even if the dialog delayed. The dialog should only appear again when the user actively interacts with the page.
Summary
showSaveFilePicker() DIalog can Overlaid on Other Origin lead to Origin Spoofing
Additional Data
Category: Security
Chrome Channel: Not sure
Regression: N/A