CVE-2026-87505
Overview
Changed Functions
| Function | Change | Notes |
|---|---|---|
TEST_Pcontent/browser/security/cpsp/child_process_security_policy_unittest.cc |
modified |
Files Changed
content/browser/security/cpsp/child_process_security_policy_impl.cccontent/browser/security/cpsp/child_process_security_policy_unittest.cc
Patch
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) {
Regression Test / PoC
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) {
Original Bug Report
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.cccontent/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:
- A user opens a webpage that hosts or embeds an attacker-controlled PDF served from
https://example.com/document.pdf. - The browser allocates a dedicated PDF renderer process for the PDF frame, locking the process to
https://example.comwith PDF-specific restrictions enabled. - The attacker exploits a vulnerability (e.g., in PDFium or V8) inside the PDF renderer to achieve remote code execution (RCE).
- The compromised renderer binds the
blink.mojom.FileSystemManagerinterface viaBrowserInterfaceBroker. - Instead of calling
Open(), the compromised renderer directly invokes any of the interface’s sibling methods, such asCreate(path="filesystem:https://example.com/temporary/malicious_file", ...)orWrite(...). - On the browser side,
FileSystemManagerImpl::Create()parses the URL and invokesChildProcessSecurityPolicyImpl::CanCreateFileSystemFile. HasPermissionsForFileSystemFileperforms aCanCommitURLcheck on the origin. Because the PDF process is authorized to commithttps://example.comURLs (AccessType::kCanCommitNewOriginevaluates totrue), the check succeeds.- Since temporary and persistent filesystem types possess
FILE_PERMISSION_SANDBOXpermissions, the request is unconditionally approved, allowing the compromised PDF renderer full read/write access tohttps://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.