Overview

Medium
Severity
8.1
CVSS
No
Exploited ITW
Fixed
Fix Status
ImpactIncorrect authorization in FileSystem
DescriptionIncorrect authorization in FileSystem
ComponentFileSystem
Bug ClassAuthorization 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.
Tracker517597701
Fix commit42d042d4eacd (chromium/src) +64/-7
CWECWE-863
CVSS vectorCVSS:3.1/AV:N/AC:L/PR:N/UI:R/S:U/C:H/I:H/A:N
CISA KEVNot listed
CreditedGoogle
Reported2026-05-28 (per vendor advisory)
Fix landed2026-07-30 (public git)
Disclosed2026-09-08
Patch gap40 days — fix public in source before the advisory

Background

`ChildProcessSecurityPolicyImpl`
the browser-process component that decides which resources (origins, files, filesystems) a given renderer/child process is allowed to touch.
`CanCommitURL()` vs `CanAccessDataForOrigin()`
two authorization checks with different strictness: the former uses AccessType::kCanCommitNewOrigin (may this process display/commit the origin) while the latter asks whether the process may actually access the data of that origin.
`FileSystemURL`
a URL abstraction for the sandboxed/temporary filesystem API that carries an origin()/StorageKey identifying whose data the file belongs to.
PDF process
a locked, restricted process type that may commit an origin (to render its PDF) but is not permitted to read or write that origin’s stored data.

Root Cause Analysis

The changed code lives in HasPermissionsForFileSystemFile() in child_process_security_policy_impl.cc (function name taken from the commit subject, not the @@ header). Before the patch, the function gated its per-origin filesystem authorization on CanCommitURL(child_id, filesystem_url.origin().GetURL()), which internally uses AccessType::kCanCommitNewOrigin — a check that only asks whether the process is allowed to commit the origin, not whether it is allowed to access that origin’s data. Because a PDF process (and similar restricted processes) can legitimately commit an origin while being forbidden from touching its stored data, the weaker CanCommitURL() gate let such a process pass the origin check and reach the per-operation Can{Read,Write,Create,...}FileSystemFile decisions. The invariant violated is that FileSystem data-access should follow the same data-access policy that FileSystemManagerImpl::Open() already enforces, but this entry point used a laxer policy.

The fix switches the gate to CanAccessDataForOrigin(child_id, filesystem_url.origin()), so a process that may commit but not access an origin’s data is now correctly rejected; the removed comment/TODO confirm the old behavior was known-suspect (the in-tree TODO(lukasza) explicitly asked whether CanAccessDataForOrigin could be used here). This patch only tightens the check — it adds no new mechanism beyond substituting the stricter authorization predicate.

Key insight
The single mistake was authorizing FileSystem data operations with a commit-oriented check (CanCommitURL / kCanCommitNewOrigin) instead of a data-access check; the fix replaces it with CanAccessDataForOrigin(), aligning the FileSystemURL entry points with the data-access policy the rest of the FileSystem stack enforces.

Attack Path

  1. Obtain a restricted-but-committing process An attacker drives the browser into a process that may commit an origin yet must not access its data, e.g. a PDF process committed to https://foo.com (the regression test PdfProcessSandboxedFileSystem models exactly this via EmbedderIsolationInfo::CreateForPdf() plus AddCommittedOrigin).
  2. Issue a FileSystem operation for that committed origin From that process, request a sandboxed/kFileSystemTypeTemporary FileSystemURL whose origin() matches the committed origin, invoking one of the Can{Read,Write,Create,Delete,Copy,Move}FileSystemFile entry points that route through HasPermissionsForFileSystemFile().
  3. Pass the too-weak origin gate Pre-patch, CanCommitURL() returns true for the committed origin, so the function does not early-return false and instead proceeds to grant the per-operation permission, giving the process incorrect authorization to that origin’s sandboxed filesystem data.
  4. Access data across the intended boundary (inferred) The resulting read/write/create/delete of another security context’s sandboxed filesystem data is the incorrect-authorization primitive; any further escalation beyond cross-context FileSystem data access is (inferred) and not demonstrated by the diff.

