Overview

Medium
Severity
CVSS
No
Exploited ITW
Fixed
Fix Status
ImpactRace in DevTools
DescriptionRace in DevTools
ComponentDevTools
Bug ClassRace
Tracker519982572
Fix commit88bce95e2b47 (chromium/src) +11/-9
CISA KEVNot listed
CreditedGoogle
Disclosed2026-07-29

Files Changed

  • content/browser/devtools/devtools_stream_file.cc
From 88bce95e2b4735c54e463782349a70be9be47e5d Mon Sep 17 00:00:00 2001
From: Philip Pfaffe <[email protected]>
Date: Mon, 08 Jun 2026 04:23:17 -0700
Subject: [PATCH] Keep temp file open during loadNetworkResource

Fixed: 519982572
Change-Id: I0cf9495c06d558719147c6dc95dcf4ac482fa666
Reviewed-on: https://chromium-review.googlesource.com/c/chromium/src/+/7902281
Reviewed-by: Danil Somsikov <[email protected]>
Commit-Queue: Philip Pfaffe <[email protected]>
Cr-Commit-Position: refs/heads/main@{#1643094}
---

diff --git a/content/browser/devtools/devtools_stream_file.cc b/content/browser/devtools/devtools_stream_file.cc
index 3e49284a..fd91aa25 100644
--- a/content/browser/devtools/devtools_stream_file.cc
+++ b/content/browser/devtools/devtools_stream_file.cc
@@ -51,23 +51,25 @@
     return false;
   if (file_.IsValid())
     return true;
-  base::FilePath temp_path;
-  if (!base::CreateTemporaryFile(&temp_path)) {
-    LOG(ERROR) << "Failed to create temporary file";
+  base::FilePath temp_dir;
+  if (!base::GetTempDir(&temp_dir)) {
+    LOG(ERROR) << "Failed to get temporary directory";
     had_errors_ = true;
     return false;
   }
-  const unsigned flags = base::File::FLAG_OPEN_TRUNCATED |
-                         base::File::FLAG_WRITE | base::File::FLAG_READ |
-                         base::File::FLAG_DELETE_ON_CLOSE;
-  file_.Initialize(temp_path, flags);
+  base::FilePath temp_path;
+  file_ = base::CreateAndOpenTemporaryFileInDir(
+      temp_dir, &temp_path,
+      base::File::FLAG_WIN_TEMPORARY | base::File::FLAG_DELETE_ON_CLOSE);
   if (!file_.IsValid()) {
-    LOG(ERROR) << "Failed to open temporary file: " << temp_path.value() << ", "
+    LOG(ERROR) << "Failed to create temporary file: "
                << base::File::ErrorToString(file_.error_details());
     had_errors_ = true;
-    base::DeleteFile(temp_path);
     return false;
   }
+#if !BUILDFLAG(IS_WIN)
+  base::DeleteFile(temp_path);
+#endif
   return true;
 }
 
Loading diff…

Original Bug Report

reported by [email protected]

Potential Sandbox Escape on macOS via DevToolsStreamFile Temporary File 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 time-of-check to time-of-use (TOCTOU) vulnerability exists in the creation of DevToolsStreamFile temporary files on macOS. Because sandboxed helper processes (such as the Network Service and GPU) share the same temporary directory with the browser process, a compromised child process could race the browser to replace a temporary file path with a symbolic link before it is reopened. This could allow an attacker to overwrite arbitrary user files at browser privilege, leading to a complete sandbox escape.

Affected files:

  • content/browser/devtools/devtools_stream_file.cc

Estimated timestamp from git blame: 2015-08-26

Detailed Description

There is a potential time-of-check to time-of-use (TOCTOU) / symbolic link vulnerability in DevToolsStreamFile::InitOnFileSequenceIfNeeded() on macOS.

In content/browser/devtools/devtools_stream_file.cc (lines 48-72), InitOnFileSequenceIfNeeded initializes a temporary file backing a Chrome DevTools Protocol (CDP) IO stream:

bool DevToolsStreamFile::InitOnFileSequenceIfNeeded() {
  ...
  base::FilePath temp_path;
  if (!base::CreateTemporaryFile(&temp_path)) {        // (1) Creates file, closes secure FD
    ...
  }
  const unsigned flags = base::File::FLAG_OPEN_TRUNCATED |
                         base::File::FLAG_WRITE | base::File::FLAG_READ |
                         base::File::FLAG_DELETE_ON_CLOSE;
  file_.Initialize(temp_path, flags);                  // (2) Reopens path, following symlinks
  ...
}

Root Cause Analysis

  1. FD Discarded Prematurely: base::CreateTemporaryFile internally creates the file using mkstemp inside CreateAndOpenFdForTemporaryFileInDir (base/files/file_util_posix.cc). However, it discards the returned ScopedFD and only returns a boolean success status. The file descriptor is closed immediately, leaving only the file path string.
  2. Insecure Reopen: When file_.Initialize(temp_path, flags) is executed, it maps to open() in base/files/file_posix.cc with O_TRUNC | O_RDWR. No symbolic link validation (such as O_NOFOLLOW) is applied, so symbolic links are followed.
  3. Shared Temp Directory on macOS: On macOS, NSTemporaryDirectory() resolves to _CS_DARWIN_USER_TEMP_DIR. This path is also supplied to sandboxed child processes (such as the Network Service, GPU, and On-Device Model processes) via sandbox::policy::kParamDarwinUserTempDir. The sandbox policies for these processes (e.g., network.sb, gpu.sb) explicitly permit full read/write access to this shared temporary folder.

Potential Attack Scenario

Because we do not have a working environment to run exploit code, these are suggested/potential steps that an attacker might follow to trigger this vulnerability:

  1. An attacker compromises a sandboxed child process (e.g., the Network Service) via a separate remote-code-execution vulnerability.
  2. The compromised sandboxed process monitors the shared Darwin temporary directory for any files matching .com.google.Chrome.XXXXXX.
  3. An authorized DevTools client (or extension utilizing chrome.debugger) initiates a resource load (such as Network.loadNetworkResource), which triggers the browser to fetch a payload and write it into a DevToolsStreamFile.
  4. When base::CreateTemporaryFile runs in the browser process, the safe file descriptor is closed. The compromised child process immediately deletes the newly created temporary file and replaces it with a symbolic link pointing to an existing, sensitive user-writable file (such as ~/.bash_profile or a user LaunchAgent plist).
  5. When the browser process calls file_.Initialize(), the kernel follows the symbolic link and truncates the targeted user file.
  6. The browser process then writes the network-provided payload directly into the target file. Since this write runs with browser-process privileges, the attacker has successfully modified an arbitrary file outside of the sandbox, enabling unsandboxed code execution on next login.

Suggested Fix

To securely remediate this, DevToolsStreamFile should avoid the close-then-reopen-by-path pattern. Instead, it should utilize base::CreateAndOpenTemporaryFileInDir to securely create and open the file descriptor atomically:

base::FilePath temp_path;
base::FilePath temp_dir;
if (!base::GetTempDir(&temp_dir)) {
  return false;
}
file_ = base::CreateAndOpenTemporaryFileInDir(temp_dir, &temp_path);
if (!file_.IsValid()) {
  ...
}

To preserve the FLAG_DELETE_ON_CLOSE behavior on POSIX, base::DeleteFile(temp_path) can be safely called immediately after creation since the browser process will hold onto the valid file descriptor.

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