Medium watch CVSS 4.2 chrome Input Validation 📄 Reporter bug report 🔧 Commit mapped

Overview

Medium
Severity
4.2
CVSS
No
Exploited ITW
Fixed
Fix Status
ImpactInsufficient validation of untrusted input in FileSystem
DescriptionInsufficient validation of untrusted input in FileSystem
ComponentFileSystem
Bug ClassInput Validation
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.
Tracker497254383
Fix commit5c6dc2fef93b (chromium/src) +12/-40
CWECWE-20 (Improper input validation)
CVSS vectorCVSS:3.1/AV:N/AC:H/PR:N/UI:R/S:U/C:L/I:L/A:N
CISA KEVNot listed
CreditedGoogle
Reported2026-03-28 (per vendor advisory)
Fix landed2026-05-07 (public git)
Disclosed2026-05-05

Background

`FileSystemManagerImpl::Open`
the browser-process (content) handler for the blink.mojom.FileSystemManager Open Mojo IPC, which opens a sandboxed filesystem for a given origin and runs an access check before proceeding.
Renderer-supplied origin
an url::Origin passed up from the (untrusted, potentially compromised) renderer in the IPC, as opposed to the browser-authoritative origin the browser already knows for that Mojo connection.
`receivers_.current_context().origin()`
the browser-side origin bound to the Mojo receiver when it was added (storage_key), i.e. the trustworthy origin the browser attributes to the calling frame/worker.
`CanAccessDataForOrigin`
the ChildProcessSecurityPolicyImpl::Handle check (run on the UI thread) that verifies the renderer process is actually allowed to act for the origin being opened.

Root Cause Analysis

Historically Open accepted an origin parameter straight from the renderer and used it (origin_to_check = origin) as the origin to open and to feed into CanAccessDataForOrigin. A compromised renderer could therefore supply an origin it does not own, and the invariant that filesystem access must be scoped to the frame’s true, browser-authoritative origin was dependent on trusting renderer input. Step 1 of the fix (a prior CL) introduced the kEnforceFileSystemManagerOpenOrigin feature flag, which when enabled overrode origin_to_check with receivers_.current_context().origin(). This CL (step 2) completes the hardening: it removes the origin parameter entirely from the Mojo interface and all renderer call sites, deletes the now-unconditional flag, and makes Open always pass receivers_.current_context().origin() to the access check.

The fix works because the origin used for the security decision can no longer be influenced by the renderer; it is taken solely from the browser-trusted receiver context.

Key insight
The core mistake was trusting a renderer-supplied origin for a security-sensitive filesystem access check; the fix removes that parameter from the IPC altogether and always uses the browser-authoritative origin from the Mojo receiver context.

Attack Path

  1. Compromise a renderer Gain the ability to send arbitrary FileSystemManager.Open IPCs from a renderer process (a standard precondition for this class of browser-process validation bug).
  2. Forge the origin argument In the pre-fix (flag-disabled) interface, call Open(origin, file_system_type, ...) with an origin the process does not legitimately control.
  3. Reach the access check with the forged origin FileSystemManagerImpl::Open sets origin_to_check = origin and forwards it to CanAccessDataForOrigin, so the decision is made against renderer-chosen input rather than the real frame origin.
  4. Open a cross-origin sandboxed filesystem If the check is satisfied against the forged origin, the renderer obtains a filesystem name/root bound to another origin (inferred: cross-origin sandboxed-filesystem data access), violating origin isolation.

Impact Assessment

A compromised or malicious renderer could influence the origin used for the FileSystem Open access check, potentially obtaining access to a sandboxed filesystem scoped to an origin it does not own (cross-origin data access, inferred). This operates at the browser-process IPC boundary and requires the ability to send crafted FileSystemManager IPCs. The advisory rates it medium and labels it “Insufficient validation of untrusted input in FileSystem”; no in-the-wild exploitation is stated, and by the time of this CL the enforcing flag was already enabled by default, so this step finalizes the removal of the untrusted parameter.

Changed Functions

FunctionChangeNotes
Blob
third_party/blink/renderer/modules/filesystem/file_system_dispatcher.h
modified
ExecutionContext
third_party/blink/renderer/modules/filesystem/file_system_dispatcher.h
modified
KURL
third_party/blink/renderer/modules/filesystem/file_system_dispatcher.h
modified
SecurityOrigin
third_party/blink/renderer/modules/filesystem/file_system_dispatcher.h
modified

