You signed in with another tab or window. Reload to refresh your session.You signed out in another tab or window. Reload to refresh your session.You switched accounts on another tab or window. Reload to refresh your session.Dismiss alert
{{ message }}
Repository navigation
Fix symlink escape vulnerability in touch_path (CWE-59) - #1060
touch_path() in gprofiler/utils/__init__.py used Path.touch() followed by os.chmod(), both of which follow a pre-existing final symlink. When gProfiler runs as root, a malicious profiled container could plant a symlink at a predictable async-profiler log/output path and cause gProfiler to chmod an attacker-chosen host file to 0o622.
This does not require winning a tight race: for a given Java PID the async-profiler filename is stable across profiling cycles, so the container can plant the symlink after one cycle removes the file and before the next cycle's touch_path() runs.
Fix
Open the path with os.open(..., O_NOFOLLOW) so the kernel atomically rejects a symlink final component (raises ELOOP).
Use os.fchmod(fd, mode) on the resulting file descriptor instead of path-based os.chmod(), eliminating the TOCTOU window and bypassing umask (preserving the exact mode the callers expect).
This mirrors the existing hardening already applied to safe_copy() and safe_read_text().
Covered by the existing Java profiler tests in CI, which exercise touch_path() for async-profiler output/log files. Legitimate paths are regular files, not symlinks, so behavior is unchanged.
touch_path() used Path.touch() and os.chmod() which both follow symlinks.
A malicious container could place a symlink at a predictable async-profiler
log/output path and cause privileged gProfiler to chmod the symlink target
on the host to 0622.
Fix: Use os.open() with O_NOFOLLOW to reject symlinks atomically at kernel
level, and os.fchmod() on the fd to avoid TOCTOU races.
Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
Preserve touch timestamp updates for existing regular files
gprofiler/utils/__init__.py:398
This no longer performs the “touch” part for an existing regular file: opening it without writing or truncating and then calling fchmod() leaves its access and modification timestamps unchanged. That regresses the previous Path.touch() behavior (and the function’s contract). Update the timestamps through the already-validated descriptor before closing it.
Add regression test rejecting symlinked final output paths
gprofiler/utils/__init__.py:390
The existing Java symlink test only exercises symlinks in parent paths; it never places a symlink at the final async-profiler output/log path. Add a focused regression test that makes the final path a symlink, asserts touch_path() rejects it, and verifies the symlink target’s mode is unchanged. Otherwise the security property this PR introduces can regress while the cited tests still pass.
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Description
touch_path()ingprofiler/utils/__init__.pyusedPath.touch()followed byos.chmod(), both of which follow a pre-existing final symlink. When gProfiler runs as root, a malicious profiled container could plant a symlink at a predictable async-profiler log/output path and cause gProfiler tochmodan attacker-chosen host file to0o622.This does not require winning a tight race: for a given Java PID the async-profiler filename is stable across profiling cycles, so the container can plant the symlink after one cycle removes the file and before the next cycle's
touch_path()runs.Fix
os.open(..., O_NOFOLLOW)so the kernel atomically rejects a symlink final component (raisesELOOP).os.fchmod(fd, mode)on the resulting file descriptor instead of path-basedos.chmod(), eliminating the TOCTOU window and bypassing umask (preserving the exact mode the callers expect).This mirrors the existing hardening already applied to
safe_copy()andsafe_read_text().Related Issue
#1056
How Has This Been Tested?
Covered by the existing Java profiler tests in CI, which exercise
touch_path()for async-profiler output/log files. Legitimate paths are regular files, not symlinks, so behavior is unchanged.