Impact Assessment

The vendor advisory rates this medium severity and does not report in-the-wild exploitation; NVD scores it CVSS 8.1. An attacker operating in a restricted process such as a PDF process could gain unauthorized read/write/create/delete access to the sandboxed/Temporary FileSystem data of an origin it has merely committed, bypassing the data-access policy enforced elsewhere. The precondition is control of code executing in such a commit-capable, data-restricted process; any escalation beyond FileSystem data disclosure or corruption is (inferred).

Changed Functions

FunctionChangeNotes
TEST_P
content/browser/security/cpsp/child_process_security_policy_unittest.cc
modified

Files Changed

  • content/browser/security/cpsp/child_process_security_policy_unittest.cc
  • content/browser/security/cpsp/child_process_security_policy_impl.cc

Audit Directions

  • Commit-check used for data authorization
    Search for other call sites that gate data read/write on CanCommitURL() or AccessType::kCanCommitNewOrigin where a CanAccessDataForOrigin() data-access check is the correct predicate.
  • Divergent policy between entry points
    Look for storage/data APIs whose direct entry point (e.g. FileSystemManagerImpl::Open()) enforces a stricter policy than a secondary authorization helper reachable for the same resource, indicating an inconsistent guard.
  • Restricted committing processes
    Audit code paths reachable from PDF, fenced, guest, or other locked processes that may commit an origin but must not touch its data, ensuring each data operation uses CanAccessDataForOrigin() rather than a commit-oriented check.
From 42d042d4eacdf396f18e106a16b3f2045781ae37 Mon Sep 17 00:00:00 2001
From: Eriko Kurimoto <[email protected]>
Date: Thu, 30 Jul 2026 11:27:09 -0700
Subject: [PATCH] Use CanAccessDataForOrigin in HasPermissionsForFileSystemFile

HasPermissionsForFileSystemFile() gated its origin check on
CanCommitURL(), which uses AccessType::kCanCommitNewOrigin and therefore
allows processes (such as PDF processes) that may commit an origin but
should not access its data. Switch to CanAccessDataForOrigin() so that
the per-operation Can{Read,Write,Create,...}FileSystemFile checks apply
the same data-access policy that FileSystemManagerImpl::Open() already
enforces. This also resolves the in-tree TODO.

Update FilePermissionGrantingAndRevoking to commit the test origin so
the stricter check passes, and add a regression test covering a
PDF-locked process and the sandboxed Temporary filesystem.

Bug: 517597701
Change-Id: I6d44550fe3a1b03e5044788f807461d0338f9b26
Reviewed-on: https://chromium-review.googlesource.com/c/chromium/src/+/8173685
Commit-Queue: Charlie Reis <[email protected]>
Reviewed-by: Charlie Reis <[email protected]>
Cr-Commit-Position: refs/heads/main@{#1671258}
---
diff --git a/content/browser/security/cpsp/child_process_security_policy_impl.cc b/content/browser/security/cpsp/child_process_security_policy_impl.cc
index 9de5bf2..9aeb6d5 100644
--- a/content/browser/security/cpsp/child_process_security_policy_impl.cc
+++ b/content/browser/security/cpsp/child_process_security_policy_impl.cc
@@ -2082,14 +2082,12 @@
         child_id, filesystem_url.mount_filesystem_id(), permissions);
   }
 
-  // If |filesystem_url.origin()| is not committable in this process, then this
-  // page should not be able to place content in that origin via the filesystem
-  // API either.
-  // TODO(lukasza): Audit whether CanAccessDataForOrigin can be used directly
-  // here.
+  // If |filesystem_url.origin()| is not accessible in this process, then this
+  // page should not be able to access or place content in that origin via the
+  // filesystem API either.
   // TODO(crbug.com/379869738) Remove GetUnsafeValue.
