freeze: deny Windows link targets relative to a drive or its root (#3623)

Co-authored-by: capy-ai[bot] <230910855+capy-ai[bot]@users.noreply.github.com>
This commit is contained in:
garrytanandcapy-ai[bot] committed 2026-10-31 20:51:56 +00:00
1 parent a5ad761f5f
commit 5a6547c552
2 files changed
+23 -4

No files matched your search

+6 -4
View File
@@ -233,9 +233,10 @@ esac
# POSIX file name. On a POSIX host a Windows path names no local file,
# so it is resolved as text and no local symlink is followed.
# Returns 1 for a symlink loop, a chain over 40 links, an unreadable or
# line-broken link, link targets that add up past 8192 characters with the
# path (the walk is quadratic, so its total input stays bounded), or an
# unplaceable Windows name; the caller fails closed.
# line-broken link, a Windows link target relative to a drive or its root,
# link targets that add up past 8192 characters with the path (the walk is
# quadratic, so its total input stays bounded), or an unplaceable Windows
# name; the caller fails closed.
_resolve_path() {
local _rem="$1" _win="${3:-0}" _lead="" _cur="" _n=0 _min=0 _reroot=1
local _part _tgt _p _tail="" _hops=0 _probe=1 _budget="${#1}"
@@ -286,6 +287,7 @@ _resolve_path() {
_tgt="${_tgt%x}"; _tgt="${_tgt%"$_FREEZE_NL"}"
case "$_tgt" in ''|*"$_FREEZE_NL"*|*"$_FREEZE_CR"*) return 1 ;; esac
if [ "$_win" = 1 ]; then
case "$_tgt" in [A-Za-z]:|[A-Za-z]:[!\\/]*|\\|\\[!\\]*) return 1 ;; esac
gstack_hook_normalize_path "$_tgt"
_tgt="$GSTACK_HOOK_PATH"
fi
@@ -319,7 +321,7 @@ _resolve_path() {
printf '%s' "$GSTACK_HOOK_PATH"
}
_FREEZE_WHY="a symlink loop, a chain over 40 links, a path and link targets over 8192 characters in all, an unreadable link, or a Windows name that cannot be placed"
_FREEZE_WHY="a symlink loop, a chain over 40 links, a path and link targets over 8192 characters in all, an unreadable link, a Windows link target relative to a drive or its root, or a Windows name that cannot be placed"
FREEZE_DIR_RAW="$FREEZE_DIR"
FREEZE_DIR=$(_resolve_path "$FREEZE_DIR_RAW" physical "$FREEZE_DIR_WIN") || _freeze_unresolved "the freeze boundary $FREEZE_DIR_RAW" "$_FREEZE_WHY"
# Hosts disagree on '<link>/..': the OS means the target's parent, a lexical
+17
View File
@@ -1626,6 +1626,23 @@ describe('check-freeze.sh', () => {
});
});
// Windows reads a link target of '\\x' against the link's drive root and
// 'C:x' against drive C's own working directory; normalized as /x or C:/x,
// either read as inside while the write landed elsewhere.
test.skipIf(process.platform === 'win32')('Windows bash: a link target relative to a drive or its root denies, fail closed', () => {
withTree(({ home, src }) => {
fs.symlinkSync(src.split('/').join('\\'), path.join(src, 'rootrel'));
fs.symlinkSync('C:x', path.join(src, 'driverel'));
fs.symlinkSync('\\', path.join(src, 'bare'));
fs.mkdirSync(path.join(src, 'real'));
fs.symlinkSync(path.join(src, 'real'), path.join(src, 'fwd'));
expect(decideOnWindowsBash(src, src + '/rootrel/x.ts', home, src)).toBe('deny');
expect(decideOnWindowsBash(src, src + '/driverel/x.ts', home, src)).toBe('deny');
expect(decideOnWindowsBash(src, src + '/bare' + src + '/x.ts', home, src)).toBe('deny');
expect(decideOnWindowsBash(src, src + '/fwd/x.ts', home, src)).toBe('allow');
});
});
// On POSIX '\\' is a filename character, so a name that spells '..' with
// backslashes is one directory, not a way back into the boundary.
test.skipIf(process.platform === 'win32')('a POSIX backslash name that spells dotdot never counts as inside', () => {