196 lines
6.8 KiB
Shell
196 lines
6.8 KiB
Shell
#!/bin/sh
|
|
set -eu
|
|
|
|
ROOT=$(CDPATH= cd -- "$(dirname "$0")/../.." && pwd)
|
|
CASE="${Z2_TEST_TMP:?}/status-snapshot-fast-path"
|
|
STATE="$CASE/state"
|
|
MODULE="$CASE/module"
|
|
OUTPUT="$CASE/status.out"
|
|
|
|
fail() { echo "FAIL: status-snapshot-fast-path: $*" >&2; exit 1; }
|
|
|
|
mkdir -p "$STATE" "$MODULE"
|
|
chmod 0700 "$STATE"
|
|
cp "$ROOT/zapret2/runtime.ini" "$MODULE/runtime.ini"
|
|
cp "${Z2_TEST_EXECUTABLE_SHELL:-$(command -v sh)}" "$MODULE/nfqws2"
|
|
chmod 0755 "$MODULE/nfqws2"
|
|
|
|
"$MODULE/nfqws2" -c 'trap "exit 0" TERM INT; while :; do sleep 1; done' &
|
|
NFQWS_PID=$!
|
|
trap 'kill "$NFQWS_PID" 2>/dev/null || true; wait "$NFQWS_PID" 2>/dev/null || true' EXIT
|
|
|
|
NFQWS_START=$(awk '{print $22}' "/proc/$NFQWS_PID/stat")
|
|
NFQWS_ARGV_SHA=$(sha256sum "/proc/$NFQWS_PID/cmdline")
|
|
NFQWS_ARGV_SHA=${NFQWS_ARGV_SHA%% *}
|
|
printf '%s\n' "$NFQWS_PID" > "$STATE/nfqws2.pid"
|
|
chmod 0600 "$STATE/nfqws2.pid"
|
|
|
|
write_snapshot() {
|
|
argv_sha="$1"
|
|
cat > "$STATE/status.snapshot" <<EOF
|
|
status=ok
|
|
boot_id=$(cat /proc/sys/kernel/random/boot_id)
|
|
rules_total=2
|
|
own_pid=$NFQWS_PID
|
|
own_pid_starttime=$NFQWS_START
|
|
own_argv_sha256=$argv_sha
|
|
owner_generation=status-fast-path
|
|
owner_metadata_verified=1
|
|
ruleset_verified=1
|
|
rules_expected=2
|
|
qnum=200
|
|
ipv4_active=1
|
|
ipv6_active=0
|
|
ipv4_rules=2
|
|
ipv6_rules=0
|
|
chains=1
|
|
anchors=1
|
|
nfqueue_supported=1
|
|
queue_bypass_supported=1
|
|
connbytes_supported=0
|
|
multiport_supported=1
|
|
mark_supported=1
|
|
error_schema=1
|
|
error_status=OK
|
|
error_domain=NONE
|
|
error_code=NONE
|
|
error_stage=NONE
|
|
error_detail=
|
|
EOF
|
|
chmod 0600 "$STATE/status.snapshot"
|
|
}
|
|
|
|
run_status() {
|
|
rc=0
|
|
STATE_DIR="$STATE" MODDIR="$CASE" ZAPRET_DIR="$MODULE" \
|
|
sh "$ROOT/zapret2/scripts/zapret-status.sh" --machine-v6 > "$OUTPUT" 2>&1 || rc=$?
|
|
}
|
|
|
|
write_snapshot "$NFQWS_ARGV_SHA"
|
|
run_status
|
|
[ "$rc" -eq 0 ] || fail "valid snapshot returned $rc"
|
|
grep -Fxq 'Z2_STATUS=ok' "$OUTPUT" || fail "valid snapshot was not accepted"
|
|
grep -Fxq 'Z2_PID_VERIFIED=1' "$OUTPUT" || fail "live process identity was not verified"
|
|
grep -Fxq 'Z2_CHAINS=1' "$OUTPUT" || fail "published chain count was not projected"
|
|
|
|
write_snapshot 0000000000000000000000000000000000000000000000000000000000000000
|
|
run_status
|
|
[ "$rc" -eq 2 ] || fail "mismatched command digest returned $rc instead of degraded"
|
|
grep -Fxq 'Z2_STATUS=degraded' "$OUTPUT" ||
|
|
fail "mismatched command digest did not fail closed"
|
|
grep -Fxq 'Z2_PID_VERIFIED=0' "$OUTPUT" ||
|
|
fail "mismatched command digest retained verified process state"
|
|
|
|
# A snapshot describes processes and netfilter objects that a reboot destroys,
|
|
# so one stamped with another boot must be rejected whole: no facts from it may
|
|
# reach the projection, not even the capability flags or the queue number.
|
|
write_snapshot "$NFQWS_ARGV_SHA"
|
|
# Give the foreign snapshot a queue number that appears nowhere else, so a
|
|
# leak is distinguishable from the value runtime.ini would supply anyway.
|
|
sed -e 's/^boot_id=.*/boot_id=11111111-1111-1111-1111-111111111111/' \
|
|
-e 's/^qnum=.*/qnum=777/' \
|
|
"$STATE/status.snapshot" > "$STATE/status.snapshot.foreign"
|
|
mv "$STATE/status.snapshot.foreign" "$STATE/status.snapshot"
|
|
chmod 0600 "$STATE/status.snapshot"
|
|
run_status
|
|
grep -Fxq 'Z2_STATUS=ok' "$OUTPUT" && fail "a snapshot from another boot was accepted"
|
|
grep -Fxq 'Z2_QNUM=777' "$OUTPUT" && fail "queue number leaked from the rejected snapshot"
|
|
# and the projection still carries the configured queue number instead of none
|
|
grep -Fxq 'Z2_QNUM=200' "$OUTPUT" || fail "rejected snapshot left the queue number unresolved"
|
|
grep -Fxq 'Z2_EXPECTED_RULES=0' "$OUTPUT" ||
|
|
fail "expected rule count leaked from the rejected snapshot"
|
|
grep -Fxq 'Z2_NFQUEUE=0' "$OUTPUT" ||
|
|
fail "capability facts leaked from the rejected snapshot"
|
|
|
|
# The pre-2.2.0 format carries no boot binding at all and is equally stale.
|
|
grep -v '^boot_id=' "$STATE/status.snapshot" > "$STATE/status.snapshot.legacy"
|
|
mv "$STATE/status.snapshot.legacy" "$STATE/status.snapshot"
|
|
chmod 0600 "$STATE/status.snapshot"
|
|
run_status
|
|
grep -Fxq 'Z2_STATUS=ok' "$OUTPUT" && fail "an unbound legacy snapshot was accepted"
|
|
|
|
# A process in another mount namespace, or a replaced package path, can retain
|
|
# the same argv[0] and canonical pathname while executing an older inode.
|
|
# Textual path equality must not authenticate that foreign generation.
|
|
write_snapshot "$NFQWS_ARGV_SHA"
|
|
cp "${Z2_TEST_EXECUTABLE_SHELL:-$(command -v sh)}" "$MODULE/nfqws2.next"
|
|
chmod 0755 "$MODULE/nfqws2.next"
|
|
mv "$MODULE/nfqws2.next" "$MODULE/nfqws2"
|
|
run_status
|
|
[ "$rc" -eq 2 ] || fail "same-path foreign executable inode returned $rc"
|
|
grep -Fxq 'Z2_PID_VERIFIED=0' "$OUTPUT" ||
|
|
fail "same-path foreign executable inode retained verified process state"
|
|
|
|
# A teardown that could not read one firewall family commits a stopped receipt
|
|
# that deliberately withholds its verification claim. By then nothing of ours
|
|
# is owned, so the projection takes its "nothing is owned" branch — and that
|
|
# branch asserted a proof this device cannot produce, laundering the single
|
|
# reservation the module kept on the only channel that still carries it.
|
|
MOCK="$CASE/bin"
|
|
mkdir -p "$MOCK"
|
|
cat > "$MOCK/iptables" <<'EOF'
|
|
#!/bin/sh
|
|
case " $* " in
|
|
*' -t mangle -L OUTPUT -n '*) exit 0 ;;
|
|
*' -t mangle -S ZAPRET2_OUT '*|*' -t mangle -S ZAPRET2_IN '*) exit 1 ;;
|
|
' -t mangle -S ') exit 0 ;;
|
|
esac
|
|
exit 1
|
|
EOF
|
|
cp "$MOCK/iptables" "$MOCK/ip6tables"
|
|
chmod 0755 "$MOCK/iptables" "$MOCK/ip6tables"
|
|
|
|
write_stopped_snapshot() {
|
|
cat > "$STATE/status.snapshot" <<EOF
|
|
status=stopped
|
|
boot_id=$(cat /proc/sys/kernel/random/boot_id)
|
|
rules_total=0
|
|
rules_expected=0
|
|
qnum=200
|
|
ipv4_active=0
|
|
ipv6_active=$2
|
|
ipv4_rules=0
|
|
ipv6_rules=0
|
|
chains=0
|
|
anchors=0
|
|
nfqueue_supported=1
|
|
queue_bypass_supported=1
|
|
connbytes_supported=0
|
|
multiport_supported=1
|
|
mark_supported=1
|
|
ruleset_verified=$1
|
|
owner_metadata_verified=0
|
|
error_schema=1
|
|
error_status=OK
|
|
error_domain=NONE
|
|
error_code=NONE
|
|
error_stage=NONE
|
|
error_detail=
|
|
EOF
|
|
chmod 0600 "$STATE/status.snapshot"
|
|
}
|
|
|
|
run_unowned_status() {
|
|
rc=0
|
|
PATH="$MOCK:$PATH" STATE_DIR="$STATE" MODDIR="$CASE" ZAPRET_DIR="$MODULE" \
|
|
sh "$ROOT/zapret2/scripts/zapret-status.sh" --machine-v6 > "$OUTPUT" 2>&1 || rc=$?
|
|
grep -Fxq 'Z2_OWNED=0' "$OUTPUT" ||
|
|
fail "the reservation case never reached the unowned projection"
|
|
}
|
|
|
|
# This is the exact shape a reserved teardown commits: it never claims an
|
|
# active IPv6 family, it only declines to certify the ruleset.
|
|
rm -f "$STATE/nfqws2.pid"
|
|
write_stopped_snapshot 0 0
|
|
run_unowned_status
|
|
grep -Fxq 'Z2_RULESET_VERIFIED=0' "$OUTPUT" ||
|
|
fail "a stopped receipt that withheld its verification claim was reported as verified"
|
|
|
|
# The ordinary stopped receipt still projects a verified ruleset, so the branch
|
|
# above narrows the claim rather than abandoning it.
|
|
write_stopped_snapshot 1 0
|
|
run_unowned_status
|
|
grep -Fxq 'Z2_RULESET_VERIFIED=1' "$OUTPUT" ||
|
|
fail "a fully verified stopped receipt lost its verification claim"
|
|
|
|
echo "Status snapshot fast-path tests passed"
|