Medium chrome Logic Error 📄 Reporter bug report 🔧 Commit mapped

Overview

Medium
Severity
CVSS
No
Exploited ITW
Fixed
Fix Status
ImpactSide-channel information leakage in Storage
DescriptionSide-channel information leakage in Storage
ComponentStorage
Bug ClassLogic Error
Tracker428189824
Fix commite7a0dbba1582 (chromium/src) +41/-17
CISA KEVNot listed
CreditedAlesandro Ortiz
Disclosed2025-09-30

Files Changed

  • content/browser/file_system_access/file_system_access_manager_impl.cc
  • content/browser/file_system_access/file_system_chooser.cc
  • content/browser/file_system_access/file_system_chooser.h
  • content/browser/file_system_access/file_system_chooser_unittest.cc
From e7a0dbba158284911c37afbc4916dfb4252f80ea Mon Sep 17 00:00:00 2001
From: Mingyu Lei <leimy@chromium.org>
Date: Wed, 13 Aug 2025 20:54:15 -0700
Subject: [PATCH] FileSystemChooser: wrap fullscreen block in ScopedObjects struct

Bug: 428189824
Change-Id: I331bf28de6ec543464037d2a7a17eb0f76e877d7
Reviewed-on: https://chromium-review.googlesource.com/c/chromium/src/+/6817757
Reviewed-by: Fergal Daly <fergal@chromium.org>
Commit-Queue: Mingyu Lei <leimy@chromium.org>
Cr-Commit-Position: refs/heads/main@{#1501187}
---

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 6c3fa9db55..78c89f7 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
@@ -191,11 +191,6 @@
                     });
   }
 
-  // Drop fullscreen mode so that the user sees the URL bar.
-  base::ScopedClosureRunner fullscreen_block =
-      web_contents->ForSecurityDropFullscreen(
-          /*display_id=*/display::kInvalidDisplayId);
-
 #if BUILDFLAG(IS_ANDROID)
   // Allow android WebView to handle chooser.
   WebContentsDelegate* delegate = web_contents->GetDelegate();
@@ -231,9 +226,13 @@
     return;
   }
 #endif
+  FileSystemChooser::ScopedObjects scoped_objects(
+      // Drop fullscreen mode so that the user sees the URL bar.
+      /*fullscreen_block=*/web_contents->ForSecurityDropFullscreen(
+          display::kInvalidDisplayId));
 
   FileSystemChooser::CreateAndShow(web_contents, options, std::move(callback),
-                                   std::move(fullscreen_block));
+                                   std::move(scoped_objects));
 }
 
 // Called after creating a file that was picked by a save file picker. If
diff --git a/content/browser/file_system_access/file_system_chooser.cc b/content/browser/file_system_access/file_system_chooser.cc
index 8156e34d0..f0ba97c2 100644
--- a/content/browser/file_system_access/file_system_chooser.cc
+++ b/content/browser/file_system_access/file_system_chooser.cc
@@ -280,17 +280,27 @@
   return suggested_name;
 }
 
+FileSystemChooser::ScopedObjects::ScopedObjects() = default;
+FileSystemChooser::ScopedObjects::~ScopedObjects() = default;
+FileSystemChooser::ScopedObjects::ScopedObjects(ScopedObjects&&) = default;
+FileSystemChooser::ScopedObjects& FileSystemChooser::ScopedObjects::operator=(
+    ScopedObjects&&) = default;
+
+FileSystemChooser::ScopedObjects::ScopedObjects(
+    base::ScopedClosureRunner&& fullscreen_block)
+    : fullscreen_block(std::move(fullscreen_block)) {}
+
 // static
 void FileSystemChooser::CreateAndShow(
     WebContents* web_contents,
     const Options& options,
     ResultCallback callback,
-    base::ScopedClosureRunner fullscreen_block) {
+    FileSystemChooser::ScopedObjects scoped_objects) {
   DCHECK_CURRENTLY_ON(BrowserThread::UI);
   TRACE_EVENT0("FileSystem", "FileSystemChooser::CreateAndShow");
   // `listener` deletes itself.
   auto* listener = new FileSystemChooser(options.type(), std::move(callback),
-                                         std::move(fullscreen_block));
+                                         std::move(scoped_objects));
   listener->dialog_ = ui::SelectFileDialog::Create(
       listener,
       GetContentClient()->browser()->CreateSelectFilePolicy(web_contents));
@@ -353,12 +363,13 @@
   return false;
 }
 
-FileSystemChooser::FileSystemChooser(ui::SelectFileDialog::Type type,
-                                     ResultCallback callback,
-                                     base::ScopedClosureRunner fullscreen_block)
+FileSystemChooser::FileSystemChooser(
+    ui::SelectFileDialog::Type type,
+    ResultCallback callback,
+    FileSystemChooser::ScopedObjects scoped_objects)
     : type_(type),
       callback_(std::move(callback)),