Files Changed

  • content/browser/file_system/file_system_manager_impl.cc
  • content/browser/file_system/file_system_manager_impl.h
  • content/common/features.cc
  • content/common/features.h
  • third_party/blink/public/mojom/filesystem/file_system.mojom
  • third_party/blink/renderer/core/fileapi/file_test.cc
  • third_party/blink/renderer/modules/filesystem/file_system_dispatcher.cc
  • third_party/blink/renderer/modules/filesystem/file_system_dispatcher.h
  • third_party/blink/renderer/modules/filesystem/local_file_system.cc

Audit Directions

  • Renderer-supplied identity in browser IPC
    Search other Mojo interfaces for origin/StorageKey/URL parameters that duplicate information the browser already holds in the receiver context, and prefer current_context() over the IPC argument.
  • Flag-to-removal transitions
    When a kEnforce... feature flag reaches default-enabled, verify the untrusted parameter it was guarding is actually removed, not merely overridden, so no legacy call path can still pass it.
  • Access checks fed by arguments
    Audit callers of CanAccessDataForOrigin and similar ChildProcessSecurityPolicy checks to ensure the origin/StorageKey argument originates from browser-authoritative state rather than the message payload.
From 5c6dc2fef93b3c4618672b2885f7b53f6038d9b9 Mon Sep 17 00:00:00 2001
From: Tsuyoshi Horo <[email protected]>
Date: Wed, 06 May 2026 18:41:10 -0700
Subject: [PATCH] Remove origin parameter from FileSystemManager::Open

This is step 2 of 2 for enforcing the browser-authoritative origin in
FileSystemManager::Open.

