CVE-2026-17856
Overview
Changed Functions
| Function | Change | Notes |
|---|---|---|
TEST_Fservices/network/public/cpp/simple_url_loader_unittest.cc |
modified | |
forservices/network/public/cpp/simple_url_loader_unittest.cc |
modified |
Files Changed
services/network/public/cpp/simple_url_loader.ccservices/network/public/cpp/simple_url_loader_unittest.cc
Patch
From 0ad3c50861b7d3f0d214b7d93af6728738c5bca5 Mon Sep 17 00:00:00 2001
From: Bryan Oltman <bryanoltman@google.com>
Date: Tue, 16 Jun 2026 13:23:32 -0700
Subject: [PATCH] Prevent downloading to symlinks in SimpleURLLoader
When SimpleURLLoader downloads to a user-specified file path, initialize
the file with base::File::FLAG_NO_FOLLOW. This prevents the loader from
resolving and overwriting the target of a symbolic link.
Fixed: 519991751
Change-Id: I72b6a516820af8cead6ddb494e393e63a59c8d0f
Reviewed-on: https://chromium-review.googlesource.com/c/chromium/src/+/7944745
Commit-Queue: Bryan Oltman <bryanoltman@google.com>
Reviewed-by: Reilly Grant <reillyg@chromium.org>
Cr-Commit-Position: refs/heads/main@{#1647848}
---
diff --git a/services/network/public/cpp/simple_url_loader.cc b/services/network/public/cpp/simple_url_loader.cc
index 1ac2f96..1ceeb607 100644
--- a/services/network/public/cpp/simple_url_loader.cc
+++ b/services/network/public/cpp/simple_url_loader.cc
@@ -1026,8 +1026,9 @@
file_ = base::CreateAndOpenTemporaryFileInDir(temp_dir, &path_);
}
} else {
- file_.Initialize(
- path_, base::File::FLAG_WRITE | base::File::FLAG_CREATE_ALWAYS);
+ file_.Initialize(path_, base::File::FLAG_WRITE |
+ base::File::FLAG_CREATE_ALWAYS |
+ base::File::FLAG_NO_FOLLOW);
}
// If CreateTemporaryFile() or File::Initialize() failed, report failure.
diff --git a/services/network/public/cpp/simple_url_loader_unittest.cc b/services/network/public/cpp/simple_url_loader_unittest.cc
index f2f2cce..9356b0a 100644
--- a/services/network/public/cpp/simple_url_loader_unittest.cc
+++ b/services/network/public/cpp/simple_url_loader_unittest.cc
@@ -3565,6 +3565,44 @@
EXPECT_FALSE(test_helper->response_body());
}
+#if BUILDFLAG(IS_POSIX)
+// Make sure that downloading to a path that is a symbolic link fails (does not
+// overwrite the target of the symlink).
+TEST_F(SimpleURLLoaderFileTest, DownloadToSymlinkFails) {
+ base::ScopedAllowBlockingForTesting allow_blocking;
+ base::ScopedTempDir target_dir;
+ ASSERT_TRUE(target_dir.CreateUniqueTempDir());
+
+ // Create a target file (sensitive file) containing "sensitive data"
+ base::FilePath target_file =
+ target_dir.GetPath().AppendASCII("sensitive_target.txt");
+ std::string sensitive_data = "sensitive data";
+ ASSERT_TRUE(base::WriteFile(target_file, sensitive_data));
+
+ // Create a symlink pointing to the target file
+ std::unique_ptr<SimpleLoaderTestHelper> test_helper =
+ CreateHelperForURL(test_server_.GetURL("/echo"));
+ base::FilePath symlink_path = test_helper->dest_path();
+
+ // Make sure dest_path doesn't exist before we create the symlink
+ base::DeleteFile(symlink_path);
+
+ ASSERT_TRUE(base::CreateSymbolicLink(target_file, symlink_path));
+
+ // Start downloading to the symlink path
+ test_helper->set_expect_path_exists_on_error(true);
+ test_helper->StartSimpleLoaderAndWait(url_loader_factory_.get());
+
+ // Verify that:
+ // - The download failed or was rejected.
+ // - The target file was NOT overwritten/modified.
+ EXPECT_NE(net::OK, test_helper->simple_url_loader()->NetError());
+ std::string actual_data;
+ ASSERT_TRUE(base::ReadFileToString(target_file, &actual_data));
+ EXPECT_EQ(sensitive_data, actual_data);
+}
+#endif // BUILDFLAG(IS_POSIX)
+
// Make sure that destroying the loader destroys a partially downloaded file.
TEST_F(SimpleURLLoaderFileTest, DeleteLoaderDuringRequestDestroysFile) {
for (bool body_data_read : {false, true}) {
Regression Test / PoC
diff --git a/services/network/public/cpp/simple_url_loader_unittest.cc b/services/network/public/cpp/simple_url_loader_unittest.cc
index f2f2cce..9356b0a 100644
--- a/services/network/public/cpp/simple_url_loader_unittest.cc
+++ b/services/network/public/cpp/simple_url_loader_unittest.cc
@@ -3565,6 +3565,44 @@
EXPECT_FALSE(test_helper->response_body());
}
+#if BUILDFLAG(IS_POSIX)
+// Make sure that downloading to a path that is a symbolic link fails (does not
+// overwrite the target of the symlink).
+TEST_F(SimpleURLLoaderFileTest, DownloadToSymlinkFails) {
+ base::ScopedAllowBlockingForTesting allow_blocking;
+ base::ScopedTempDir target_dir;
+ ASSERT_TRUE(target_dir.CreateUniqueTempDir());
+
+ // Create a target file (sensitive file) containing "sensitive data"
+ base::FilePath target_file =
+ target_dir.GetPath().AppendASCII("sensitive_target.txt");
+ std::string sensitive_data = "sensitive data";
+ ASSERT_TRUE(base::WriteFile(target_file, sensitive_data));
+
+ // Create a symlink pointing to the target file
+ std::unique_ptr<SimpleLoaderTestHelper> test_helper =
+ CreateHelperForURL(test_server_.GetURL("/echo"));
+ base::FilePath symlink_path = test_helper->dest_path();
+
+ // Make sure dest_path doesn't exist before we create the symlink
+ base::DeleteFile(symlink_path);
+
+ ASSERT_TRUE(base::CreateSymbolicLink(target_file, symlink_path));
+
+ // Start downloading to the symlink path
+ test_helper->set_expect_path_exists_on_error(true);
+ test_helper->StartSimpleLoaderAndWait(url_loader_factory_.get());
+
+ // Verify that:
+ // - The download failed or was rejected.
+ // - The target file was NOT overwritten/modified.
+ EXPECT_NE(net::OK, test_helper->simple_url_loader()->NetError());
+ std::string actual_data;
+ ASSERT_TRUE(base::ReadFileToString(target_file, &actual_data));
+ EXPECT_EQ(sensitive_data, actual_data);
+}
+#endif // BUILDFLAG(IS_POSIX)
+
// Make sure that destroying the loader destroys a partially downloaded file.
TEST_F(SimpleURLLoaderFileTest, DeleteLoaderDuringRequestDestroysFile) {
for (bool body_data_read : {false, true}) {
Original Bug Report
Potential Sandbox Escape on macOS via base::ScopedTempFile and SimpleURLLoader TOCTOU
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 potential systemic Time-of-Check to Time-of-Use (TOCTOU) vulnerability exists when using base::ScopedTempFile in conjunction with path-based reopens (such as SimpleURLLoader::DownloadToFile). Because macOS GPU and Network sandbox profiles permit write and unlink access to the shared temporary directory, a compromised child process could replace the temporary file with a symbolic link. Subsequent write operations by the unsandboxed browser process would then follow the symlink, leading to arbitrary file write or truncation outside the sandbox.
Affected files:
base/files/scoped_temp_file.ccbase/files/scoped_temp_file.hbase/files/file_proxy.ccchrome/browser/enterprise/data_protection/file_snapshot_creator.cccomponents/webapps/isolated_web_apps/download/bundle_downloader.ccservices/network/public/cpp/simple_url_loader.cc
Estimated timestamp from git blame: 2023-09-20
Potential Systemic TOCTOU vulnerability in base::ScopedTempFile and SimpleURLLoader on macOS
Summary
A potential design flaw in base::ScopedTempFile exposes callers to path-based reopening races. On macOS, sandboxed child processes (e.g., GPU or Network processes) share access to the system temporary directory (darwin-user-temp-dir). Because base::ScopedTempFile closes its file descriptor immediately after creation and only maintains the string path, a compromised child process can race to replace the temp file with a symlink. When the browser process subsequently reopens the file path (for example, via SimpleURLLoader::DownloadToFile with create_temp_file = false), it uses base::File::Initialize with FLAG_CREATE_ALWAYS (equivalent to POSIX O_CREAT | O_TRUNC) without O_NOFOLLOW. This causes the unsandboxed browser process to follow the symlink and write or truncate arbitrary files at browser-process privilege levels, resulting in a potential sandbox escape.
Potential Vulnerability Mechanism
- Creation: A high-privilege component in the browser process (such as
IsolatedWebAppUpdateDiscoveryTaskduring IWA updates orIwaInstallerduring policy-enforced installation) initiates a temporary file allocation usingScopedTempWebBundleFile::Create()(components/webapps/isolated_web_apps/download/bundle_downloader.cc:25). - Handle Release: Under the hood,
base::ScopedTempFile::Create()usesbase::CreateTemporaryFilewhich executesmkstemp(base/files/file_util_posix.cc:865). It returns aScopedFDwhich is immediately destroyed on scope exit, closing the secure file handle. The class only retains the string path (base/files/scoped_temp_file.cc:37-40). - The Race: Under macOS sandbox policies, sandboxed child processes (such as GPU or Network) are granted
file-write-unlinkandfile-write-createaccess to the shared subpathdarwin-user-temp-dir(sandbox/policy/mac/gpu.sb:124-128). A compromised process can monitor the temporary directory, detect the creation of the temporary file,unlink()it, and place a symlink at the same path pointing to a sensitive file (e.g.,~/Library/LaunchAgents/pwn.plist). - Reopening and Write: The browser process instructs
SimpleURLLoader::DownloadToFileto write downloaded data to theScopedTempWebBundleFile’s path. Because an explicit path is provided, the network service invokesSaveToFileBodyHandler::FileWriterwithcreate_temp_file_ = false(services/network/public/cpp/simple_url_loader.cc:1349-1352). - Unchecked Open:
FileWriteropens the target file usingbase::File::Initializewithbase::File::FLAG_CREATE_ALWAYS(services/network/public/cpp/simple_url_loader.cc:1029), which compiles toopen(..., O_CREAT | O_TRUNC | O_WRONLY)(base/files/file_posix.cc:573-577). Crucially,O_NOFOLLOWis not specified. The kernel follows the symlink, truncating and overwriting the target file outside the sandbox at browser-process privilege level.
Note: Our static analysis tools cannot execute code or generate live proofs of concept, so these steps represent a potential attack sequence derived from manual code tracing.
Impact
If successfully exploited, a compromised GPU or Network child process could escape the macOS App Sandbox by writing to user configuration files (such as LaunchAgents), executing arbitrary code at the user’s full privilege level.
Suggested Remediation
- Harden
base::File::DoInitializeto restrict symlink traversal when opening/creating temporary files or whenFLAG_CREATE_ALWAYSis specified, possibly by leveragingO_NOFOLLOWor verifying file descriptors usingfstat. - Deprecate path-only temporary file structures (like
base::ScopedTempFile) in favor of classes that retain and pass an openbase::FileorScopedFDhandle (such asCreateAndOpenTemporaryFileInDir), eliminating the necessity of performing path-based reopens in the browser process.
Evaluated with Chrome root at commit: 57b021e1fdae94a215627d29aeb1ccf2eb5b3e91
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.
- https://source.chromium.org/chromium/chromium/src/+/main:base/files/file_posix.cc;l=573
- https://source.chromium.org/chromium/chromium/src/+/main:base/files/file_util_posix.cc;l=865
- https://source.chromium.org/chromium/chromium/src/+/main:base/files/scoped_temp_file.cc;l=37
- https://source.chromium.org/chromium/chromium/src/+/main:components/webapps/isolated_web_apps/download/bundle_downloader.cc;l=25
- https://source.chromium.org/chromium/chromium/src/+/main:sandbox/policy/mac/gpu.sb;l=124-128
- https://source.chromium.org/chromium/chromium/src/+/main:services/network/public/cpp/simple_url_loader.cc;l=1029
- https://source.chromium.org/chromium/chromium/src/+/main:services/network/public/cpp/simple_url_loader.cc;l=1349