-      fullscreen_block_(std::move(fullscreen_block)) {
+      scoped_objects_(std::move(scoped_objects)) {
   CHECK(IsValidFileDialogType(type_));
 }
 
diff --git a/content/browser/file_system_access/file_system_chooser.h b/content/browser/file_system_access/file_system_chooser.h
index 310bb0a..f05aa355 100644
--- a/content/browser/file_system_access/file_system_chooser.h
+++ b/content/browser/file_system_access/file_system_chooser.h
@@ -74,10 +74,24 @@
     base::FilePath default_path_;
   };
 
+  // Struct to hold objects that should be kept alive for the lifetime of the
+  // chooser.
+  struct CONTENT_EXPORT ScopedObjects {
+    ScopedObjects();
+    ~ScopedObjects();
+    ScopedObjects(ScopedObjects&&);
+    ScopedObjects& operator=(ScopedObjects&&);
+    ScopedObjects(const ScopedObjects&) = delete;
+    ScopedObjects& operator=(const ScopedObjects&) = delete;
+    explicit ScopedObjects(base::ScopedClosureRunner&& fullscreen_block);
+
+    base::ScopedClosureRunner fullscreen_block;
+  };
+
   static void CreateAndShow(WebContents* web_contents,
                             const Options& options,
                             ResultCallback callback,
-                            base::ScopedClosureRunner fullscreen_block);
+                            ScopedObjects scoped_objects);
 
   // Returns whether the specified extension receives special handling by the
   // Windows shell. These extensions should be sanitized before being shown in
@@ -87,7 +101,7 @@
 
   FileSystemChooser(ui::SelectFileDialog::Type type,
                     ResultCallback callback,
-                    base::ScopedClosureRunner fullscreen_block);
+                    ScopedObjects scoped_objects);
 
  private:
   ~FileSystemChooser() override;
@@ -102,8 +116,7 @@
 
   const ui::SelectFileDialog::Type type_;
   ResultCallback callback_ GUARDED_BY_CONTEXT(sequence_checker_);
-  base::ScopedClosureRunner fullscreen_block_
-      GUARDED_BY_CONTEXT(sequence_checker_);
+  ScopedObjects scoped_objects_ GUARDED_BY_CONTEXT(sequence_checker_);
 
   scoped_refptr<ui::SelectFileDialog> dialog_;
 };
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 956da7d..1fa969e 100644
--- a/content/browser/file_system_access/file_system_chooser_unittest.cc
+++ b/content/browser/file_system_access/file_system_chooser_unittest.cc
@@ -13,6 +13,7 @@
 #include "base/strings/utf_string_conversions.h"
 #include "base/test/bind.h"
 #include "base/test/test_future.h"
+#include "content/browser/file_system_access/file_system_chooser.h"
 #include "content/public/test/browser_task_environment.h"
 #include "content/public/test/file_system_chooser_test_helpers.h"
 #include "content/public/test/web_contents_tester.h"
@@ -50,7 +51,7 @@
                                        std::move(accepts), include_accepts_all),
                                    std::u16string(), default_directory,
                                    suggested_name),
-        future.GetCallback(), base::ScopedClosureRunner());
+        future.GetCallback(), FileSystemChooser::ScopedObjects());
     return std::get<1>(future.Take());
   }
 
Loading diff…

Regression Test / PoC

shipped with the fix
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 956da7d..1fa969e 100644
--- a/content/browser/file_system_access/file_system_chooser_unittest.cc
+++ b/content/browser/file_system_access/file_system_chooser_unittest.cc
@@ -13,6 +13,7 @@
 #include "base/strings/utf_string_conversions.h"
 #include "base/test/bind.h"
 #include "base/test/test_future.h"
+#include "content/browser/file_system_access/file_system_chooser.h"
 #include "content/public/test/browser_task_environment.h"
 #include "content/public/test/file_system_chooser_test_helpers.h"
 #include "content/public/test/web_contents_tester.h"
@@ -50,7 +51,7 @@
                                        std::move(accepts), include_accepts_all),
                                    std::u16string(), default_directory,
                                    suggested_name),
-        future.GetCallback(), base::ScopedClosureRunner());
+        future.GetCallback(), FileSystemChooser::ScopedObjects());
     return std::get<1>(future.Take());
   }
Loading diff…

Original Bug Report

reported by al...@alesandroortiz.com

Security: PiP window obscures FSA API file picker dialog (env var leak)

SUMMARY

A PiP window can obscure the file/directory picker dialog, using the File System Access (FSA) API. Without user awareness, an attacker can:

  • Read environment variables (similar to issue 40057200)
  • Read/write files (similar to issue 40076120)

Using the FSA API bypasses existing protections that only apply to file/dir picker dialogs opened from file-type inputs.

VULNERABILITY DETAILS

A PiP window can obscure a file/directory picker dialog opened through the FSA API: showSaveFilePicker(), showOpenFilePicker(), or showDirectoryPicker().

