2198 lines
103 KiB
Bash
Executable File
2198 lines
103 KiB
Bash
Executable File
#!/usr/bin/env bash
|
|
|
|
set -Eeuo pipefail
|
|
|
|
readonly ROOT="$(cd -- "$(dirname -- "${BASH_SOURCE[0]}")/../.." && pwd -P)"
|
|
readonly PROD="$ROOT/scripts/bootstrap/backup-slack-webhook-recovery.sh"
|
|
readonly LIB="$ROOT/scripts/lib/slack-webhook-recovery.sh"
|
|
fixture_dir=''
|
|
runtime_fixture=''
|
|
owned_work_fixture=''
|
|
boundary_cases=0
|
|
review_failures=0
|
|
transaction_cases=0
|
|
production_runtime_root_definition=''
|
|
|
|
fail() {
|
|
printf 'TEST FAILURE: %s\n' "$*" >&2
|
|
exit 1
|
|
}
|
|
|
|
assert_eq() {
|
|
local expected="$1" actual="$2" description="$3"
|
|
[[ "$actual" == "$expected" ]] || fail "$description"
|
|
}
|
|
|
|
assert_fails() {
|
|
if "$@" >/dev/null 2>&1; then
|
|
fail "expected failure: $*"
|
|
fi
|
|
}
|
|
|
|
pass_boundary_case() {
|
|
boundary_cases=$((boundary_cases + 1))
|
|
}
|
|
|
|
pass_transaction_case() {
|
|
transaction_cases=$((transaction_cases + 1))
|
|
}
|
|
|
|
write_recovery_contract() {
|
|
local destination="$1"
|
|
builtin printf '%s\n' \
|
|
'K3SLR_SCHEMA_VERSION=1' \
|
|
'K3SLR_RECOVERY_DISK_BY_ID=/dev/disk/by-id/wwn-0x500a0751e6aa6254' \
|
|
'K3SLR_RECOVERY_PARTITION_BY_ID=/dev/disk/by-id/wwn-0x500a0751e6aa6254-part3' \
|
|
'K3SLR_RECOVERY_FS_UUID=4EA0196C0C5FA27E' \
|
|
'K3SLR_RECOVERY_PARTUUID=4670aa9f-9045-4bce-930d-9e84dfec9f38' \
|
|
'K3SLR_RECOVERY_MODEL=CT1000MX500SSD1' \
|
|
'K3SLR_RECOVERY_SERIAL=2306E6AA6254' \
|
|
'K3SLR_RECOVERY_WWN=0x500a0751e6aa6254' \
|
|
'K3SLR_K3S_DISK_BY_ID=/dev/disk/by-id/wwn-0x500a07512df28bd1' \
|
|
'K3SLR_K3S_PARTITION_BY_ID=/dev/disk/by-id/wwn-0x500a07512df28bd1-part1' \
|
|
'K3SLR_K3S_FS_UUID=b86086ef-2b3c-4638-abcf-fc7f137dcb97' \
|
|
'K3SLR_K3S_PARTUUID=b081b955-d6bc-442c-ac0f-db76560a5245' \
|
|
'K3SLR_K3S_MODEL=CT250MX500SSD1' \
|
|
'K3SLR_K3S_SERIAL=21132DF28BD1' \
|
|
'K3SLR_K3S_WWN=0x500a07512df28bd1' \
|
|
'K3SLR_OWNER_UID=1000' \
|
|
'K3SLR_OWNER_GID=1000' \
|
|
'K3SLR_OUTER_MOUNT=/mnt/k3s-recovery-ssd' \
|
|
'K3SLR_INNER_MOUNT=/srv/recovery/k3s' \
|
|
'K3SLR_ROOT_RELATIVE=HyeonworksRecovery' \
|
|
'K3SLR_DATABASE_RELATIVE=HyeonworksRecovery/vault/hyeonworks-recovery.kdbx' \
|
|
'K3SLR_CONTAINER_RELATIVE=HyeonworksRecovery/containers/k3s-recovery.luks' \
|
|
'K3SLR_RUNTIME_METADATA_RELATIVE=HyeonworksRecovery/volume.env' \
|
|
'K3SLR_MAPPING_NAME=k3s-recovery' \
|
|
'K3SLR_PROOF_MAPPING_NAME=k3s-recovery-proof' \
|
|
'K3SLR_INNER_LABEL=K3S_RECOVERY' \
|
|
'K3SLR_CONTAINER_SIZE_BYTES=34359738368' \
|
|
'K3SLR_MINIMUM_FREE_BYTES=10737418240' \
|
|
'K3SLR_OUTER_MIN_REMAINING_PERCENT=20' >"$destination"
|
|
chmod 0600 -- "$destination"
|
|
}
|
|
|
|
reset_fake_boundary() {
|
|
: >"$fake_log"
|
|
builtin printf '%s\n' absent >"$fake_mount_state"
|
|
builtin printf '%s\n' 0 >"$fake_probe_status"
|
|
builtin printf '%s\n' 0 >"$fake_mount_status"
|
|
builtin printf '%s\n' 0 >"$fake_umount_status"
|
|
builtin printf '%s\n' absent >"$fake_umount_after"
|
|
builtin printf '%s\n' 0 >"$fake_sudo_status"
|
|
builtin printf '%s\n' 0 >"$fake_source_query_count"
|
|
builtin printf '%s\n' 0 >"$fake_action_count"
|
|
builtin printf '%s\n' 0 >"$fake_mount_uncertain"
|
|
builtin printf '%s\n' valid >"$fake_case_file"
|
|
}
|
|
|
|
fake_log_command() {
|
|
local separator='' argument
|
|
for argument in "$@"; do
|
|
builtin printf '%s%s' "$separator" "$argument" >>"$fake_log"
|
|
separator=$'\t'
|
|
done
|
|
builtin printf '\n' >>"$fake_log"
|
|
}
|
|
|
|
_swr_fake_runtime_root() {
|
|
builtin printf '%s\n' "$fake_runtime_root"
|
|
}
|
|
|
|
_swr_fake_command() {
|
|
fake_log_command "$@"
|
|
local executable="$1" fake_case mount_state phase options
|
|
shift
|
|
if [[ "$executable" == /fixture/fake-only-sentinel ]]; then
|
|
[[ $# -eq 0 ]]
|
|
return
|
|
fi
|
|
fake_case="$(<"$fake_case_file")"
|
|
case "$executable" in
|
|
/usr/bin/readlink)
|
|
[[ "$1" == -e && "$2" == -- && $# -eq 3 ]] || return 97
|
|
case "$3" in
|
|
/run/user/1000) builtin printf '%s\n' /run/user/1000 ;;
|
|
/dev/disk/by-id/wwn-0x500a0751e6aa6254) builtin printf '%s\n' /dev/fixture-recovery-disk ;;
|
|
/dev/disk/by-id/wwn-0x500a0751e6aa6254-part3) builtin printf '%s\n' /dev/fixture-recovery-partition ;;
|
|
/dev/disk/by-id/wwn-0x500a07512df28bd1) builtin printf '%s\n' /dev/fixture-k3s-disk ;;
|
|
/dev/disk/by-id/wwn-0x500a07512df28bd1-part1) builtin printf '%s\n' /dev/fixture-k3s-partition ;;
|
|
*) return 97 ;;
|
|
esac
|
|
;;
|
|
/usr/sbin/blkid)
|
|
[[ "$1" == --output && "$2" == value && "$3" == --match-tag && "$5" == -- && $# -eq 6 ]] || return 97
|
|
case "$4|$6" in
|
|
'UUID|/dev/fixture-recovery-partition')
|
|
[[ "$fake_case" == wrong_uuid ]] && builtin printf '%s\n' WRONG || builtin printf '%s\n' 4EA0196C0C5FA27E
|
|
;;
|
|
'PARTUUID|/dev/fixture-recovery-partition') builtin printf '%s\n' 4670aa9f-9045-4bce-930d-9e84dfec9f38 ;;
|
|
'TYPE|/dev/fixture-recovery-partition')
|
|
[[ "$fake_case" == wrong_type ]] && builtin printf '%s\n' ext4 || builtin printf '%s\n' ntfs
|
|
;;
|
|
*) return 97 ;;
|
|
esac
|
|
;;
|
|
/usr/bin/lsblk)
|
|
[[ "$1" == --noheadings && "$2" == --raw && "$3" == --paths ]] || return 97
|
|
if [[ "$4" == --output && "$6" == -- && $# -eq 7 ]]; then
|
|
case "$5|$7" in
|
|
'PKNAME|/dev/fixture-recovery-partition')
|
|
[[ "$fake_case" == wrong_parent ]] && builtin printf '%s\n' /dev/fixture-other-disk || builtin printf '%s\n' /dev/fixture-recovery-disk
|
|
;;
|
|
'PKNAME|/dev/fixture-k3s-partition')
|
|
[[ "$fake_case" == same_parent ]] && builtin printf '%s\n' /dev/fixture-recovery-disk || builtin printf '%s\n' /dev/fixture-k3s-disk
|
|
;;
|
|
'MODEL|/dev/fixture-recovery-disk')
|
|
[[ "$fake_case" == wrong_model ]] && builtin printf '%s\n' WRONGMODEL '' || builtin printf '%s\n' CT1000MX500SSD1 ''
|
|
;;
|
|
'SERIAL|/dev/fixture-recovery-disk') builtin printf '%s\n' 2306E6AA6254 '' ;;
|
|
'WWN|/dev/fixture-recovery-disk') builtin printf '%s\n' 0x500a0751e6aa6254 '' ;;
|
|
*) return 97 ;;
|
|
esac
|
|
elif [[ "$4" == --nodeps && "$5" == --output && "$7" == -- && $# -eq 8 ]]; then
|
|
case "$6|$8" in
|
|
'MODEL|/dev/fixture-recovery-disk')
|
|
[[ "$fake_case" == wrong_model ]] && builtin printf '%s\n' WRONGMODEL || builtin printf '%s\n' CT1000MX500SSD1
|
|
;;
|
|
'SERIAL|/dev/fixture-recovery-disk') builtin printf '%s\n' 2306E6AA6254 ;;
|
|
'WWN|/dev/fixture-recovery-disk') builtin printf '%s\n' 0x500a0751e6aa6254 ;;
|
|
*) return 97 ;;
|
|
esac
|
|
else
|
|
return 97
|
|
fi
|
|
;;
|
|
/usr/bin/findmnt)
|
|
if [[ "$1" == --json && "$2" == --source && "$3" == /dev/fixture-recovery-partition &&
|
|
"$4" == --output && "$5" == SOURCE,TARGET,FSTYPE,VFS-OPTIONS,FS-OPTIONS && $# -eq 5 ]]; then
|
|
local source_query_count
|
|
source_query_count="$(( $(<"$fake_source_query_count") + 1 ))"
|
|
builtin printf '%s\n' "$source_query_count" >"$fake_source_query_count"
|
|
mount_state="$(<"$fake_mount_state")"
|
|
[[ "$fake_case" == source_ambiguous ]] && return 2
|
|
if [[ "$fake_case" == source_rc1_nonempty ||
|
|
"$fake_case" == source_rc1_nonempty_poll1 && "$source_query_count" == 1 ||
|
|
"$fake_case" == source_rc1_nonempty_poll2 && "$source_query_count" == 2 ]]; then
|
|
builtin printf '%s\n' '{"filesystems":['
|
|
return 1
|
|
fi
|
|
if [[ "$fake_case" == source_rc1_overflow ]]; then
|
|
local overflow
|
|
builtin printf -v overflow '%*s' 16385 ''
|
|
builtin printf '%s' "${overflow// /x}"
|
|
return 1
|
|
fi
|
|
case "$fake_case" in
|
|
source_rc1_newline_1) builtin printf '\n'; return 1 ;;
|
|
source_rc1_newline_2) builtin printf '\n\n'; return 1 ;;
|
|
source_rc1_newline_overflow)
|
|
local newline_overflow
|
|
builtin printf -v newline_overflow '%*s' 16385 ''
|
|
builtin printf '%s' "${newline_overflow// /$'\n'}"
|
|
return 1
|
|
;;
|
|
esac
|
|
[[ "$mount_state" == absent ]] && return 1
|
|
builtin printf '{"filesystems":[{"source":"/dev/fixture-recovery-partition","target":"%s","fstype":"fuseblk","vfs-options":"%s,nosuid,nodev,noexec","fs-options":"%s,allow_other,uid=1000,gid=1000,fmask=0177,dmask=0077"}]}\n' \
|
|
"$fake_mountpoint" "$mount_state" "$mount_state"
|
|
return 0
|
|
fi
|
|
if [[ "$1" == --json && "$2" == --mountpoint &&
|
|
"$3" == "$fake_runtime_root/slack-webhook-recovery."* &&
|
|
"$4" == --output && "$5" == SOURCE,TARGET,FSTYPE,VFS-OPTIONS,FS-OPTIONS && $# -eq 5 ]]; then
|
|
fake_mountpoint="$3"
|
|
mount_state="$(<"$fake_mount_state")"
|
|
[[ "$fake_case" == target_reused && "$mount_state" == absent ]] && {
|
|
builtin printf '{"filesystems":[{"source":"/dev/foreign","target":"%s","fstype":"ext4","vfs-options":"rw","fs-options":"rw"}]}\n' "$fake_mountpoint"
|
|
return 0
|
|
}
|
|
[[ "$mount_state" == absent ]] && return 1
|
|
local source=/dev/fixture-recovery-partition fstype=fuseblk
|
|
local vfs_options="nodev,noexec,nosuid,relatime,${mount_state}"
|
|
local fs_options="allow_other,blksize=4096,default_permissions,group_id=0,${mount_state},user_id=0"
|
|
[[ "$fake_case" == foreign_source ]] && source=/dev/foreign
|
|
[[ "$fake_case" == foreign_fstype ]] && fstype=ext4
|
|
[[ "$fake_case" == unsafe_allow_other ]] && fs_options="${mount_state},allow_other"
|
|
[[ "$fake_case" == missing_nosuid ]] && vfs_options="nodev,noexec,relatime,${mount_state}"
|
|
[[ "$fake_case" == vfs_relatime_missing ]] && vfs_options="nodev,noexec,nosuid,${mount_state}"
|
|
[[ "$fake_case" == vfs_relatime_duplicate ]] && vfs_options+=",relatime"
|
|
[[ "$fake_case" == vfs_phase_missing ]] && vfs_options="nodev,noexec,nosuid,relatime"
|
|
[[ "$fake_case" == executable ]] && vfs_options="nodev,exec,nosuid,relatime,${mount_state}"
|
|
[[ "$fake_case" == suid ]] && vfs_options="nodev,noexec,relatime,suid,${mount_state}"
|
|
[[ "$fake_case" == dev ]] && vfs_options="dev,noexec,nosuid,relatime,${mount_state}"
|
|
[[ "$fake_case" == wrong_phase ]] && vfs_options="nodev,noexec,nosuid,relatime,rw"
|
|
[[ "$fake_case" == fs_allow_other_missing ]] && fs_options="${fs_options/allow_other,/}"
|
|
[[ "$fake_case" == fs_allow_other_duplicate ]] && fs_options+=",allow_other"
|
|
[[ "$fake_case" == fs_phase_missing ]] && fs_options="${fs_options/,${mount_state},/,}"
|
|
[[ "$fake_case" == fs_wrong_phase ]] && fs_options="${fs_options/,${mount_state},/,rw,}"
|
|
[[ "$fake_case" == legacy_explicit_mapping ]] && {
|
|
vfs_options="${mount_state},nosuid,nodev,noexec"
|
|
fs_options="${mount_state},allow_other,uid=1000,gid=1000,fmask=0177,dmask=0077"
|
|
}
|
|
[[ "$fake_case" == fs_default_permissions_missing ]] && fs_options="${fs_options/default_permissions,/}"
|
|
[[ "$fake_case" == fs_default_permissions_duplicate ]] && fs_options+=",default_permissions"
|
|
[[ "$fake_case" == fs_user_id_missing ]] && fs_options="${fs_options/,user_id=0/}"
|
|
[[ "$fake_case" == fs_user_id_wrong ]] && fs_options="${fs_options/user_id=0/user_id=1000}"
|
|
[[ "$fake_case" == fs_user_id_duplicate ]] && fs_options+=",user_id=0"
|
|
[[ "$fake_case" == fs_group_id_missing ]] && fs_options="${fs_options/group_id=0,/}"
|
|
[[ "$fake_case" == fs_group_id_wrong ]] && fs_options="${fs_options/group_id=0/group_id=1000}"
|
|
[[ "$fake_case" == fs_group_id_duplicate ]] && fs_options+=",group_id=0"
|
|
[[ "$fake_case" == fs_blksize_missing ]] && fs_options="${fs_options/blksize=4096,/}"
|
|
[[ "$fake_case" == fs_blksize_wrong ]] && fs_options="${fs_options/blksize=4096/blksize=8192}"
|
|
[[ "$fake_case" == fs_blksize_duplicate ]] && fs_options+=",blksize=4096"
|
|
[[ "$fake_case" == field_placement_vfs_default_permissions ]] && vfs_options+=",default_permissions"
|
|
[[ "$fake_case" == field_placement_fs_nodev ]] && fs_options+=",nodev"
|
|
[[ "$fake_case" == hybrid_actual_legacy ]] && fs_options+=",uid=1000,gid=1000,fmask=0177,dmask=0077"
|
|
[[ "$fake_case" == observed_permuted ]] && {
|
|
vfs_options="${mount_state},relatime,nosuid,noexec,nodev"
|
|
fs_options="${mount_state},user_id=0,group_id=0,default_permissions,blksize=4096,allow_other"
|
|
}
|
|
[[ "$fake_case" == json_fs_trailing_lf ]] && fs_options+=$'\n'
|
|
[[ "$fake_case" == json_fs_trailing_lf_garbage ]] && fs_options+=$'\nunknown'
|
|
[[ "$fake_case" == json_vfs_cross_field_reframe ]] && {
|
|
vfs_options+=$'\n'"$fs_options"
|
|
fs_options=''
|
|
}
|
|
[[ "$fake_case" == fs_uid_conflict ]] && fs_options+=",uid=0"
|
|
[[ "$fake_case" == fs_uid_duplicate ]] && fs_options+=",uid=1000"
|
|
[[ "$fake_case" == fs_gid_conflict ]] && fs_options+=",gid=0"
|
|
[[ "$fake_case" == fs_gid_duplicate ]] && fs_options+=",gid=1000"
|
|
[[ "$fake_case" == fs_fmask_conflict ]] && fs_options+=",fmask=0000"
|
|
[[ "$fake_case" == fs_fmask_duplicate ]] && fs_options+=",fmask=0177"
|
|
[[ "$fake_case" == fs_dmask_conflict ]] && fs_options+=",dmask=0000"
|
|
[[ "$fake_case" == fs_dmask_duplicate ]] && fs_options+=",dmask=0077"
|
|
[[ "$fake_case" == fs_umask ]] && fs_options+=",umask=0000"
|
|
[[ "$fake_case" == fs_phase_conflict ]] && fs_options+=",rw"
|
|
[[ "$fake_case" == fs_phase_duplicate ]] && fs_options+=",ro"
|
|
[[ "$fake_case" == vfs_phase_duplicate ]] && vfs_options+=",ro"
|
|
[[ "$fake_case" == vfs_nosuid_duplicate ]] && vfs_options+=",nosuid"
|
|
[[ "$fake_case" == vfs_nodev_duplicate ]] && vfs_options+=",nodev"
|
|
[[ "$fake_case" == vfs_noexec_duplicate ]] && vfs_options+=",noexec"
|
|
[[ "$fake_case" == vfs_suid_conflict ]] && vfs_options+=",suid"
|
|
[[ "$fake_case" == vfs_dev_conflict ]] && vfs_options+=",dev"
|
|
[[ "$fake_case" == vfs_exec_conflict ]] && vfs_options+=",exec"
|
|
case "$fake_case" in
|
|
vfs_permissions) vfs_options+=",permissions" ;;
|
|
vfs_acl) vfs_options+=",acl" ;;
|
|
vfs_inherit) vfs_options+=",inherit" ;;
|
|
vfs_unknown) vfs_options+=",unknown_fixture_token" ;;
|
|
fs_permissions) fs_options+=",permissions" ;;
|
|
fs_acl) fs_options+=",acl" ;;
|
|
fs_inherit) fs_options+=",inherit" ;;
|
|
fs_unknown) fs_options+=",unknown_fixture_token" ;;
|
|
fs_arbitrary) fs_options+=",arbitrary_fixture_option" ;;
|
|
vfs_empty) vfs_options='' ;;
|
|
vfs_leading_comma) vfs_options=",${vfs_options}" ;;
|
|
vfs_trailing_comma) vfs_options+="," ;;
|
|
vfs_double_comma) vfs_options="${vfs_options/nosuid,/nosuid,,}" ;;
|
|
fs_empty) fs_options='' ;;
|
|
fs_leading_comma) fs_options=",${fs_options}" ;;
|
|
fs_trailing_comma) fs_options+="," ;;
|
|
fs_double_comma) fs_options="${fs_options/ro,/ro,,}" ;;
|
|
esac
|
|
builtin printf '{"filesystems":[{"source":"%s","target":"%s","fstype":"%s","vfs-options":"%s","fs-options":"%s"}]}\n' \
|
|
"$source" "$fake_mountpoint" "$fstype" "$vfs_options" "$fs_options"
|
|
return 0
|
|
fi
|
|
return 97
|
|
;;
|
|
/usr/bin/stat)
|
|
if [[ "$1" == --format=%u:%g:%a && "$2" == -- &&
|
|
"$3" == /run/user/1000/platform-slack-keepass.* && $# -eq 3 ]]; then
|
|
command /usr/bin/stat "$@"
|
|
return
|
|
fi
|
|
if [[ "$1" == --file-system && "$2" == --format=%T && "$3" == -- &&
|
|
"$4" == /run/user/1000/platform-slack-keepass.* && $# -eq 4 ]]; then
|
|
command /usr/bin/stat "$@"
|
|
return
|
|
fi
|
|
[[ "$1" == --format=%u:%g:%a && "$2" == -- &&
|
|
"$3" == "$fake_runtime_root/slack-webhook-recovery."* && $# -eq 3 ]] || return 97
|
|
[[ "$fake_case" == wrong_owner ]] && builtin printf '%s\n' 1001:1000:700 && return 0
|
|
[[ "$fake_case" == wrong_gid ]] && builtin printf '%s\n' 1000:1001:700 && return 0
|
|
[[ "$fake_case" == wrong_mode ]] && builtin printf '%s\n' 1000:1000:755 && return 0
|
|
builtin printf '%s\n' 1000:1000:700
|
|
;;
|
|
/usr/bin/mktemp)
|
|
[[ "$1" == -d && "$2" == -- && "$3" == "$fake_runtime_root/slack-webhook-recovery.XXXXXXXX" && $# -eq 3 ]] || return 97
|
|
command /usr/bin/mktemp "$@"
|
|
;;
|
|
/usr/bin/chmod)
|
|
command /usr/bin/chmod "$@"
|
|
;;
|
|
/usr/bin/rmdir)
|
|
command /usr/bin/rmdir "$@"
|
|
;;
|
|
/usr/bin/jq)
|
|
command /usr/bin/jq "$@"
|
|
;;
|
|
/usr/bin/base64)
|
|
command /usr/bin/base64 "$@"
|
|
;;
|
|
/usr/bin/sudo)
|
|
if [[ "$1" == -v && $# -eq 1 ]]; then
|
|
return "$(<"$fake_sudo_status")"
|
|
fi
|
|
[[ "$1" == -n && "$2" == /usr/bin/timeout && "$3" == --signal=TERM ]] || return 97
|
|
if [[ "$4" == --kill-after=5s && "$5" == 20s && "$6" == /usr/bin/ntfs-3g.probe &&
|
|
"$7" == --readwrite && "$8" == /dev/fixture-recovery-partition && $# -eq 8 ]]; then
|
|
return "$(<"$fake_probe_status")"
|
|
fi
|
|
if [[ "$4" == --kill-after=10s && "$5" == 45s && "$6" == /usr/bin/ntfs-3g &&
|
|
"$7" == -o && "$9" == /dev/fixture-recovery-partition &&
|
|
"${10}" == "$fake_runtime_root/slack-webhook-recovery."* && $# -eq 10 ]]; then
|
|
fake_mountpoint="${10}"
|
|
options="$8"
|
|
case ",$options," in
|
|
*,ro,*) phase=ro ;;
|
|
*,rw,*) phase=rw ;;
|
|
*) return 97 ;;
|
|
esac
|
|
if [[ "$(<"$fake_mount_status")" != 0 ]]; then
|
|
[[ "$(<"$fake_mount_uncertain")" == 1 ]] && builtin printf '%s\n' "$phase" >"$fake_mount_state"
|
|
return "$(<"$fake_mount_status")"
|
|
fi
|
|
builtin printf '%s\n' "$phase" >"$fake_mount_state"
|
|
return 0
|
|
fi
|
|
if [[ "$4" == --kill-after=10s && "$5" == 45s && "$6" == /usr/bin/umount &&
|
|
"$7" == -- && "$8" == "$fake_mountpoint" && $# -eq 8 ]]; then
|
|
builtin printf '%s\n' "$(<"$fake_umount_after")" >"$fake_mount_state"
|
|
return "$(<"$fake_umount_status")"
|
|
fi
|
|
return 97
|
|
;;
|
|
*)
|
|
return 97
|
|
;;
|
|
esac
|
|
}
|
|
|
|
_swr_fake_volume_action() {
|
|
local result_name="$1" phase="$2"
|
|
builtin printf '%s\n' "$(( $(<"$fake_action_count") + 1 ))" >"$fake_action_count"
|
|
case "$fake_action" in
|
|
noop) builtin printf -v "$result_name" '%s' exact-noop ;;
|
|
mutation)
|
|
[[ "$phase" == ro ]] && builtin printf -v "$result_name" '%s' mutation || builtin printf -v "$result_name" '%s' complete
|
|
;;
|
|
error) return 71 ;;
|
|
signal)
|
|
kill -TERM "$BASHPID"
|
|
builtin printf -v "$result_name" '%s' exact-noop
|
|
;;
|
|
*) return 97 ;;
|
|
esac
|
|
}
|
|
|
|
assert_log_line() {
|
|
local expected="$1" description="$2"
|
|
/usr/bin/grep -Fqx -- "$expected" "$fake_log" || fail "$description"
|
|
}
|
|
|
|
test_contract_and_device_boundary() {
|
|
local contract="$fixture_dir/local-recovery.env" variant="$fixture_dir/variant.env"
|
|
local _swr_device='' _swr_no_nodeps='' identity_trace
|
|
write_recovery_contract "$contract"
|
|
|
|
_swr_load_recovery_contract "$contract" || fail 'strict synthetic recovery contract was rejected'
|
|
assert_eq /dev/disk/by-id/wwn-0x500a0751e6aa6254 "$SWR_RECOVERY_DISK_BY_ID" 'recovery disk contract differs'
|
|
assert_eq /dev/disk/by-id/wwn-0x500a0751e6aa6254-part3 "$SWR_RECOVERY_PARTITION_BY_ID" 'recovery partition contract differs'
|
|
assert_eq 4EA0196C0C5FA27E "$SWR_RECOVERY_FS_UUID" 'recovery UUID contract differs'
|
|
pass_boundary_case
|
|
|
|
/usr/bin/awk 'NR != 3' "$contract" >"$variant"
|
|
assert_fails _swr_load_recovery_contract "$variant"
|
|
pass_boundary_case
|
|
{ command /usr/bin/cp -- "$contract" "$variant"; builtin printf '%s\n' 'K3SLR_SCHEMA_VERSION=1' >>"$variant"; }
|
|
assert_fails _swr_load_recovery_contract "$variant"
|
|
pass_boundary_case
|
|
/usr/bin/awk 'NR == 2 { a=$0; next } NR == 3 { print; print a; next } { print }' "$contract" >"$variant"
|
|
assert_fails _swr_load_recovery_contract "$variant"
|
|
pass_boundary_case
|
|
/usr/bin/sed '2s/^K3SLR_RECOVERY_DISK_BY_ID=/K3SLR_UNKNOWN=/' "$contract" >"$variant"
|
|
assert_fails _swr_load_recovery_contract "$variant"
|
|
pass_boundary_case
|
|
/usr/bin/sed '2s/=/ /' "$contract" >"$variant"
|
|
assert_fails _swr_load_recovery_contract "$variant"
|
|
pass_boundary_case
|
|
|
|
reset_fake_boundary
|
|
assert_fails _swr_capture_single_line _swr_no_nodeps /usr/bin/lsblk \
|
|
--noheadings --raw --paths --output MODEL -- /dev/fixture-recovery-disk
|
|
pass_boundary_case
|
|
|
|
reset_fake_boundary
|
|
_swr_load_recovery_contract "$contract" || fail 'valid contract reload failed'
|
|
_swr_resolve_device _swr_device || fail 'exact synthetic device was rejected'
|
|
assert_eq /dev/fixture-recovery-partition "$_swr_device" 'canonical recovery partition differs'
|
|
assert_log_line $'/usr/sbin/blkid\t--output\tvalue\t--match-tag\tUUID\t--\t/dev/fixture-recovery-partition' 'fixed UUID probe argv differs'
|
|
assert_log_line $'/usr/bin/lsblk\t--noheadings\t--raw\t--paths\t--output\tPKNAME\t--\t/dev/fixture-recovery-partition' 'fixed parent probe argv differs'
|
|
identity_trace="$(/usr/bin/grep -E $'^/usr/bin/lsblk\t.*\t(MODEL|SERIAL|WWN)\t' "$fake_log")"
|
|
assert_eq \
|
|
$'/usr/bin/lsblk\t--noheadings\t--raw\t--paths\t--nodeps\t--output\tMODEL\t--\t/dev/fixture-recovery-disk\n/usr/bin/lsblk\t--noheadings\t--raw\t--paths\t--nodeps\t--output\tSERIAL\t--\t/dev/fixture-recovery-disk\n/usr/bin/lsblk\t--noheadings\t--raw\t--paths\t--nodeps\t--output\tWWN\t--\t/dev/fixture-recovery-disk' \
|
|
"$identity_trace" 'fixed disk identity probe argv/order differs'
|
|
pass_boundary_case
|
|
|
|
local rejection
|
|
for rejection in wrong_uuid wrong_type wrong_parent same_parent wrong_model; do
|
|
reset_fake_boundary
|
|
builtin printf '%s\n' "$rejection" >"$fake_case_file"
|
|
assert_fails _swr_resolve_device _swr_device
|
|
pass_boundary_case
|
|
done
|
|
|
|
reset_fake_boundary
|
|
_swr_require_unmounted /dev/fixture-recovery-partition || fail 'stable absent source was rejected'
|
|
pass_boundary_case
|
|
builtin printf '%s\n' ro >"$fake_mount_state"
|
|
assert_fails _swr_require_unmounted /dev/fixture-recovery-partition
|
|
pass_boundary_case
|
|
reset_fake_boundary
|
|
builtin printf '%s\n' source_ambiguous >"$fake_case_file"
|
|
assert_fails _swr_require_unmounted /dev/fixture-recovery-partition
|
|
pass_boundary_case
|
|
}
|
|
|
|
test_probe_mount_and_validation_boundary() {
|
|
local status first_stat_line findmnt_line last_stat_line _swr_created=''
|
|
reset_fake_boundary
|
|
_swr_probe_rw /dev/fixture-recovery-partition || fail 'successful read-write probe was rejected'
|
|
assert_log_line $'/usr/bin/sudo\t-n\t/usr/bin/timeout\t--signal=TERM\t--kill-after=5s\t20s\t/usr/bin/ntfs-3g.probe\t--readwrite\t/dev/fixture-recovery-partition' 'fixed privileged probe argv differs'
|
|
pass_boundary_case
|
|
for status in 1 124 137 143 255; do
|
|
reset_fake_boundary
|
|
builtin printf '%s\n' "$status" >"$fake_probe_status"
|
|
assert_fails _swr_probe_rw /dev/fixture-recovery-partition
|
|
pass_boundary_case
|
|
done
|
|
|
|
reset_fake_boundary
|
|
_swr_create_mountpoint _swr_created || fail 'private fixture mountpoint creation failed'
|
|
fake_mountpoint="$_swr_created"
|
|
[[ "$fake_mountpoint" == "$fake_runtime_root/slack-webhook-recovery."* ]] || fail 'mountpoint escaped private runtime root'
|
|
[[ "$(/usr/bin/stat -c %a -- "$fake_mountpoint")" == 700 ]] || fail 'mountpoint mode differs'
|
|
|
|
# Production break caught: installed ntfs-3g does not accept a standalone
|
|
# `--` before DEVICE. The strict fake accepts only its documented argv.
|
|
_swr_mount_volume /dev/fixture-recovery-partition "$fake_mountpoint" ro || fail 'closed read-only mount argv was rejected'
|
|
assert_log_line $'/usr/bin/sudo\t-n\t/usr/bin/timeout\t--signal=TERM\t--kill-after=10s\t45s\t/usr/bin/ntfs-3g\t-o\tnorecover,no_def_opts,allow_other,nodev,nosuid,noexec,uid=1000,gid=1000,fmask=0177,dmask=0077,ro\t/dev/fixture-recovery-partition\t'"$fake_mountpoint" 'fixed read-only mount argv differs'
|
|
|
|
# Production break caught: removing the unsupported separator must not
|
|
# allow an untrusted device or mountpoint to reach the privileged leaf.
|
|
local adversarial_mountpoint
|
|
: >"$fake_log"
|
|
assert_fails _swr_mount_volume_command -o,allow_other "$fake_mountpoint" ro
|
|
assert_fails _swr_mount_volume_command /dev/fixture-recovery-partition -o,allow_other ro
|
|
assert_fails _swr_mount_volume_command /tmp/arbitrary-device "$fake_mountpoint" ro
|
|
assert_fails _swr_mount_volume_command /dev/fixture-recovery-partition-sibling "$fake_mountpoint" ro
|
|
assert_fails _swr_mount_volume_command /dev/fixture-recovery-partition /tmp/arbitrary-mountpoint ro
|
|
|
|
adversarial_mountpoint="$fake_runtime_root/slack-webhook-recovery.foreign"
|
|
assert_fails _swr_mount_volume_command /dev/fixture-recovery-partition "$adversarial_mountpoint" ro
|
|
/usr/bin/ln -s -- "$fixture_dir" "$adversarial_mountpoint"
|
|
assert_fails _swr_mount_volume_command /dev/fixture-recovery-partition "$adversarial_mountpoint" ro
|
|
/usr/bin/rm -f -- "$adversarial_mountpoint"
|
|
|
|
builtin printf '%s\n' wrong_owner >"$fake_case_file"
|
|
assert_fails _swr_mount_volume_command /dev/fixture-recovery-partition "$fake_mountpoint" ro
|
|
builtin printf '%s\n' wrong_mode >"$fake_case_file"
|
|
assert_fails _swr_mount_volume_command /dev/fixture-recovery-partition "$fake_mountpoint" ro
|
|
builtin printf '%s\n' valid >"$fake_case_file"
|
|
|
|
! /usr/bin/grep -Fq $'/usr/bin/sudo\t-n\t/usr/bin/timeout\t--signal=TERM\t--kill-after=10s\t45s\t/usr/bin/ntfs-3g\t' "$fake_log" ||
|
|
fail 'untrusted mount operand reached the privileged command boundary'
|
|
pass_boundary_case
|
|
# Production break caught: findmnt reports the effective ntfs-3g FUSE
|
|
# policy in normalized kernel-facing form rather than echoing uid/gid/masks.
|
|
: >"$fake_log"
|
|
builtin printf '%s\n' observed_normalized >"$fake_case_file"
|
|
_swr_validate_mount /dev/fixture-recovery-partition "$fake_mountpoint" ro ||
|
|
fail 'observed normalized read-only FUSE evidence was rejected'
|
|
assert_eq 2 "$(/usr/bin/grep -Fc $'/usr/bin/stat\t--format=%u:%g:%a\t--\t'"$fake_mountpoint" "$fake_log")" \
|
|
'read-only mount validation did not couple option evidence to two private-root attestations'
|
|
assert_log_line $'/usr/bin/findmnt\t--json\t--mountpoint\t'"$fake_mountpoint"$'\t--output\tSOURCE,TARGET,FSTYPE,VFS-OPTIONS,FS-OPTIONS' \
|
|
'read-only mount validation findmnt argv differs'
|
|
first_stat_line="$(/usr/bin/grep -nF $'/usr/bin/stat\t--format=%u:%g:%a\t--\t'"$fake_mountpoint" "$fake_log" | /usr/bin/head -n 1)"
|
|
findmnt_line="$(/usr/bin/grep -nF $'/usr/bin/findmnt\t--json\t--mountpoint\t'"$fake_mountpoint"$'\t--output\tSOURCE,TARGET,FSTYPE,VFS-OPTIONS,FS-OPTIONS' "$fake_log")"
|
|
last_stat_line="$(/usr/bin/grep -nF $'/usr/bin/stat\t--format=%u:%g:%a\t--\t'"$fake_mountpoint" "$fake_log" | /usr/bin/tail -n 1)"
|
|
(( ${first_stat_line%%:*} < ${findmnt_line%%:*} && ${findmnt_line%%:*} < ${last_stat_line%%:*} )) ||
|
|
fail 'read-only option observation was not bracketed by private-root attestations'
|
|
pass_boundary_case
|
|
|
|
: >"$fake_log"
|
|
builtin printf '%s\n' rw >"$fake_mount_state"
|
|
_swr_validate_mount /dev/fixture-recovery-partition "$fake_mountpoint" rw ||
|
|
fail 'observed normalized writable FUSE evidence was rejected'
|
|
assert_eq 2 "$(/usr/bin/grep -Fc $'/usr/bin/stat\t--format=%u:%g:%a\t--\t'"$fake_mountpoint" "$fake_log")" \
|
|
'writable mount validation did not couple option evidence to two private-root attestations'
|
|
assert_log_line $'/usr/bin/findmnt\t--json\t--mountpoint\t'"$fake_mountpoint"$'\t--output\tSOURCE,TARGET,FSTYPE,VFS-OPTIONS,FS-OPTIONS' \
|
|
'writable mount validation findmnt argv differs'
|
|
first_stat_line="$(/usr/bin/grep -nF $'/usr/bin/stat\t--format=%u:%g:%a\t--\t'"$fake_mountpoint" "$fake_log" | /usr/bin/head -n 1)"
|
|
findmnt_line="$(/usr/bin/grep -nF $'/usr/bin/findmnt\t--json\t--mountpoint\t'"$fake_mountpoint"$'\t--output\tSOURCE,TARGET,FSTYPE,VFS-OPTIONS,FS-OPTIONS' "$fake_log")"
|
|
last_stat_line="$(/usr/bin/grep -nF $'/usr/bin/stat\t--format=%u:%g:%a\t--\t'"$fake_mountpoint" "$fake_log" | /usr/bin/tail -n 1)"
|
|
(( ${first_stat_line%%:*} < ${findmnt_line%%:*} && ${findmnt_line%%:*} < ${last_stat_line%%:*} )) ||
|
|
fail 'writable option observation was not bracketed by private-root attestations'
|
|
pass_boundary_case
|
|
|
|
builtin printf '%s\n' observed_permuted >"$fake_case_file"
|
|
_swr_validate_mount /dev/fixture-recovery-partition "$fake_mountpoint" rw ||
|
|
fail 'permuted normalized writable FUSE evidence was rejected'
|
|
pass_boundary_case
|
|
|
|
builtin printf '%s\n' ro >"$fake_mount_state"
|
|
|
|
local rejection
|
|
for rejection in \
|
|
foreign_source foreign_fstype wrong_owner wrong_gid wrong_mode unsafe_allow_other \
|
|
missing_nosuid vfs_relatime_missing vfs_relatime_duplicate vfs_phase_missing \
|
|
executable suid dev wrong_phase legacy_explicit_mapping \
|
|
fs_allow_other_missing fs_allow_other_duplicate fs_phase_missing fs_wrong_phase \
|
|
fs_default_permissions_missing fs_default_permissions_duplicate \
|
|
fs_user_id_missing fs_user_id_wrong fs_user_id_duplicate \
|
|
fs_group_id_missing fs_group_id_wrong fs_group_id_duplicate \
|
|
fs_blksize_missing fs_blksize_wrong fs_blksize_duplicate \
|
|
field_placement_vfs_default_permissions field_placement_fs_nodev hybrid_actual_legacy \
|
|
fs_uid_conflict fs_uid_duplicate fs_gid_conflict fs_gid_duplicate \
|
|
fs_fmask_conflict fs_fmask_duplicate fs_dmask_conflict fs_dmask_duplicate \
|
|
fs_permissions fs_acl fs_inherit fs_arbitrary; do
|
|
builtin printf '%s\n' "$rejection" >"$fake_case_file"
|
|
assert_fails _swr_validate_mount /dev/fixture-recovery-partition "$fake_mountpoint" ro
|
|
pass_boundary_case
|
|
done
|
|
builtin printf '%s\n' valid >"$fake_case_file"
|
|
builtin printf '%s\n' absent >"$fake_mount_state"
|
|
builtin printf '%s\n' target_reused >"$fake_case_file"
|
|
assert_fails _swr_require_mountpoint_unused "$fake_mountpoint"
|
|
pass_boundary_case
|
|
builtin printf '%s\n' valid >"$fake_case_file"
|
|
command /usr/bin/rmdir -- "$fake_mountpoint"
|
|
}
|
|
|
|
test_unmount_and_cycle_boundary() {
|
|
local status mode second_probe_line mount_line post_probe_trace probes mounts _swr_created=''
|
|
for status in 0 32 124 143 255; do
|
|
reset_fake_boundary
|
|
builtin printf '%s\n' ro >"$fake_mount_state"
|
|
builtin printf '%s\n' "$status" >"$fake_umount_status"
|
|
_swr_unmount_volume /dev/fixture-recovery-partition "$fake_mountpoint" ||
|
|
fail "stable absence after unmount status $status was rejected"
|
|
assert_log_line $'/usr/bin/sudo\t-n\t/usr/bin/timeout\t--signal=TERM\t--kill-after=10s\t45s\t/usr/bin/umount\t--\t'"$fake_mountpoint" 'fixed privileged unmount argv differs'
|
|
pass_boundary_case
|
|
done
|
|
|
|
reset_fake_boundary
|
|
builtin printf '%s\n' ro >"$fake_mount_state"
|
|
builtin printf '%s\n' ro >"$fake_umount_after"
|
|
assert_fails _swr_unmount_volume /dev/fixture-recovery-partition "$fake_mountpoint"
|
|
[[ "$(/usr/bin/grep -Fc $'/usr/bin/sudo\t-n\t/usr/bin/timeout\t--signal=TERM\t--kill-after=10s\t45s\t/usr/bin/umount\t--\t'"$fake_mountpoint" "$fake_log")" == 1 ]] || fail 'unmount retried a retained source'
|
|
! /usr/bin/grep -Eq $'\t(-l|-f|--lazy|--force)(\t|$)' "$fake_log" || fail 'lazy or force unmount was attempted'
|
|
pass_boundary_case
|
|
|
|
reset_fake_boundary
|
|
builtin printf '%s\n' ro >"$fake_mount_state"
|
|
builtin printf '%s\n' source_ambiguous >"$fake_case_file"
|
|
assert_fails _swr_unmount_volume /dev/fixture-recovery-partition "$fake_mountpoint"
|
|
pass_boundary_case
|
|
|
|
for mode in noop mutation error signal; do
|
|
reset_fake_boundary
|
|
fake_action="$mode"
|
|
_swr_create_mountpoint _swr_created || fail 'cycle mountpoint creation failed'
|
|
fake_mountpoint="$_swr_created"
|
|
if [[ "$mode" == noop || "$mode" == mutation ]]; then
|
|
_swr_run_volume_cycle /dev/fixture-recovery-partition "$fake_mountpoint" || fail "$mode cycle failed"
|
|
else
|
|
assert_fails _swr_run_volume_cycle /dev/fixture-recovery-partition "$fake_mountpoint"
|
|
fi
|
|
assert_eq absent "$(<"$fake_mount_state")" "$mode cycle did not unmount"
|
|
probes="$(/usr/bin/grep -Fc $'/usr/bin/sudo\t-n\t/usr/bin/timeout\t--signal=TERM\t--kill-after=5s\t20s\t/usr/bin/ntfs-3g.probe' "$fake_log")"
|
|
mounts="$(/usr/bin/grep -Fc $'/usr/bin/sudo\t-n\t/usr/bin/timeout\t--signal=TERM\t--kill-after=10s\t45s\t/usr/bin/ntfs-3g\t-o' "$fake_log")"
|
|
if [[ "$mode" == mutation ]]; then
|
|
assert_eq 2 "$probes" 'mutation cycle probe count differs'
|
|
assert_eq 2 "$mounts" 'mutation cycle mount count differs'
|
|
second_probe_line="$(/usr/bin/grep -nF $'/usr/bin/sudo\t-n\t/usr/bin/timeout\t--signal=TERM\t--kill-after=5s\t20s\t/usr/bin/ntfs-3g.probe\t--readwrite\t/dev/fixture-recovery-partition' "$fake_log" | /usr/bin/tail -n 1)"
|
|
mount_line="$(/usr/bin/grep -nF $'/usr/bin/sudo\t-n\t/usr/bin/timeout\t--signal=TERM\t--kill-after=10s\t45s\t/usr/bin/ntfs-3g\t-o\tnorecover,no_def_opts,allow_other,nodev,nosuid,noexec,uid=1000,gid=1000,fmask=0177,dmask=0077,rw\t/dev/fixture-recovery-partition\t'"$fake_mountpoint" "$fake_log" | /usr/bin/tail -n 1)"
|
|
post_probe_trace="$(/usr/bin/sed -n "$(( ${second_probe_line%%:*} + 1 )),$(( ${mount_line%%:*} ))p" "$fake_log")"
|
|
assert_eq \
|
|
$'/usr/bin/stat\t--format=%u:%g:%a\t--\t'"$fake_mountpoint"$'\n/usr/bin/sudo\t-n\t/usr/bin/timeout\t--signal=TERM\t--kill-after=10s\t45s\t/usr/bin/ntfs-3g\t-o\tnorecover,no_def_opts,allow_other,nodev,nosuid,noexec,uid=1000,gid=1000,fmask=0177,dmask=0077,rw\t/dev/fixture-recovery-partition\t'"$fake_mountpoint" \
|
|
"$post_probe_trace" 'fresh mountpoint attestation was not immediately between second probe and writable mount'
|
|
else
|
|
assert_eq 1 "$probes" "$mode cycle probe count differs"
|
|
assert_eq 1 "$mounts" "$mode cycle mount count differs"
|
|
fi
|
|
command /usr/bin/rmdir -- "$fake_mountpoint"
|
|
pass_boundary_case
|
|
done
|
|
}
|
|
|
|
test_full_fake_boundary_ordering() {
|
|
local contract="$fixture_dir/local-recovery.env" first_sudo last_preflight _swr_result=''
|
|
reset_fake_boundary
|
|
fake_action=noop
|
|
write_recovery_contract "$contract"
|
|
_swr_open_recovery_boundary _swr_result "$contract" || fail 'full fake recovery boundary failed'
|
|
assert_eq exact-noop "$_swr_result" 'full fake no-op result differs'
|
|
first_sudo="$(/usr/bin/grep -n '^/usr/bin/sudo' "$fake_log" | /usr/bin/head -n 1)"
|
|
last_preflight="$(/usr/bin/grep -n $'^/usr/bin/findmnt\t--json\t--source' "$fake_log" | /usr/bin/head -n 1)"
|
|
(( ${first_sudo%%:*} > ${last_preflight%%:*} )) || fail 'sudo validation ran before user-space preflight completed'
|
|
assert_log_line $'/usr/bin/sudo\t-v' 'fixed sudo validation argv differs'
|
|
[[ ! -e "$fake_mountpoint" ]] || fail 'full boundary left its mountpoint behind'
|
|
pass_boundary_case
|
|
}
|
|
|
|
review_findmnt_initial_rc1_nonempty() {
|
|
reset_fake_boundary
|
|
builtin printf '%s\n' source_rc1_nonempty >"$fake_case_file"
|
|
_swr_require_unmounted /dev/fixture-recovery-partition &&
|
|
fail 'findmnt RC1 nonempty initial output was accepted as absence'
|
|
return 0
|
|
}
|
|
|
|
review_findmnt_post_poll1_rc1_nonempty() {
|
|
reset_fake_boundary
|
|
builtin printf '%s\n' ro >"$fake_mount_state"
|
|
builtin printf '%s\n' source_rc1_nonempty_poll1 >"$fake_case_file"
|
|
_swr_unmount_volume /dev/fixture-recovery-partition "$fake_mountpoint" >/dev/null 2>&1 &&
|
|
fail 'findmnt RC1 nonempty first cleanup poll was accepted as absence'
|
|
return 0
|
|
}
|
|
|
|
review_findmnt_post_poll2_rc1_nonempty() {
|
|
reset_fake_boundary
|
|
builtin printf '%s\n' ro >"$fake_mount_state"
|
|
builtin printf '%s\n' source_rc1_nonempty_poll2 >"$fake_case_file"
|
|
_swr_unmount_volume /dev/fixture-recovery-partition "$fake_mountpoint" >/dev/null 2>&1 &&
|
|
fail 'findmnt RC1 nonempty second cleanup poll was accepted as absence'
|
|
return 0
|
|
}
|
|
|
|
review_findmnt_rc1_overflow() {
|
|
reset_fake_boundary
|
|
builtin printf '%s\n' source_rc1_overflow >"$fake_case_file"
|
|
_swr_require_unmounted /dev/fixture-recovery-partition &&
|
|
fail 'findmnt RC1 overflowing output was accepted as absence'
|
|
return 0
|
|
}
|
|
|
|
review_sudo_after_complete_preflight() {
|
|
local contract="$fixture_dir/local-recovery.env" _swr_result=''
|
|
local sudo_line mktemp_line chmod_line stat_line unused_line probe_line
|
|
local post_sudo_trace
|
|
reset_fake_boundary
|
|
fake_action=noop
|
|
write_recovery_contract "$contract"
|
|
_swr_open_recovery_boundary _swr_result "$contract" || fail 'ordered fake boundary unexpectedly failed'
|
|
sudo_line="$(/usr/bin/grep -n $'^/usr/bin/sudo\t-v$' "$fake_log" | /usr/bin/head -n 1)"
|
|
mktemp_line="$(/usr/bin/grep -n $'^/usr/bin/mktemp\t-d\t--' "$fake_log" | /usr/bin/head -n 1)"
|
|
chmod_line="$(/usr/bin/grep -n $'^/usr/bin/chmod\t0700\t--' "$fake_log" | /usr/bin/head -n 1)"
|
|
stat_line="$(/usr/bin/grep -n $'^/usr/bin/stat\t--format=%u:%g:%a\t--' "$fake_log" | /usr/bin/head -n 1)"
|
|
unused_line="$(/usr/bin/grep -n $'^/usr/bin/findmnt\t--json\t--mountpoint' "$fake_log" | /usr/bin/head -n 1)"
|
|
[[ -n "$sudo_line" && -n "$mktemp_line" && -n "$chmod_line" && -n "$stat_line" && -n "$unused_line" ]] ||
|
|
fail 'complete mountpoint preflight trace was absent'
|
|
(( ${mktemp_line%%:*} < ${sudo_line%%:*} && ${chmod_line%%:*} < ${sudo_line%%:*} &&
|
|
${stat_line%%:*} < ${sudo_line%%:*} && ${unused_line%%:*} < ${sudo_line%%:*} )) ||
|
|
fail 'sudo refresh preceded complete mountpoint preflight'
|
|
probe_line="$(/usr/bin/grep -n $'^/usr/bin/sudo\t-n\t/usr/bin/timeout\t--signal=TERM\t--kill-after=5s' "$fake_log" | /usr/bin/head -n 1)"
|
|
post_sudo_trace="$(/usr/bin/sed -n "$(( ${sudo_line%%:*} + 1 )),$(( ${probe_line%%:*} - 1 ))p" "$fake_log")"
|
|
[[ "$post_sudo_trace" == *$'/usr/bin/stat\t--format=%u:%g:%a\t--'* &&
|
|
"$post_sudo_trace" == *$'/usr/bin/findmnt\t--json\t--mountpoint'* ]] ||
|
|
fail 'post-sudo defensive mountpoint recheck was absent'
|
|
}
|
|
|
|
review_sudo_failure_cleans_owned_mountpoint() {
|
|
local contract="$fixture_dir/local-recovery.env" _swr_result='' cleaned_mountpoint
|
|
reset_fake_boundary
|
|
fake_action=noop
|
|
write_recovery_contract "$contract"
|
|
builtin printf '%s\n' 1 >"$fake_sudo_status"
|
|
_swr_open_recovery_boundary _swr_result "$contract" >/dev/null 2>&1 &&
|
|
fail 'sudo refresh failure was accepted'
|
|
cleaned_mountpoint="$(/usr/bin/grep $'^/usr/bin/chmod\t0700\t--\t' "$fake_log" | /usr/bin/cut -f4 | /usr/bin/head -n 1)"
|
|
[[ "$(/usr/bin/grep -Fc $'/usr/bin/mktemp\t-d\t--' "$fake_log")" == 1 &&
|
|
"$(/usr/bin/grep -Fc $'/usr/bin/rmdir\t--' "$fake_log")" == 1 &&
|
|
"$(/usr/bin/grep -Fc $'/usr/bin/sudo\t-n\t' "$fake_log")" == 0 &&
|
|
-n "$cleaned_mountpoint" && ! -e "$cleaned_mountpoint" ]] || fail 'sudo failure did not clean only its owned unused mountpoint'
|
|
}
|
|
|
|
review_preflight_failure_has_no_privileged_call() {
|
|
local contract="$fixture_dir/local-recovery.env" _swr_result=''
|
|
reset_fake_boundary
|
|
fake_action=noop
|
|
write_recovery_contract "$contract"
|
|
builtin printf '%s\n' target_reused >"$fake_case_file"
|
|
_swr_open_recovery_boundary _swr_result "$contract" >/dev/null 2>&1 &&
|
|
fail 'reused mountpoint preflight was accepted'
|
|
! /usr/bin/grep -q '^/usr/bin/sudo' "$fake_log" ||
|
|
fail 'privileged call occurred before reused-mountpoint rejection'
|
|
}
|
|
|
|
review_mount_option_conflicts() {
|
|
local rejection _swr_created=''
|
|
local -a rejections=(
|
|
legacy_explicit_mapping
|
|
vfs_relatime_missing vfs_relatime_duplicate vfs_phase_missing
|
|
fs_allow_other_missing fs_allow_other_duplicate fs_phase_missing fs_wrong_phase
|
|
fs_default_permissions_missing fs_default_permissions_duplicate
|
|
fs_user_id_missing fs_user_id_wrong fs_user_id_duplicate
|
|
fs_group_id_missing fs_group_id_wrong fs_group_id_duplicate
|
|
fs_blksize_missing fs_blksize_wrong fs_blksize_duplicate
|
|
field_placement_vfs_default_permissions field_placement_fs_nodev hybrid_actual_legacy
|
|
fs_uid_conflict fs_uid_duplicate fs_gid_conflict fs_gid_duplicate
|
|
fs_fmask_conflict fs_fmask_duplicate fs_dmask_conflict fs_dmask_duplicate
|
|
fs_umask fs_phase_conflict fs_phase_duplicate vfs_phase_duplicate
|
|
vfs_nosuid_duplicate vfs_nodev_duplicate vfs_noexec_duplicate
|
|
vfs_suid_conflict vfs_dev_conflict vfs_exec_conflict
|
|
)
|
|
reset_fake_boundary
|
|
_swr_create_mountpoint _swr_created || fail 'option-conflict mountpoint creation failed'
|
|
fake_mountpoint="$_swr_created"
|
|
builtin printf '%s\n' ro >"$fake_mount_state"
|
|
for rejection in "${rejections[@]}"; do
|
|
builtin printf '%s\n' "$rejection" >"$fake_case_file"
|
|
_swr_validate_mount /dev/fixture-recovery-partition "$fake_mountpoint" ro >/dev/null 2>&1 &&
|
|
fail "mount option conflict was accepted: $rejection"
|
|
done
|
|
builtin printf '%s\n' observed_normalized >"$fake_case_file"
|
|
_swr_validate_mount /dev/fixture-recovery-partition "$fake_mountpoint" ro ||
|
|
fail 'observed normalized mount options were rejected'
|
|
builtin printf '%s\n' observed_permuted >"$fake_case_file"
|
|
_swr_validate_mount /dev/fixture-recovery-partition "$fake_mountpoint" ro ||
|
|
fail 'permuted normalized mount options were rejected'
|
|
builtin printf '%s\n' valid >"$fake_case_file"
|
|
command /usr/bin/rmdir -- "$fake_mountpoint"
|
|
}
|
|
|
|
review_normalized_mount_option_grammar_both_phases() {
|
|
local phase opposite vfs fs candidate
|
|
local -a invalid_vfs=() invalid_fs=()
|
|
for phase in ro rw; do
|
|
[[ "$phase" == ro ]] && opposite=rw || opposite=ro
|
|
vfs="nodev,noexec,nosuid,relatime,${phase}"
|
|
fs="allow_other,blksize=4096,default_permissions,group_id=0,${phase},user_id=0"
|
|
_swr_mount_options_are_safe "$phase" "$vfs" "$fs" ||
|
|
fail "normalized $phase mount option set was rejected"
|
|
_swr_mount_options_are_safe "$phase" \
|
|
"${phase},relatime,nosuid,noexec,nodev" \
|
|
"${phase},user_id=0,group_id=0,default_permissions,blksize=4096,allow_other" ||
|
|
fail "permuted normalized $phase mount option set was rejected"
|
|
|
|
invalid_vfs=(
|
|
"nodev,noexec,nosuid,${phase}"
|
|
"nodev,noexec,nosuid,relatime,relatime,${phase}"
|
|
'nodev,noexec,nosuid,relatime'
|
|
"nodev,noexec,nosuid,relatime,${opposite}"
|
|
"nodev,noexec,nosuid,relatime,${phase},${phase}"
|
|
"nodev,noexec,nosuid,relatime,${phase},default_permissions"
|
|
)
|
|
for candidate in "${invalid_vfs[@]}"; do
|
|
assert_fails _swr_mount_options_are_safe "$phase" "$candidate" "$fs"
|
|
done
|
|
|
|
invalid_fs=(
|
|
"blksize=4096,default_permissions,group_id=0,${phase},user_id=0"
|
|
"allow_other,allow_other,blksize=4096,default_permissions,group_id=0,${phase},user_id=0"
|
|
'allow_other,blksize=4096,default_permissions,group_id=0,user_id=0'
|
|
"allow_other,blksize=4096,default_permissions,group_id=0,${opposite},user_id=0"
|
|
"allow_other,blksize=4096,default_permissions,group_id=0,${phase},${phase},user_id=0"
|
|
"allow_other,blksize=4096,group_id=0,${phase},user_id=0"
|
|
"allow_other,blksize=4096,default_permissions,default_permissions,group_id=0,${phase},user_id=0"
|
|
"allow_other,blksize=4096,default_permissions,group_id=0,${phase}"
|
|
"allow_other,blksize=4096,default_permissions,group_id=0,${phase},user_id=1000"
|
|
"allow_other,blksize=4096,default_permissions,group_id=0,${phase},user_id=0,user_id=0"
|
|
"allow_other,blksize=4096,default_permissions,${phase},user_id=0"
|
|
"allow_other,blksize=4096,default_permissions,group_id=1000,${phase},user_id=0"
|
|
"allow_other,blksize=4096,default_permissions,group_id=0,group_id=0,${phase},user_id=0"
|
|
"allow_other,default_permissions,group_id=0,${phase},user_id=0"
|
|
"allow_other,blksize=8192,default_permissions,group_id=0,${phase},user_id=0"
|
|
"allow_other,blksize=4096,blksize=4096,default_permissions,group_id=0,${phase},user_id=0"
|
|
"allow_other,blksize=4096,default_permissions,group_id=0,${phase},user_id=0,uid=1000"
|
|
"allow_other,blksize=4096,default_permissions,group_id=0,nodev,${phase},user_id=0"
|
|
"allow_other,blksize=4096,default_permissions,group_id=0,${phase},user_id=0,arbitrary"
|
|
)
|
|
for candidate in "${invalid_fs[@]}"; do
|
|
assert_fails _swr_mount_options_are_safe "$phase" "$vfs" "$candidate"
|
|
done
|
|
done
|
|
}
|
|
|
|
review_mount_option_whitespace_is_lossless() {
|
|
local canonical_vfs='nodev,noexec,nosuid,relatime,ro'
|
|
local canonical_fs='allow_other,blksize=4096,default_permissions,group_id=0,ro,user_id=0'
|
|
local field whitespace candidate json _swr_source='' _swr_target=''
|
|
local _swr_fstype='' _swr_vfs='' _swr_fs=''
|
|
|
|
# Production breaks caught: read/here-string parsing must not discard a
|
|
# suffix, and JSON field framing must not let one field become another.
|
|
assert_fails _swr_mount_options_are_safe ro "$canonical_vfs" "$canonical_fs"$'\n'
|
|
assert_fails _swr_mount_options_are_safe ro "$canonical_vfs" "$canonical_fs"$'\nunknown'
|
|
assert_fails _swr_mount_options_are_safe ro "$canonical_vfs"$'\n' "$canonical_fs"
|
|
assert_fails _swr_mount_options_are_safe ro "$canonical_vfs"$'\nunknown' "$canonical_fs"
|
|
|
|
for field in vfs fs; do
|
|
for whitespace in ' ' $'\t' $'\r' $'\n'; do
|
|
if [[ "$field" == vfs ]]; then
|
|
candidate="${canonical_vfs/relatime/relatime${whitespace}}"
|
|
assert_fails _swr_mount_options_are_safe ro "$candidate" "$canonical_fs"
|
|
else
|
|
candidate="${canonical_fs/default_permissions/default_permissions${whitespace}}"
|
|
assert_fails _swr_mount_options_are_safe ro "$canonical_vfs" "$candidate"
|
|
fi
|
|
done
|
|
done
|
|
|
|
for json in \
|
|
'{"filesystems":[{"source":"/dev/fixture recovery","target":"/run/user/1000/mount","fstype":"fuseblk","vfs-options":"nodev,noexec,nosuid,relatime,ro","fs-options":"allow_other,blksize=4096,default_permissions,group_id=0,ro,user_id=0"}]}' \
|
|
'{"filesystems":[{"source":"/dev/fixture","target":"/run/user/1000/mount\tforeign","fstype":"fuseblk","vfs-options":"nodev,noexec,nosuid,relatime,ro","fs-options":"allow_other,blksize=4096,default_permissions,group_id=0,ro,user_id=0"}]}' \
|
|
'{"filesystems":[{"source":"/dev/fixture","target":"/run/user/1000/mount","fstype":"fuseblk\r","vfs-options":"nodev,noexec,nosuid,relatime,ro","fs-options":"allow_other,blksize=4096,default_permissions,group_id=0,ro,user_id=0"}]}' \
|
|
'{"filesystems":[{"source":"/dev/fixture","target":"/run/user/1000/mount","fstype":"fuseblk","vfs-options":"nodev,noexec,nosuid,relatime,ro\nallow_other,blksize=4096,default_permissions,group_id=0,ro,user_id=0","fs-options":""}]}' \
|
|
'{"filesystems":[{"source":"/dev/fixture","target":"/run/user/1000/mount","fstype":"fuseblk","vfs-options":"nodev,noexec,nosuid,relatime,ro","fs-options":"allow_other,blksize=4096,default_permissions,group_id=0,ro,user_id=0\n"}]}'; do
|
|
assert_fails _swr_parse_findmnt_json "$json" _swr_source _swr_target \
|
|
_swr_fstype _swr_vfs _swr_fs
|
|
done
|
|
}
|
|
|
|
review_validate_mount_rejects_json_whitespace_reframing() {
|
|
local fake_case _swr_created=''
|
|
reset_fake_boundary
|
|
_swr_create_mountpoint _swr_created || fail 'JSON whitespace mountpoint creation failed'
|
|
fake_mountpoint="$_swr_created"
|
|
builtin printf '%s\n' ro >"$fake_mount_state"
|
|
for fake_case in \
|
|
json_fs_trailing_lf json_fs_trailing_lf_garbage json_vfs_cross_field_reframe; do
|
|
builtin printf '%s\n' "$fake_case" >"$fake_case_file"
|
|
assert_fails _swr_validate_mount /dev/fixture-recovery-partition "$fake_mountpoint" ro
|
|
done
|
|
builtin printf '%s\n' valid >"$fake_case_file"
|
|
command /usr/bin/rmdir -- "$fake_mountpoint"
|
|
}
|
|
|
|
review_cycle_uncertain_cleanup() {
|
|
local status rc _swr_created='' mounts umounts
|
|
for status in 32 124 255; do
|
|
reset_fake_boundary
|
|
fake_action=mutation
|
|
_swr_create_mountpoint _swr_created || fail 'uncertain-cycle mountpoint creation failed'
|
|
fake_mountpoint="$_swr_created"
|
|
builtin printf '%s\n' "$status" >"$fake_mount_status"
|
|
builtin printf '%s\n' 1 >"$fake_mount_uncertain"
|
|
builtin printf '%s\n' source_rc1_nonempty_poll2 >"$fake_case_file"
|
|
if _swr_run_volume_cycle /dev/fixture-recovery-partition "$fake_mountpoint" >/dev/null 2>&1; then
|
|
rc=0
|
|
else
|
|
rc=$?
|
|
fi
|
|
assert_eq 1 "$rc" "cleanup ambiguity did not win after mount status $status"
|
|
mounts="$(/usr/bin/grep -Fc $'/usr/bin/sudo\t-n\t/usr/bin/timeout\t--signal=TERM\t--kill-after=10s\t45s\t/usr/bin/ntfs-3g\t-o' "$fake_log")"
|
|
umounts="$(/usr/bin/grep -Fc $'/usr/bin/sudo\t-n\t/usr/bin/timeout\t--signal=TERM\t--kill-after=10s\t45s\t/usr/bin/umount\t--' "$fake_log")"
|
|
assert_eq 1 "$mounts" "mount status $status triggered a later writable mount"
|
|
assert_eq 1 "$umounts" "mount status $status unmount count differs"
|
|
assert_eq 2 "$(<"$fake_source_query_count")" "mount status $status source absence query count differs"
|
|
assert_eq 0 "$(<"$fake_action_count")" "mount status $status invoked the volume action"
|
|
builtin printf '%s\n' absent >"$fake_mount_state"
|
|
builtin printf '%s\n' valid >"$fake_case_file"
|
|
command /usr/bin/rmdir -- "$fake_mountpoint"
|
|
done
|
|
|
|
reset_fake_boundary
|
|
fake_action=mutation
|
|
_swr_create_mountpoint _swr_created || fail 'validation-cycle mountpoint creation failed'
|
|
fake_mountpoint="$_swr_created"
|
|
builtin printf '%s\n' foreign_source >"$fake_case_file"
|
|
_swr_run_volume_cycle /dev/fixture-recovery-partition "$fake_mountpoint" >/dev/null 2>&1 &&
|
|
fail 'mounted validation failure was accepted'
|
|
assert_eq 1 "$(/usr/bin/grep -Fc $'/usr/bin/sudo\t-n\t/usr/bin/timeout\t--signal=TERM\t--kill-after=10s\t45s\t/usr/bin/umount\t--' "$fake_log")" 'validation failure unmount count differs'
|
|
assert_eq 2 "$(<"$fake_source_query_count")" 'validation failure source absence query count differs'
|
|
assert_eq 0 "$(<"$fake_action_count")" 'validation failure invoked the volume action'
|
|
builtin printf '%s\n' valid >"$fake_case_file"
|
|
command /usr/bin/rmdir -- "$fake_mountpoint"
|
|
}
|
|
|
|
review_runtime_root_uses_fixed_command_leaf() {
|
|
local direct_marker="$fixture_dir/direct-runtime-readlink" resolved=''
|
|
reset_fake_boundary
|
|
builtin eval "$production_runtime_root_definition"
|
|
function /usr/bin/readlink() {
|
|
builtin printf '%s\n' direct >"$direct_marker"
|
|
return 98
|
|
}
|
|
if ! resolved="$(_swr_runtime_root)"; then
|
|
[[ ! -e "$direct_marker" ]] || fail 'runtime root resolution bypassed fixed command leaf'
|
|
fail 'intercepted runtime root resolution failed'
|
|
fi
|
|
[[ ! -e "$direct_marker" ]] || fail 'runtime root resolution bypassed fixed command leaf'
|
|
assert_eq /run/user/1000 "$resolved" 'fixed runtime root result differs'
|
|
assert_log_line $'/usr/bin/readlink\t-e\t--\t/run/user/1000' 'runtime root fixed command leaf argv differs'
|
|
}
|
|
|
|
run_review_case() {
|
|
local function_name="$1"
|
|
shift
|
|
if ( "$function_name" "$@" ); then
|
|
pass_boundary_case
|
|
else
|
|
review_failures=$((review_failures + 1))
|
|
fi
|
|
}
|
|
|
|
test_review_fix_round_one() {
|
|
run_review_case review_findmnt_initial_rc1_nonempty
|
|
run_review_case review_findmnt_post_poll1_rc1_nonempty
|
|
run_review_case review_findmnt_post_poll2_rc1_nonempty
|
|
run_review_case review_findmnt_rc1_overflow
|
|
run_review_case review_sudo_after_complete_preflight
|
|
run_review_case review_sudo_failure_cleans_owned_mountpoint
|
|
run_review_case review_preflight_failure_has_no_privileged_call
|
|
run_review_case review_mount_option_conflicts
|
|
run_review_case review_normalized_mount_option_grammar_both_phases
|
|
run_review_case review_mount_option_whitespace_is_lossless
|
|
run_review_case review_validate_mount_rejects_json_whitespace_reframing
|
|
run_review_case review_cycle_uncertain_cleanup
|
|
run_review_case review_runtime_root_uses_fixed_command_leaf
|
|
(( review_failures == 0 )) || fail "Task 4 review regressions failed: $review_failures"
|
|
}
|
|
|
|
review_findmnt_newline_initial() {
|
|
local fake_case="$1" label="$2"
|
|
reset_fake_boundary
|
|
builtin printf '%s\n' "$fake_case" >"$fake_case_file"
|
|
_swr_require_unmounted /dev/fixture-recovery-partition &&
|
|
fail "findmnt RC1 newline-only $label initial output was accepted as absence"
|
|
return 0
|
|
}
|
|
|
|
review_findmnt_newline_post_unmount() {
|
|
local fake_case="$1" label="$2"
|
|
reset_fake_boundary
|
|
builtin printf '%s\n' ro >"$fake_mount_state"
|
|
builtin printf '%s\n' "$fake_case" >"$fake_case_file"
|
|
_swr_unmount_volume /dev/fixture-recovery-partition "$fake_mountpoint" >/dev/null 2>&1 &&
|
|
fail "findmnt RC1 newline-only $label post-unmount output was accepted as absence"
|
|
return 0
|
|
}
|
|
|
|
review_mount_uncharacterized_extras() {
|
|
local field="$1" rejection _swr_created=''
|
|
local -a rejections=()
|
|
reset_fake_boundary
|
|
_swr_create_mountpoint _swr_created || fail 'unknown-extra mountpoint creation failed'
|
|
fake_mountpoint="$_swr_created"
|
|
builtin printf '%s\n' ro >"$fake_mount_state"
|
|
case "$field" in
|
|
vfs) rejections=(vfs_permissions vfs_acl vfs_inherit vfs_unknown) ;;
|
|
fs) rejections=(fs_permissions fs_acl fs_inherit fs_unknown) ;;
|
|
*) fail 'unknown option field selector' ;;
|
|
esac
|
|
for rejection in "${rejections[@]}"; do
|
|
builtin printf '%s\n' "$rejection" >"$fake_case_file"
|
|
_swr_validate_mount /dev/fixture-recovery-partition "$fake_mountpoint" ro >/dev/null 2>&1 &&
|
|
fail "uncharacterized $field mount option was accepted: $rejection"
|
|
done
|
|
builtin printf '%s\n' valid >"$fake_case_file"
|
|
command /usr/bin/rmdir -- "$fake_mountpoint"
|
|
}
|
|
|
|
review_mount_malformed_csv() {
|
|
local rejection _swr_created=''
|
|
local -a rejections=(
|
|
vfs_trailing_comma fs_trailing_comma
|
|
vfs_empty vfs_leading_comma vfs_double_comma
|
|
fs_empty fs_leading_comma fs_double_comma
|
|
)
|
|
reset_fake_boundary
|
|
_swr_create_mountpoint _swr_created || fail 'malformed-CSV mountpoint creation failed'
|
|
fake_mountpoint="$_swr_created"
|
|
builtin printf '%s\n' ro >"$fake_mount_state"
|
|
for rejection in "${rejections[@]}"; do
|
|
builtin printf '%s\n' "$rejection" >"$fake_case_file"
|
|
_swr_validate_mount /dev/fixture-recovery-partition "$fake_mountpoint" ro >/dev/null 2>&1 &&
|
|
fail "malformed mount option CSV was accepted: $rejection"
|
|
done
|
|
builtin printf '%s\n' valid >"$fake_case_file"
|
|
command /usr/bin/rmdir -- "$fake_mountpoint"
|
|
}
|
|
|
|
test_review_fix_round_two() {
|
|
run_review_case review_findmnt_newline_initial source_rc1_newline_1 one-LF
|
|
run_review_case review_findmnt_newline_initial source_rc1_newline_2 two-LF
|
|
run_review_case review_findmnt_newline_initial source_rc1_newline_overflow over-16KiB-LF
|
|
run_review_case review_findmnt_newline_post_unmount source_rc1_newline_1 one-LF
|
|
run_review_case review_findmnt_newline_post_unmount source_rc1_newline_2 two-LF
|
|
run_review_case review_findmnt_newline_post_unmount source_rc1_newline_overflow over-16KiB-LF
|
|
run_review_case review_mount_uncharacterized_extras vfs
|
|
run_review_case review_mount_uncharacterized_extras fs
|
|
run_review_case review_mount_malformed_csv
|
|
(( review_failures == 0 )) || fail "Task 4 review round 2 regressions failed: $review_failures"
|
|
}
|
|
|
|
review_mountpoint_path_is_prearmed_before_attestation() {
|
|
local failure="$1" _swr_created='' foreign="$fixture_dir/foreign-mount-target"
|
|
mkdir -p -m 0700 -- "$foreign"
|
|
_swr_runtime_root() { builtin printf '%s\n' "$fake_runtime_root"; }
|
|
_swr_command() {
|
|
if [[ "$1:$failure" == /usr/bin/chmod:chmod ]]; then
|
|
return 1
|
|
fi
|
|
if [[ "$1:$failure" == /usr/bin/stat:attest ]]; then
|
|
return 1
|
|
fi
|
|
if [[ "$1:$failure" == /usr/bin/chmod:foreign ]]; then
|
|
command /usr/bin/rmdir -- "${@: -1}"
|
|
command /usr/bin/ln -s -- "$foreign" "${@: -1}"
|
|
return 1
|
|
fi
|
|
command "$@"
|
|
}
|
|
_swr_create_mountpoint _swr_created && fail "mountpoint $failure failure was accepted"
|
|
[[ "$_swr_created" == "$fake_runtime_root/slack-webhook-recovery."* ]] ||
|
|
fail "mountpoint $failure path was not prearmed"
|
|
if [[ "$failure" == foreign ]]; then
|
|
[[ -L "$_swr_created" && "$(command /usr/bin/readlink -- "$_swr_created")" == "$foreign" ]] ||
|
|
fail 'foreign mountpoint replacement was removed'
|
|
elif [[ -d "$_swr_created" ]]; then
|
|
failure=cleanup
|
|
_swr_remove_mountpoint "$_swr_created" || true
|
|
[[ ! -e "$_swr_created" ]] || fail "owned mountpoint $failure residue remained"
|
|
fi
|
|
}
|
|
|
|
review_prepare_publishes_partial_mountpoint() {
|
|
local _swr_work='' _swr_device='' _swr_mount=''
|
|
_swr_load_recovery_contract() { return 0; }
|
|
_swr_resolve_device() { builtin printf -v "$1" '%s' /dev/fixture; }
|
|
_swr_require_unmounted() { return 0; }
|
|
_swr_runtime_root() { builtin printf '%s\n' /run/user/1000; }
|
|
_swr_capture_single_line() {
|
|
local name="$1"; shift
|
|
case "$*" in
|
|
'/usr/bin/stat --format=%u:%g:%a -- /run/user/1000') builtin printf -v "$name" '%s' 1000:1000:700 ;;
|
|
'/usr/bin/stat --file-system --format=%T -- /run/user/1000') builtin printf -v "$name" '%s' tmpfs ;;
|
|
'/usr/bin/mktemp -d --tmpdir=/run/user/1000 platform-slack-keepass.XXXXXXXX') builtin printf -v "$name" '%s' /run/user/1000/platform-slack-keepass.SYNTHETIC ;;
|
|
*) return 1 ;;
|
|
esac
|
|
}
|
|
_swr_command() { return 0; }
|
|
_swr_work_root_is_safe() { return 0; }
|
|
_swr_create_mountpoint() {
|
|
builtin printf -v "$1" '%s' /run/user/1000/slack-webhook-recovery.SYNTHETIC
|
|
return 1
|
|
}
|
|
_swr_prepare_transaction _swr_work _swr_device _swr_mount /fixture/webhook /fixture/contract &&
|
|
fail 'partial mountpoint preparation failure was accepted'
|
|
assert_eq /run/user/1000/slack-webhook-recovery.SYNTHETIC "$_swr_mount" \
|
|
'preparation did not publish the partial mountpoint'
|
|
}
|
|
|
|
review_stop_vault_tears_down_failed_persistent_client() {
|
|
local socket="$fixture_dir/stop-failure.sock" vault_pid='' client_pid=''
|
|
/usr/bin/python3 -c 'import socket,sys,time; s=socket.socket(socket.AF_UNIX); s.bind(sys.argv[1]); time.sleep(30)' "$socket" &
|
|
vault_pid=$!
|
|
/usr/bin/sleep 0.05
|
|
/usr/bin/sleep 30 & client_pid=$!
|
|
trap 'kill -TERM '"$vault_pid"' '"$client_pid"' 2>/dev/null || true; wait '"$vault_pid"' '"$client_pid"' 2>/dev/null || true' EXIT
|
|
exec {_SWR_VAULT_CLIENT_READ_FD}</dev/null
|
|
exec {_SWR_VAULT_CLIENT_WRITE_FD}>/dev/null
|
|
_SWR_VAULT_CLIENT_SOCKET="$socket"
|
|
_SWR_VAULT_CLIENT_PID="$client_pid"
|
|
_swr_vault_request() { return 1; }
|
|
_swr_stop_vault "$socket" "$vault_pid" && fail 'failed SHUTDOWN was accepted'
|
|
[[ -z "$_SWR_VAULT_CLIENT_SOCKET$_SWR_VAULT_CLIENT_PID$_SWR_VAULT_CLIENT_READ_FD$_SWR_VAULT_CLIENT_WRITE_FD" ]] ||
|
|
fail 'failed SHUTDOWN retained persistent client state'
|
|
kill -0 "$client_pid" 2>/dev/null && fail 'failed SHUTDOWN retained persistent client process'
|
|
trap - EXIT
|
|
}
|
|
|
|
test_start_vault_waits_for_delayed_operator_input() {
|
|
# Production break caught: the coordinator kills the healthy vault after
|
|
# five seconds even though the vault intentionally withholds its socket
|
|
# until both operator inputs have been retained.
|
|
local repository="$fixture_dir/delayed-vault-repository"
|
|
local runtime="$fixture_dir/delayed-vault-runtime"
|
|
local helper="$repository/scripts/libexec/slack-webhook-recovery-vault.py"
|
|
local webhook="$fixture_dir/delayed-vault-webhook" input_fifo="$fixture_dir/delayed-vault-webhook.input"
|
|
local pre_input="$fixture_dir/delayed-vault-pre-input" helper_pid_file="$runtime/helper.pid"
|
|
local saved_repository_root="$SWR_REPOSITORY_ROOT"
|
|
local _swr_delayed_pid='' _swr_delayed_socket='' writer_pid='' helper_pid='' start_rc=0 stop_rc=1
|
|
|
|
mkdir -p -m 0700 -- "${helper%/*}" "$runtime"
|
|
builtin printf '%s\n' \
|
|
'import os' \
|
|
'import socket' \
|
|
'import sys' \
|
|
'runtime = sys.argv[sys.argv.index("--runtime-root") + 1]' \
|
|
'socket_path = os.path.join(runtime, "vault.sock")' \
|
|
'with open(os.path.join(runtime, "helper.pid"), "w", encoding="ascii") as stream:' \
|
|
' stream.write(str(os.getpid()))' \
|
|
'input_path = sys.argv[sys.argv.index("--webhook-file") + 1] + ".input"' \
|
|
'with open(input_path, "rb", buffering=0) as input_stream:' \
|
|
' if not input_stream.readline() or not input_stream.readline():' \
|
|
' raise SystemExit(70)' \
|
|
'server = socket.socket(socket.AF_UNIX)' \
|
|
'try:' \
|
|
' server.bind(socket_path)' \
|
|
' os.chmod(socket_path, 0o600)' \
|
|
' server.listen(1)' \
|
|
' connection, _ = server.accept()' \
|
|
' with connection:' \
|
|
' command = b""' \
|
|
' while not command.endswith(b"\\n"):' \
|
|
' command += connection.recv(64)' \
|
|
' if command != b"SHUTDOWN\\n":' \
|
|
' raise SystemExit(71)' \
|
|
' connection.sendall(b"stopped\\n")' \
|
|
'finally:' \
|
|
' server.close()' \
|
|
' if os.path.exists(socket_path):' \
|
|
' os.unlink(socket_path)' >"$helper"
|
|
chmod 0600 -- "$helper"
|
|
builtin printf '%s\n' synthetic-webhook >"$webhook"
|
|
chmod 0600 -- "$webhook"
|
|
mkfifo -m 0600 -- "$input_fifo"
|
|
|
|
(
|
|
exec 3<>"$input_fifo"
|
|
/usr/bin/sleep 5.2
|
|
if [[ -e "$runtime/vault.sock" || -L "$runtime/vault.sock" ]]; then
|
|
builtin printf '%s\n' published >"$pre_input"
|
|
else
|
|
builtin printf '%s\n' absent >"$pre_input"
|
|
fi
|
|
builtin printf '%s\n' synthetic-app synthetic-input >&3
|
|
) &
|
|
writer_pid=$!
|
|
|
|
SWR_REPOSITORY_ROOT="$repository"
|
|
set +e
|
|
_swr_start_vault _swr_delayed_pid _swr_delayed_socket "$runtime" "$webhook"
|
|
start_rc=$?
|
|
set -e
|
|
SWR_REPOSITORY_ROOT="$saved_repository_root"
|
|
wait "$writer_pid" 2>/dev/null || true
|
|
|
|
if (( start_rc == 0 )); then
|
|
helper_pid="$(<"$helper_pid_file")"
|
|
[[ "$(<"$pre_input")" == absent ]] || start_rc=72
|
|
[[ "$_swr_delayed_socket" == "$runtime/vault.sock" && -S "$_swr_delayed_socket" ]] || start_rc=73
|
|
[[ "$_swr_delayed_pid" == "$helper_pid" ]] || start_rc=74
|
|
/usr/bin/sleep 0.1
|
|
kill -0 "$_swr_delayed_pid" 2>/dev/null || start_rc=75
|
|
if (( start_rc == 0 )); then
|
|
set +e
|
|
_swr_terminate_vault_child "$_swr_delayed_pid"
|
|
stop_rc=$?
|
|
set -e
|
|
fi
|
|
elif [[ -f "$helper_pid_file" ]]; then
|
|
helper_pid="$(<"$helper_pid_file")"
|
|
fi
|
|
|
|
if [[ "$helper_pid" =~ ^[1-9][0-9]*$ ]] && kill -0 "$helper_pid" 2>/dev/null; then
|
|
kill -TERM "$_swr_delayed_pid" "$helper_pid" 2>/dev/null || true
|
|
wait "$_swr_delayed_pid" 2>/dev/null || true
|
|
fi
|
|
rm -f -- "$input_fifo" "$runtime/vault.sock"
|
|
|
|
assert_eq 0 "$start_rc" 'vault startup rejected operator input delayed beyond five seconds'
|
|
assert_eq 0 "$stop_rc" "delayed-input vault did not stop cleanly (rc=$stop_rc)"
|
|
[[ ! "$helper_pid" =~ ^[1-9][0-9]*$ ]] ||
|
|
! kill -0 "$helper_pid" 2>/dev/null || fail 'delayed-input vault helper remained alive'
|
|
}
|
|
|
|
test_vault_readiness_failure_boundaries() {
|
|
# Production breaks caught: unbounded readiness, zombie blindness,
|
|
# signal deferral through the full human window, foreign-path deletion,
|
|
# and TERM-only cleanup that can orphan a resistant child.
|
|
local root="$fixture_dir/vault-readiness-boundaries" socket="$fixture_dir/vault-readiness-boundaries/vault.sock"
|
|
local pid='' rc=0 code started_ns ended_ns elapsed_ms
|
|
mkdir -m 0700 -- "$root"
|
|
|
|
/usr/bin/python3 -c 'import time; time.sleep(30)' & pid=$!
|
|
set +e
|
|
_swr_wait_vault_ready "$socket" "$pid" 2
|
|
rc=$?
|
|
set -e
|
|
assert_eq 1 "$rc" 'short vault readiness deadline was accepted'
|
|
_swr_terminate_vault_child "$pid"
|
|
! kill -0 "$pid" 2>/dev/null || fail 'deadline cleanup retained vault child'
|
|
|
|
/usr/bin/python3 -c 'raise SystemExit(17)' & pid=$!
|
|
started_ns="$(/usr/bin/date +%s%N)"
|
|
set +e
|
|
_swr_wait_vault_ready "$socket" "$pid" 100
|
|
rc=$?
|
|
set -e
|
|
ended_ns="$(/usr/bin/date +%s%N)"
|
|
elapsed_ms=$(((ended_ns - started_ns) / 1000000))
|
|
assert_eq 1 "$rc" 'dead vault child was accepted as waiting'
|
|
(( elapsed_ms < 1000 )) || fail 'dead vault child did not fail promptly'
|
|
wait "$pid" 2>/dev/null || true
|
|
|
|
builtin printf '%s\n' foreign >"$socket"
|
|
/usr/bin/python3 -c 'import time; time.sleep(30)' & pid=$!
|
|
set +e
|
|
_swr_wait_vault_ready "$socket" "$pid" 2
|
|
rc=$?
|
|
set -e
|
|
assert_eq 1 "$rc" 'foreign vault socket path was accepted'
|
|
assert_eq foreign "$(<"$socket")" 'foreign vault socket path was changed'
|
|
_swr_terminate_vault_child "$pid"
|
|
rm -f -- "$socket"
|
|
|
|
for code in 129 130 143; do
|
|
/usr/bin/python3 -c 'import time; time.sleep(30)' & pid=$!
|
|
SWR_PENDING_SIGNAL="$code"
|
|
set +e
|
|
_swr_wait_vault_ready "$socket" "$pid" 100
|
|
rc=$?
|
|
set -e
|
|
assert_eq 2 "$rc" "pending signal $code did not win vault readiness boundary"
|
|
_swr_terminate_vault_child "$pid"
|
|
! kill -0 "$pid" 2>/dev/null || fail "pending signal $code retained vault child"
|
|
assert_eq "$code" "$SWR_PENDING_SIGNAL" "pending signal $code changed during cleanup"
|
|
done
|
|
SWR_PENDING_SIGNAL=0
|
|
|
|
/usr/bin/python3 -c 'import signal,time; signal.signal(signal.SIGTERM, signal.SIG_IGN); time.sleep(30)' & pid=$!
|
|
/usr/bin/sleep 0.05
|
|
started_ns="$(/usr/bin/date +%s%N)"
|
|
_swr_terminate_vault_child "$pid"
|
|
ended_ns="$(/usr/bin/date +%s%N)"
|
|
elapsed_ms=$(((ended_ns - started_ns) / 1000000))
|
|
(( elapsed_ms < 3000 )) || fail 'TERM-resistant vault cleanup exceeded bound'
|
|
! kill -0 "$pid" 2>/dev/null || fail 'TERM-resistant vault child remained alive'
|
|
}
|
|
|
|
test_pending_signal_after_readiness_prevents_publication() {
|
|
# Production break caught: a signal delivered after readiness returns but
|
|
# before output publication exposes a live socket/PID and is not forwarded
|
|
# to the vault until generic SHUTDOWN cleanup.
|
|
local repository="$fixture_dir/post-ready-signal-repository"
|
|
local helper="$repository/scripts/libexec/slack-webhook-recovery-vault.py"
|
|
local runtime webhook saved_repository_root="$SWR_REPOSITORY_ROOT"
|
|
local saved_wait_definition signal_name code signum helper_pid rc
|
|
local _swr_signal_pid _swr_signal_socket
|
|
saved_wait_definition="$(declare -f _swr_wait_vault_ready)"
|
|
mkdir -p -m 0700 -- "${helper%/*}"
|
|
builtin printf '%s\n' \
|
|
'import os' \
|
|
'import signal' \
|
|
'import sys' \
|
|
'runtime = sys.argv[sys.argv.index("--runtime-root") + 1]' \
|
|
'signal_file = os.path.join(runtime, "signal")' \
|
|
'def finish(signum, _frame):' \
|
|
' with open(signal_file, "w", encoding="ascii") as stream:' \
|
|
' stream.write(str(signum))' \
|
|
' raise SystemExit(128 + signum)' \
|
|
'for signum in (signal.SIGHUP, signal.SIGINT, signal.SIGTERM):' \
|
|
' signal.signal(signum, finish)' \
|
|
'with open(os.path.join(runtime, "helper.pid"), "w", encoding="ascii") as stream:' \
|
|
' stream.write(str(os.getpid()))' \
|
|
'signal.pause()' >"$helper"
|
|
chmod 0600 -- "$helper"
|
|
|
|
for signal_name in HUP INT TERM; do
|
|
case "$signal_name" in
|
|
HUP) code=129; signum=1 ;;
|
|
INT) code=130; signum=2 ;;
|
|
TERM) code=143; signum=15 ;;
|
|
esac
|
|
runtime="$fixture_dir/post-ready-signal-$signal_name"
|
|
webhook="$fixture_dir/post-ready-signal-$signal_name-webhook"
|
|
mkdir -m 0700 -- "$runtime"
|
|
builtin printf synthetic >"$webhook"; chmod 0600 -- "$webhook"
|
|
_swr_wait_vault_ready() {
|
|
local index
|
|
for (( index = 0; index < 100; index++ )); do
|
|
[[ -f "$runtime/helper.pid" ]] && break
|
|
/usr/bin/sleep 0.01
|
|
done
|
|
[[ -f "$runtime/helper.pid" ]] || return 1
|
|
SWR_PENDING_SIGNAL="$code"
|
|
return 0
|
|
}
|
|
SWR_REPOSITORY_ROOT="$repository"
|
|
_swr_signal_pid=sentinel-pid
|
|
_swr_signal_socket=sentinel-socket
|
|
set +e
|
|
_swr_start_vault _swr_signal_pid _swr_signal_socket "$runtime" "$webhook"
|
|
rc=$?
|
|
set -e
|
|
SWR_REPOSITORY_ROOT="$saved_repository_root"
|
|
helper_pid="$(<"$runtime/helper.pid")"
|
|
if kill -0 "$helper_pid" 2>/dev/null; then
|
|
kill -KILL "$helper_pid" 2>/dev/null || true
|
|
wait "$helper_pid" 2>/dev/null || true
|
|
fi
|
|
assert_eq 1 "$rc" "post-ready $signal_name signal was accepted"
|
|
assert_eq sentinel-pid "$_swr_signal_pid" "post-ready $signal_name published vault PID"
|
|
assert_eq sentinel-socket "$_swr_signal_socket" "post-ready $signal_name published socket"
|
|
assert_eq "$signum" "$(<"$runtime/signal")" "post-ready $signal_name was not forwarded"
|
|
! kill -0 "$helper_pid" 2>/dev/null || fail "post-ready $signal_name retained vault child"
|
|
SWR_PENDING_SIGNAL=0
|
|
done
|
|
eval "$saved_wait_definition"
|
|
}
|
|
|
|
review_work_root_inventory_precedes_deletion() {
|
|
local case_name="$1" root baseline violation=''
|
|
_swr_command() { command "$@"; }
|
|
root="$(/usr/bin/mktemp -d --tmpdir=/run/user/1000 platform-slack-keepass.XXXXXXXX)"
|
|
chmod 0700 -- "$root"
|
|
baseline="$root/baseline.kdbx"
|
|
builtin printf x >"$baseline"; chmod 0600 -- "$baseline"
|
|
case "$case_name" in
|
|
unexpected)
|
|
builtin printf y >"$root/unexpected"; chmod 0600 -- "$root/unexpected"
|
|
_swr_remove_work_root "$root" && violation='unexpected work-root content was accepted'
|
|
[[ -f "$baseline" ]] || violation='inventory deleted known evidence before finding unexpected content'
|
|
/usr/bin/rm -f -- "$root/unexpected" "$baseline"
|
|
;;
|
|
wrong-mode)
|
|
builtin printf y >"$root/current.kdbx"; chmod 0644 -- "$root/current.kdbx"
|
|
_swr_remove_work_root "$root" && violation='wrong-mode work-root content was accepted'
|
|
[[ -f "$baseline" ]] || violation='inventory deleted known evidence before finding wrong mode'
|
|
/usr/bin/rm -f -- "$root/current.kdbx" "$baseline"
|
|
;;
|
|
*) fail 'unknown topology case' ;;
|
|
esac
|
|
/usr/bin/rmdir -- "$root"
|
|
[[ -z "$violation" ]] || fail "$violation"
|
|
}
|
|
|
|
review_failed_find_inventory_preserves_work_root() {
|
|
local producer="$1" root baseline violation=''
|
|
root="$(/usr/bin/mktemp -d --tmpdir=/run/user/1000 platform-slack-keepass.XXXXXXXX)"
|
|
chmod 0700 -- "$root"
|
|
baseline="$root/baseline.kdbx"
|
|
builtin printf x >"$baseline"; chmod 0600 -- "$baseline"
|
|
_swr_command() {
|
|
if [[ "$1" == /usr/bin/find && "$producer" == root && "$2" == "$root" && "$3" == -mindepth ]]; then
|
|
builtin printf '%s\n' baseline.kdbx
|
|
return 71
|
|
fi
|
|
command "$@"
|
|
}
|
|
_swr_remove_work_root "$root" && violation="$producer failed inventory was accepted"
|
|
[[ -f "$baseline" ]] || violation="$producer failed inventory deleted evidence"
|
|
_swr_command() { command "$@"; }
|
|
/usr/bin/rm -f -- "$baseline"
|
|
/usr/bin/rmdir -- "$root"
|
|
[[ -z "$violation" ]] || fail "$violation"
|
|
}
|
|
|
|
review_work_root_rejects_unexpected_vault_home() {
|
|
local root baseline violation=''
|
|
root="$(/usr/bin/mktemp -d --tmpdir=/run/user/1000 platform-slack-keepass.XXXXXXXX)"
|
|
chmod 0700 -- "$root"
|
|
baseline="$root/baseline.kdbx"
|
|
builtin printf x >"$baseline"; chmod 0600 -- "$baseline"
|
|
mkdir -m 0700 -- "$root/vault-home"
|
|
_swr_command() { command "$@"; }
|
|
_swr_remove_work_root "$root" && violation='unexpected vault-home was accepted'
|
|
[[ -f "$baseline" ]] || violation='unexpected vault-home deleted known evidence'
|
|
[[ -d "$root/vault-home" ]] || violation='unexpected vault-home was deleted'
|
|
[[ ! -d "$root/vault-home" ]] || /usr/bin/rmdir -- "$root/vault-home"
|
|
[[ ! -f "$baseline" ]] || /usr/bin/rm -f -- "$baseline"
|
|
[[ ! -d "$root" ]] || /usr/bin/rmdir -- "$root"
|
|
[[ -z "$violation" ]] || fail "$violation"
|
|
}
|
|
|
|
review_nul_inventory_rejects_ambiguous_or_partial_name() {
|
|
local inventory_case="$1" root baseline hostile violation=''
|
|
root="$(/usr/bin/mktemp -d --tmpdir=/run/user/1000 platform-slack-keepass.XXXXXXXX)"
|
|
chmod 0700 -- "$root"
|
|
baseline="$root/baseline.kdbx"
|
|
builtin printf x >"$baseline"; chmod 0600 -- "$baseline"
|
|
hostile="$root/"$'baseline.kdbx\ncurrent.kdbx'
|
|
if [[ "$inventory_case" == lf-name ]]; then
|
|
builtin printf y >"$hostile"; chmod 0600 -- "$hostile"
|
|
_swr_command() { command "$@"; }
|
|
else
|
|
_swr_command() {
|
|
if [[ "$1" == /usr/bin/find && "$2" == "$root" && "$3" == -mindepth ]]; then
|
|
builtin printf '%s' baseline.kdbx
|
|
return 0
|
|
fi
|
|
command "$@"
|
|
}
|
|
fi
|
|
_swr_remove_work_root "$root" && violation="$inventory_case inventory was accepted"
|
|
[[ -f "$baseline" ]] || violation="$inventory_case inventory deleted known evidence"
|
|
_swr_command() { command "$@"; }
|
|
/usr/bin/rm -f -- "$baseline"
|
|
[[ ! -e "$hostile" ]] || /usr/bin/rm -f -- "$hostile"
|
|
/usr/bin/rmdir -- "$root"
|
|
[[ -z "$violation" ]] || fail "$violation"
|
|
}
|
|
|
|
review_inventory_stops_at_bounded_input_limit() {
|
|
local inventory_case="$1" caller="$2" root baseline producer_pid='' violation=''
|
|
local complete_marker="$fixture_dir/inventory-${inventory_case}-${caller}.complete"
|
|
local pid_marker="$fixture_dir/inventory-${inventory_case}-${caller}.pid"
|
|
local -a _swr_inventory=()
|
|
root="$(/usr/bin/mktemp -d --tmpdir=/run/user/1000 platform-slack-keepass.XXXXXXXX)"
|
|
chmod 0700 -- "$root"
|
|
baseline="$root/baseline.kdbx"
|
|
builtin printf x >"$baseline"; chmod 0600 -- "$baseline"
|
|
_swr_command() {
|
|
if [[ "$1" == /usr/bin/find && "$2" == "$root" && "$3" == -mindepth ]]; then
|
|
builtin printf '%s' "$BASHPID" >"$pid_marker"
|
|
local chunk='' record='' index
|
|
case "$inventory_case" in
|
|
long-non-nul)
|
|
builtin printf -v chunk '%256s' ''
|
|
chunk="${chunk// /x}"
|
|
for (( index = 0; index < 8192; index++ )); do
|
|
builtin printf '%s' "$chunk" || return 74
|
|
done
|
|
;;
|
|
count-total-overflow)
|
|
for (( index = 0; index < 10000; index++ )); do
|
|
builtin printf -v record 'n%0254d' "$index"
|
|
builtin printf '%s\0' "$record" || return 75
|
|
done
|
|
;;
|
|
*) return 97 ;;
|
|
esac
|
|
builtin printf complete >"$complete_marker"
|
|
return 0
|
|
fi
|
|
command "$@"
|
|
}
|
|
|
|
if [[ "$caller" == parser ]]; then
|
|
_swr_inventory_directory _swr_inventory "$root" &&
|
|
violation="$inventory_case bounded inventory was accepted"
|
|
if [[ "$inventory_case" == long-non-nul ]]; then
|
|
(( ${#_swr_inventory[@]} == 0 )) ||
|
|
violation="$inventory_case retained an overlong record"
|
|
else
|
|
(( ${#_swr_inventory[@]} == 16 )) ||
|
|
violation="$inventory_case retained ${#_swr_inventory[@]} records after limit"
|
|
fi
|
|
else
|
|
_swr_remove_work_root "$root" &&
|
|
violation="$inventory_case work-root cleanup accepted failed inventory"
|
|
fi
|
|
[[ -n "$violation" || ! -e "$complete_marker" ]] ||
|
|
violation="$inventory_case consumed the entire lazy producer"
|
|
producer_pid="$(<"$pid_marker")"
|
|
[[ "$producer_pid" =~ ^[1-9][0-9]*$ ]] ||
|
|
violation="$inventory_case producer PID was not captured"
|
|
if [[ "$producer_pid" =~ ^[1-9][0-9]*$ ]] && kill -0 "$producer_pid" 2>/dev/null; then
|
|
violation="$inventory_case producer was not terminated and waited"
|
|
fi
|
|
|
|
[[ -f "$baseline" ]] || violation="$inventory_case inventory deleted evidence"
|
|
_swr_command() { command "$@"; }
|
|
/usr/bin/rm -f -- "$baseline" "$complete_marker" "$pid_marker"
|
|
/usr/bin/rmdir -- "$root"
|
|
[[ -z "$violation" ]] || fail "$violation"
|
|
}
|
|
|
|
test_task5_fix_round_one_boundaries() {
|
|
run_review_case review_mountpoint_path_is_prearmed_before_attestation chmod
|
|
run_review_case review_mountpoint_path_is_prearmed_before_attestation attest
|
|
run_review_case review_mountpoint_path_is_prearmed_before_attestation foreign
|
|
run_review_case review_prepare_publishes_partial_mountpoint
|
|
run_review_case review_stop_vault_tears_down_failed_persistent_client
|
|
run_review_case review_work_root_inventory_precedes_deletion unexpected
|
|
run_review_case review_work_root_inventory_precedes_deletion wrong-mode
|
|
run_review_case review_failed_find_inventory_preserves_work_root root
|
|
run_review_case review_work_root_rejects_unexpected_vault_home
|
|
run_review_case review_nul_inventory_rejects_ambiguous_or_partial_name lf-name
|
|
run_review_case review_nul_inventory_rejects_ambiguous_or_partial_name partial-frame
|
|
(( review_failures == 0 )) || fail "Task 5 fix round one regressions failed: $review_failures"
|
|
}
|
|
|
|
test_task5_fix_round_four_inventory_boundaries() {
|
|
run_review_case review_inventory_stops_at_bounded_input_limit long-non-nul parser
|
|
run_review_case review_inventory_stops_at_bounded_input_limit long-non-nul cleanup
|
|
run_review_case review_inventory_stops_at_bounded_input_limit count-total-overflow parser
|
|
run_review_case review_inventory_stops_at_bounded_input_limit count-total-overflow cleanup
|
|
(( review_failures == 0 )) || fail "Task 5 fix round four regressions failed: $review_failures"
|
|
}
|
|
|
|
transaction_log_event() {
|
|
builtin printf '%s\n' "$1" >>"$transaction_log"
|
|
}
|
|
|
|
_swr_fake_signal_after_event() {
|
|
local event="$1" signal_name scenario_event
|
|
[[ "$transaction_scenario" == signal-* ]] || return 0
|
|
signal_name="${transaction_scenario#signal-}"
|
|
signal_name="${signal_name%%-*}"
|
|
scenario_event="${transaction_scenario#signal-${signal_name}-}"
|
|
[[ "$scenario_event" == "$event" ]] || return 0
|
|
kill -"$signal_name" "$BASHPID"
|
|
}
|
|
|
|
reset_transaction_fake() {
|
|
: >"$transaction_log"
|
|
transaction_phase=ro
|
|
transaction_commit_calls=0
|
|
SWR_TRANSACTION_STATE=''
|
|
SWR_PENDING_SIGNAL=0
|
|
}
|
|
|
|
_swr_fake_prepare_transaction() {
|
|
transaction_log_event validate-runtime
|
|
builtin printf -v "$1" '%s' /fixture/work-root
|
|
builtin printf -v "$2" '%s' /dev/fixture-recovery-partition
|
|
builtin printf -v "$3" '%s' /fixture/mountpoint
|
|
_swr_fake_signal_after_event validate-runtime
|
|
}
|
|
|
|
_swr_fake_start_transaction_vault() {
|
|
transaction_log_event start-vault-and-retain-inputs
|
|
builtin printf -v "$1" '%s' 4242
|
|
builtin printf -v "$2" '%s' /fixture/work-root/vault.sock
|
|
_swr_fake_signal_after_event start-vault-and-retain-inputs
|
|
}
|
|
|
|
_swr_fake_refresh_sudo() {
|
|
transaction_log_event sudo-refresh
|
|
_swr_fake_signal_after_event sudo-refresh
|
|
}
|
|
|
|
_swr_fake_probe_transaction_rw() {
|
|
transaction_log_event "probe-${transaction_phase}-safety"
|
|
[[ "$transaction_scenario:$transaction_phase" == term-rw-probe:rw ]] && kill -TERM "$BASHPID"
|
|
_swr_fake_signal_after_event "probe-${transaction_phase}-safety"
|
|
return 0
|
|
}
|
|
|
|
_swr_fake_mount_transaction_volume() {
|
|
transaction_phase="$3"
|
|
transaction_log_event "mount-${transaction_phase}"
|
|
_swr_fake_signal_after_event "mount-${transaction_phase}"
|
|
case "$transaction_scenario:$transaction_phase" in
|
|
mount-ro-failure:ro|mount-ro-response-loss-unresolved-source:ro) return 32 ;;
|
|
esac
|
|
}
|
|
|
|
_swr_fake_validate_transaction_mount() {
|
|
transaction_log_event "validate-${3}-mount"
|
|
[[ "$transaction_scenario:$3" == int-rw-validate:rw ]] && kill -INT "$BASHPID"
|
|
_swr_fake_signal_after_event "validate-${3}-mount"
|
|
case "$transaction_scenario:$3" in
|
|
validate-ro-wrong-uid:ro|validate-ro-wrong-gid:ro|validate-ro-wrong-mode:ro) return 1 ;;
|
|
esac
|
|
return 0
|
|
}
|
|
|
|
_swr_fake_dirfd_snapshot() {
|
|
transaction_log_event snapshot
|
|
_swr_fake_signal_after_event snapshot
|
|
}
|
|
|
|
_swr_fake_unmount_transaction_volume() {
|
|
transaction_log_event "unmount-${transaction_phase}"
|
|
_swr_fake_signal_after_event "unmount-${transaction_phase}"
|
|
[[ "$transaction_scenario:$transaction_phase" == hup-final-unmount:rw ]] && kill -HUP "$BASHPID"
|
|
case "$transaction_scenario:$transaction_phase" in
|
|
mount-ro-response-loss-unresolved-source:ro) return 1 ;;
|
|
unmount-ro:ro|unmount-rw:rw|unmount-ambiguous:*) return 1 ;;
|
|
esac
|
|
}
|
|
|
|
_swr_fake_transaction_stably_unmounted() {
|
|
case "$transaction_scenario" in
|
|
mount-ro-response-loss-unresolved-source|unmount-ro|unmount-rw|unmount-ambiguous) return 1 ;;
|
|
esac
|
|
}
|
|
|
|
_swr_fake_vault_transaction_request() {
|
|
local result_name="$1" command="$3" reply=''
|
|
case "$command" in
|
|
PREPARE)
|
|
transaction_log_event classify
|
|
_swr_fake_signal_after_event classify
|
|
case "$transaction_scenario" in
|
|
noop) reply=exact-noop ;;
|
|
hup-noop) kill -HUP "$BASHPID"; reply=exact-noop ;;
|
|
signal-INT-confirm-update) reply=mismatch ;;
|
|
absent|original-drift|backup-collision|backup-fsync|rename-failure|post-rename|final-verification|hup-before|int-before|term-before|hup-during|int-during|term-during|term-rw-probe|int-rw-validate|lost-helper|lost-after-verified|hup-final-unmount|term-cleanup|term-post-cleanup|unmount-ro|unmount-rw|unmount-ambiguous|signal-*) reply=absent ;;
|
|
mismatch-no-update|mismatch-update) reply=mismatch ;;
|
|
duplicate) reply=ambiguous ;;
|
|
wrong-master|retained-input-drift) reply=failed ;;
|
|
*) return 97 ;;
|
|
esac
|
|
;;
|
|
'BUILD ADD') transaction_log_event build-add; _swr_fake_signal_after_event build-add; reply=candidate-ready ;;
|
|
'BUILD UPDATE') transaction_log_event build-update; _swr_fake_signal_after_event build-update; reply=candidate-ready ;;
|
|
VERIFY_CANDIDATE) transaction_log_event verify-candidate; _swr_fake_signal_after_event verify-candidate; reply=verified ;;
|
|
'VERIFY_COMMITTED '*)
|
|
transaction_log_event verify-committed-copy
|
|
[[ "$transaction_scenario" == final-verification ]] && reply=failed || reply=verified
|
|
;;
|
|
*) return 97 ;;
|
|
esac
|
|
builtin printf -v "$result_name" '%s' "$reply"
|
|
}
|
|
|
|
_swr_fake_confirm_transaction() {
|
|
local expected="$1"
|
|
transaction_log_event "confirm-${expected,,}"
|
|
_swr_fake_signal_after_event "confirm-${expected,,}"
|
|
case "$transaction_scenario:$expected" in
|
|
mismatch-no-update:UPDATE) return 1 ;;
|
|
hup-before:WRITE) kill -HUP "$BASHPID"; return 1 ;;
|
|
int-before:WRITE) kill -INT "$BASHPID"; return 1 ;;
|
|
term-before:WRITE) kill -TERM "$BASHPID"; return 1 ;;
|
|
esac
|
|
[[ "$expected" == WRITE ]] && transaction_phase=rw
|
|
return 0
|
|
}
|
|
|
|
_swr_fake_dirfd_commit() {
|
|
local result_name="$1"
|
|
transaction_commit_calls=$((transaction_commit_calls + 1))
|
|
transaction_log_event commit
|
|
case "$transaction_scenario" in
|
|
original-drift|backup-collision|backup-fsync)
|
|
builtin printf -v "$result_name" '%s' precommit-failure
|
|
return 1
|
|
;;
|
|
rename-failure|post-rename)
|
|
builtin printf -v "$result_name" '%s' committed-but-uncertain
|
|
return 1
|
|
;;
|
|
lost-helper)
|
|
builtin printf -v "$result_name" '%s' rename-attempted
|
|
return 1
|
|
;;
|
|
lost-after-verified)
|
|
builtin printf -v "$result_name" '%s' verified-commit
|
|
return 0
|
|
;;
|
|
hup-during) kill -HUP "$BASHPID" ;;
|
|
int-during) kill -INT "$BASHPID" ;;
|
|
term-during) kill -TERM "$BASHPID" ;;
|
|
esac
|
|
builtin printf -v "$result_name" '%s' verified-commit
|
|
}
|
|
|
|
_swr_fake_dirfd_snapshot_current() {
|
|
transaction_log_event snapshot-current
|
|
}
|
|
|
|
_swr_fake_stop_transaction_vault() {
|
|
transaction_log_event stop-vault
|
|
[[ "$transaction_scenario" == term-cleanup ]] && kill -TERM "$BASHPID"
|
|
return 0
|
|
}
|
|
|
|
_swr_fake_audit_runtime_processes() { transaction_log_event audit-processes; }
|
|
|
|
_swr_fake_remove_transaction_work_root() {
|
|
transaction_log_event remove-work-root
|
|
[[ "$transaction_scenario" == term-post-cleanup ]] && kill -TERM "$BASHPID"
|
|
return 0
|
|
}
|
|
|
|
assert_transaction_prefix() {
|
|
local expected="$1" description="$2" actual
|
|
actual="$(/usr/bin/head -n "$(builtin printf '%s\n' "$expected" | /usr/bin/wc -l)" "$transaction_log")"
|
|
assert_eq "$expected" "$actual" "$description"
|
|
}
|
|
|
|
run_transaction_case() {
|
|
local scenario="$1" expected_state="$2" expected_output="$3" expected_rc="${4-}" output='' rc=0
|
|
reset_transaction_fake
|
|
transaction_scenario="$scenario"
|
|
set +e
|
|
execute_transaction /fixture/input-webhook /fixture/contract >"$fixture_dir/transaction.output"
|
|
rc=$?
|
|
set -e
|
|
output="$(<"$fixture_dir/transaction.output")"
|
|
[[ "$SWR_TRANSACTION_STATE" == "$expected_state" ]] ||
|
|
fail "$scenario terminal state differs: expected=$expected_state actual=$SWR_TRANSACTION_STATE log=$(/usr/bin/tr '\n' ' ' <"$transaction_log")"
|
|
assert_eq "$expected_output" "$output" "$scenario output differs"
|
|
if [[ ( "$expected_state" == NOOP || "$expected_state" == VERIFIED_COMMIT ) &&
|
|
-z "$expected_rc" ]]; then
|
|
(( rc == 0 )) || fail "$scenario unexpectedly failed"
|
|
else
|
|
(( rc != 0 )) || fail "$scenario unexpectedly succeeded"
|
|
[[ "$output" != *'SLACK_KEEPASS_RECOVERY=NOOP'* &&
|
|
"$output" != *'SLACK_KEEPASS_RECOVERY=COMMITTED'* ]] ||
|
|
fail "$scenario emitted a success token"
|
|
fi
|
|
[[ -z "$expected_rc" || "$rc" == "$expected_rc" ]] ||
|
|
fail "$scenario signal status differs: expected=$expected_rc actual=$rc"
|
|
(( transaction_commit_calls <= 1 )) || fail "$scenario retried commit"
|
|
pass_transaction_case
|
|
}
|
|
|
|
signal_boundary_case() {
|
|
local event="$1" signal_name="${2-TERM}" expected_rc=143 output='' rc=0 later_effects
|
|
[[ "$signal_name" == HUP ]] && expected_rc=129
|
|
[[ "$signal_name" == INT ]] && expected_rc=130
|
|
reset_transaction_fake
|
|
transaction_scenario="signal-${signal_name}-${event}"
|
|
set +e
|
|
execute_transaction /fixture/input-webhook /fixture/contract >"$fixture_dir/transaction.output"
|
|
rc=$?
|
|
set -e
|
|
output="$(<"$fixture_dir/transaction.output")"
|
|
assert_eq "$expected_rc" "$rc" "signal after $event status differs"
|
|
assert_eq '' "$output" "signal after $event emitted output"
|
|
assert_eq PRECOMMIT_FAILURE "$SWR_TRANSACTION_STATE" \
|
|
"signal after $event rewrote precommit state"
|
|
later_effects="$(/usr/bin/sed -n "0,/^${event}$/d; p" "$transaction_log" |
|
|
/usr/bin/grep -Ev '^(unmount-ro|unmount-rw|stop-vault|audit-processes|remove-work-root)$' || true)"
|
|
assert_eq '' "$later_effects" "signal after $event allowed a later side effect"
|
|
}
|
|
|
|
late_post_cleanup_signal_case() {
|
|
local output='' rc=0 old_trap_marker="$fixture_dir/old-term-trap"
|
|
reset_transaction_fake
|
|
transaction_scenario=absent
|
|
: >"$old_trap_marker"
|
|
builtin trap 'builtin printf old >"$old_trap_marker"' TERM
|
|
set -T
|
|
builtin trap 'if [[ "$BASH_COMMAND" == *"final_rc == 0 && SWR_PENDING_SIGNAL == 0"* ]]; then builtin trap - DEBUG; kill -TERM "$BASHPID"; fi' DEBUG
|
|
set +e
|
|
execute_transaction /fixture/input-webhook /fixture/contract >"$fixture_dir/transaction.output"
|
|
rc=$?
|
|
set -e
|
|
builtin trap - DEBUG
|
|
set +T
|
|
builtin trap - TERM
|
|
output="$(<"$fixture_dir/transaction.output")"
|
|
assert_eq 143 "$rc" 'post-cleanup/pre-output TERM status differs'
|
|
assert_eq '' "$output" 'post-cleanup/pre-output TERM emitted output'
|
|
assert_eq VERIFIED_COMMIT "$SWR_TRANSACTION_STATE" \
|
|
'post-cleanup/pre-output TERM lost verified commit evidence'
|
|
assert_eq '' "$(<"$old_trap_marker")" \
|
|
'post-cleanup/pre-output TERM reached the restored caller trap'
|
|
}
|
|
|
|
test_transaction_state_machine() {
|
|
declare -F execute_transaction >/dev/null || {
|
|
plain_fail 'full Slack KeePass transaction state machine is unavailable'
|
|
return 1
|
|
}
|
|
declare -F cleanup_transaction >/dev/null || fail 'cleanup_transaction is unavailable'
|
|
declare -F defer_signal >/dev/null || fail 'defer_signal is unavailable'
|
|
|
|
_swr_dirfd_helper() {
|
|
if [[ "$1" == commit ]]; then
|
|
return 75
|
|
fi
|
|
if [[ "$1" == status ]]; then
|
|
builtin printf '%s\n' verified-commit
|
|
return 0
|
|
fi
|
|
return 97
|
|
}
|
|
local _swr_durable_status=''
|
|
_swr_command() { command "$@"; }
|
|
_swr_dirfd_commit _swr_durable_status /fixture/mount /fixture/work backup.kdbx ||
|
|
fail 'durable verified commit was overridden by lost helper exit status'
|
|
_swr_command() { _swr_fake_command "$@"; }
|
|
assert_eq verified-commit "$_swr_durable_status" 'durable verified status differs'
|
|
pass_transaction_case
|
|
|
|
_swr_prepare_transaction() { _swr_fake_prepare_transaction "$@"; }
|
|
_swr_start_vault() { _swr_fake_start_transaction_vault "$@"; }
|
|
_swr_refresh_sudo() { _swr_fake_refresh_sudo "$@"; }
|
|
_swr_probe_rw() { _swr_fake_probe_transaction_rw "$@"; }
|
|
_swr_mount_volume_command() { _swr_fake_mount_transaction_volume "$@"; }
|
|
_swr_validate_mount() { _swr_fake_validate_transaction_mount "$@"; }
|
|
_swr_dirfd_snapshot() { _swr_fake_dirfd_snapshot "$@"; }
|
|
_swr_unmount_volume() { _swr_fake_unmount_transaction_volume "$@"; }
|
|
_swr_stably_unmounted() { _swr_fake_transaction_stably_unmounted "$@"; }
|
|
_swr_vault_request() { _swr_fake_vault_transaction_request "$@"; }
|
|
_swr_confirm_token() { _swr_fake_confirm_transaction "$@"; }
|
|
_swr_dirfd_commit() { _swr_fake_dirfd_commit "$@"; }
|
|
_swr_dirfd_snapshot_current() { _swr_fake_dirfd_snapshot_current "$@"; }
|
|
_swr_stop_vault() { _swr_fake_stop_transaction_vault "$@"; }
|
|
_swr_audit_runtime_processes() { _swr_fake_audit_runtime_processes "$@"; }
|
|
_swr_remove_work_root() { _swr_fake_remove_transaction_work_root "$@"; }
|
|
|
|
local initial_order mutation_tail
|
|
initial_order=$'validate-runtime\nstart-vault-and-retain-inputs\nsudo-refresh\nprobe-ro-safety\nmount-ro\nvalidate-ro-mount\nsnapshot\nunmount-ro\nclassify'
|
|
|
|
run_transaction_case noop NOOP $'SLACK_KEEPASS_RECOVERY=NOOP\nWINDOWS_SSD_UNMOUNTED=PASS\nOFF_HOST_RECOVERY_SATISFIED=NO'
|
|
assert_transaction_prefix "$initial_order" 'initial no-op order differs'
|
|
|
|
# Production break caught: a known RO mount-command failure with stable
|
|
# source absence is precommit failure, while response loss with a retained
|
|
# or ambiguous source still requires manual recovery. Reconcile only once.
|
|
run_transaction_case mount-ro-failure PRECOMMIT_FAILURE ''
|
|
assert_eq \
|
|
$'validate-runtime\nstart-vault-and-retain-inputs\nsudo-refresh\nprobe-ro-safety\nmount-ro\nunmount-ro\nstop-vault\naudit-processes\nremove-work-root' \
|
|
"$(<"$transaction_log")" 'known RO mount failure cleanup trace differs'
|
|
assert_eq 1 "$(/usr/bin/grep -Fc unmount-ro "$transaction_log")" \
|
|
'known RO mount failure reconciliation count differs'
|
|
! /usr/bin/grep -Eq '^(snapshot|classify|probe-rw-safety|mount-rw|commit)$' "$transaction_log" ||
|
|
fail 'known RO mount failure reached a later transaction effect'
|
|
|
|
run_transaction_case mount-ro-response-loss-unresolved-source MANUAL_RECOVERY_REQUIRED \
|
|
'SLACK_KEEPASS_RECOVERY=MANUAL_RECOVERY_REQUIRED'
|
|
assert_eq 1 "$(/usr/bin/grep -Fc unmount-ro "$transaction_log")" \
|
|
'RO mount response-loss reconciliation count differs'
|
|
! /usr/bin/grep -Eq '^(snapshot|classify|probe-rw-safety|mount-rw|commit)$' "$transaction_log" ||
|
|
fail 'RO mount response loss reached a later transaction effect'
|
|
|
|
local metadata_scenario
|
|
for metadata_scenario in \
|
|
validate-ro-wrong-uid validate-ro-wrong-gid validate-ro-wrong-mode; do
|
|
run_transaction_case "$metadata_scenario" PRECOMMIT_FAILURE ''
|
|
assert_eq 1 "$(/usr/bin/grep -Fc unmount-ro "$transaction_log")" \
|
|
"$metadata_scenario reconciliation count differs"
|
|
! /usr/bin/grep -Eq '^(snapshot|classify|probe-rw-safety|mount-rw|commit)$' "$transaction_log" ||
|
|
fail "$metadata_scenario reached a later transaction effect"
|
|
done
|
|
|
|
run_transaction_case absent VERIFIED_COMMIT $'SLACK_KEEPASS_RECOVERY=COMMITTED\nKDBX_PRECHANGE_BACKUP=CREATED\nWINDOWS_SSD_UNMOUNTED=PASS\nOFF_HOST_RECOVERY_SATISFIED=NO'
|
|
mutation_tail="$(/usr/bin/sed -n '/^classify$/,$p' "$transaction_log")"
|
|
[[ "$mutation_tail" == $'classify\nbuild-add\nverify-candidate\nconfirm-write\nprobe-rw-safety\nmount-rw\nvalidate-rw-mount\ncommit\nsnapshot-current\nverify-committed-copy\nunmount-rw\nstop-vault\naudit-processes\nremove-work-root' ]] ||
|
|
fail "absent mutation order differs: ${mutation_tail//$'\n'/ -> }"
|
|
|
|
run_transaction_case mismatch-no-update PRECOMMIT_FAILURE ''
|
|
run_transaction_case mismatch-update VERIFIED_COMMIT $'SLACK_KEEPASS_RECOVERY=COMMITTED\nKDBX_PRECHANGE_BACKUP=CREATED\nWINDOWS_SSD_UNMOUNTED=PASS\nOFF_HOST_RECOVERY_SATISFIED=NO'
|
|
mutation_tail="$(/usr/bin/sed -n '/^classify$/,$p' "$transaction_log")"
|
|
[[ "$mutation_tail" == $'classify\nconfirm-update\nbuild-update\nverify-candidate\nconfirm-write\nprobe-rw-safety\nmount-rw\nvalidate-rw-mount\ncommit\nsnapshot-current\nverify-committed-copy\nunmount-rw\nstop-vault\naudit-processes\nremove-work-root' ]] ||
|
|
fail "mismatch mutation order differs: ${mutation_tail//$'\n'/ -> }"
|
|
|
|
local scenario state expected
|
|
for scenario in duplicate wrong-master retained-input-drift original-drift backup-collision backup-fsync; do
|
|
run_transaction_case "$scenario" PRECOMMIT_FAILURE ''
|
|
done
|
|
run_transaction_case hup-before PRECOMMIT_FAILURE '' 129
|
|
run_transaction_case int-before PRECOMMIT_FAILURE '' 130
|
|
run_transaction_case term-before PRECOMMIT_FAILURE '' 143
|
|
run_transaction_case hup-noop PRECOMMIT_FAILURE '' 129
|
|
run_transaction_case term-rw-probe PRECOMMIT_FAILURE '' 143
|
|
assert_eq 0 "$transaction_commit_calls" 'pending TERM after RW probe reached commit'
|
|
run_transaction_case int-rw-validate PRECOMMIT_FAILURE '' 130
|
|
assert_eq 0 "$transaction_commit_calls" 'pending INT after RW validation reached commit'
|
|
run_transaction_case lost-after-verified VERIFIED_COMMIT $'SLACK_KEEPASS_RECOVERY=COMMITTED\nKDBX_PRECHANGE_BACKUP=CREATED\nWINDOWS_SSD_UNMOUNTED=PASS\nOFF_HOST_RECOVERY_SATISFIED=NO'
|
|
for scenario in rename-failure post-rename final-verification lost-helper; do
|
|
run_transaction_case "$scenario" COMMITTED_BUT_UNCERTAIN 'SLACK_KEEPASS_RECOVERY=MANUAL_RECOVERY_REQUIRED'
|
|
done
|
|
run_transaction_case hup-during COMMITTED_BUT_UNCERTAIN 'SLACK_KEEPASS_RECOVERY=MANUAL_RECOVERY_REQUIRED' 129
|
|
run_transaction_case int-during COMMITTED_BUT_UNCERTAIN 'SLACK_KEEPASS_RECOVERY=MANUAL_RECOVERY_REQUIRED' 130
|
|
run_transaction_case term-during COMMITTED_BUT_UNCERTAIN 'SLACK_KEEPASS_RECOVERY=MANUAL_RECOVERY_REQUIRED' 143
|
|
run_transaction_case hup-final-unmount VERIFIED_COMMIT '' 129
|
|
run_transaction_case term-cleanup VERIFIED_COMMIT '' 143
|
|
run_transaction_case term-post-cleanup VERIFIED_COMMIT '' 143
|
|
late_post_cleanup_signal_case
|
|
local signal_event signal_failures=0
|
|
for signal_event in \
|
|
validate-runtime start-vault-and-retain-inputs sudo-refresh probe-ro-safety \
|
|
mount-ro validate-ro-mount snapshot unmount-ro classify build-add \
|
|
verify-candidate confirm-write probe-rw-safety mount-rw validate-rw-mount; do
|
|
( signal_boundary_case "$signal_event" TERM ) || {
|
|
builtin printf 'SIGNAL RED: %s\n' "$signal_event" >&2
|
|
signal_failures=$((signal_failures + 1))
|
|
}
|
|
done
|
|
( signal_boundary_case confirm-update INT ) || {
|
|
builtin printf 'SIGNAL RED: %s\n' confirm-update >&2
|
|
signal_failures=$((signal_failures + 1))
|
|
}
|
|
(( signal_failures == 0 )) || fail "$signal_failures signal boundaries failed"
|
|
for scenario in unmount-ro unmount-rw unmount-ambiguous; do
|
|
run_transaction_case "$scenario" MANUAL_RECOVERY_REQUIRED 'SLACK_KEEPASS_RECOVERY=MANUAL_RECOVERY_REQUIRED'
|
|
local unmount_phase="${scenario#unmount-}"
|
|
[[ "$unmount_phase" == ambiguous ]] && unmount_phase=ro
|
|
assert_eq 1 "$(/usr/bin/grep -Fc "unmount-${unmount_phase}" "$transaction_log")" \
|
|
"$scenario retried an ambiguous unmount"
|
|
done
|
|
}
|
|
|
|
run_worker() {
|
|
local fixture_dir="$1" worker_token="$2"
|
|
shift 2
|
|
/usr/bin/timeout --signal=TERM --kill-after=5s 30s \
|
|
/usr/bin/bash "$0" --worker "$worker_token" "$fixture_dir" "$@"
|
|
}
|
|
|
|
audit_fixture_pids() {
|
|
local fixture_dir="$1" pid_file pid orphan_count=0
|
|
shopt -s nullglob
|
|
for pid_file in "$fixture_dir"/pid.*; do
|
|
pid="${pid_file##*.}"
|
|
if kill -0 "$pid" 2>/dev/null; then
|
|
orphan_count=$((orphan_count + 1))
|
|
fi
|
|
done
|
|
shopt -u nullglob
|
|
printf '%s\n' "$orphan_count"
|
|
}
|
|
|
|
create_synthetic_kdbx() {
|
|
local fixture_dir="$1" database socket_path server_pid attempt
|
|
local fixture_master="fixture-$RANDOM-$RANDOM-$$"
|
|
local fixture_webhook='https://hooks.'
|
|
fixture_webhook+='slack.com/services/T01234567/B01234567/XXXXXXXXXXXXXXXXXXXXXXXX'
|
|
runtime_fixture="$(mktemp -d "/run/user/${UID}/slack-webhook-recovery-bash.XXXXXX")"
|
|
chmod 0700 -- "$runtime_fixture"
|
|
database="$runtime_fixture/baseline.kdbx"
|
|
socket_path="$runtime_fixture/vault.sock"
|
|
{
|
|
builtin printf '%s\n' "$fixture_master"
|
|
builtin printf '%s\n' "$fixture_master"
|
|
} | HOME=/dev/null XDG_CONFIG_HOME=/dev/null LC_ALL=C.UTF-8 PATH=/usr/bin:/bin \
|
|
/usr/bin/keepassxc-cli db-create -q -p "$database" \
|
|
>"$fixture_dir/db-create.stdout" 2>"$fixture_dir/db-create.stderr" ||
|
|
fail 'synthetic KeePass database creation failed'
|
|
chmod 0600 -- "$database"
|
|
builtin printf '%s' "$fixture_webhook" >"$runtime_fixture/input-webhook"
|
|
chmod 0600 -- "$runtime_fixture/input-webhook"
|
|
if grep -Fq -- "$fixture_master" \
|
|
"$fixture_dir/db-create.stdout" "$fixture_dir/db-create.stderr"; then
|
|
fail 'synthetic master appeared in captured KeePassXC output'
|
|
fi
|
|
|
|
{
|
|
builtin printf '%s\n' 'Synthetic Slack App'
|
|
builtin printf '%s\n' "$fixture_master"
|
|
} | /usr/bin/python3 -c '
|
|
import importlib.util
|
|
import os
|
|
import sys
|
|
spec = importlib.util.spec_from_file_location("swr_vault", sys.argv[1])
|
|
if spec is None or spec.loader is None:
|
|
raise SystemExit(2)
|
|
module = importlib.util.module_from_spec(spec)
|
|
spec.loader.exec_module(module)
|
|
output_fd = os.open(os.devnull, os.O_WRONLY | os.O_CLOEXEC)
|
|
try:
|
|
module.serve_private_socket(
|
|
sys.argv[2], sys.argv[3], input_fd=0, output_fd=output_fd,
|
|
accept_timeout=10.0, io_timeout=10.0, child_timeout=20.0,
|
|
)
|
|
finally:
|
|
os.close(output_fd)
|
|
' "$ROOT/scripts/libexec/slack-webhook-recovery-vault.py" \
|
|
"$runtime_fixture" "$runtime_fixture/input-webhook" \
|
|
>"$fixture_dir/vault.stdout" 2>"$fixture_dir/vault.stderr" &
|
|
server_pid=$!
|
|
builtin printf '' >"$fixture_dir/pid.$server_pid"
|
|
for (( attempt = 0; attempt < 100; attempt++ )); do
|
|
[[ -S "$socket_path" ]] && break
|
|
kill -0 "$server_pid" 2>/dev/null || fail 'imported vault server exited early'
|
|
/usr/bin/sleep 0.05
|
|
done
|
|
[[ -S "$socket_path" ]] || fail 'imported vault socket did not appear'
|
|
[[ "$(stat -c '%a' "$socket_path")" == 600 ]] || fail 'vault socket mode differs'
|
|
|
|
if grep -Fq -- "$fixture_master" "/proc/$server_pid/cmdline" \
|
|
"/proc/$server_pid/environ" "$fixture_dir/vault.stdout" \
|
|
"$fixture_dir/vault.stderr"; then
|
|
fail 'synthetic master escaped the vault process boundary'
|
|
fi
|
|
if grep -Fq -- "$fixture_webhook" "/proc/$server_pid/cmdline" \
|
|
"/proc/$server_pid/environ" "$fixture_dir/vault.stdout" \
|
|
"$fixture_dir/vault.stderr"; then
|
|
fail 'synthetic webhook escaped the vault process boundary'
|
|
fi
|
|
|
|
local _swr_reply=''
|
|
_swr_vault_request _swr_reply "$socket_path" PREPARE ||
|
|
fail 'vault PREPARE request failed'
|
|
assert_eq absent "$_swr_reply" 'absent database classification differs'
|
|
_swr_vault_request _swr_reply "$socket_path" 'BUILD ADD' ||
|
|
fail 'vault BUILD ADD request failed'
|
|
assert_eq candidate-ready "$_swr_reply" 'candidate response differs'
|
|
_swr_vault_request _swr_reply "$socket_path" VERIFY_CANDIDATE ||
|
|
fail 'vault VERIFY_CANDIDATE request failed'
|
|
assert_eq verified "$_swr_reply" 'candidate verification response differs'
|
|
_swr_vault_request _swr_reply "$socket_path" \
|
|
"VERIFY_COMMITTED $runtime_fixture/candidate.kdbx" ||
|
|
fail 'vault VERIFY_COMMITTED request failed'
|
|
assert_eq verified "$_swr_reply" 'candidate verification response differs'
|
|
_swr_stop_vault "$socket_path" "$server_pid" || fail 'vault shutdown failed'
|
|
[[ ! -S "$socket_path" ]] || fail 'vault socket remained after shutdown'
|
|
unset fixture_master
|
|
unset fixture_webhook
|
|
}
|
|
|
|
cleanup_runtime_fixture() {
|
|
if [[ "$runtime_fixture" == "/run/user/${UID}/slack-webhook-recovery-bash."* ]]; then
|
|
rm -rf -- "$runtime_fixture"
|
|
fi
|
|
if [[ "$owned_work_fixture" == /run/user/1000/platform-slack-keepass.* &&
|
|
-d "$owned_work_fixture" && ! -L "$owned_work_fixture" && -O "$owned_work_fixture" ]]; then
|
|
/usr/bin/rmdir -- "$owned_work_fixture" 2>/dev/null || true
|
|
fi
|
|
}
|
|
|
|
cleanup_fixture() {
|
|
cleanup_runtime_fixture
|
|
[[ "$fixture_dir" == /tmp/slack-webhook-recovery-test.* || "$fixture_dir" == "${TMPDIR:-/tmp}"/slack-webhook-recovery-test.* ]] || return 0
|
|
rm -rf -- "$fixture_dir"
|
|
}
|
|
|
|
worker_main() {
|
|
local worker_token="$1" fixture_dir="$2"
|
|
trap cleanup_runtime_fixture EXIT
|
|
shift 2
|
|
local expected_token_file="$fixture_dir/worker-token"
|
|
[[ -f "$expected_token_file" ]] || fail 'worker token file is absent'
|
|
[[ "$( <"$expected_token_file" )" == "$worker_token" ]] || fail 'worker token mismatch'
|
|
[[ $# -eq 0 ]] || fail 'focused suite worker accepts no public argv'
|
|
|
|
# The production change this catches is widening the closed CLI boundary or
|
|
# contacting any external secret/mount path on the default invocation.
|
|
[[ -x "$PROD" ]] || fail 'production Slack KeePass recovery helper is absent'
|
|
|
|
local output relative_output
|
|
output="$(/usr/bin/env -i PATH=/definitely-invalid /usr/bin/bash "$PROD")" ||
|
|
fail 'default invocation failed'
|
|
assert_eq \
|
|
$'SLACK_KEEPASS_RECOVERY_MODE=DRY_RUN\nWINDOWS_SSD_UUID=4EA0196C0C5FA27E\nKDBX_RELATIVE_PATH=HyeonworksRecovery/vault/hyeonworks-recovery.kdbx\nMUTATION=NOT_REQUESTED\nOFF_HOST_RECOVERY_SATISFIED=NO' \
|
|
"$output" \
|
|
'default output differs'
|
|
|
|
relative_output="$(
|
|
cd -- "$ROOT" &&
|
|
/usr/bin/env -i PATH=/definitely-invalid /usr/bin/bash \
|
|
scripts/bootstrap/backup-slack-webhook-recovery.sh
|
|
)" || fail 'repository-relative default invocation failed'
|
|
assert_eq "$output" "$relative_output" 'repository-relative default output differs'
|
|
|
|
/usr/bin/env -i PATH=/usr/bin:/bin /usr/bin/bash "$PROD" --unknown >/dev/null 2>&1 &&
|
|
fail 'unknown argv was accepted'
|
|
/usr/bin/env -i PATH=/usr/bin:/bin /usr/bin/bash "$PROD" --execute --execute >/dev/null 2>&1 &&
|
|
fail 'duplicate execute argv was accepted'
|
|
/usr/bin/env -i PATH=/usr/bin:/bin /usr/bin/bash "$PROD" --execute --slack-webhook-file >/dev/null 2>&1 &&
|
|
fail 'missing webhook path was accepted'
|
|
/usr/bin/env -i PATH=/usr/bin:/bin /usr/bin/bash "$PROD" --execute --slack-webhook-file relative/path >/dev/null 2>&1 &&
|
|
fail 'relative webhook path was accepted'
|
|
/usr/bin/env -i PATH=/usr/bin:/bin /usr/bin/bash "$PROD" --slack-webhook-file /tmp/placeholder --execute >/dev/null 2>&1 &&
|
|
fail 'reordered argv was accepted'
|
|
/usr/bin/env -i PATH=/usr/bin:/bin CDPATH=/tmp /usr/bin/bash "$PROD" >/dev/null 2>&1 &&
|
|
fail 'CDPATH override was accepted'
|
|
/usr/bin/env -i PATH=/usr/bin:/bin BASH_ENV=/dev/null /usr/bin/bash "$PROD" >/dev/null 2>&1 &&
|
|
fail 'BASH_ENV override was accepted'
|
|
/usr/bin/env -i PATH=/usr/bin:/bin ENV=/dev/null /usr/bin/bash "$PROD" >/dev/null 2>&1 &&
|
|
fail 'ENV override was accepted'
|
|
/usr/bin/env -i PATH=/usr/bin:/bin 'BASH_FUNC_printf%%=() { :; }' /usr/bin/bash "$PROD" >/dev/null 2>&1 &&
|
|
fail 'imported command override was accepted'
|
|
/usr/bin/env -i PATH=/usr/bin:/bin SHELLOPTS=braceexpand:hashall /usr/bin/bash "$PROD" >/dev/null 2>&1 &&
|
|
fail 'inherited SHELLOPTS was accepted'
|
|
/usr/bin/env -i PATH=/usr/bin:/bin /usr/bin/bash "$PROD" --execute --slack-webhook-file /tmp/placeholder >/dev/null 2>&1 &&
|
|
fail 'execute mode without tty was accepted'
|
|
|
|
# shellcheck source=/dev/null
|
|
SWR_REPOSITORY_ROOT="$ROOT"
|
|
source "$LIB"
|
|
production_runtime_root_definition="$(declare -f _swr_runtime_root)"
|
|
_swr_command() { _swr_fake_command "$@"; }
|
|
_swr_runtime_root() { _swr_fake_runtime_root "$@"; }
|
|
_swr_volume_action() { _swr_fake_volume_action "$@"; }
|
|
local required_boundary
|
|
for required_boundary in \
|
|
_swr_load_recovery_contract _swr_resolve_device _swr_require_unmounted \
|
|
_swr_probe_rw _swr_mount_volume _swr_validate_mount _swr_unmount_volume \
|
|
_swr_create_mountpoint _swr_require_mountpoint_unused \
|
|
_swr_run_volume_cycle _swr_open_recovery_boundary; do
|
|
declare -F "$required_boundary" >/dev/null ||
|
|
fail 'exact Windows SSD mount boundary is unavailable'
|
|
done
|
|
_swr_initial_guard 0 '' '' '' "$PROD" &&
|
|
fail 'root EUID guard was accepted'
|
|
|
|
fake_runtime_root="$fixture_dir/runtime-root"
|
|
fake_log="$fixture_dir/fake-command.log"
|
|
fake_mount_state="$fixture_dir/fake-mount-state"
|
|
fake_probe_status="$fixture_dir/fake-probe-status"
|
|
fake_mount_status="$fixture_dir/fake-mount-status"
|
|
fake_umount_status="$fixture_dir/fake-umount-status"
|
|
fake_umount_after="$fixture_dir/fake-umount-after"
|
|
fake_sudo_status="$fixture_dir/fake-sudo-status"
|
|
fake_source_query_count="$fixture_dir/fake-source-query-count"
|
|
fake_action_count="$fixture_dir/fake-action-count"
|
|
fake_mount_uncertain="$fixture_dir/fake-mount-uncertain"
|
|
fake_case_file="$fixture_dir/fake-case"
|
|
fake_mountpoint="$fake_runtime_root/not-created"
|
|
fake_action=noop
|
|
mkdir -m 0700 -- "$fake_runtime_root"
|
|
|
|
_swr_command /fixture/fake-only-sentinel ||
|
|
fail 'fixed command leaf was not intercepted after production source'
|
|
assert_log_line '/fixture/fake-only-sentinel' 'fake-only command sentinel was not logged'
|
|
pass_boundary_case
|
|
|
|
owned_work_fixture="$(/usr/bin/mktemp -d --tmpdir=/run/user/1000 platform-slack-keepass.XXXXXXXX)"
|
|
/usr/bin/chmod 0700 -- "$owned_work_fixture"
|
|
_swr_work_root_is_safe "$owned_work_fixture" ||
|
|
fail 'owned private tmpfs work root was rejected'
|
|
/usr/bin/rmdir -- "$owned_work_fixture"
|
|
owned_work_fixture=''
|
|
pass_transaction_case
|
|
|
|
test_contract_and_device_boundary
|
|
test_probe_mount_and_validation_boundary
|
|
test_unmount_and_cycle_boundary
|
|
test_full_fake_boundary_ordering
|
|
test_review_fix_round_one
|
|
test_review_fix_round_two
|
|
test_task5_fix_round_one_boundaries
|
|
test_task5_fix_round_four_inventory_boundaries
|
|
test_vault_readiness_failure_boundaries
|
|
test_pending_signal_after_readiness_prevents_publication
|
|
test_start_vault_waits_for_delayed_operator_input
|
|
|
|
create_synthetic_kdbx "$fixture_dir"
|
|
declare -F _swr_start_vault >/dev/null || fail 'KeePass database classifier is unavailable'
|
|
transaction_log="$fixture_dir/transaction.log"
|
|
test_transaction_state_machine
|
|
builtin printf 'PASS: %s exact device/probe/mount boundary cases; %s transaction cases; fake-only sentinel=pass; real privileged calls=0\n' \
|
|
"$boundary_cases" "$transaction_cases"
|
|
}
|
|
|
|
supervisor_main() {
|
|
(( $# == 0 )) || fail 'focused suite accepts no public argv'
|
|
|
|
fixture_dir="$(mktemp -d "${TMPDIR:-/tmp}/slack-webhook-recovery-test.XXXXXX")"
|
|
trap cleanup_fixture EXIT
|
|
|
|
local worker_token
|
|
printf -v worker_token 'worker-%(%s)T-%s-%s' -1 "$$" "$RANDOM"
|
|
printf '%s' "$worker_token" >"$fixture_dir/worker-token"
|
|
|
|
local stdout_file="$fixture_dir/stdout" stderr_file="$fixture_dir/stderr"
|
|
local started_at finished_at rc orphan_count
|
|
printf -v started_at '%(%s)T' -1
|
|
set +e
|
|
run_worker "$fixture_dir" "$worker_token" >"$stdout_file" 2>"$stderr_file"
|
|
rc=$?
|
|
set -e
|
|
printf -v finished_at '%(%s)T' -1
|
|
|
|
orphan_count="$(audit_fixture_pids "$fixture_dir")"
|
|
if [[ "$orphan_count" != 0 ]]; then
|
|
cat "$stderr_file" >&2
|
|
fail 'worker left fixture-owned processes behind'
|
|
fi
|
|
(( finished_at - started_at <= 30 )) || fail 'worker exceeded supervisor wall bound'
|
|
|
|
cat "$stdout_file"
|
|
if (( rc == 0 )); then
|
|
cleanup_fixture
|
|
trap - EXIT
|
|
return 0
|
|
fi
|
|
cat "$stderr_file" >&2
|
|
cleanup_fixture
|
|
trap - EXIT
|
|
return "$rc"
|
|
}
|
|
|
|
if [[ "${1-}" == --worker ]]; then
|
|
[[ "${SWR_TEST_WORKER_TOKEN:-}" == '' ]] || fail 'worker token must not arrive from environment'
|
|
(( $# >= 3 )) || fail 'worker dispatch is incomplete'
|
|
worker_main "$2" "$3" "${@:4}"
|
|
exit 0
|
|
fi
|
|
|
|
supervisor_main "$@"
|