-  if (!CanCommitURL(child_id.GetUnsafeValue(),
-                    filesystem_url.origin().GetURL())) {
+  if (!CanAccessDataForOrigin(child_id.GetUnsafeValue(),
+                              filesystem_url.origin())) {
     return false;
   }
 
diff --git a/content/browser/security/cpsp/child_process_security_policy_unittest.cc b/content/browser/security/cpsp/child_process_security_policy_unittest.cc
index 11cec88..4f97a94 100644
--- a/content/browser/security/cpsp/child_process_security_policy_unittest.cc
+++ b/content/browser/security/cpsp/child_process_security_policy_unittest.cc
@@ -1007,6 +1007,7 @@
 
   p->AddForTesting(kRendererProcess, browser_context());
   LockProcessIfNeeded(kRendererProcess, browser_context(), GURL("http://foo/"));
+  p->AddCommittedOrigin(kRendererID, url::Origin::Create(GURL("http://foo/")));
 
   base::FilePath file(TEST_PATH("/dir/testfile"));
   file = file.NormalizePathSeparators();
@@ -1059,6 +1060,7 @@
   p->AddForTesting(kRendererProcess, browser_context());
   CheckHasNoFileSystemFilePermission(p, file, url);
   LockProcessIfNeeded(kRendererProcess, browser_context(), GURL("http://foo/"));
+  p->AddCommittedOrigin(kRendererID, url::Origin::Create(GURL("http://foo/")));
   CheckHasNoFileSystemFilePermission(p, file, url);
 
   // Cleanup.
@@ -1842,6 +1844,63 @@
   p->Remove(kRendererProcess);
 }
 