With the kEnforceFileSystemManagerOpenOrigin feature flag from step 1
(https://crrev.com/c/7717899) handling the transition, this CL removes
the origin parameter from the Mojo IPC interface and all associated
renderer calls.

Bug: 497254383
Change-Id: I41f29d395d7b0812b13b0535aad095a7622c3302
Reviewed-on: https://chromium-review.googlesource.com/c/chromium/src/+/7720200
Reviewed-by: Rakina Zata Amni <[email protected]>
Commit-Queue: Tsuyoshi Horo <[email protected]>
Reviewed-by: Takashi Toyoshima <[email protected]>
Cr-Commit-Position: refs/heads/main@{#1626655}
---

diff --git a/content/browser/file_system/file_system_manager_impl.cc b/content/browser/file_system/file_system_manager_impl.cc
index cc9c6ae..3ffe9a4 100644
--- a/content/browser/file_system/file_system_manager_impl.cc
+++ b/content/browser/file_system/file_system_manager_impl.cc
@@ -19,7 +19,6 @@
 #include "content/browser/blob_storage/chrome_blob_storage_context.h"
 #include "content/browser/child_process_security_policy_impl.h"
 #include "content/browser/file_system/browser_file_system_helper.h"
-#include "content/common/features.h"
 #include "content/public/browser/browser_thread.h"
 #include "content/public/browser/render_process_host.h"
 #include "content/public/common/child_process_id.h"
@@ -214,17 +213,10 @@
   receivers_.Add(this, std::move(receiver), storage_key);
 }
 
-void FileSystemManagerImpl::Open(const url::Origin& origin,
-                                 blink::mojom::FileSystemType file_system_type,
+void FileSystemManagerImpl::Open(blink::mojom::FileSystemType file_system_type,
                                  OpenCallback callback) {
   DCHECK_CURRENTLY_ON(BrowserThread::IO);
 
-  url::Origin origin_to_check = origin;
-  if (base::FeatureList::IsEnabled(
-          features::kEnforceFileSystemManagerOpenOrigin)) {
-    origin_to_check = receivers_.current_context().origin();
-  }
-
   // Run the access check on the UI thread using a duplicated
   // ChildProcessSecurityPolicy::Handle, ensuring the SecurityState exists when
   // the task runs even if this instance and its Handle are gone at the time.
@@ -234,7 +226,7 @@
           &ChildProcessSecurityPolicyImpl::Handle::CanAccessDataForOrigin,
           std::make_unique<ChildProcessSecurityPolicyImpl::Handle>(
               security_policy_handle_.Duplicate()),
-          origin_to_check),
+          receivers_.current_context().origin()),
       base::BindOnce(&FileSystemManagerImpl::ContinueOpen,
                      weak_factory_.GetWeakPtr(), file_system_type,
                      receivers_.GetBadMessageCallback(), std::move(callback),
diff --git a/content/browser/file_system/file_system_manager_impl.h b/content/browser/file_system/file_system_manager_impl.h
index 24ec7ecd..bf9ed95 100644
--- a/content/browser/file_system/file_system_manager_impl.h
+++ b/content/browser/file_system/file_system_manager_impl.h
@@ -74,8 +74,7 @@
       mojo::PendingReceiver<blink::mojom::FileSystemManager> receiver);
 
   // blink::mojom::FileSystem
-  void Open(const url::Origin& origin,
-            blink::mojom::FileSystemType file_system_type,
+  void Open(blink::mojom::FileSystemType file_system_type,
             OpenCallback callback) override;
   void ResolveURL(const GURL& filesystem_url,
                   ResolveURLCallback callback) override;
diff --git a/content/common/features.cc b/content/common/features.cc
index b0f87821..118dc8d 100644
--- a/content/common/features.cc
+++ b/content/common/features.cc
@@ -220,13 +220,6 @@
              base::FEATURE_DISABLED_BY_DEFAULT);
 #endif  // BUILDFLAG(IS_LINUX) || BUILDFLAG(IS_CHROMEOS)
 
-// Enforces the use of the browser-authoritative origin from the Mojo receiver
-// context instead of the renderer-supplied origin in FileSystemManager::Open.
-// TODO(crbug.com/497254383): Remove this flag and the origin parameter from
-// the Mojo interface.
-BASE_FEATURE(kEnforceFileSystemManagerOpenOrigin,
-             base::FEATURE_ENABLED_BY_DEFAULT);
-
 // When enabled, enforces that same-document navigations must not change
 // the committed origin, insecure request policy, or insecure navigations set.
 // Any mismatch will result in a renderer kill via bad_message handling.
diff --git a/content/common/features.h b/content/common/features.h
index 6a95343..1d89379 100644
--- a/content/common/features.h
+++ b/content/common/features.h
@@ -86,7 +86,6 @@
 CONTENT_EXPORT BASE_DECLARE_FEATURE(kEnableDevToolsJsErrorReporting);
 #endif  // BUILDFLAG(IS_LINUX) || BUILDFLAG(IS_CHROMEOS)
 CONTENT_EXPORT BASE_DECLARE_FEATURE(kEnforceSameDocumentOriginInvariants);
-CONTENT_EXPORT BASE_DECLARE_FEATURE(kEnforceFileSystemManagerOpenOrigin);
 CONTENT_EXPORT BASE_DECLARE_FEATURE(kEmbeddingRequiresOptIn);
 CONTENT_EXPORT BASE_DECLARE_FEATURE(kExperimentalContentSecurityPolicyFeatures);
 CONTENT_EXPORT BASE_DECLARE_FEATURE(kFedCmNonStringToken);
diff --git a/third_party/blink/public/mojom/filesystem/file_system.mojom b/third_party/blink/public/mojom/filesystem/file_system.mojom
index c4033f8..32cc697 100644
--- a/third_party/blink/public/mojom/filesystem/file_system.mojom
+++ b/third_party/blink/public/mojom/filesystem/file_system.mojom
@@ -73,10 +73,8 @@
   // Opens a new filesystem and returns a name and root path for the requested
   // filesystem and a success error code if the operation succeeds. If the
   // operation fails, |error_code| indicates the reason for failure.
-  // TODO(https://crbug.com/873661): Make interface per frame/worker and remove
-  // |origin|.
   [Sync]
-  Open(url.mojom.Origin origin, blink.mojom.FileSystemType file_system_type) =>
+  Open(blink.mojom.FileSystemType file_system_type) =>
       (string name,
        url.mojom.Url root_url,
        mojo_base.mojom.FileError error_code);
diff --git a/third_party/blink/renderer/core/fileapi/file_test.cc b/third_party/blink/renderer/core/fileapi/file_test.cc
index cef630ac..493a027 100644
--- a/third_party/blink/renderer/core/fileapi/file_test.cc
+++ b/third_party/blink/renderer/core/fileapi/file_test.cc
@@ -86,8 +86,7 @@
   }
 
   // mojom::blink::FileSystem
