CVE-2026-113329
Overview
Fix not yet public
Original Bug Report
MacOS Installer TOCTOU Local Privilege Escalation in postinstall Script
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 TOCTOU vulnerability exists in the macOS installer where postinstall executes an updater binary from /Applications as root. Because /Applications is group-writable by admin on macOS, an admin-group attacker can race the installer to swap the path. This allows a local administrative user to potentially escalate privileges to root.
Affected files:
chrome/installer/mac/pkg_postinstall.inchrome/installer/mac/pkg_preinstall.inchrome/installer/mac/signing/pipeline.pychrome/installer/mac/signing/config.py
Estimated timestamp from git blame: 2023-11-03
Potential macOS Installer TOCTOU Local Privilege Escalation in postinstall Script
Overview
During a system-wide macOS .pkg installation of Chrome, the post-installation script runs with root privileges and directly executes the GoogleUpdater binary from under /Applications without verifying its code signature or copying it to a safe location. Because /Applications is writable by the admin group on default macOS installations (and lacks a sticky bit), a local attacker in the admin group can potentially race the installer to swap the directory tree with a malicious payload, resulting in execution of arbitrary code as root.
Root Cause Analysis
In chrome/installer/mac/pkg_postinstall.in:
readonly INSTALLATION_PATH=${2}
...
readonly FRAMEWORK_DIR="@FRAMEWORK_DIR@"
...
# 1. Install GoogleUpdater.
echo "Installing GoogleUpdater."
"${INSTALLATION_PATH}/${FRAMEWORK_DIR}/Helpers/GoogleUpdater.app/Contents/MacOS/GoogleUpdater" "--install" "--system"
- The installer script runs as
root(due to<domains enable_localSystem="true" ... />in the distribution XML, configured inchrome/installer/mac/signing/pipeline.py). $2points to the target installation directory, which defaults to/Applications(--install-location /Applicationsinpipeline.py).@FRAMEWORK_DIR@expands toGoogle Chrome.app/Contents/Frameworks/Google Chrome Framework.framework.- Therefore, the command executes a binary from:
/Applications/Google Chrome.app/Contents/Frameworks/Google Chrome Framework.framework/Helpers/GoogleUpdater.app/Contents/MacOS/GoogleUpdater
On macOS, /Applications is owned by root:admin with permissions drwxrwxr-x. Any local process running in the admin group can rename a root-owned directory inside /Applications using the standard rename(2) system call because the write permission on the parent directory governs the ability to rename entries within it.
The pre-installation script pkg_preinstall.in only verifies that the application location does not contain a symlink or an incorrect bundle ID, but it runs before payload extraction and does not offer protection for the window of time between extraction and the execution of postinstall.
Potential Trigger Path (Theoretical)
Note: These steps are suggested/potential steps; our tooling does not yet have the ability to run code or verify live exploits.
- An attacker with local admin-group access prepares a malicious directory tree at
/Applications/.staged-evil.appcontaining a payload atContents/Frameworks/Google Chrome Framework.framework/Helpers/GoogleUpdater.app/Contents/MacOS/GoogleUpdater. - The attacker monitors
/ApplicationsusingFSEventsorkqueuefor the creation ofGoogle Chrome.app. - When the installer package starts extracting the payload, the attacker’s script races the installer to perform:
rename("/Applications/Google Chrome.app", "/Applications/Google Chrome.app.orig")rename("/Applications/.staged-evil.app", "/Applications/Google Chrome.app") - The installer completes extraction and proceeds to run
postinstallas root. - The postinstall script executes the binary at the expected sub-path, spawning the attacker’s payload with
euid=0(root).
Fix Suggestion
An analogous root execution flow was already hardened in the privileged helper tool chrome/updater/mac/privileged_helper/service.mm (via VerifyUpdaterSignature).
To address this vulnerability, the installer scripts or updater initiation should adopt a similar pattern:
- Copy the updater bundle from the target installation path
/Applicationsto a root-only temporary directory (e.g., usingmktemp -dor a unique path created under/private/var/tmpowned strictly byroot:wheelwith0700permissions). - Verify the code signature of the copied bundle (e.g., checking the cryptographic signature and matching the expected Team ID) using system utilities (such as
codesign --verifyorSecStaticCodeCheckValidity). - Execute
GoogleUpdaterfrom that secure, verified temporary directory.
Evaluated with Chrome root at commit: e9507a33bb4148ee071aaaf8a7e9ad68770359bf
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.