CVE-2026-7964
Overview
Changed Functions
| Function | Change | Notes |
|---|---|---|
Blobthird_party/blink/renderer/modules/filesystem/file_system_dispatcher.h |
modified | |
ExecutionContextthird_party/blink/renderer/modules/filesystem/file_system_dispatcher.h |
modified | |
KURLthird_party/blink/renderer/modules/filesystem/file_system_dispatcher.h |
modified | |
SecurityOriginthird_party/blink/renderer/modules/filesystem/file_system_dispatcher.h |
modified |
Files Changed
content/browser/file_system/file_system_manager_impl.cccontent/browser/file_system/file_system_manager_impl.hcontent/common/features.cccontent/common/features.hthird_party/blink/public/mojom/filesystem/file_system.mojomthird_party/blink/renderer/core/fileapi/file_test.ccthird_party/blink/renderer/modules/filesystem/file_system_dispatcher.ccthird_party/blink/renderer/modules/filesystem/file_system_dispatcher.hthird_party/blink/renderer/modules/filesystem/local_file_system.cc
Patch
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);
Regression Test / PoC
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 {}
Original Bug Report
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.cccontent/browser/child_process_security_policy_impl.ccthird_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:
- The origin is parsed as opaque.
origin.GetTupleOrPrecursorTupleIfOpaque()returns the empty, invalid tuple.- 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). - This path only verifies that the child process has a valid
SecurityState(which it does) and returnstrue.
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.)
- Compromise a renderer running in a process where
ProcessLock.is_sandboxed()orProcessLock.is_pdf()is true. - Obtain a
FileSystemManagerMojo binding via the frame’sBrowserInterfaceBroker. - Craft an
Open()IPC call with an opaque origin having an invalid precursor ({scheme:"", host:"", port:0, nonce_if_opaque:<any UnguessableToken>}). - The security check in
ChildProcessSecurityPolicyImplis bypassed due to the invalid precursor, andFileSystemManageropens the filesystem for the binding’s actualStorageKey. - Issue subsequent
Create,Write, orReadDirectoryIPCs using the returned root URL. These will succeed becauseCanCommitURLallows 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.