Prior fixes only mitigate for file-type inputs, not FSA API

There are two fixes for issue 40076120, but neither mitigate the same vulnerability in file/dir pickers opened through the FSA API. They only mitigate the vuln for file/dir pickers opened through file-type inputs.

The first fix in https://crrev.com/c/5753110 (September 2024) blocked or closed PiP windows if a file picker was open (feature flag FileDialogsBlockPictureInPicture).

The first fix was disabled in https://crrev.com/c/6318768 (March 3rd, 2025) and a new fix was introduced in https://crrev.com/c/6449682 (June 10th, 2025). The new fix tucks the PiP window instead of blocking/closing it (feature flag FileDialogsTuckPictureInPicture).

Neither fix mitigates the vulnerability when using FSA API file/dir pickers, verified by enabling/disabling the features (see attached videos). On current 138.0.7204.50 Stable, neither fix is enabled so both attack approaches work anyway.

Impacts

Scenario 1: Read environment variables

An attacker can exfiltrate environment variables (similar to issue 40057200) on Windows by saving a file with the environment variable names (e.g. %username%-%service_secret_key%). The env var names are replaced with their values when saving the file, and we can read the file name containing the values.

To perform the attack, we write the env var names into the clipboard and then ask user to paste the payload into the file picker dialog while it’s obscured by the PiP window. After the user presses enter to save the file, we read the file name which now contains the env var values. We then delete the file to clean up the attack.

There’s code to sanitize pre-filled file names in the file picker dialog, but we work around this by having the user paste our payload into the dialog.

Env var exfil is possible with the FSA API but not with file-type inputs because file-type inputs use open dialogs, not save dialogs. An attacker needs to use save dialogs to get a file handle with the env var values in its file name.

Scenario 2: Read+write file/dir

An attacker can read and write to a mostly-arbitrary file/dir without user awareness.

Similar to the first scenario, we write the file/dir path into the clipboard and ask user to paste payload into the dialog while it’s obscured. This allows us to use env vars or known paths that would otherwise not be pre-fillable through the FSA API.

After issue 40076120 was filed, more protections were added to block access to common sensitive directories, but users may still have files of interest in non-blocked directories. Outside of these blocked dirs, path is arbitrary.

VERSION

Chrome version: 138.0.7204.50 Stable, 140.0.7262.0 Canary

For potentially mitigating feature flags (FileDialogsBlockPictureInPicture, FileDialogsTuckPictureInPicture) I verified repro with:

  • default config (on Stable, both disabled; on Canary, only tuck enabled)
  • both disabled
  • only one enabled

Operating System: Windows 10

REPRODUCTION CASE

Minimal comparison: Mitigation on file-type input dialog vs. FSA API dialog

This page shows observed mitigation differences between file-type input dialog (mitigated) and FSA API dialog (not mitigated). If mitigated, close/tuck occurs even if file picker and PiP window don’t overlap.

  1. Run Chrome with --enable-features=FileDialogsTuckPictureInPicture --disable-features=FileDialogsBlockPictureInPicture (or the opposite enabled/disabled feature combination) to enable one of the mitigations.
  2. Navigate to https://alesandroortiz.com/security/chromium/filepicker-pip-minimal.html and choose the opening method.
  3. Click anywhere to open PiP window.
  4. Click again to open file picker.

Observed: When file picker dialog is opened using file-type input, PiP window is closed or tucked (based on enabled mitigation). When same dialog is opened using FSA API, PiP window is not closed or tucked.

Expected: When file picker dialog is opened by attacker in any way, PiP window is closed or tucked (based on enabled mitigation).

Scenario 1: Read environment variables

  1. Navigate to https://alesandroortiz.com/security/chromium/filepicker-pip.html
  2. Press any key twice (or press and hold any key).
  3. Press Ctrl+V, then enter.

Observed: File picker dialog is obscured by PiP window. Attacker can read environment variables without user awareness.

Expected: FIle picker dialog is visible to user. Ideally, attacker cannot read sensitive environment variables even if not obscured.

Scenario 2a: Read file

Setup: Create a file in Documents named example.txt with any content.

  1. Navigate to https://alesandroortiz.com/security/chromium/filepicker-pip.html?mode=readfile

Steps 2-3: Same as first scenario.

Observed: File picker dialog is obscured by PiP window. File is read from attacker-specified path without user awareness.

Expected: File picker dialog is visible to user.

Scenario 2b: Write file

  1. Navigate to https://alesandroortiz.com/security/chromium/filepicker-pip.html?mode=writefile

Steps 2-3: Same as first scenario.

Observed: File picker dialog is obscured by PiP window. File is written to attacker-specified path without user awareness.

Expected: File picker dialog is visible to user.

Credit Information

Reporter credit: Alesandro Ortiz https://AlesandroOrtiz.com

View on issue tracker