-  void Open(const scoped_refptr<const SecurityOrigin>& origin,
-            mojom::blink::FileSystemType file_system_type,
+  void Open(mojom::blink::FileSystemType file_system_type,
             OpenCallback callback) override {}
   void ResolveURL(const KURL& filesystem_url,
                   ResolveURLCallback callback) override {}
diff --git a/third_party/blink/renderer/modules/filesystem/file_system_dispatcher.cc b/third_party/blink/renderer/modules/filesystem/file_system_dispatcher.cc
index 2afbd35e..23e6075 100644
--- a/third_party/blink/renderer/modules/filesystem/file_system_dispatcher.cc
+++ b/third_party/blink/renderer/modules/filesystem/file_system_dispatcher.cc
@@ -114,23 +114,20 @@
 }
 
 void FileSystemDispatcher::OpenFileSystem(
-    const SecurityOrigin* origin,
     mojom::blink::FileSystemType type,
     std::unique_ptr<FileSystemCallbacks> callbacks) {
   GetFileSystemManager().Open(
-      origin, type,
-      blink::BindOnce(&FileSystemDispatcher::DidOpenFileSystem,
-                      WrapWeakPersistent(this), std::move(callbacks)));
+      type, blink::BindOnce(&FileSystemDispatcher::DidOpenFileSystem,
+                            WrapWeakPersistent(this), std::move(callbacks)));
 }
 
 void FileSystemDispatcher::OpenFileSystemSync(
-    const SecurityOrigin* origin,
     mojom::blink::FileSystemType type,
     std::unique_ptr<FileSystemCallbacks> callbacks) {
   String name;
   KURL root_url;
   base::File::Error error_code = base::File::FILE_ERROR_FAILED;
-  GetFileSystemManager().Open(origin, type, &name, &root_url, &error_code);
+  GetFileSystemManager().Open(type, &name, &root_url, &error_code);
   DidOpenFileSystem(std::move(callbacks), std::move(name), root_url,
                     error_code);
 }
diff --git a/third_party/blink/renderer/modules/filesystem/file_system_dispatcher.h b/third_party/blink/renderer/modules/filesystem/file_system_dispatcher.h
index af12f6b1..e754a9b3 100644
--- a/third_party/blink/renderer/modules/filesystem/file_system_dispatcher.h
+++ b/third_party/blink/renderer/modules/filesystem/file_system_dispatcher.h
@@ -22,7 +22,6 @@
 class Blob;
 class ExecutionContext;
 class KURL;
-class SecurityOrigin;
 
 // Sends messages via mojo to the blink::mojom::FileSystemManager service
 // running in the browser process. It is owned by ExecutionContext, and
@@ -43,11 +42,9 @@
 
   mojom::blink::FileSystemManager& GetFileSystemManager();
 
