mirror of
https://github.com/garrytan/gstack.git
synced 2026-11-03 15:27:56 +01:00
fix(freeze): deny paths over PATH_MAX before the quadratic walk (#3623)
A PreToolUse hook that outlives the host's timeout does not block the edit, and the component walk is quadratic in path length. Deny a file_path over 4096 characters, and links that grow the walk past 8192, before walking. Co-authored-by: capy-ai[bot] <230910855+capy-ai[bot]@users.noreply.github.com>
This commit is contained in:
1 parent
d419ba18ec
commit
3ad63ec637
2 files changed
+20
-1
No files matched your search
@@ -168,6 +168,12 @@ _FREEZE_CR=$'\r'
|
||||
case "$FILE_PATH" in
|
||||
*"$_FREEZE_NL"*|*"$_FREEZE_CR"*) _freeze_unresolved "$TOOL_LABEL $FILE_PATH" "a line break in the path" ;;
|
||||
esac
|
||||
# The walk below costs time quadratic in the path length, and a hook that
|
||||
# outlives the host's timeout lets the edit through. No real path is longer
|
||||
# than PATH_MAX (4096 on Linux), so bound the work before it starts.
|
||||
if [ "${#FILE_PATH}" -gt 4096 ]; then
|
||||
_freeze_unresolved "$TOOL_LABEL path of ${#FILE_PATH} characters" "longer than 4096 characters"
|
||||
fi
|
||||
|
||||
# A leading ~ is the home directory to the host's file tools, never a
|
||||
# directory named '~' under cwd: joined onto cwd, '~/.zshrc' passed whenever
|
||||
@@ -227,7 +233,8 @@ 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, or an unplaceable Windows name; the caller fails closed.
|
||||
# line-broken link, links that grow the path past 8192 characters, 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
|
||||
@@ -283,6 +290,7 @@ _resolve_path() {
|
||||
fi
|
||||
case "$_tgt" in /*) _reroot=1 ;; esac
|
||||
_rem="${_tgt%/}${_rem:+/$_rem}"
|
||||
[ "${#_rem}" -le 8192 ] || return 1
|
||||
done
|
||||
_p="$_lead$_cur"
|
||||
if [ "${2:-}" != lexical ] && [ "$_probe" = 1 ]; then
|
||||
|
||||
@@ -1546,6 +1546,17 @@ describe('check-freeze.sh', () => {
|
||||
});
|
||||
});
|
||||
|
||||
// The walk is quadratic in path length, and a PreToolUse hook that times
|
||||
// out does not block the edit; an over-long path must deny fast.
|
||||
test('a path longer than PATH_MAX denies quickly instead of stalling the hook', () => {
|
||||
withTree(({ base, home, src }) => {
|
||||
const started = Date.now();
|
||||
expect(decide(src, src + '/a/..'.repeat(20000) + '/../outside/x.ts', home, base)).toBe('deny');
|
||||
expect(Date.now() - started).toBeLessThan(4000);
|
||||
expect(decide(src, src + '/a/..'.repeat(500) + '/x.ts', home, base)).toBe('allow');
|
||||
});
|
||||
});
|
||||
|
||||
test('Windows separators: a C:\\ dotdot escape through a missing directory denies, a new C:\\ subdirectory allows', () => {
|
||||
withTree(({ base, home }) => {
|
||||
expect(decide('C:\\dev\\proj\\src', 'C:\\dev\\proj\\src\\..\\..\\other\\new\\x.ts', home, base)).toBe('deny');
|
||||
|
||||
Reference in new issue
Block a user