#!/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//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//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//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 ]