P1: a failed mktemp, mkdir or output redirect now stops the script with exit 2 before it derives a path, sets a trap or starts anything. The sandbox root is checked to be absolute, a new empty directory this user owns and its own physical path, and its device:inode is recorded. sweep_sandbox and remove_sandbox in scripts/case2/sandbox.sh each refuse unless the root still matches, so an early, late or empty call gets a refusal instead of a sweep of the host. P2: the sweep reads every /proc/<pid>/cwd target with one find and compares it as a string, root or root plus "/", so *, ? and [ in the path are literal. A cwd in a deleted directory under the root matches. P3: a TMPDIR whose tools socket path would pass the 107-byte limit in packages/seat/src/session.mjs is refused at entry with one line naming the limit and both lengths. The length comes from the socket path built on the real sandbox root. scripts/case2/sandbox-check.sh checks the guard and the match against dummy symlinks; nothing in it sends a signal.
123 lines
7.1 KiB
Bash
123 lines
7.1 KiB
Bash
#!/usr/bin/env bash
|
|
# C2-14 (#1728, row 227 R3): checks for scripts/case2/sandbox.sh, the sandbox guard and the literal
|
|
# cwd match behind the kill sweep in scripts/test-case2.sh. The match runs against dummy symlinks in
|
|
# a scratch directory. Nothing here sends a signal: kill is a stub that records its arguments, so
|
|
# even a broken guard could not reach a process. One sweep call and the timing line read every
|
|
# /proc/<pid>/cwd target, as the suite's sweep does, with a root that holds no process.
|
|
# Run: bash scripts/case2/sandbox-check.sh (exit 0 when every check holds)
|
|
set -uo pipefail
|
|
cd "$(dirname "$0")/../.." || exit 2
|
|
. scripts/case2/sandbox.sh || exit 2
|
|
case "${TMPDIR:-/tmp}" in /*) ;; *) echo 'sandbox-check: TMPDIR must be absolute' >&2; exit 2 ;; esac
|
|
made="$(mktemp -d)" || exit 2
|
|
case "$made" in /?*) ;; *) exit 2 ;; esac
|
|
W="$(cd -P -- "$made" && pwd -P)" && WID="$(sandbox_id "$W")" && sandbox_ok "$W" "$WID" || exit 2
|
|
trap 'remove_sandbox "$W" "$WID"' EXIT
|
|
kill() { printf '%s\n' "$*" >>"$W/kill-calls"; return 0; }
|
|
PASS=0 FAIL=0
|
|
expect() { # expect NAME WANT(0|1) COMMAND...: COMMAND exits 0 when WANT is 0, non-zero when 1
|
|
local name=$1 want=$2 got
|
|
shift 2
|
|
"$@" 2>/dev/null; got=$?
|
|
if { [ "$want" = 0 ] && [ "$got" = 0 ]; } || { [ "$want" = 1 ] && [ "$got" != 0 ]; }; then
|
|
PASS=$((PASS+1)); echo "ok $name"
|
|
else
|
|
FAIL=$((FAIL+1)); echo "FAIL $name (exit $got)"
|
|
fi
|
|
}
|
|
same() { # same NAME WANT GOT
|
|
if [ "$2" = "$3" ]; then PASS=$((PASS+1)); echo "ok $1"
|
|
else FAIL=$((FAIL+1)); printf 'FAIL %s\n want %q\n got %q\n' "$1" "$2" "$3"; fi
|
|
}
|
|
|
|
# The guard. Roots with *, ? and [ are real directories here; the guard takes them as paths.
|
|
for name in 'a[bc]' 's*t' 'q?x' 'ab' 'sXt' 'qZx' 'keep'; do mkdir -p -- "$W/$name/tmp.example/root"; done
|
|
R="$W/a[bc]/tmp.example"
|
|
RID=$(sandbox_id "$R")
|
|
mkdir -- "$W/real" && ln -s -- "$W/real" "$W/alias"
|
|
expect 'guard: the created root with its id' 0 sandbox_ok "$R" "$RID"
|
|
expect 'guard: empty root' 1 sandbox_ok '' "$RID"
|
|
expect 'guard: empty id' 1 sandbox_ok "$R" ''
|
|
expect 'guard: relative root' 1 sandbox_ok 'scripts/case2' "$(sandbox_id scripts/case2)"
|
|
expect 'guard: missing root' 1 sandbox_ok "$W/missing" "$RID"
|
|
expect 'guard: / as root' 1 sandbox_ok / "$(sandbox_id /)"
|
|
expect 'guard: trailing slash' 1 sandbox_ok "$R/" "$RID"
|
|
expect 'guard: a . part' 1 sandbox_ok "$W/a[bc]/./tmp.example" "$RID"
|
|
expect 'guard: a symlink as root' 1 sandbox_ok "$W/alias" "$(sandbox_id "$W/real")"
|
|
expect 'guard: a path through a symlink' 1 sandbox_ok "$W/alias/../a[bc]/tmp.example" "$RID"
|
|
expect 'guard: the right path, another id' 1 sandbox_ok "$R" "$(sandbox_id "$W/ab/tmp.example")"
|
|
expect 'guard: a sibling with the root id' 1 sandbox_ok "$W/ab/tmp.example" "$RID"
|
|
expect 'guard: root with *' 0 sandbox_ok "$W/s*t/tmp.example" "$(sandbox_id "$W/s*t/tmp.example")"
|
|
expect 'guard: root with ?' 0 sandbox_ok "$W/q?x/tmp.example" "$(sandbox_id "$W/q?x/tmp.example")"
|
|
expect 'guard: empty directory' 0 dir_empty "$W/real"
|
|
touch -- "$W/real/.dot"
|
|
expect 'guard: a dot entry is not empty' 1 dir_empty "$W/real"
|
|
|
|
# The sweep and the removal refuse on their own, before they read a link or remove anything.
|
|
expect 'sweep: refuses an empty root' 1 sweep_sandbox '' ''
|
|
expect 'sweep: refuses / as root' 1 sweep_sandbox / "$(sandbox_id /)"
|
|
expect 'sweep: refuses a missing root' 1 sweep_sandbox "$W/missing" "$RID"
|
|
expect 'remove: refuses an empty root' 1 remove_sandbox '' ''
|
|
expect 'remove: refuses a root with the wrong id' 1 remove_sandbox "$W/keep" "$RID"
|
|
expect 'remove: the refused root is still there' 0 test -d "$W/keep/tmp.example/root"
|
|
expect 'sweep: a valid root with no process in it' 0 sweep_sandbox "$W/keep" "$(sandbox_id "$W/keep")"
|
|
same 'sweep: no signal was asked for' '' "$(cat -- "$W/kill-calls" 2>/dev/null)"
|
|
|
|
# The match. Dummy links stand in for /proc/<pid>/cwd; a regular file stands in for a link this
|
|
# user may not read (find prints an empty target for both).
|
|
n=0
|
|
link() { n=$((n+1)); mkdir -p -- "$W/links/$n"; ln -s -- "$1" "$W/links/$n/cwd"; LINKS+=("$W/links/$n/cwd"); }
|
|
match() { # match ROOT: the indices of the dummy links cwd_links_in selects, space-separated
|
|
local l out=''
|
|
while IFS= read -r -d '' l; do l=${l#"$W/links/"}; out="$out ${l%/cwd}"; done \
|
|
< <(cwd_links_in "$1" "${LINKS[@]}")
|
|
printf '%s' "${out# }"
|
|
}
|
|
LINKS=()
|
|
link "$R" # 1 the root itself
|
|
link "$R/root" # 2 under the root
|
|
link "$R/root/x (deleted)" # 3 a deleted directory under the root
|
|
link "$R (deleted)" # 4 another directory: the root exists
|
|
link "$W/ab/tmp.example" # 5 what [bc] would match as a pattern
|
|
link "$W/ab/tmp.example/root" # 6 the same, deeper
|
|
link "$W/a[bc]/tmp.example2" # 7 a sibling sharing a prefix
|
|
link "$W/a[bc]/tmp.exampl" # 8 a shorter sibling
|
|
link / # 9
|
|
link /opt/unrelated/work # 10
|
|
link "$R/line"$'\n'"break" # 11 a newline, under the root
|
|
link "$R/a"$'\t'"b" # 12 a tab, under the root
|
|
link "$R"$'\n' # 13 the root plus a newline: another directory
|
|
n=$((n+1)); mkdir -p -- "$W/links/$n"; : >"$W/links/$n/cwd"; LINKS+=("$W/links/$n/cwd") # 14 no target
|
|
link "$W/s*t/tmp.example/root" # 15
|
|
link "$W/sXt/tmp.example/root" # 16 what * would match as a pattern
|
|
link "$W/q?x/tmp.example/root" # 17
|
|
link "$W/qZx/tmp.example/root" # 18 what ? would match as a pattern
|
|
same 'match: root with [bc] selects only its own tree, deleted dirs inside included' '1 2 3 11 12' "$(match "$R")"
|
|
same 'match: root with *' '15' "$(match "$W/s*t/tmp.example")"
|
|
same 'match: root with ?' '17' "$(match "$W/q?x/tmp.example")"
|
|
same 'match: the ab sibling selects only its own tree' '5 6' "$(match "$W/ab/tmp.example")"
|
|
same 'match: an empty root selects nothing' '' "$(match '')"
|
|
same 'match: / as root selects nothing' '' "$(match /)"
|
|
same 'match: a relative root selects nothing' '' "$(match 'tmp.example')"
|
|
same 'match: no links reads nothing' '' "$(cwd_links_in "$R" | tr '\0' ' ')"
|
|
|
|
# The pid of a link: only /proc/<digits>/cwd.
|
|
same 'pid: /proc/123/cwd' '123' "$(link_pid /proc/123/cwd)"
|
|
expect 'pid: /proc/self/cwd' 1 link_pid /proc/self/cwd
|
|
expect 'pid: /proc//cwd' 1 link_pid /proc//cwd
|
|
expect 'pid: /proc/12/task/13/cwd' 1 link_pid /proc/12/task/13/cwd
|
|
expect 'pid: a dummy link' 1 link_pid "$W/links/1/cwd"
|
|
|
|
# The socket shape and byte length.
|
|
same 'socket: shape' "$R/root/data/launches/acme/r000000000000/tools.sock" "$(socket_shape "$R")"
|
|
same 'socket: bytes, not characters' 5 "$(LC_ALL=C.UTF-8 byte_length $'/a/\xc3\xa9')"
|
|
|
|
links=(/proc/[0-9]*/cwd)
|
|
start=${EPOCHREALTIME/[.,]/}
|
|
hits=$(cwd_links_in "$W/keep" "${links[@]}" | tr -cd '\0' | wc -c)
|
|
end=${EPOCHREALTIME/[.,]/}
|
|
same "speed: no process under an empty root among ${#links[@]} cwd links" 0 "$hits"
|
|
echo "time: one read of ${#links[@]} cwd links took $(( (end - start) / 1000 )) ms"
|
|
printf '\nsandbox-check: %s passed, %s failed\n' "$PASS" "$FAIL"
|
|
[ "$FAIL" -eq 0 ]
|