Overview

High
Severity
CVSS
No
Exploited ITW
Fixed
Fix Status
ImpactInappropriate implementation in Updater
DescriptionInappropriate implementation in Updater
ComponentUpdater
Bug ClassLogic Error
Tracker521485244
Fix commit591ea6ba7679 (chromium/src) +5/-1
CISA KEVNot listed
CreditedGoogle
Disclosed2026-06-16

Files Changed

  • chrome/browser/ui/webui/updater/updater_page_handler.cc
From 591ea6ba7679ca32b09009904894820c3983b9dc Mon Sep 17 00:00:00 2001
From: Noah Rose Ledesma <noahrose@google.com>
Date: Tue, 09 Jun 2026 13:27:38 -0700
Subject: [PATCH] Avoid TOCTOU in UnzipUpdaterHistoryFilesImpl

On some platforms, the temporary directory created by ScopedTempDir is
in a location accessible by some sandboxed processes. Avoid possible
sandbox escapes leveraging the UnzipUpdaterHistoryFilesImpl
functionality which replace the "input.zip" with a symbolic link.

Fixed: 521485244
Change-Id: Ic7e64f34f5f6f6740ca456b67a2ad18a6a6a6964
Reviewed-on: https://chromium-review.googlesource.com/c/chromium/src/+/7911844
Auto-Submit: Noah Rose Ledesma <noahrose@google.com>
Commit-Queue: Sorin Jianu <sorin@chromium.org>
Reviewed-by: Sorin Jianu <sorin@chromium.org>
Cr-Commit-Position: refs/heads/main@{#1644216}
---

diff --git a/chrome/browser/ui/webui/updater/updater_page_handler.cc b/chrome/browser/ui/webui/updater/updater_page_handler.cc
index a99a9f3..711d174 100644
--- a/chrome/browser/ui/webui/updater/updater_page_handler.cc
+++ b/chrome/browser/ui/webui/updater/updater_page_handler.cc
@@ -14,6 +14,7 @@
 
 #include "base/barrier_closure.h"
 #include "base/containers/flat_map.h"
+#include "base/files/file.h"
 #include "base/files/file_enumerator.h"
 #include "base/files/file_path.h"
 #include "base/files/file_util.h"
@@ -226,7 +227,10 @@
   }
 
   base::FilePath archive_path = temp_dir.GetPath().AppendASCII("input.zip");
-  if (!base::WriteFile(archive_path, zip_data)) {
+  base::File archive(archive_path,
+                     base::File::FLAG_CREATE | base::File::FLAG_WRITE);
+  if (!archive.IsValid() ||
+      !archive.WriteAtCurrentPosAndCheck(base::span(zip_data))) {
     std::move(callback).Run(
         base::unexpected(updater_ui::mojom::UnzipUpdaterHistoryFilesError::New(
             "Failed to write user-supplied zip data to storage")));
Loading diff…

Original Bug Report

reported by vm...@google.com

Symlink TOCTOU in UnzipUpdaterHistoryFilesImpl allows macOS Sandbox Escape

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 race condition in UnzipUpdaterHistoryFilesImpl on macOS allows a compromised sandboxed process to plant a symbolic link in a shared temporary directory. Because base::WriteFile uses POSIX creat(), which follows symbolic links, the browser process can be tricked into truncating and overwriting arbitrary files outside the sandbox. This can potentially be leveraged to escape sandbox isolation.

Affected files:

  • chrome/browser/ui/webui/updater/updater_page_handler.cc

Estimated timestamp from git blame: 2026-03-19

Description

A potential Time-of-Check to Time-of-Use (TOCTOU) vulnerability exists in the UnzipUpdaterHistoryFilesImpl function on macOS. The issue arises from the use of a shared temporary directory in combination with a file creation utility that follows symbolic links.

Root Cause

  1. Shared Temporary Directory: In chrome/browser/ui/webui/updater/updater_page_handler.cc (lines 220-221), UnzipUpdaterHistoryFilesImpl creates a temporary directory using temp_dir.CreateUniqueTempDir(). On macOS, this resolves to NSTemporaryDirectory() (defined via _CS_DARWIN_USER_TEMP_DIR in base/files/file_util_apple.mm). Several heavily sandboxed child processes (such as the GPU, Network, and On-Device Model Execution processes) are explicitly granted read and write access to darwin-user-temp-dir in their Seatbelt profiles (e.g., sandbox/policy/mac/network.sb lines 50-53, sandbox/policy/mac/gpu.sb lines 124-128). Because these child processes run under the same User ID (UID) as the browser process, standard UNIX Discretionary Access Control (DAC) does not isolate their files.

  2. Symlink-Following File Write: The browser process subsequently creates a file with a static name within the temporary directory and writes the user-supplied ZIP data into it:

    base::FilePath archive_path = temp_dir.GetPath().AppendASCII("input.zip");
    if (!base::WriteFile(archive_path, zip_data)) { ... }
    

    On POSIX platforms, base::WriteFile is implemented using the creat() system call (base/files/file_util_posix.cc line 1157), which is equivalent to open(..., O_WRONLY | O_CREAT | O_TRUNC):

    int fd = HANDLE_EINTR(creat(filename.value().c_str(), 0666));
    

    Because this call does not specify the O_NOFOLLOW flag, it resolves and follows symbolic links.


Potential Attack Scenario

Note: The following steps are potential/theoretical. Our analysis is based on static code review, and we have not validated this path with an active exploit payload.

  1. An attacker compromises a sandboxed child process (e.g., the Network or GPU process) via an independent renderer-to-helper vulnerability.
  2. In the compromised process, the attacker executes a fast loop calling readdir on the directory returned by confstr(_CS_DARWIN_USER_TEMP_DIR). It monitors the creation of any new directories starting with scoped_dir.
  3. The victim user navigates to chrome://updater and triggers a history log ZIP upload.
  4. The browser process executes temp_dir.CreateUniqueTempDir() to create a directory (e.g., scoped_dirA1B2C3).
  5. The attacker’s loop instantly detects the new directory and creates a symbolic link inside it:
    symlink("/Users/[username]/.zshrc", "scoped_dirA1B2C3/input.zip");
    
  6. The browser process proceeds to call base::WriteFile(archive_path, zip_data). The kernel follows the input.zip symbolic link, truncating and overwriting the user’s .zshrc file with the ZIP payload.
  7. When the user later opens a terminal, the overwritten .zshrc is sourced, executing arbitrary commands outside the sandbox with full user privileges.

Suggested Remediation

To mitigate this risk, avoid using symbolic-link-following file writes in shared temporary directories.

  1. Use O_NOFOLLOW: Modify the file-writing logic to open the target path with the O_NOFOLLOW option (or use base::File with base::File::FLAG_WIN_SHARE_DELETE or similar flags that prevent symbolic link resolution on POSIX).
  2. Use Safe Temp File APIs: Instead of appending a static filename to a directory, use base::CreateTemporaryFileInDir which safely creates a uniquely named file with restrictive file permissions.
  3. Isolate Extraction: Alternatively, perform extraction in a location that is not shared with sandboxed child processes.

Evaluated with Chrome root at commit: 3947e01999a53d4e2382e39736cb79d79c7dffcf


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