CVE-2026-17709
Overview
Files Changed
components/download/internal/common/download_item_impl.cc
Patch
From c0ce4e540370f07afd1608a205e24a0166bd2efc Mon Sep 17 00:00:00 2001 From: Min Qin <[email protected]> Date: Fri, 05 Jun 2026 15:41:18 -0700 Subject: [PATCH] Open and copy temporary file atomically in MakeCopyOfDownloadFile. This CL does the following: 1. Create and open the temporary file atomically using base::CreateAndOpenTemporaryFileInDir, which returns an open base::File. 2. Open the source file. 3. Copy the contents using base::CopyFileContents, which operates directly on the open file descriptors. This prevents the path-reopening race condition and is secure on all platforms. Bug: 519981494 Test: components_unittests --gtest_filter=DownloadItemTest.CopyDownload Change-Id: I48e908ae4711653e76c08f0ccafe3dfeda01163c Reviewed-on: https://chromium-review.googlesource.com/c/chromium/src/+/7905881 Reviewed-by: Yaw Frempong <[email protected]> Reviewed-by: Lily Chen <[email protected]> Commit-Queue: Min Qin <[email protected]> Cr-Commit-Position: refs/heads/main@{#1642651} --- diff --git a/components/download/internal/common/download_item_impl.cc b/components/download/internal/common/download_item_impl.cc index a9c5c3d..4b6015cc 100644 --- a/components/download/internal/common/download_item_impl.cc +++ b/components/download/internal/common/download_item_impl.cc @@ -29,6 +29,7 @@ #include <vector> #include "base/check_is_test.h" +#include "base/files/file.h" #include "base/files/file_util.h" #include "base/format_macros.h" #include "base/functional/bind.h" @@ -101,11 +102,26 @@ base::FilePath MakeCopyOfDownloadFile(DownloadFile* download_file) { DCHECK(GetDownloadTaskRunner()->RunsTasksInCurrentSequence()); - base::FilePath temp_file_path; - if (!base::CreateTemporaryFile(&temp_file_path)) + base::FilePath temp_dir; + if (!base::GetTempDir(&temp_dir)) { return base::FilePath(); + } - if (!base::CopyFile(download_file->FullPath(), temp_file_path)) { + base::FilePath temp_file_path; + base::File temp_file = + base::CreateAndOpenTemporaryFileInDir(temp_dir, &temp_file_path); + if (!temp_file.IsValid()) { + return base::FilePath(); + } + + base::File source_file(download_file->FullPath(), + base::File::FLAG_OPEN | base::File::FLAG_READ); + if (!source_file.IsValid()) { + DeleteDownloadedFile(temp_file_path); + return base::FilePath(); + } + + if (!base::CopyFileContents(source_file, temp_file)) { DeleteDownloadedFile(temp_file_path); return base::FilePath(); }
Original Bug Report
Potential macOS Sandbox Escape via Symlink TOCTOU in MakeCopyOfDownloadFile
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 Time-of-Check to Time-of-Use (TOCTOU) vulnerability in MakeCopyOfDownloadFile may allow a compromised sandboxed process to escape the macOS sandbox. By exploiting a race condition in the shared temporary directory, an attacker could replace a closed temporary file with a symbolic link. Because the subsequent copy operation follows symbolic links, this could redirect the unsandboxed browser process to write attacker-controlled bytes to arbitrary file paths.
Affected files:
components/download/internal/common/download_item_impl.cc
Estimated timestamp from git blame: 2016-11-16
Root Cause Analysis
In components/download/internal/common/download_item_impl.cc, the function MakeCopyOfDownloadFile prepares a copy of a downloaded file to a temporary location:
base::FilePath MakeCopyOfDownloadFile(DownloadFile* download_file) {
DCHECK(GetDownloadTaskRunner()->RunsTasksInCurrentSequence());
base::FilePath temp_file_path;
if (!base::CreateTemporaryFile(&temp_file_path)) // (1) File created, FD closed
return base::FilePath();
if (!base::CopyFile(download_file->FullPath(), // (2) Copied by path
temp_file_path)) {
DeleteDownloadedFile(temp_file_path);
return base::FilePath();
}
return temp_file_path;
}
- FD is closed early:
base::CreateTemporaryFileinternally creates the temporary file and immediately closes the file descriptor (seebase/files/file_util_posix.cc). - Path-based copy follows symlinks: On macOS,
base::CopyFileuses Apple’scopyfile()API withCOPYFILE_DATAbut without theCOPYFILE_NOFOLLOW_DSTflag (seebase/files/file_util_apple.mm):By default,bool CopyFile(const FilePath& from_path, const FilePath& to_path) { ... return (copyfile(from_path.value().c_str(), to_path.value().c_str(), /*state=*/nullptr, COPYFILE_DATA) == 0); }copyfile()opens the destination withO_WRONLY|O_CREAT|O_TRUNCand follows symbolic links unlessCOPYFILE_NOFOLLOW_DSTis explicitly passed in the flags.
Shared Temp Directory Across Trust Boundary
The temporary file is created in _CS_DARWIN_USER_TEMP_DIR. This same directory is shared with sandboxed child processes (such as the Network Service or GPU process) under the Seatbelt parameter darwin-user-temp-dir. These sandboxed processes are granted write/create/unlink permissions in this subpath:
- Network Service sandbox profile (
network.sb):(allow file-read* file-write* (subpath (param darwin-user-temp-dir))) - GPU sandbox profile (
gpu.sb):(allow file-read* file-write-data file-write-create file-write-owner file-write-unlink (subpath (param darwin-user-temp-dir)))
Potential Exploitation Path
Because our tooling agent does not have the ability to run code, these are suggested/potential exploitation steps an attacker could follow to trigger the vulnerability from a compromised sandboxed process (e.g., the Network Service):
- The compromised process spawns a thread to poll the shared temporary directory for new entries matching the
.com.google.Chrome.*prefix. - The attacker triggers a Safe Browsing download feedback flow (e.g., forging a
ClientDownloadResponsewithupload=trueand an eligible verdict likeUNCOMMON). - The browser process receives the response and dispatches
MakeCopyOfDownloadFileto the ThreadPool. - When
MakeCopyOfDownloadFilecallsbase::CreateTemporaryFile(), the temporary file is created and its file descriptor is closed. - The attacker’s polling thread detects the newly created file, immediately calls
unlink()on it, and creates asymlink()with the same name pointing to a sensitive target (such as~/Library/LaunchAgents/com.attacker.plist). - The browser process calls
copyfile()on the path. Becausecopyfile()follows symlinks, it writes the attacker-controlled download payload into the targeted configuration file outside the sandbox. - On next login, the newly written configuration file executes arbitrary code outside the sandbox.
Suggested Fix
To resolve this potential TOCTOU vulnerability, Chrome should avoid closing the file descriptor and copying by path. Instead, the temporary file should be kept open, and the contents should be copied directly via the open file descriptor:
- Retrieve the file handle directly during creation (using
CreateAndOpenTemporaryFileInDir). - Perform the copy operation utilizing
base::CopyFileContentswhich takes openbase::Filedescriptors, ensuring that filesystem path-resolution races are completely neutralized.
Alternatively, COPYFILE_NOFOLLOW_DST could be added to base::CopyFile flags on Apple platforms, though keeping the file descriptor open remains the most robust cross-platform solution.
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.