Medium watch CVSS 6.5 chrome Logic Error 📄 Reporter bug report 🔧 Commit mapped

Overview

Medium
Severity
6.5
CVSS
No
Exploited ITW
Fixed
Fix Status
ImpactInappropriate implementation in FileSystemAccess API
DescriptionInappropriate implementation in FileSystemAccess API
ComponentFileSystemAccess API
Sub-componentBlink>Forms>File, UI>Browser>Navigation
Bug ClassLogic Error
Action tierwatch — logic/policy bug — security-relevant, no memory-unsafety
Computed by PatchGap's deterministic exploitability ruler from the fixing commit. patch-now = RCE-class & attacker-reachable in a default config, or exploited in the wild; watch = real security signal below that bar; noise = no security relevance.
Tracker40059071
Fix commitd5561f9e792b (chromium/src) +13/-0
CWECWE-451
CVSS vectorCVSS:3.1/AV:N/AC:L/PR:N/UI:R/S:U/C:H/I:N/A:N
CISA KEVNot listed
CreditedNDevTK
Reported2022-03-11 · bounty $2000 (per vendor advisory)
Fix landed2025-04-18 (public git)
Disclosed2025-05-27
Patch gap39 days — fix public in source before the advisory

Background

File System Access API
the web API (showOpenFilePicker/showSaveFilePicker/showDirectoryPicker) whose picker launch is mediated in the browser process by content/browser/file_system_access/file_system_access_manager_impl.cc.
Transient user activation
a short-lived, gesture-derived state indicating the user recently interacted with the page, required to gate privileged actions like opening a file/directory picker.
Consuming activation
spending the transient activation so it cannot be reused for a second privileged action, versus merely checking it and leaving it available.
`FrameTreeNode::UpdateUserActivationState`
the browser-process method used here to consume transient activation via kConsumeTransientActivation.

Root Cause Analysis

The File System Access picker path in file_system_access_manager_impl.cc required transient user activation but only checked it; it never consumed it, so a single user gesture could satisfy the activation requirement for more than one picker invocation. Because the existing activation check lives in the browser process, consuming activation in the renderer would have made that browser-side check fail (as the commit message explains), so the pre-patch code left activation intact after passing the check.

The fix adds, before the existing “already active file picker” guard, a block that consumes activation in the browser process: if content_browser_client->IsTransientActivationRequiredForShowFileOrDirectoryPicker(web_contents) is true, it calls FrameTreeNode::From(rfh)->UpdateUserActivationState(kConsumeTransientActivation, kNone). This enforces the intended one-gesture-per-picker invariant so the activation cannot be silently reused. The change is a tightening; it consumes activation on each qualifying call rather than leaving it available for subsequent invocations, and the added frame_tree_node.h include supports the new FrameTreeNode call.

Key insight
The single core mistake was requiring but not consuming transient user activation for File System Access pickers, letting one gesture authorize multiple picker launches; the fix consumes the activation in the browser process via UpdateUserActivationState(kConsumeTransientActivation, ...) so each launch needs its own gesture.

Attack Path

  1. Obtain one user gesture A page gets a single legitimate user interaction that grants transient activation.
  2. Launch a picker Script calls a File System Access picker method; the browser-process check confirms activation is present and (pre-patch) leaves it intact.
  3. Reuse the same activation Because activation was never consumed, script can invoke the picker again (or another activation-gated action) on the same gesture, defeating the one-gesture requirement.
  4. Abuse repeated/unexpected prompts The attacker leverages the un-consumed activation to trigger picker behavior the user did not separately authorize, the “inappropriate implementation” the advisory describes. (inferred user-facing abuse such as spoofing/annoyance)

Impact Assessment

A malicious page could reuse a single user activation to invoke File System Access pickers more times than the single gesture should permit, an inappropriate implementation in the File System Access API per the advisory; the impact is confined to renderer-driven web content interacting with the browser-mediated picker and requires one genuine user gesture as a precondition. This is a logic/activation-enforcement flaw with no memory corruption shown. The vendor advisory rates this medium severity and does not report in-the-wild exploitation; NVD scores it CVSS 6.5. Any escalation beyond reuse of activation is (inferred).

