Overview

High
Severity
CVSS
No
Exploited ITW
Fixed
Fix Status
ImpactRace in Downloads
DescriptionRace in Downloads
ComponentDownloads
Bug ClassRace
Tracker519981494
Fix commitc0ce4e540370 (chromium/src) +19/-3
CISA KEVNot listed
CreditedGoogle
Disclosed2026-07-29

Files Changed

  • components/download/internal/common/download_item_impl.cc
From c0ce4e540370f07afd1608a205e24a0166bd2efc Mon Sep 17 00:00:00 2001
From: Min Qin <qinmin@chromium.org>
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 <yawfrempong@google.com>
Reviewed-by: Lily Chen <chlily@chromium.org>
Commit-Queue: Min Qin <qinmin@chromium.org>
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();
   }
Loading diff…

Original Bug Report

reported by vm...@google.com

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;
}
  1. FD is closed early: base::CreateTemporaryFile internally creates the temporary file and immediately closes the file descriptor (see base/files/file_util_posix.cc).
  2. Path-based copy follows symlinks: On macOS, base::CopyFile uses Apple’s copyfile() API with COPYFILE_DATA but without the COPYFILE_NOFOLLOW_DST flag (see base/files/file_util_apple.mm):
    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);
    }
    
    By default, copyfile() opens the destination with O_WRONLY|O_CREAT|O_TRUNC and follows symbolic links unless COPYFILE_NOFOLLOW_DST is 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):

  1. The compromised process spawns a thread to poll the shared temporary directory for new entries matching the .com.google.Chrome.* prefix.
  2. The attacker triggers a Safe Browsing download feedback flow (e.g., forging a ClientDownloadResponse with upload=true and an eligible verdict like UNCOMMON).
  3. The browser process receives the response and dispatches MakeCopyOfDownloadFile to the ThreadPool.
  4. When MakeCopyOfDownloadFile calls base::CreateTemporaryFile(), the temporary file is created and its file descriptor is closed.
  5. The attacker’s polling thread detects the newly created file, immediately calls unlink() on it, and creates a symlink() with the same name pointing to a sensitive target (such as ~/Library/LaunchAgents/com.attacker.plist).
  6. The browser process calls copyfile() on the path. Because copyfile() follows symlinks, it writes the attacker-controlled download payload into the targeted configuration file outside the sandbox.
  7. 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:

  1. Retrieve the file handle directly during creation (using CreateAndOpenTemporaryFileInDir).
  2. Perform the copy operation utilizing base::CopyFileContents which takes open base::File descriptors, 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.

View on issue tracker