+// Verify that a PDF process is denied access to the sandboxed filesystem of
+// the origin it has committed. This mirrors the data-access expectations in
+// PdfProcessEnforcements above for the FileSystemURL-based entry points.
+TEST_P(ChildProcessSecurityPolicyTest, PdfProcessSandboxedFileSystem) {
+  ChildProcessSecurityPolicyImpl* p =
+      ChildProcessSecurityPolicyImpl::GetInstance();
+
+  p->RegisterFileSystemPermissionPolicy(storage::kFileSystemTypeTemporary,
+                                        storage::FILE_PERMISSION_SANDBOX);
+
+  TestBrowserContext browser_context;
+  p->AddForTesting(kRendererProcess, &browser_context);
+
+  UrlInfo pdf_url_info(
+      UrlInfoInit(GURL("https://foo.com"))
+          .WithEmbedderIsolationInfo(EmbedderIsolationInfo::CreateForPdf()));
+  scoped_refptr<SiteInstanceImpl> pdf_instance =
+      SiteInstanceImpl::CreateForUrlInfo(&browser_context, pdf_url_info,
+                                         /*is_guest=*/false,
+                                         /*is_fenced=*/false,
+                                         /*is_fixed_storage_partition=*/false);
+  p->LockProcess(pdf_instance->GetIsolationContext(), kRendererProcess,
+                 /*is_process_used=*/false,
+                 ProcessLock::FromSiteInfo(pdf_instance->GetSiteInfo()));
+
+  auto foo_origin = url::Origin::Create(GURL("https://foo.com"));
+  p->AddCommittedOrigin(kRendererID, foo_origin);
+
+  base::FilePath file(TEST_PATH("/dir/testfile"));
+  file = file.NormalizePathSeparators();
+  storage::FileSystemURL url = storage::FileSystemURL::CreateForTest(
+      blink::StorageKey::CreateFirstParty(foo_origin),
+      storage::kFileSystemTypeTemporary, file);
+
+  // A PDF process should not be able to access data for any origin, including
+  // an origin that it has committed, so all sandboxed filesystem operations
+  // for that origin should be rejected.
+  EXPECT_FALSE(p->CanReadFileSystemFile(kRendererProcess, url));
+  EXPECT_FALSE(p->CanWriteFileSystemFile(kRendererProcess, url));
+  EXPECT_FALSE(p->CanCreateFileSystemFile(kRendererProcess, url));
+  EXPECT_FALSE(p->CanCreateReadWriteFileSystemFile(kRendererProcess, url));
+  EXPECT_FALSE(p->CanCopyIntoFileSystemFile(kRendererProcess, url));
+  EXPECT_FALSE(p->CanDeleteFileSystemFile(kRendererProcess, url));
+  EXPECT_FALSE(p->CanMoveFileSystemFile(kRendererProcess, url, url));
+  EXPECT_FALSE(p->CanCopyFileSystemFile(kRendererProcess, url, url));
+
+  auto handle = p->CreateHandle(kRendererProcess);
+  EXPECT_FALSE(handle.CanReadFileSystemFile(url));
+  EXPECT_FALSE(handle.CanWriteFileSystemFile(url));
+  EXPECT_FALSE(handle.CanCreateFileSystemFile(url));
+  EXPECT_FALSE(handle.CanDeleteFileSystemFile(url));
+  EXPECT_FALSE(handle.CanMoveFileSystemFile(url, url));
+  EXPECT_FALSE(handle.CanCopyFileSystemFile(url, url));
+
+  p->Remove(kRendererProcess);
+}
+
 // Test the granting of origin permissions, and their interactions with
 // granting scheme permissions.
 TEST_P(ChildProcessSecurityPolicyTest, OriginGranting) {
Loading diff…

Regression Test / PoC

shipped with the fix
diff --git a/content/browser/security/cpsp/child_process_security_policy_unittest.cc b/content/browser/security/cpsp/child_process_security_policy_unittest.cc
index 11cec88..4f97a94 100644
--- a/content/browser/security/cpsp/child_process_security_policy_unittest.cc
+++ b/content/browser/security/cpsp/child_process_security_policy_unittest.cc
@@ -1007,6 +1007,7 @@
 
   p->AddForTesting(kRendererProcess, browser_context());
   LockProcessIfNeeded(kRendererProcess, browser_context(), GURL("http://foo/"));
+  p->AddCommittedOrigin(kRendererID, url::Origin::Create(GURL("http://foo/")));
 
   base::FilePath file(TEST_PATH("/dir/testfile"));
   file = file.NormalizePathSeparators();
@@ -1059,6 +1060,7 @@
   p->AddForTesting(kRendererProcess, browser_context());
   CheckHasNoFileSystemFilePermission(p, file, url);
   LockProcessIfNeeded(kRendererProcess, browser_context(), GURL("http://foo/"));
+  p->AddCommittedOrigin(kRendererID, url::Origin::Create(GURL("http://foo/")));
   CheckHasNoFileSystemFilePermission(p, file, url);
 
   // Cleanup.
@@ -1842,6 +1844,63 @@
   p->Remove(kRendererProcess);
 }
 
