diff --git a/modules/system-customize.sh b/modules/system-customize.sh index 31d4a39..c290ed6 100644 --- a/modules/system-customize.sh +++ b/modules/system-customize.sh @@ -2825,7 +2825,8 @@ xanmod_pending_allocation_proof_trusted() { { IFS= read -r proof_token || return 1 IFS= read -r proof_identity || return 1 - if IFS= read -r extra_line; then + # read returns failure at EOF even after consuming an unterminated tail. + if IFS= read -r extra_line || [[ -n "$extra_line" ]]; then return 1 fi } < "$proof_path" diff --git a/tests/test-xanmod.sh b/tests/test-xanmod.sh index a703256..59fa3be 100755 --- a/tests/test-xanmod.sh +++ b/tests/test-xanmod.sh @@ -53,6 +53,75 @@ make_layout "$TEST_DIR/tool" # shellcheck source=../tools/xanmod-install.sh source "$TOOL" trap 'rm -rf "$TEST_DIR"' EXIT +# Proof parser fixtures: no APT, services or production paths. +( + proof_root="$TEST_DIR/proof-tail" + mkdir -m 0700 "$proof_root" + XANMOD_ALLOCATION_CANDIDATE="$proof_root/candidate" + XANMOD_ALLOCATION_KIND=file + XANMOD_ALLOCATION_OWNER_TOKEN=fixture-token + XANMOD_ALLOCATION_EXPECTED_MODE=600 + touch "$XANMOD_ALLOCATION_CANDIDATE" + chmod 600 "$XANMOD_ALLOCATION_CANDIDATE" + proof="$proof_root/proof" + xanmod_allocation_proof_path() { printf "%s\n" "$proof"; } + identity=$(stat -c "%d:%i" "$XANMOD_ALLOCATION_CANDIDATE") + printf "%s\n%s\n" fixture-token "$identity" > "$proof" + chmod 600 "$proof" + assert_ok "exact two-line proof accepted" xanmod_pending_allocation_owned + for tail in unterminated "$identity"; do + printf "%s\n%s\n%s" fixture-token "$identity" "$tail" > "$proof" + assert_fail "unterminated proof tail rejected" xanmod_pending_allocation_proof_trusted + assert_fail "malformed proof cannot establish ownership" xanmod_pending_allocation_owned + [[ -f "$XANMOD_ALLOCATION_CANDIDATE" ]] || fail "candidate unexpectedly removed" + done + printf "%s\n%s\nextra\n" fixture-token "$identity" > "$proof" + assert_fail "complete third line rejected" xanmod_pending_allocation_owned + printf "%s\n%s\n\n" fixture-token "$identity" > "$proof" + assert_fail "empty third line rejected" xanmod_pending_allocation_owned + + # Reproduce the old EOF mistake with inert text, not an old production script. + legacy_accepts_tail() { + local token identity extra_line + { + IFS= read -r token || return 1 + IFS= read -r identity || return 1 + if IFS= read -r extra_line; then return 1; fi + } < "$proof" + [[ "$token" == fixture-token && "$identity" =~ ^[0-9]+:[0-9]+$ ]] + } + printf "%s\n%s\ntail" fixture-token "$identity" > "$proof" + assert_ok "old parser incorrectly accepts unterminated tail" legacy_accepts_tail + assert_fail "fixed parser rejects same counterexample" xanmod_pending_allocation_proof_trusted + + # Only candidate state without prior ownership is covered here. + # Residue/previously-owned resources use a separate existing cleanup contract. + # Exercise the real dispatcher; the deletion primitive is a spy. + cleanup_calls=0 + xanmod_cleanup_created_allocation() { cleanup_calls=$((cleanup_calls + 1)); return 0; } + for candidate_exists in yes no; do + XANMOD_ALLOCATION_CANDIDATE="$proof_root/candidate" + XANMOD_ALLOCATION_KIND=file + XANMOD_ALLOCATION_OWNER_TOKEN=fixture-token + XANMOD_ALLOCATION_EXPECTED_MODE=600 + XANMOD_ALLOCATION_STATE=candidate + XANMOD_ALLOCATION_PROOF_OWNED=false + if [[ "$candidate_exists" == no ]]; then command rm -- "$XANMOD_ALLOCATION_CANDIDATE"; fi + assert_ok "unowned candidate cleanup returns safely" cleanup_xanmod_pending_allocation + assert_eq "" "$XANMOD_ALLOCATION_CANDIDATE" "candidate path cleared" + assert_eq "" "$XANMOD_ALLOCATION_KIND" "allocation kind cleared" + assert_eq "" "$XANMOD_ALLOCATION_OWNER_TOKEN" "owner token cleared" + assert_eq "" "$XANMOD_ALLOCATION_EXPECTED_MODE" "expected mode cleared" + assert_eq "" "$XANMOD_ALLOCATION_STATE" "allocation state cleared" + assert_eq false "$XANMOD_ALLOCATION_PROOF_OWNED" "proof ownership remains false" + assert_eq 0 "$cleanup_calls" "malformed proof does not authorize deletion of unowned candidate" + [[ -f "$proof" ]] || fail "untrusted proof was removed" + if [[ "$candidate_exists" == yes ]]; then + [[ -f "$proof_root/candidate" ]] || fail "untrusted candidate was removed" + fi + done +) + OTHER_UID=65534; [[ "$OTHER_UID" == "$XANMOD_TRUSTED_UID" ]] && OTHER_UID=0 OTHER_GID=65534; [[ "$OTHER_GID" == "$XANMOD_TRUSTED_GID" ]] && OTHER_GID=0 diff --git a/tools/xanmod-install.sh b/tools/xanmod-install.sh index 9c7839e..a7fa655 100755 --- a/tools/xanmod-install.sh +++ b/tools/xanmod-install.sh @@ -724,7 +724,8 @@ xanmod_pending_allocation_proof_trusted() { { IFS= read -r proof_token || return 1 IFS= read -r proof_identity || return 1 - if IFS= read -r extra_line; then + # read returns failure at EOF even after consuming an unterminated tail. + if IFS= read -r extra_line || [[ -n "$extra_line" ]]; then return 1 fi } < "$proof_path"