Close sysroot escape via relative symlinks#185
Merged
Conversation
A directory fd opened inside the sysroot let a relative openat(dirfd, name) escape it entirely. proc_resolve_sysroot_path_ flags() only runs its sysroot-prefix + realpath() containment check on absolute guest paths, since it has no dirfd context to reconstruct a host location from a relative one. A symlink reachable through dirfd -- a relative target with enough ".." components, or an absolute target -- therefore walked straight out of the sysroot with no check at all, even though the identical escape through an absolute guest path was already rejected with ELOOP. Verified by staging such a symlink and reading a host file outside the sysroot through the relative dirfd path. path_translate_at() now reconstructs the absolute guest path from the dirfd's guest base path and re-validates it through the same containment-checked resolver the absolute-path surface already uses; realpath() inside that resolver collapses ".." and any symlink indirection, including an absolute target, before the prefix check runs. Separately, Darwin's linkat(2) can reject a hard link to a symlink itself (without AT_SYMLINK_FOLLOW) on non-APFS filesystems -- reproduced as ENOTSUP on a Case-sensitive HFS+ volume, where Linux allows it unconditionally. sys_linkat() now falls back to creating a plain symlink with the same target when linkat() fails with EPERM/ENOTSUP and the source is a symlink. Close sysprog21#130
Collaborator
1. Directory Traversal Vulnerability Fix
Execution Command & Log Output: $elfuse/build/elfuse --sysroot ~/prefixes/ubuntu-arm64/rootfs /usr/bin/python3 -c "
import os
try:
os.stat('/abs-link')
except OSError as e:
print('Error:', e)
"
Error: [Errno 40] Too many levels of symbolic links: '/abs-link'
2.
|
e9d380f to
ad4c615
Compare
jserv
requested changes
Jul 10, 2026
jserv
left a comment
Contributor
There was a problem hiding this comment.
Squash and refine commit messages.
ad4c615 to
65fb574
Compare
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
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
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.
Summary
openat(dirfd, name)against a dirfd already inside--sysrootskipped containment entirely; a symlink reachable through it (absolute target, or relative with enough..) escaped the sysroot with no check, unlike the identical escape through an absolute path. Fixed by re-validating the reconstructed absolute path through the existing containment-checked resolver.linkat(2)can reject a hard link to a symlink source on non-APFS filesystems (reproduced as ENOTSUP on Case-sensitive HFS+);sys_linkatnow falls back tosymlinkatwith the same target.Close #130
Test
make check(green, includes newtest-sysroot-symlink-escape)make test-linkat-symlink-fallback(standalone, builds a scratch HFS+ image via hdiutil; green)Summary by cubic
Fixes a sysroot escape where relative dirfd
*at()calls could traverse symlinks outside--sysroot. Also adds a Darwinlinkatfallback so linking a symlink source behaves like Linux.*at()under--sysroot, rebuild the absolute guest path from the dirfd base, run realpath + containment checks, and use the verified host path; reject absolute-target and deep-..symlink escapes with ELOOP, includingAT_FDCWDafterchdirandfaccessat.linkaton a symlink source fails with EPERM/ENOTSUP (withoutAT_SYMLINK_FOLLOW), fall back tosymlinkatwith the same target to match Linux.test-sysroot-symlink-escapetomake check, and a standalonetest-linkat-symlink-fallback(HFS+ image; skips when unavailable).Written for commit 65fb574. Summary will update on new commits.