Files Changed

  • content/browser/file_system_access/file_system_access_manager_impl.cc

Audit Directions

  • Check-without-consume activation
    Audit other activation-gated browser-process actions (pickers, permission prompts, downloads, popups) to confirm they consume transient activation rather than merely checking it, preventing gesture reuse.
  • Renderer/browser activation split
    Look for features whose activation check runs in the browser process while consumption (if any) happens in the renderer, since the two can drift and leave activation un-consumed.
  • File System Access entry points
    Review each showOpenFilePicker/showSaveFilePicker/showDirectoryPicker and related privileged entry point for consistent activation consumption and for the browser-to-renderer notification gap noted in the fix’s TODO.
From d5561f9e792bef4da69074b13cc04bbf38f95d69 Mon Sep 17 00:00:00 2001
From: Joey Arhar <[email protected]>
Date: Fri, 18 Apr 2025 08:57:42 -0700
Subject: [PATCH] Make FileSystemAccess APIs consume user activation

User activation is consumed in the browser process instead of the
renderer process like input.showPicker() because consuming user
activation in the renderer would make the existing user activation check
in the browser process fail.

There is no browsertest because content::EvalJs() sets user activation
after running script, even with the flag to not run user activation, and
I don't know how to make it stop.

There is no web_test because consuming user activation in the browser
process does not notify the renderer process that it lost user
activation. It does not look like there is any existing interface to
make the browser tell the renderer that it lost user activation, only
the other way around.

I manually verified that the attached bug is fixed.

Spec issue: https://github.com/WICG/file-system-access/issues/458

Fixed: 40059071
Change-Id: Ia1f5ff63a8bdf6cdec70dea1406e9865123f96a5
Reviewed-on: https://chromium-review.googlesource.com/c/chromium/src/+/5735618
Reviewed-by: Mustaq Ahmed <[email protected]>
Reviewed-by: Fergal Daly <[email protected]>
Commit-Queue: Joey Arhar <[email protected]>
Cr-Commit-Position: refs/heads/main@{#1448885}
---

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 b5bd63d..9387190f 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
@@ -49,6 +49,7 @@
 #include "content/browser/file_system_access/file_system_access_watcher_manager.h"
 #include "content/browser/file_system_access/file_system_chooser.h"
 #include "content/browser/file_system_access/fixed_file_system_access_permission_grant.h"
+#include "content/browser/renderer_host/frame_tree_node.h"
 #include "content/public/browser/browser_task_traits.h"
 #include "content/public/browser/browser_thread.h"
 #include "content/public/browser/content_browser_client.h"
@@ -583,6 +584,18 @@
     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
+  // activation.
+  if (content_browser_client
+          ->IsTransientActivationRequiredForShowFileOrDirectoryPicker(
+              web_contents)) {
+    FrameTreeNode::From(rfh)->UpdateUserActivationState(
+        blink::mojom::UserActivationUpdateType::kConsumeTransientActivation,
+        blink::mojom::UserActivationNotificationType::kNone);
+  }
+
   // Don't show the file picker if there is an already active file picker for
   // this render frame host.
   GlobalRenderFrameHostId global_rfh_id = rfh->GetGlobalId();
Loading diff…

Original Bug Report

reported by [email protected]

File picker UI spoof

UserAgent: Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/99.0.4844.51 Safari/537.36

Steps to reproduce the problem: onmouseup = _ => { let fs = showOpenFilePicker(); open(‘https://www.google.com’); }

Click

What is the expected behavior? For open() to require its own user interaction. (it also allows for a window popunder) Stuff like window.close location.href should lose the FileSystemHandle anyway.

What went wrong? File picker shown on the wrong origin while keeping FileSystemHandle.

Did this work before? N/A

Chrome version: 99.0.4844.51 Channel: stable OS Version: 10.0

View on issue tracker