CVE-2026-79176
Overview
Changed Functions
| Function | Change | Notes |
|---|---|---|
IN_PROC_BROWSER_TEST_Fchrome/browser/extensions/api/file_system/file_system_apitest.cc |
modified | |
ifextensions/browser/api/file_system/file_system_api.cc |
modified |
Files Changed
chrome/browser/extensions/api/file_system/file_system_apitest.ccchrome/browser/extensions/api/file_system/file_system_apitest_chromeos.ccchrome/test/data/extensions/api_test/file_system/open_suggested_name_ignored/background.jschrome/test/data/extensions/api_test/file_system/open_suggested_name_ignored/manifest.jsonchrome/test/data/extensions/api_test/file_system/open_suggested_name_ignored/test.htmlchrome/test/data/extensions/api_test/file_system/open_suggested_name_ignored/test.jsextensions/browser/api/file_system/file_system_api.ccextensions/common/api/file_system.idl
Patch
From 8fdebd5b1fb9c1dd6f9dc23579d498262e9d9ca1 Mon Sep 17 00:00:00 2001 From: Giovanni Pezzino <[email protected]> Date: Wed, 01 Jul 2026 11:23:13 -0700 Subject: [PATCH] fileSystem: Ignore suggestedName for non-save chooseEntry chrome.fileSystem.chooseEntry honored suggestedName for all picker types, appending it to the initial path passed to the native file dialog. This pre-filled the filename input even for "open" prompts where the user is selecting an existing entry rather than naming a new one. Restrict the suggested name to type 'saveFile' so that open and directory pickers start at the default directory without a pre-selected filename. The suggested extension is still applied to the accept-type filter so existing accept handling is unchanged. Update the Drive open-multiple test to pick the test file explicitly now that the open picker no longer derives it from suggestedName, and add coverage that the open picker's initial path does not include the suggested name. TAG=agy Bug: 497538341 Change-Id: I8154088fedc16e919f471c54be54157e3ff3703b Reviewed-on: https://chromium-review.googlesource.com/c/chromium/src/+/8021142 Reviewed-by: Cassy Chun-Crogan <[email protected]> Commit-Queue: Giovanni Pezzino <[email protected]> Reviewed-by: Andrew Rayskiy <[email protected]> Reviewed-by: Toni Barzic <[email protected]> Reviewed-by: Bo Majewski <[email protected]> Cr-Commit-Position: refs/heads/main@{#1655642} --- diff --git a/chrome/browser/extensions/api/file_system/file_system_apitest.cc b/chrome/browser/extensions/api/file_system/file_system_apitest.cc index 5dbf7569..abf2a0f 100644 --- a/chrome/browser/extensions/api/file_system/file_system_apitest.cc +++ b/chrome/browser/extensions/api/file_system/file_system_apitest.cc @@ -303,6 +303,27 @@ CheckStoredDirectoryMatches(test_file); } +IN_PROC_BROWSER_TEST_F(FileSystemApiTest, + FileSystemApiOpenSuggestedNameIgnoredTest) { + base::FilePath test_dir = TempFilePath("sub_dir", true); + ASSERT_FALSE(test_dir.empty()); + { + base::ScopedAllowBlockingForTesting allow_blocking; + ASSERT_TRUE(base::DeleteFile(test_dir)); + ASSERT_TRUE(base::CreateDirectory(test_dir)); + ASSERT_TRUE(base::PathService::OverrideAndCreateIfNeeded( + chrome::DIR_USER_DOCUMENTS, test_dir.DirName(), false, false)); + } + const FileSystemChooseEntryFunction::TestOptions test_options{ + .use_suggested_path = true}; + auto reset_options = + FileSystemChooseEntryFunction::SetOptionsForTesting(test_options); + ASSERT_TRUE( + RunExtensionTest("api_test/file_system/open_suggested_name_ignored", + {.launch_as_platform_app = true})) + << message_; +} + IN_PROC_BROWSER_TEST_F(FileSystemApiTest, FileSystemApiOpenMultipleSuggested) { base::FilePath test_file = TempFilePath("open_existing.txt", true); ASSERT_FALSE(test_file.empty()); diff --git a/chrome/browser/extensions/api/file_system/file_system_apitest_chromeos.cc b/chrome/browser/extensions/api/file_system/file_system_apitest_chromeos.cc index 582a53f7..eba11e8 100644 --- a/chrome/browser/extensions/api/file_system/file_system_apitest_chromeos.cc +++ b/chrome/browser/extensions/api/file_system/file_system_apitest_chromeos.cc @@ -328,7 +328,7 @@ ASSERT_TRUE(base::PathService::OverrideAndCreateIfNeeded( chrome::DIR_USER_DOCUMENTS, test_file.DirName(), true, false)); const FileSystemChooseEntryFunction::TestOptions test_options{ - .use_suggested_path = true}; + .path_to_be_picked = &test_file}; auto reset_options = FileSystemChooseEntryFunction::SetOptionsForTesting(test_options); ASSERT_TRUE( diff --git a/chrome/test/data/extensions/api_test/file_system/open_suggested_name_ignored/background.js b/chrome/test/data/extensions/api_test/file_system/open_suggested_name_ignored/background.js new file mode 100644 index 0000000..0245e2d --- /dev/null +++ b/chrome/test/data/extensions/api_test/file_system/open_suggested_name_ignored/background.js @@ -0,0 +1,7 @@ +// Copyright 2026 The Chromium Authors +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +chrome.app.runtime.onLaunched.addListener(function() { + chrome.app.window.create('test.html'); +}); diff --git a/chrome/test/data/extensions/api_test/file_system/open_suggested_name_ignored/manifest.json b/chrome/test/data/extensions/api_test/file_system/open_suggested_name_ignored/manifest.json new file mode 100644 index 0000000..ee5c1d8c --- /dev/null +++ b/chrome/test/data/extensions/api_test/file_system/open_suggested_name_ignored/manifest.json @@ -0,0 +1,16 @@ +{ + "name": "chrome.fileSystem open suggested name ignored", + "manifest_version": 2, + "version": "0.1", + "description": "Test for chrome.fileSystem.chooseEntry ignoring suggestedName when type is openFile.", + "app": { + "background": { + "scripts": ["background.js"] + } + }, + "permissions": [ + { + "fileSystem": ["directory", "write"] + } + ] +} diff --git a/chrome/test/data/extensions/api_test/file_system/open_suggested_name_ignored/test.html b/chrome/test/data/extensions/api_test/file_system/open_suggested_name_ignored/test.html new file mode 100644 index 0000000..8d7d1db --- /dev/null +++ b/chrome/test/data/extensions/api_test/file_system/open_suggested_name_ignored/test.html @@ -0,0 +1,3 @@ +<html> +<script src="test.js"></script> +</html> diff --git a/chrome/test/data/extensions/api_test/file_system/open_suggested_name_ignored/test.js b/chrome/test/data/extensions/api_test/file_system/open_suggested_name_ignored/test.js new file mode 100644 index 0000000..ceca4e19 --- /dev/null +++ b/chrome/test/data/extensions/api_test/file_system/open_suggested_name_ignored/test.js @@ -0,0 +1,22 @@ +// Copyright 2026 The Chromium Authors +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +chrome.test.runTests([ + function openDirectorySuggestedNameIgnored() { + chrome.fileSystem.chooseEntry( + {type: 'openDirectory', suggestedName: 'sub_dir'}, + chrome.test.callbackPass(function(entry) { + chrome.test.assertNe('sub_dir', entry.name); + }), + ); + }, + function saveFileSuggestedNameHonored() { + chrome.fileSystem.chooseEntry( + {type: 'saveFile', suggestedName: 'new_file.txt'}, + chrome.test.callbackPass(function(entry) { + chrome.test.assertEq('new_file.txt', entry.name); + }), + ); + }, +]); diff --git a/extensions/browser/api/file_system/file_system_api.cc b/extensions/browser/api/file_system/file_system_api.cc index 1cfe10e9..6deca90 100644 --- a/extensions/browser/api/file_system/file_system_api.cc +++ b/extensions/browser/api/file_system/file_system_api.cc @@ -766,6 +766,10 @@ BuildFileTypeInfo(&file_type_info, suggested_extension, options.accepts, options.accepts_all_types); + + if (picker_type != ui::SelectFileDialog::SELECT_SAVEAS_FILE) { + suggested_name.clear(); + } } file_type_info.allowed_paths = ui::SelectFileDialog::FileTypeInfo::ANY_PATH; diff --git a/extensions/common/api/file_system.idl b/extensions/common/api/file_system.idl index a9b5bdd..5ba68ece 100644 --- a/extensions/common/api/file_system.idl +++ b/extensions/common/api/file_system.idl @@ -57,7 +57,7 @@ ChooseEntryType? type; // The suggested file name that will be presented to the user as the - // default name to read or write. This is optional. + // default name to save. This is optional and only applies to 'saveFile'. DOMString? suggestedName; // The optional list of accept options for this file opener. Each option
Regression Test / PoC
diff --git a/chrome/browser/extensions/api/file_system/file_system_apitest.cc b/chrome/browser/extensions/api/file_system/file_system_apitest.cc
index 5dbf7569..abf2a0f 100644
--- a/chrome/browser/extensions/api/file_system/file_system_apitest.cc
+++ b/chrome/browser/extensions/api/file_system/file_system_apitest.cc
@@ -303,6 +303,27 @@
CheckStoredDirectoryMatches(test_file);
}
+IN_PROC_BROWSER_TEST_F(FileSystemApiTest,
+ FileSystemApiOpenSuggestedNameIgnoredTest) {
+ base::FilePath test_dir = TempFilePath("sub_dir", true);
+ ASSERT_FALSE(test_dir.empty());
+ {
+ base::ScopedAllowBlockingForTesting allow_blocking;
+ ASSERT_TRUE(base::DeleteFile(test_dir));
+ ASSERT_TRUE(base::CreateDirectory(test_dir));
+ ASSERT_TRUE(base::PathService::OverrideAndCreateIfNeeded(
+ chrome::DIR_USER_DOCUMENTS, test_dir.DirName(), false, false));
+ }
+ const FileSystemChooseEntryFunction::TestOptions test_options{
+ .use_suggested_path = true};
+ auto reset_options =
+ FileSystemChooseEntryFunction::SetOptionsForTesting(test_options);
+ ASSERT_TRUE(
+ RunExtensionTest("api_test/file_system/open_suggested_name_ignored",
+ {.launch_as_platform_app = true}))
+ << message_;
+}
+
IN_PROC_BROWSER_TEST_F(FileSystemApiTest, FileSystemApiOpenMultipleSuggested) {
base::FilePath test_file = TempFilePath("open_existing.txt", true);
ASSERT_FALSE(test_file.empty());
diff --git a/chrome/browser/extensions/api/file_system/file_system_apitest_chromeos.cc b/chrome/browser/extensions/api/file_system/file_system_apitest_chromeos.cc
index 582a53f7..eba11e8 100644
--- a/chrome/browser/extensions/api/file_system/file_system_apitest_chromeos.cc
+++ b/chrome/browser/extensions/api/file_system/file_system_apitest_chromeos.cc
@@ -328,7 +328,7 @@
ASSERT_TRUE(base::PathService::OverrideAndCreateIfNeeded(
chrome::DIR_USER_DOCUMENTS, test_file.DirName(), true, false));
const FileSystemChooseEntryFunction::TestOptions test_options{
- .use_suggested_path = true};
+ .path_to_be_picked = &test_file};
auto reset_options =
FileSystemChooseEntryFunction::SetOptionsForTesting(test_options);
ASSERT_TRUE(
diff --git a/chrome/test/data/extensions/api_test/file_system/open_suggested_name_ignored/background.js b/chrome/test/data/extensions/api_test/file_system/open_suggested_name_ignored/background.js
new file mode 100644
index 0000000..0245e2d
--- /dev/null
+++ b/chrome/test/data/extensions/api_test/file_system/open_suggested_name_ignored/background.js
@@ -0,0 +1,7 @@
+// Copyright 2026 The Chromium Authors
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+chrome.app.runtime.onLaunched.addListener(function() {
+ chrome.app.window.create('test.html');
+});
diff --git a/chrome/test/data/extensions/api_test/file_system/open_suggested_name_ignored/manifest.json b/chrome/test/data/extensions/api_test/file_system/open_suggested_name_ignored/manifest.json
new file mode 100644
index 0000000..ee5c1d8c
--- /dev/null
+++ b/chrome/test/data/extensions/api_test/file_system/open_suggested_name_ignored/manifest.json
@@ -0,0 +1,16 @@
+{
+ "name": "chrome.fileSystem open suggested name ignored",
+ "manifest_version": 2,
+ "version": "0.1",
+ "description": "Test for chrome.fileSystem.chooseEntry ignoring suggestedName when type is openFile.",
+ "app": {
+ "background": {
+ "scripts": ["background.js"]
+ }
+ },
+ "permissions": [
+ {
+ "fileSystem": ["directory", "write"]
+ }
+ ]
+}
diff --git a/chrome/test/data/extensions/api_test/file_system/open_suggested_name_ignored/test.html b/chrome/test/data/extensions/api_test/file_system/open_suggested_name_ignored/test.html
new file mode 100644
index 0000000..8d7d1db
--- /dev/null
+++ b/chrome/test/data/extensions/api_test/file_system/open_suggested_name_ignored/test.html
@@ -0,0 +1,3 @@
+<html>
+<script src="test.js"></script>
+</html>
diff --git a/chrome/test/data/extensions/api_test/file_system/open_suggested_name_ignored/test.js b/chrome/test/data/extensions/api_test/file_system/open_suggested_name_ignored/test.js
new file mode 100644
index 0000000..ceca4e19
--- /dev/null
+++ b/chrome/test/data/extensions/api_test/file_system/open_suggested_name_ignored/test.js
@@ -0,0 +1,22 @@
+// Copyright 2026 The Chromium Authors
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+chrome.test.runTests([
+ function openDirectorySuggestedNameIgnored() {
+ chrome.fileSystem.chooseEntry(
+ {type: 'openDirectory', suggestedName: 'sub_dir'},
+ chrome.test.callbackPass(function(entry) {
+ chrome.test.assertNe('sub_dir', entry.name);
+ }),
+ );
+ },
+ function saveFileSuggestedNameHonored() {
+ chrome.fileSystem.chooseEntry(
+ {type: 'saveFile', suggestedName: 'new_file.txt'},
+ chrome.test.callbackPass(function(entry) {
+ chrome.test.assertEq('new_file.txt', entry.name);
+ }),
+ );
+ },
+]);
Original Bug Report
Potential local file disclosure via pre-filled filename in chrome.fileSystem.chooseEntry
Flapjack, an experimental security project, has identified the following potential security issue.
Overview: The chrome.fileSystem.chooseEntry extension API lacks a user gesture requirement and allows a suggestedName to pre-fill the filename in “Open” dialogs. A malicious or compromised extension can exploit this via UI redress, tricking a user into inadvertently selecting and disclosing a sensitive local file.
Affected files:
extensions/browser/api/file_system/file_system_api.ccchrome/browser/extensions/api/file_system/file_entry_picker.cc
Estimated timestamp from git blame: 2023-03-01
Description
The chrome.fileSystem.chooseEntry extension API (implemented in FileSystemChooseEntryFunction in extensions/browser/api/file_system/file_system_api.cc) allows a caller to specify a suggestedName in the ChooseEntryOptions. This suggested name is processed and utilized even when the picker type is openFile or openWritableFile.
In extensions/browser/api/file_system/file_system_api.cc:
- The
Run()method processes the user-providedsuggested_nameviaBuildSuggestion. - It then calls
CalculateInitialPathAndShowPicker, which appends the suggested name to the initial directory path. - This combined path is passed to
ShowSelectFileDialogin theFileSystemDelegate.
In the Chrome-specific implementation (chrome/browser/extensions/api/file_system/file_entry_picker.cc), this path is passed as the default_path to the native SelectFileDialog::SelectFile. On most platforms, including Windows, this causes the native “Open” dialog to pre-fill the “File name” box with the provided filename, even though it is an “Open” dialog rather than a “Save” dialog.
Crucially, FileSystemChooseEntryFunction::Run() lacks a browser-side check for a user gesture (e.g., it is missing a user_gesture() or EXTENSION_FUNCTION_VALIDATE(has_user_gesture()) call). This allows a compromised extension renderer with the fileSystem permission (such as the PDF Viewer, or a malicious platform app) to trigger the file picker dialog unexpectedly at any time.
Potential Attack Scenario
(Note: These are potential steps based on static analysis, as our tooling does not currently run exploit code.)
- An attacker compromises an extension with the
fileSystempermission or convinces the user to install a malicious platform app. - The attacker predicts the filename of a sensitive file in a known default directory (e.g., a credential file).
- The attacker’s extension prompts the user to engage in an activity requiring rapid or sustained key presses (e.g., repeatedly pressing the “Enter” key in a game).
- While the user is pressing “Enter”, the attacker’s JavaScript programmatically calls
chrome.fileSystem.chooseEntry({type: 'openFile', suggestedName: 'credentials.json'}). - Because there is no user gesture requirement, the native “Open File” dialog appears instantly, stealing window focus.
- The “File name” input box is pre-filled with
credentials.json. - The user’s ongoing “Enter” keystroke is consumed by the dialog’s default “Open” button before they can react.
- The dialog closes, and the API returns a
FileEntryfor the sensitive file to the attacker’s JavaScript callback, granting unauthorized read access.
Suggested Fix
- Require a User Gesture: Add a check for user activation in
FileSystemChooseEntryFunction::Run(). For example:if (!user_gesture()) { return RespondNow(Error("User gesture is required")); } - Ignore
suggestedNamefor Open Dialogs: Modify the API implementation so that thesuggestedNameparameter is only used when the picker type iskSaveFile. ForkOpenFileandkOpenWritableFile, thesuggestedNameshould be ignored so the native dialog does not pre-fill a filename.
Evaluated with Chrome root at commit: 876d480da1f794d87813cfa2e6ff4fcf9771e939
Results so far have been promising, but there can be wrong deductions. If this proves to be a false positive, please close as WAI; data from false positives will be used to improve accuracy over time. And please feel free to reach out to me directly if you have concerns or feedback on the project.