+// Verify that a PDF process is denied access to the sandboxed filesystem of
+// the origin it has committed. This mirrors the data-access expectations in
+// PdfProcessEnforcements above for the FileSystemURL-based entry points.
+TEST_P(ChildProcessSecurityPolicyTest, PdfProcessSandboxedFileSystem) {
+  ChildProcessSecurityPolicyImpl* p =
+      ChildProcessSecurityPolicyImpl::GetInstance();
+
+  p->RegisterFileSystemPermissionPolicy(storage::kFileSystemTypeTemporary,
+                                        storage::FILE_PERMISSION_SANDBOX);
+
+  TestBrowserContext browser_context;
+  p->AddForTesting(kRendererProcess, &browser_context);
+
+  UrlInfo pdf_url_info(
+      UrlInfoInit(GURL("https://foo.com"))
+          .WithEmbedderIsolationInfo(EmbedderIsolationInfo::CreateForPdf()));
+  scoped_refptr<SiteInstanceImpl> pdf_instance =
+      SiteInstanceImpl::CreateForUrlInfo(&browser_context, pdf_url_info,
+                                         /*is_guest=*/false,
+                                         /*is_fenced=*/false,
+                                         /*is_fixed_storage_partition=*/false);
+  p->LockProcess(pdf_instance->GetIsolationContext(), kRendererProcess,
+                 /*is_process_used=*/false,
+                 ProcessLock::FromSiteInfo(pdf_instance->GetSiteInfo()));
+
+  auto foo_origin = url::Origin::Create(GURL("https://foo.com"));
+  p->AddCommittedOrigin(kRendererID, foo_origin);
+
+  base::FilePath file(TEST_PATH("/dir/testfile"));
+  file = file.NormalizePathSeparators();
+  storage::FileSystemURL url = storage::FileSystemURL::CreateForTest(
+      blink::StorageKey::CreateFirstParty(foo_origin),
+      storage::kFileSystemTypeTemporary, file);
+
+  // A PDF process should not be able to access data for any origin, including
+  // an origin that it has committed, so all sandboxed filesystem operations
+  // for that origin should be rejected.
+  EXPECT_FALSE(p->CanReadFileSystemFile(kRendererProcess, url));
+  EXPECT_FALSE(p->CanWriteFileSystemFile(kRendererProcess, url));
+  EXPECT_FALSE(p->CanCreateFileSystemFile(kRendererProcess, url));
+  EXPECT_FALSE(p->CanCreateReadWriteFileSystemFile(kRendererProcess, url));
+  EXPECT_FALSE(p->CanCopyIntoFileSystemFile(kRendererProcess, url));
+  EXPECT_FALSE(p->CanDeleteFileSystemFile(kRendererProcess, url));
+  EXPECT_FALSE(p->CanMoveFileSystemFile(kRendererProcess, url, url));
+  EXPECT_FALSE(p->CanCopyFileSystemFile(kRendererProcess, url, url));
+
+  auto handle = p->CreateHandle(kRendererProcess);
+  EXPECT_FALSE(handle.CanReadFileSystemFile(url));
+  EXPECT_FALSE(handle.CanWriteFileSystemFile(url));
+  EXPECT_FALSE(handle.CanCreateFileSystemFile(url));
+  EXPECT_FALSE(handle.CanDeleteFileSystemFile(url));
+  EXPECT_FALSE(handle.CanMoveFileSystemFile(url, url));
+  EXPECT_FALSE(handle.CanCopyFileSystemFile(url, url));
+
+  p->Remove(kRendererProcess);
+}
+
 // Test the granting of origin permissions, and their interactions with
 // granting scheme permissions.
 TEST_P(ChildProcessSecurityPolicyTest, OriginGranting) {
Loading diff…

Original Bug Report

reported by [email protected]

PDF Sandbox Bypass via FileSystemManagerImpl Sibling Methods

Project Fortify, an experimental security project, has identified the following potential security issue. If you’re a feature owner CC-ed on this bug, please do your best to review these reports. Please see https://chromium.googlesource.com/chromium/src/+/main/docs/security/ai-generated-security-bugs-faq.md for more information.

Overview: A compromised PDF renderer process can potentially bypass Site Isolation data restrictions and read, write, create, or delete files in its serving origin’s sandboxed HTML5 FileSystem. While FileSystemManagerImpl::Open() correctly validates access using CanAccessDataForOrigin, its sibling methods utilize CanCommitURL, which inadvertently permits PDF processes to access partitioned storage. This allows a compromised PDF renderer to access same-origin temporary or persistent storage without calling Open().

Affected files:

  • content/browser/child_process_security_policy_impl.cc
  • content/browser/file_system/file_system_manager_impl.cc

Estimated timestamp from git blame: 2024-06-26

Description

A potential security boundary bypass exists in the blink.mojom.FileSystemManager IPC validation layer where a compromised PDF renderer process can access, modify, or delete files within the HTML5 sandboxed FileSystem (Temporary and Persistent storage) of its serving origin.

Normally, PDF renderer processes are isolated from accessing storage, passwords, or other sensitive user data of their hosting origins. This restriction is enforced by ChildProcessSecurityPolicyImpl::IsAccessAllowedForPdfProcess which returns false for AccessType::kCanAccessDataForCommittedOrigin checks.

While FileSystemManagerImpl::Open() correctly validates access via CanAccessDataForOrigin (and thus properly blocks PDF processes), its 13 sibling methods (including Create, Write, Remove, Move, Copy, etc.) bypass Open() and perform authorization checks that resolve to HasPermissionsForFileSystemFile. This function gates access on CanCommitURL instead of CanAccessDataForOrigin. Because PDF processes must be allowed to commit hosting origins for normal frame behavior, CanCommitURL permits the check, resulting in a full storage policy bypass.

Potential Attack Path

Because our analysis is performed statically without a live execution environment, the following steps represent a potential attack scenario:

  1. A user opens a webpage that hosts or embeds an attacker-controlled PDF served from https://example.com/document.pdf.
  2. The browser allocates a dedicated PDF renderer process for the PDF frame, locking the process to https://example.com with PDF-specific restrictions enabled.
  3. The attacker exploits a vulnerability (e.g., in PDFium or V8) inside the PDF renderer to achieve remote code execution (RCE).
  4. The compromised renderer binds the blink.mojom.FileSystemManager interface via BrowserInterfaceBroker.
  5. Instead of calling Open(), the compromised renderer directly invokes any of the interface’s sibling methods, such as Create(path="filesystem:https://example.com/temporary/malicious_file", ...) or Write(...).
  6. On the browser side, FileSystemManagerImpl::Create() parses the URL and invokes ChildProcessSecurityPolicyImpl::CanCreateFileSystemFile.
  7. HasPermissionsForFileSystemFile performs a CanCommitURL check on the origin. Because the PDF process is authorized to commit https://example.com URLs (AccessType::kCanCommitNewOrigin evaluates to true), the check succeeds.
  8. Since temporary and persistent filesystem types possess FILE_PERMISSION_SANDBOX permissions, the request is unconditionally approved, allowing the compromised PDF renderer full read/write access to https://example.com’s sandboxed filesystem.

Code References

In content/browser/child_process_security_policy_impl.cc (lines 1965–1971):

  // If |filesystem_url.origin()| is not committable in this process, then this
  // page should not be able to place content in that origin via the filesystem
  // API either.
  // TODO(lukasza): Audit whether CanAccessDataForOrigin can be used directly
  // here.
  if (!CanCommitURL(child_id.GetUnsafeValue(),
                    filesystem_url.origin().GetURL())) {
    return false;
  }

In content/browser/child_process_security_policy_impl.cc (lines 2284–2288):

  switch (access_type) {
    case AccessType::kCanCommitNewOrigin:
    case AccessType::kHostsOrigin:
      return true;
    case AccessType::kCanAccessDataForCommittedOrigin:
      return false;
  }

Suggested Fix

Update ChildProcessSecurityPolicyImpl::HasPermissionsForFileSystemFile to use CanAccessDataForOrigin rather than CanCommitURL to perform the security check, as suggested by the in-tree TODO comment. This ensures that PDF processes (and other processes with data-access restrictions) are correctly prevented from accessing storage via the file system sibling IPC methods.

Evaluated with Chrome root at commit: 5133b93d189b383c37805b1cf3a9d2dbfe8d7379


Results so far have been promising, but there can be wrong deductions. Feel free to adjust as follows:

  • If you are familiar with the severity guidelines, you may adjust the severity.
  • If this is a false positive, and there’s no work to be done, please close as WAI.
  • If there is work to do here but not a vulnerability, please change the issue type to Task/Bug/FR.

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
Links in the report