CVE-2026-17660
Overview
Background
- Browser-initiated file upload
- a network request (e.g.
Web Share Target) whoserequest_bodyreferences localbase::FilePathfiles, submitted to the network service on behalf of the browser process rather than a renderer. - `ChildProcessSecurityPolicy`
- the browser-side authority that tracks which files each process is permitted to read via checks like
CanReadFile(). - `OriginatingProcessId`
- the identifier the network context client uses to distinguish a browser-originated file upload (
is_browser()) from a renderer-originated one. - `kBrowserInitiatedFileUploadValidation`
- the feature flag, enabled by default, that gates the new token-based file-access registration and validation path.
Root Cause Analysis
When NetworkContextClientBaseImpl::HandleFileUploadRequest (via OnFileUploadRequested) processed an upload, it only ran cpsp->CanReadFile() for renderer processes; when process_id.is_browser() was true it bypassed all validation and opened every requested base::FilePath unconditionally. This meant any file path that reached the network service under the browser’s OriginatingProcessId was read with the browser’s full privilege, so a compromised process path feeding browser-tagged upload requests could reference files the browser had no legitimate reason to open.
The fix introduces token-scoped tracking: ScopedBrowserFileAccess registers the referenced files with ChildProcessSecurityPolicy for the navigation’s lifetime, and the client now calls cpsp->CanReadFileForBrowserUpload(file_path) for browser-originated uploads, denying with net::ERR_ACCESS_DENIED any path that was not explicitly granted. Because the grant is created only for files legitimately referenced by a navigation’s request_body and revoked when the loader is destroyed, arbitrary browser-tagged paths no longer satisfy the check.
OriginatingProcessId as inherently trusted and skipping file-access validation entirely for it; the fix replaces that blanket trust with an explicit per-file token grant (CanReadFileForBrowserUpload()) so browser-initiated uploads must also prove authorization.Attack Path
- Reach the browser upload path
Drive a browser-initiated upload flow such as
Web Share Target, where files are attached to a navigationrequest_bodyand forwarded to the network service withOriginatingProcessId::browser(). - Exploit the trust bypass
In the pre-fix
for (const auto& file_path : file_paths)loop,process_id.is_browser()short-circuits theCanReadFile()check, so no per-file authorization is enforced. - Reference unauthorized files Supply or influence file paths that the browser should not read; because validation is skipped, the network service opens them with browser privilege.
- Exfiltrate contents
The opened
base::Filehandles are returned and their contents are sent as part of the upload request body to an attacker-controllable destination.
Impact Assessment
CanReadFile() validation. This operates in the browser/network-service context with the browser’s file-read authority; the precondition is a code path (e.g. Web Share Target) that submits attacker-influenced base::FilePath values under OriginatingProcessId::browser().Changed Functions
| Function | Change | Notes |
|---|---|---|
BindRepeatingcontent/browser/browser_main_loop.cc |
modified | |
forcontent/browser/network_context_client_base_impl.cc |
modified | |
ifcontent/browser/network_context_client_base_impl.cc |
modified | |
TEST_Fcontent/browser/network_context_client_base_impl_unittest.cc |
modified |
Files Changed
content/browser/BUILD.gncontent/browser/browser_main_loop.cccontent/browser/loader/navigation_url_loader_impl.cccontent/browser/loader/navigation_url_loader_impl.hcontent/browser/network_context_client_base_impl.cccontent/browser/network_context_client_base_impl_unittest.cc
Audit Directions
- Process-identity trust shortcutsSearch for authorization checks skipped when a request claims to originate from the browser (
is_browser()), where the code assumes browser origin equals trusted input. - Unvalidated `base::FilePath` to network serviceAudit other paths that forward file paths or
request_bodyreferenced files to the network service without a correspondingChildProcessSecurityPolicygrant/validation. - Scoped-grant lifetime gapsReview
ScopedBrowserFileAccess-style grants to confirm every register (GrantFileForBrowserUpload) is paired with a revoke on loader destruction, so access cannot outlive the navigation that justified it.
Patch
From 1fc7b5f1b300973956c4edcb7e3f8f8d56a56e6a Mon Sep 17 00:00:00 2001
From: Nidhi Jaju <nidhijaju@chromium.org>
Date: Mon, 06 Jul 2026 03:52:17 -0700
Subject: [PATCH] Validate browser-initiated file uploads
This CL introduces token-based tracking and validation for files
uploaded by the browser process to the network service.
We introduce `BrowserFileAccessCallbacks` to `SimpleURLLoader`, allowing
the browser to register file paths with `ChildProcessSecurityPolicy`.
`ScopedBrowserFileAccess` manages token lifetimes for file paths
originating from navigations (e.g., Web Share Target).
Finally, `NetworkContextClientBaseImpl::HandleFileUploadRequest` is
updated to validate the tokens using `CanBrowserReadFile()` when the
originating process is the browser. This entire mechanism is gated
behind the `kFileUploadTokenRegistration` feature flag, which is
enabled by default.
Bug: 497428001
Change-Id: I8a06849dc69b19d3fa71c62ba2ed63813db686fa
Reviewed-on: https://chromium-review.googlesource.com/c/chromium/src/+/7968017
Reviewed-by: Alex Moshchuk <alexmos@chromium.org>
Auto-Submit: Nidhi Jaju <nidhijaju@chromium.org>
Commit-Queue: Nidhi Jaju <nidhijaju@chromium.org>
Reviewed-by: Takashi Toyoshima <toyoshim@chromium.org>
Cr-Commit-Position: refs/heads/main@{#1657089}
---
diff --git a/content/browser/BUILD.gn b/content/browser/BUILD.gn
index 6be9945..700ebede 100644
--- a/content/browser/BUILD.gn
+++ b/content/browser/BUILD.gn
@@ -2172,6 +2172,8 @@
"scheduler/responsiveness/watcher.h",
"scoped_active_url.cc",
"scoped_active_url.h",
+ "scoped_browser_file_access.cc",
+ "scoped_browser_file_access.h",
"screen_details/screen_change_monitor.cc",
"screen_details/screen_change_monitor.h",
"screen_orientation/screen_orientation_provider.cc",
diff --git a/content/browser/browser_main_loop.cc b/content/browser/browser_main_loop.cc
index 865c302..bc44a5da 100644
--- a/content/browser/browser_main_loop.cc
+++ b/content/browser/browser_main_loop.cc
@@ -112,6 +112,7 @@
#include "content/public/browser/browser_main_parts.h"
#include "content/public/browser/browser_task_traits.h"
#include "content/public/browser/browser_thread.h"
+#include "content/public/browser/child_process_security_policy.h"
#include "content/public/browser/content_browser_client.h"
#include "content/public/browser/device_service.h"
#include "content/public/browser/network_service_instance.h"
@@ -147,7 +148,9 @@
#include "services/audio/service.h"
#include "services/data_decoder/public/cpp/service_provider.h"
#include "services/data_decoder/public/mojom/data_decoder_service.mojom.h"
+#include "services/network/public/cpp/features.h"
#include "services/network/public/cpp/network_switches.h"
+#include "services/network/public/cpp/simple_url_loader.h"
#include "services/network/public/mojom/network_service.mojom.h"
#include "services/network/transitional_url_loader_factory_owner.h"
#include "services/tracing/public/cpp/background_tracing/background_tracing_manager.h"
@@ -830,6 +833,24 @@
RenderProcessHost::SetRunRendererInProcess(true);
#endif
+ // Set up the callbacks used by the network layer to track and validate
+ // file access for browser-initiated uploads.
+ if (base::FeatureList::IsEnabled(
+ network::features::kBrowserInitiatedFileUploadValidation)) {
+ network::SimpleURLLoader::FileUploadEventCallbacks callbacks;
+ callbacks.register_callback = base::BindRepeating(
+ [](const base::UnguessableToken& token, const base::FilePath& path) {
+ ChildProcessSecurityPolicy::GetInstance()->GrantFileForBrowserUpload(
+ token, path);
+ });
+ callbacks.revoke_callback =
+ base::BindRepeating([](const base::UnguessableToken& token) {
+ ChildProcessSecurityPolicy::GetInstance()->RevokeFileForBrowserUpload(
+ token);
+ });
+ network::SimpleURLLoader::SetFileUploadEventCallbacks(callbacks);
+ }
+
// Initialize origins that require process isolation. Must be done
// after base::FeatureList is initialized, but before any navigations can
// happen.
diff --git a/content/browser/loader/navigation_url_loader_impl.cc b/content/browser/loader/navigation_url_loader_impl.cc
index d004578..c5073932 100644
--- a/content/browser/loader/navigation_url_loader_impl.cc
+++ b/content/browser/loader/navigation_url_loader_impl.cc
@@ -2170,6 +2170,17 @@
browser_context_, storage_partition_, frame_tree_node,
ukm::SourceIdObj::FromInt64(ukm_source_id_), &bypass_redirect_checks_,
allow_same_site_none_cookies_override_);
+
+ if (base::FeatureList::IsEnabled(
+ network::features::kBrowserInitiatedFileUploadValidation) &&
+ resource_request_->request_body) {
+ std::vector<base::FilePath> files =
+ resource_request_->request_body->GetReferencedFiles();
+ if (!files.empty()) {
+ scoped_browser_file_access_ =
+ std::make_unique<ScopedBrowserFileAccess>(std::move(files));
+ }
+ }
}
// static
diff --git a/content/browser/loader/navigation_url_loader_impl.h b/content/browser/loader/navigation_url_loader_impl.h
index 8dcbce48..9dbfdec 100644
--- a/content/browser/loader/navigation_url_loader_impl.h
+++ b/content/browser/loader/navigation_url_loader_impl.h
@@ -14,6 +14,7 @@
#include "content/browser/loader/navigation_url_loader.h"
#include "content/browser/loader/response_head_update_params.h"
#include "content/browser/navigation_subresource_loader_params.h"
+#include "content/browser/scoped_browser_file_access.h"
#include "content/common/content_export.h"
#include "content/public/browser/frame_tree_node_id.h"
#include "content/public/browser/global_request_id.h"
@@ -599,6 +600,12 @@
// response.
ResponseHeadUpdateParams head_update_params_;
+ // If the navigation request includes a file upload, this object ensures the
+ // browser process is aware that the Network Service is allowed to read the
+ // files. The files are granted access upon creation and revoked when this
+ // loader is destroyed.
+ std::unique_ptr<ScopedBrowserFileAccess> scoped_browser_file_access_;
+
base::WeakPtrFactory<NavigationURLLoaderImpl> weak_factory_{this};
};
diff --git a/content/browser/network_context_client_base_impl.cc b/content/browser/network_context_client_base_impl.cc
index ecc0497c..b4c5a2b4 100644
--- a/content/browser/network_context_client_base_impl.cc
+++ b/content/browser/network_context_client_base_impl.cc
@@ -19,6 +19,7 @@
#include "content/public/common/content_client.h"
#include "mojo/public/cpp/bindings/remote.h"
#include "net/base/net_errors.h"
+#include "services/network/public/cpp/features.h"
#include "services/network/public/mojom/trust_tokens.mojom.h"
namespace content {
@@ -38,9 +39,17 @@
(async ? base::File::FLAG_ASYNC : 0);
ChildProcessSecurityPolicy* cpsp = ChildProcessSecurityPolicy::GetInstance();
for (const auto& file_path : file_paths) {
- if (!process_id.is_browser() &&
- !cpsp->CanReadFile(ToChildProcessId(process_id.renderer_process_id()),
- file_path)) {
+ bool access_denied = false;
+ if (base::FeatureList::IsEnabled(
+ network::features::kBrowserInitiatedFileUploadValidation) &&
+ process_id.is_browser()) {
+ access_denied = !cpsp->CanReadFileForBrowserUpload(file_path);
+ } else if (!process_id.is_browser()) {
+ access_denied = !cpsp->CanReadFile(
+ ToChildProcessId(process_id.renderer_process_id()), file_path);
+ }
+
+ if (access_denied) {
task_runner->PostTask(
FROM_HERE, base::BindOnce(std::move(callback), net::ERR_ACCESS_DENIED,
std::vector<base::File>()));
diff --git a/content/browser/network_context_client_base_impl_unittest.cc b/content/browser/network_context_client_base_impl_unittest.cc
index 2cbf0e6..7da23ceb 100644
--- a/content/browser/network_context_client_base_impl_unittest.cc
+++ b/content/browser/network_context_client_base_impl_unittest.cc
@@ -14,7 +14,9 @@
#include "base/strings/string_view_util.h"
#include "base/test/task_environment.h"
#include "base/test/test_file_util.h"
+#include "base/test/test_future.h"
#include "base/types/fixed_array.h"
+#include "base/unguessable_token.h"
#include "build/build_config.h"
#include "content/browser/security/cpsp/child_process_security_policy_impl.h"
#include "content/public/browser/network_context_client_base.h"
@@ -223,19 +225,39 @@
EXPECT_EQ(0U, response.opened_files.size());
}
-TEST_F(NetworkContextClientBaseTest, UploadFromBrowserProcess) {
+TEST_F(NetworkContextClientBaseTest,
+ OnFileUploadRequested_BrowserProcess_AccessDenied) {
base::FilePath path = temp_dir_.GetPath().AppendASCII("filename");
CreateFile(path, kFileContent1);
- // No grant necessary for browser process.
- UploadResponse response;
+ base::test::TestFuture<int, std::vector<base::File>> future;
client_.OnFileUploadRequested(
network::OriginatingProcessId::browser(), false, {path},
- /*destination_url=*/GURL(), std::move(response.callback));
- task_environment_.RunUntilIdle();
Regression Test / PoC
diff --git a/content/browser/network_context_client_base_impl_unittest.cc b/content/browser/network_context_client_base_impl_unittest.cc
index 2cbf0e6..7da23ceb 100644
--- a/content/browser/network_context_client_base_impl_unittest.cc
+++ b/content/browser/network_context_client_base_impl_unittest.cc
@@ -14,7 +14,9 @@
#include "base/strings/string_view_util.h"
#include "base/test/task_environment.h"
#include "base/test/test_file_util.h"
+#include "base/test/test_future.h"
#include "base/types/fixed_array.h"
+#include "base/unguessable_token.h"
#include "build/build_config.h"
#include "content/browser/security/cpsp/child_process_security_policy_impl.h"
#include "content/public/browser/network_context_client_base.h"
@@ -223,19 +225,39 @@
EXPECT_EQ(0U, response.opened_files.size());
}
-TEST_F(NetworkContextClientBaseTest, UploadFromBrowserProcess) {
+TEST_F(NetworkContextClientBaseTest,
+ OnFileUploadRequested_BrowserProcess_AccessDenied) {
base::FilePath path = temp_dir_.GetPath().AppendASCII("filename");
CreateFile(path, kFileContent1);
- // No grant necessary for browser process.
- UploadResponse response;
+ base::test::TestFuture<int, std::vector<base::File>> future;
client_.OnFileUploadRequested(
network::OriginatingProcessId::browser(), false, {path},
- /*destination_url=*/GURL(), std::move(response.callback));
- task_environment_.RunUntilIdle();
- EXPECT_EQ(net::OK, response.error_code);
- ASSERT_EQ(1U, response.opened_files.size());
- ValidateFileContents(response.opened_files[0], kFileContent1);
+ /*destination_url=*/GURL(), future.GetCallback());
+ EXPECT_EQ(net::ERR_ACCESS_DENIED, future.Get<0>());
+ EXPECT_EQ(0U, future.Get<1>().size());
+}
+
+TEST_F(NetworkContextClientBaseTest,
+ OnFileUploadRequested_BrowserProcess_AccessGranted) {
+ base::FilePath path = temp_dir_.GetPath().AppendASCII("filename");
+ CreateFile(path, kFileContent1);
+
+ base::UnguessableToken token = base::UnguessableToken::Create();
+ ChildProcessSecurityPolicyImpl::GetInstance()->GrantFileForBrowserUpload(
+ token, path);
+
+ base::test::TestFuture<int, std::vector<base::File>> future;
+ client_.OnFileUploadRequested(
+ network::OriginatingProcessId::browser(), false, {path},
+ /*destination_url=*/GURL(), future.GetCallback());
+ EXPECT_EQ(net::OK, future.Get<0>());
+ std::vector<base::File> opened_files = std::get<1>(future.Take());
+ ASSERT_EQ(1U, opened_files.size());
+ ValidateFileContents(opened_files[0], kFileContent1);
+
+ ChildProcessSecurityPolicyImpl::GetInstance()->RevokeFileForBrowserUpload(
+ token);
}
} // namespace content
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 9f442fed..11cec88 100644
--- a/content/browser/security/cpsp/child_process_security_policy_unittest.cc
+++ b/content/browser/security/cpsp/child_process_security_policy_unittest.cc
@@ -3595,6 +3595,101 @@
p->EraseOriginAgentClusterState(browsing_instance_id);
}
+TEST_P(ChildProcessSecurityPolicyTest,
+ BrowserFileAccess_GrantAndRevokeSingleToken) {
+ auto* p = ChildProcessSecurityPolicyImpl::GetInstance();
+ base::FilePath file(TEST_PATH("/foo/bar"));
+ base::UnguessableToken token = base::UnguessableToken::Create();
+
+ EXPECT_FALSE(p->CanReadFileForBrowserUpload(file));
+
+ p->GrantFileForBrowserUpload(token, file);
+ EXPECT_TRUE(p->CanReadFileForBrowserUpload(file));
+
+ p->RevokeFileForBrowserUpload(token);
+ EXPECT_FALSE(p->CanReadFileForBrowserUpload(file));
+}
+
+TEST_P(ChildProcessSecurityPolicyTest,
+ BrowserFileAccess_MultipleTokensSameFile) {
+ auto* p = ChildProcessSecurityPolicyImpl::GetInstance();
+ base::FilePath file(TEST_PATH("/foo/bar"));
+ base::UnguessableToken token1 = base::UnguessableToken::Create();
+ base::UnguessableToken token2 = base::UnguessableToken::Create();
+
+ EXPECT_FALSE(p->CanReadFileForBrowserUpload(file));
+
+ p->GrantFileForBrowserUpload(token1, file);
+ p->GrantFileForBrowserUpload(token2, file);
+ EXPECT_TRUE(p->CanReadFileForBrowserUpload(file));
+
+ p->RevokeFileForBrowserUpload(token1);
+ EXPECT_TRUE(p->CanReadFileForBrowserUpload(file));
+
+ p->RevokeFileForBrowserUpload(token2);
+ EXPECT_FALSE(p->CanReadFileForBrowserUpload(file));
+}
+
+TEST_P(ChildProcessSecurityPolicyTest,
+ BrowserFileAccess_MultipleTokensDifferentFiles) {
+ auto* p = ChildProcessSecurityPolicyImpl::GetInstance();
+ base::FilePath file1(TEST_PATH("/foo/bar1"));
+ base::FilePath file2(TEST_PATH("/foo/bar2"));
+ base::UnguessableToken token1 = base::UnguessableToken::Create();
+ base::UnguessableToken token2 = base::UnguessableToken::Create();
+
+ p->GrantFileForBrowserUpload(token1, file1);
+ p->GrantFileForBrowserUpload(token2, file2);
+
+ EXPECT_TRUE(p->CanReadFileForBrowserUpload(file1));
+ EXPECT_TRUE(p->CanReadFileForBrowserUpload(file2));
+
+ p->RevokeFileForBrowserUpload(token1);
+ EXPECT_FALSE(p->CanReadFileForBrowserUpload(file1));
+ EXPECT_TRUE(p->CanReadFileForBrowserUpload(file2));
+
+ p->RevokeFileForBrowserUpload(token2);
+ EXPECT_FALSE(p->CanReadFileForBrowserUpload(file1));
+ EXPECT_FALSE(p->CanReadFileForBrowserUpload(file2));
+}
+
+TEST_P(ChildProcessSecurityPolicyTest,
+ BrowserFileAccess_OneTokenMultipleFiles) {
+ auto* p = ChildProcessSecurityPolicyImpl::GetInstance();
+ base::FilePath file1(TEST_PATH("/foo/bar1"));
+ base::FilePath file2(TEST_PATH("/foo/bar2"));
+ base::UnguessableToken token = base::UnguessableToken::Create();
+
+ p->GrantFileForBrowserUpload(token, file1);
+ p->GrantFileForBrowserUpload(token, file2);
+
+ EXPECT_TRUE(p->CanReadFileForBrowserUpload(file1));
+ EXPECT_TRUE(p->CanReadFileForBrowserUpload(file2));
+
+ p->RevokeFileForBrowserUpload(token);
+ EXPECT_FALSE(p->CanReadFileForBrowserUpload(file1));
+ EXPECT_FALSE(p->CanReadFileForBrowserUpload(file2));
+}
+
+TEST_P(ChildProcessSecurityPolicyTest,
+ BrowserFileAccess_RevokeNonExistentToken) {
+ auto* p = ChildProcessSecurityPolicyImpl::GetInstance();
+ base::FilePath file(TEST_PATH("/foo/bar"));
+ base::UnguessableToken valid_token = base::UnguessableToken::Create();
+ base::UnguessableToken invalid_token = base::UnguessableToken::Create();
+
+ p->GrantFileForBrowserUpload(valid_token, file);
+ EXPECT_TRUE(p->CanReadFileForBrowserUpload(file));
+
+ // Revoking a token that was never granted access shouldn't crash or modify
+ // other grants.
+ p->RevokeFileForBrowserUpload(invalid_token);
+ EXPECT_TRUE(p->CanReadFileForBrowserUpload(file));
+
+ p->RevokeFileForBrowserUpload(valid_token);
+ EXPECT_FALSE(p->CanReadFileForBrowserUpload(file));
+}
+
// This intentionally excludes {kCppOnly, kProcessState} since that behaves
// the same as {kCppOnly, kMain} and is redundant to test separately.
const CpspTestParam kCpspTestParams[] = {
Original Bug Report
Sandbox Escape: Arbitrary File Read via forged OriginatingProcessId in Network Service
Project Fortify, an experimental security project, has identified the following potential security issue.
Overview: A compromised Network Service can potentially bypass file access checks and read arbitrary host files. By forging the OriginatingProcessId parameter in the NetworkContextClient::OnFileUploadRequested Mojo IPC, the Network Service can trick the browser process into skipping ChildProcessSecurityPolicy validation, returning open handles for sensitive files.
Affected files:
content/browser/network_context_client_base_impl.cccontent/browser/storage_partition_impl.ccservices/network/public/mojom/originating_process_id.mojom
Estimated timestamp from git blame: 2026-02-25
Summary
A logical flaw in the handling of file upload requests allows a compromised Network Service to read arbitrary files, leading to a potential sandbox escape. The NetworkContextClient::OnFileUploadRequested Mojo interface allows the Network Service to request the browser process to open files for upload. This method accepts an OriginatingProcessId parameter. Because the browser process explicitly trusts this parameter without secondary validation, a compromised Network Service can claim to be the browser process, thereby bypassing ChildProcessSecurityPolicy checks and obtaining readable file handles to any file on the host OS.
Technical Details
The vulnerability exists within content/browser/network_context_client_base_impl.cc. When the Network Service sends an OnFileUploadRequested IPC, the request is eventually handled by HandleFileUploadRequest.
void HandleFileUploadRequest(
network::OriginatingProcessId process_id,
// ...
const std::vector<base::FilePath>& file_paths,
/* ... */) {
// ...
ChildProcessSecurityPolicy* cpsp = ChildProcessSecurityPolicy::GetInstance();
for (const auto& file_path : file_paths) {
if (!process_id.is_browser() &&
!cpsp->CanReadFile(ToChildProcessId(process_id.renderer_process_id()),
file_path)) {
// Access Denied
return;
}
// File is opened with base::File::FLAG_OPEN | base::File::FLAG_READ
files.emplace_back(file_path, file_flags);
// ...
}
// Returns the opened file handles to the Network Service over IPC
}
The process_id parameter is fully controlled by the Network Service. If the Network Service populates the union such that process_id.is_browser() returns true, the first half of the logical && condition evaluates to false. Due to C++ short-circuit evaluation, cpsp->CanReadFile(...) is never called.
As a result, the unsandboxed browser process opens the requested file paths with read privileges and returns the resulting base::File handles to the Network Service. On platforms like macOS, Windows, and Linux where the Network Service is sandboxed, this allows the attacker to silently escape the sandbox and exfiltrate sensitive files (e.g., ~/.ssh/id_rsa, Login Data, etc.).
(Note: These are suggested/potential steps based on source code analysis, as our tooling agent does not yet have the ability to run code or execute a live proof-of-concept).
Suggested Attacker Steps
- Exploit a vulnerability (e.g., memory corruption) in the Network Service to achieve arbitrary code execution within its sandboxed process.
- Obtain the existing
pending_remote<NetworkContextClient>Mojo endpoint, which the browser passed to the Network Service upon initialization. - Construct an
OnFileUploadRequestedMojo message. Set theprocess_idparameter toOriginatingProcessId::browser(). - Populate the
file_pathsarray with absolute paths to sensitive host files. - Send the IPC to the browser process and wait for the callback.
- Retrieve the
ReadOnlyFilehandles from the IPC callback response and read the file contents directly into the Network Service.
Suggested Fix
The browser process must not trust the OriginatingProcessId provided by the Network Service to bypass critical security checks, as the Network Service itself is sandboxed and considered a lesser-trusted domain than the browser process.
To fix this:
- Do not allow the Network Service to request file opens on behalf of the browser. If the browser initiates a network request involving local files, the browser should open the files itself and pass the resulting file handles (or a
DataPipe) down to the Network Service, rather than passing a file path and expecting the Network Service to ask the browser to open it later. - Alternatively, if the architecture strictly requires this round-trip, securely validate that the request originated from the browser process. This could be done by associating requests with secure, unguessable tokens generated by the browser when the upload was initially dispatched, ensuring the Network Service cannot forge a browser-originated request.
Evaluated with Chrome root at commit: 876d480da1f794d87813cfa2e6ff4fcf9771e939
Results 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.