-  void OpenFileSystem(const SecurityOrigin* origin,
-                      mojom::blink::FileSystemType type,
+  void OpenFileSystem(mojom::blink::FileSystemType type,
                       std::unique_ptr<FileSystemCallbacks> callbacks);
-  void OpenFileSystemSync(const SecurityOrigin* origin,
-                          mojom::blink::FileSystemType type,
+  void OpenFileSystemSync(mojom::blink::FileSystemType type,
                           std::unique_ptr<FileSystemCallbacks> callbacks);
 
   void ResolveURL(const KURL& filesystem_url,
diff --git a/third_party/blink/renderer/modules/filesystem/local_file_system.cc b/third_party/blink/renderer/modules/filesystem/local_file_system.cc
index f73fcd0..1c60ea7 100644
--- a/third_party/blink/renderer/modules/filesystem/local_file_system.cc
+++ b/third_party/blink/renderer/modules/filesystem/local_file_system.cc
@@ -146,11 +146,9 @@
   ExecutionContext* context = GetSupplementable();
   FileSystemDispatcher& dispatcher = FileSystemDispatcher::From(context);
Loading diff…

Regression Test / PoC

shipped with the fix
diff --git a/third_party/blink/renderer/core/fileapi/file_test.cc b/third_party/blink/renderer/core/fileapi/file_test.cc
index cef630ac..493a027 100644
--- a/third_party/blink/renderer/core/fileapi/file_test.cc
+++ b/third_party/blink/renderer/core/fileapi/file_test.cc
@@ -86,8 +86,7 @@
   }
 
   // mojom::blink::FileSystem
-  void Open(const scoped_refptr<const SecurityOrigin>& origin,
-            mojom::blink::FileSystemType file_system_type,
+  void Open(mojom::blink::FileSystemType file_system_type,
             OpenCallback callback) override {}
   void ResolveURL(const KURL& filesystem_url,
                   ResolveURLCallback callback) override {}
Loading diff…

Original Bug Report

reported by [email protected]

FileSystem API access from PDF/sandboxed renderers via malformed opaque origin IPC

Project Fortify, an experimental security project, has identified the following potential security issue.

Overview: A compromised renderer can bypass ChildProcessSecurityPolicy restrictions by supplying a malformed opaque origin to FileSystemManager::Open. This hits an early-return path that skips is_pdf() and is_sandboxed() checks, granting the restricted process full read/write access to the hosting origin’s filesystem.

Affected files:

  • content/browser/file_system/file_system_manager_impl.cc
  • content/browser/child_process_security_policy_impl.cc
  • third_party/blink/public/mojom/filesystem/file_system.mojom

Estimated timestamp from git blame: 2026-02-03

Description

There is a potential defense-in-depth bypass in FileSystemManagerImpl::Open() that allows heavily restricted renderers (such as those hosting PDFs or sandboxed iframes) to access the FileSystem API for their hosting origin.

The vulnerability stems from inconsistent origin validation. FileSystemManagerImpl::Open() performs a security check (CanAccessDataForOrigin) using a renderer-supplied url::Origin parameter, but subsequently calls OpenFileSystem() using the browser-authoritative StorageKey securely captured from the Mojo receiver context.

An attacker in a compromised renderer can exploit this by sending an Open() IPC with a specially crafted, malformed opaque origin. Specifically, they can construct an opaque origin with an empty precursor tuple (i.e., scheme="", host="", port=0) but a valid nonce_if_opaque.

When ChildProcessSecurityPolicyImpl::CanAccessOrigin evaluates this decoy origin, the following occurs:

  1. The origin is parsed as opaque.
  2. origin.GetTupleOrPrecursorTupleIfOpaque() returns the empty, invalid tuple.
  3. The code hits an early-allow path (content/browser/child_process_security_policy_impl.cc, line 2215) designed for opaque origins without precursors (e.g., browser-initiated data: URLs).
  4. This path only verifies that the child process has a valid SecurityState (which it does) and returns true.

Crucially, taking this early-return path bypasses the subsequent call to CanAccessMaybeOpaqueOrigin, skipping the rigorous actual_process_lock.is_sandboxed() and actual_process_lock.is_pdf() checks entirely.

Once the Open() check is bypassed, ContinueOpen proceeds to open the filesystem using the browser-authoritative StorageKey (the victim’s actual origin). Subsequent FileSystem operations (like Read/Write/Create) enforce permissions via CanCommitURL, which passes AccessType::kCanCommitNewOrigin to the policy checks. Because PDF processes and sandboxed frames are explicitly allowed to commit new origins (even if they are restricted from accessing their data), these subsequent checks pass, granting the attacker full access to the victim origin’s sandboxed filesystem.

Impact

This flaw allows process types that are architecturally intended to have zero storage access (like PDF renderers) to access the FileSystem API for the origin hosting them. While the binding’s StorageKey prevents cross-origin data access, this is a significant bypass of the restrictions placed on sandboxed and PDF processes.

Potential Steps to Reproduce

(Note: These are suggested steps based on code analysis; a working PoC has not been executed.)

  1. Compromise a renderer running in a process where ProcessLock.is_sandboxed() or ProcessLock.is_pdf() is true.
  2. Obtain a FileSystemManager Mojo binding via the frame’s BrowserInterfaceBroker.
  3. Craft an Open() IPC call with an opaque origin having an invalid precursor ({scheme:"", host:"", port:0, nonce_if_opaque:<any UnguessableToken>}).
  4. The security check in ChildProcessSecurityPolicyImpl is bypassed due to the invalid precursor, and FileSystemManager opens the filesystem for the binding’s actual StorageKey.
  5. Issue subsequent Create, Write, or ReadDirectory IPCs using the returned root URL. These will succeed because CanCommitURL allows sandboxed/PDF processes to commit new origins.

Suggested Fix

The vestigial origin parameter should be removed from the FileSystemManager::Open Mojo interface entirely. The security check in FileSystemManagerImpl::Open should instead directly use the origin from the securely captured StorageKey (receivers_.current_context().origin()), ensuring validation is always performed against the browser-authoritative origin. (There is already an existing TODO in third_party/blink/public/mojom/filesystem/file_system.mojom tracking the removal of this parameter).

Evaluated with Chrome root at commit: a9cbf6e8b275fe4147435aa905f3b7f5a656f5f0


Results from 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.

View on issue tracker