3058 lines
122 KiB
Bash
3058 lines
122 KiB
Bash
#!/usr/bin/env bash
|
|
|
|
# Source-safe RED coverage for the A1 payload verifier.
|
|
set -Eeuo pipefail
|
|
|
|
readonly REPOSITORY_ROOT="$(cd -- "$(dirname -- "${BASH_SOURCE[0]}")/../.." && pwd -P)"
|
|
readonly A1_LIBRARY_PATH="${REPOSITORY_ROOT}/scripts/lib/k3s-local-recovery-a1.sh"
|
|
readonly A1_HELPER_PATH="${REPOSITORY_ROOT}/scripts/libexec/k3s-local-recovery-a1-check.sh"
|
|
readonly AUTHORITY_DESIGN_PATH="/home/donghyeon/workspace/docs/platform/plans/2026-08-09-k3s-luks-header-proof-design.md"
|
|
readonly AUTHORITY_PLAN_PATH="/home/donghyeon/workspace/docs/platform/plans/2026-08-09-k3s-luks-header-proof-implementation.md"
|
|
readonly AUTHORITY_DESIGN_SHA256='eb9500f949c97e4099a6ff36edb38ad5cea7c62fdbe4f7bae822b4be3129b4da'
|
|
readonly AUTHORITY_PLAN_SHA256='9f5c5cfdc769177db06ed46b88c4847b54b2d278c56b65e3180b19cbadf72fa6'
|
|
|
|
fail() {
|
|
printf 'TEST FAILURE: %s\n' "$*" >&2
|
|
exit 1
|
|
}
|
|
|
|
# This guard intentionally runs before mktemp, sourcing, or fixture allocation.
|
|
[[ -f "$A1_LIBRARY_PATH" && ! -L "$A1_LIBRARY_PATH" && -r "$A1_LIBRARY_PATH" ]] ||
|
|
fail 'A1 library missing'
|
|
[[ -f "$A1_HELPER_PATH" && ! -L "$A1_HELPER_PATH" &&
|
|
-r "$A1_HELPER_PATH" ]] || fail 'A1 helper missing'
|
|
|
|
[[ "$(/usr/bin/sha256sum -- "$AUTHORITY_DESIGN_PATH")" == \
|
|
"${AUTHORITY_DESIGN_SHA256} ${AUTHORITY_DESIGN_PATH}" ]] ||
|
|
fail 'A1 authority design drift'
|
|
[[ "$(/usr/bin/sha256sum -- "$AUTHORITY_PLAN_PATH")" == \
|
|
"${AUTHORITY_PLAN_SHA256} ${AUTHORITY_PLAN_PATH}" ]] ||
|
|
fail 'A1 authority plan drift'
|
|
|
|
a1_static_source_is_safe() {
|
|
local source_path="${1-}" kind="${2-}"
|
|
(( $# == 2 )) || return 97
|
|
[[ "$kind" == library || "$kind" == helper ]] || return 97
|
|
[[ -f "$source_path" && ! -L "$source_path" ]] || return 1
|
|
/usr/bin/awk -v kind="$kind" '
|
|
function trim(value) {
|
|
sub(/^[[:space:]]+/, "", value)
|
|
sub(/[[:space:]]+$/, "", value)
|
|
return value
|
|
}
|
|
function is_absolute_external(value) {
|
|
return value ~ /\/(usr\/)?(s?bin)\/[A-Za-z0-9._+:-]+/
|
|
}
|
|
function is_command_position_variable(value, prefix) {
|
|
prefix = "(^|(&&|\\|\\||;)[[:space:]]*)(((if|while|until)[[:space:]]+)?(![[:space:]]+)?)"
|
|
return value ~ (prefix "[\"\047]?\\$\\{?[A-Za-z0-9_@*#?-]")
|
|
}
|
|
function is_bare_sensitive_command(value, prefix, names) {
|
|
prefix = "(^|(&&|\\|\\||;)[[:space:]]*)(((if|while|until)[[:space:]]+)?(![[:space:]]+)?)"
|
|
names = "(sudo|findmnt|blockdev|mount|umount|losetup|cryptsetup|sqlite3|readlink|lsblk|stat|sha256sum|find|od|wc|unlink|env|bash)"
|
|
return value ~ (prefix "[\"\047]?" names "([\"\047]?[[:space:]]|[\"\047]?$)")
|
|
}
|
|
function is_allowed_command_token(token, seam) {
|
|
if (token == seam || token == "k3slra1_check_main") return 1
|
|
if (token ~ /^_k3slr(a1|a1h)?_[A-Za-z0-9_]+$/) return 1
|
|
return token ~ /^(:|printf|read|mapfile|return|shift|break|continue|declare|local|readonly|unset|export|trap|set|source|true|false|test)$/
|
|
}
|
|
function is_single_seam_command(value, seam, probe, direct_regex,
|
|
conditional_regex, substitution_regex) {
|
|
probe = value
|
|
sub(/[[:space:]]*;[[:space:]]*then$/, "", probe)
|
|
sub(/[[:space:]]+\|\|[[:space:]]+return[[:space:]]+[0-9]+$/, "", probe)
|
|
if (probe ~ /&&|\|\||;|\|/) return 0
|
|
direct_regex = "^" seam "[[:space:]]+\/(usr\/)?(s?bin)\/"
|
|
conditional_regex = "^(if|while|until)[[:space:]]+(![[:space:]]+)?" seam "[[:space:]]+\/(usr\/)?(s?bin)\/"
|
|
substitution_regex = "\\$\\(" seam "[[:space:]]+\/(usr\/)?(s?bin)\/"
|
|
return (probe ~ direct_regex || probe ~ conditional_regex ||
|
|
probe ~ substitution_regex)
|
|
}
|
|
function process_substitutions_are_closed(value) {
|
|
if (value !~ /[<>]\(/) return 1
|
|
if (kind == "library") {
|
|
if (value ~ /^if[[:space:]]+_k3slra1_command[[:space:]]+\/(usr\/)?(s?bin)\/[A-Za-z0-9._+:-]+[^<>]*2>[[:space:]]+>\(_k3slra1_stderr_marker\);[[:space:]]+then$/)
|
|
return 1
|
|
if (value ~ /^(findmnt|metadata|fd-metadata|mapper-stat|size-stat|wc|od|sha256|readlink|lsblk|blockdev)\)[[:space:]]+mapfile[[:space:]]+-d[[:space:]]+\047\047[[:space:]]+-t[[:space:]]+records[[:space:]]+<[[:space:]]+<\(_k3slra1_(findmnt|metadata|fd_metadata|mapper_stat|size_stat|wc|od|sha256|readlink|lsblk|blockdev)_packet[[:space:]]+"\$argument"\)[[:space:]]+;;$/)
|
|
return 1
|
|
if (value ~ /^mapfile[[:space:]]+-d[[:space:]]+\047\047[[:space:]]+-t[[:space:]]+entries[[:space:]]+<[[:space:]]+<\(_k3slra1_find_mount_children_packet[[:space:]]+"\$mount_root"\)$/)
|
|
return 1
|
|
if (value ~ /^mapfile[[:space:]]+-d[[:space:]]+\047\047[[:space:]]+-t[[:space:]]+inventory[[:space:]]+<[[:space:]]+<\(_k3slra1_find_regular_packet[[:space:]]+"\$pre_root"\)$/)
|
|
return 1
|
|
} else {
|
|
if (value ~ /^if[[:space:]]+_k3slra1h_command[[:space:]]+\/(usr\/)?(s?bin)\/[A-Za-z0-9._+:-]+[^<>]*2>[[:space:]]+>\(_k3slra1h_stderr_marker\);[[:space:]]+then$/)
|
|
return 1
|
|
if (value ~ /^(metadata|readlink)\)[[:space:]]+mapfile[[:space:]]+-d[[:space:]]+\047\047[[:space:]]+-t[[:space:]]+records[[:space:]]+<[[:space:]]+<\(_k3slra1h_(metadata|readlink)_packet[[:space:]]+"\$argument"\)[[:space:]]+;;$/)
|
|
return 1
|
|
}
|
|
return 0
|
|
}
|
|
function commands_are_closed(value, function_name, seam, count, item_index,
|
|
part, token, parts) {
|
|
if (function_name == seam && value == "\"$@\"") return 1
|
|
if (is_single_seam_command(value, seam)) return 1
|
|
if (kind == "library" && function_name == "_k3slra1_sqlite_quick_check") {
|
|
if (value == "trap \047_capture_signal=130; if (( transaction_rc == 0 )); then transaction_rc=130; fi\047 INT") return 1
|
|
if (value == "trap \047_capture_signal=143; if (( transaction_rc == 0 )); then transaction_rc=143; fi\047 TERM") return 1
|
|
if (value == "if (( _capture_signal != 0 && transaction_rc == 0 )); then") return 1
|
|
}
|
|
if (value ~ /^(\[\[|\(\(|case[[:space:]]|for[[:space:]]|select[[:space:]])/) return 1
|
|
count = split(value, parts, /&&|\|\||;/)
|
|
for (item_index = 1; item_index <= count; item_index++) {
|
|
part = trim(parts[item_index])
|
|
sub(/^(if|while|until)[[:space:]]+/, "", part)
|
|
sub(/^![[:space:]]*/, "", part)
|
|
if (part == "" || part ~ /^(then|do|done|fi|else|esac|\{|\})$/) continue
|
|
if (part ~ /^IFS=[^[:space:]]*[[:space:]]+read[[:space:]]/) {
|
|
sub(/^IFS=[^[:space:]]*[[:space:]]+/, "", part)
|
|
}
|
|
if (part ~ /^(\[\[|\(\()/) continue
|
|
if (part == "umask 077") continue
|
|
token = part
|
|
sub(/[[:space:]].*$/, "", token)
|
|
if (token ~ /\)$/) continue
|
|
if (token ~ /^[A-Za-z_][A-Za-z0-9_]*=/) {
|
|
if (part == token) continue
|
|
return 0
|
|
}
|
|
if (!is_allowed_command_token(token, seam)) return 0
|
|
}
|
|
return 1
|
|
}
|
|
function inspect_body(value, function_name, probe, seam, external_ok,
|
|
direct_regex, conditional_regex, substitution_regex) {
|
|
seam = (kind == "library" ? "_k3slra1_command" : "_k3slra1h_command")
|
|
if (value ~ /`/) return 0
|
|
if (!process_substitutions_are_closed(value)) return 0
|
|
if (value ~ /^(readonly|local)[[:space:]]/ && value ~ /\$\(/) return 0
|
|
probe = value
|
|
gsub("\\$\\(" seam "[[:space:]]+", "", probe)
|
|
if (probe ~ /\$\(/) return 0
|
|
if (value ~ /(^|[;[:space:]])(eval|builtin|command|exec)[[:space:]]/) return 0
|
|
if (value ~ /(^|[;[:space:]])source[[:space:]]/) {
|
|
if (kind != "helper" ||
|
|
(value != "source \"$shared_library\" || return 1" &&
|
|
value != "source \"$a1_library\" || return 1")) return 0
|
|
}
|
|
if (value == "source \"$shared_library\" || return 1") shared_source++
|
|
if (value == "source \"$a1_library\" || return 1") a1_source++
|
|
if (value == "_k3slr_load_contract \"$contract_file\" || return 1") contract_call++
|
|
if (function_name == seam && value == "\"$@\"") return 1
|
|
if (is_command_position_variable(value) || is_bare_sensitive_command(value)) return 0
|
|
if (!commands_are_closed(value, function_name, seam)) return 0
|
|
if (is_absolute_external(value)) {
|
|
if (value ~ /(&&|\|\||;|\|)[[:space:]]*[\"\047]?\/(usr\/)?(s?bin)\//)
|
|
return 0
|
|
direct_regex = "^" seam "[[:space:]]+\/(usr\/)?(s?bin)\/[A-Za-z0-9._+:-]+"
|
|
conditional_regex = "^(if|while|until)[[:space:]]+(![[:space:]]+)?" seam "[[:space:]]+\/(usr\/)?(s?bin)\/[A-Za-z0-9._+:-]+"
|
|
substitution_regex = "\\$\\(" seam "[[:space:]]+\/(usr\/)?(s?bin)\/[A-Za-z0-9._+:-]+"
|
|
external_ok = (value ~ direct_regex || value ~ conditional_regex ||
|
|
value ~ substitution_regex)
|
|
if (!external_ok) return 0
|
|
}
|
|
if (value ~ /_k3slra1_command[[:space:]]+\/usr\/bin\/sudo([[:space:]]|$)/)
|
|
sudo_count++
|
|
return 1
|
|
}
|
|
{
|
|
raw = $0
|
|
value = trim(raw)
|
|
if (value == "" || value ~ /^#/) next
|
|
if (value ~ /\/home\/donghyeon\/workspace/) bad = 1
|
|
if (value ~ /lib\/k3s-local-recovery\.sh/) shared_path++
|
|
if (value ~ /lib\/k3s-local-recovery-a1\.sh/) a1_path++
|
|
if (value ~ /contract\/local-recovery\.env/) contract_path++
|
|
if (in_function) {
|
|
if ((!subshell_function && raw == "}") ||
|
|
(subshell_function && raw == ")")) {
|
|
in_function = 0
|
|
function_name = ""
|
|
subshell_function = 0
|
|
next
|
|
}
|
|
if (!inspect_body(value, function_name)) bad = 1
|
|
next
|
|
}
|
|
if (kind == "library" &&
|
|
value == "_k3slra1_sqlite_quick_check() (") {
|
|
function_name = "_k3slra1_sqlite_quick_check"
|
|
subshell_function = 1
|
|
in_function = 1
|
|
next
|
|
}
|
|
if (value ~ /^[A-Za-z_][A-Za-z0-9_]*\(\)[[:space:]]*\{$/) {
|
|
function_name = value
|
|
sub(/\(\)[[:space:]]*\{$/, "", function_name)
|
|
in_function = 1
|
|
next
|
|
}
|
|
if (value ~ /^readonly[[:space:]]+/) {
|
|
if (is_absolute_external(value) ||
|
|
value ~ /=(\047)?(sudo|findmnt|blockdev|mount|umount|losetup|cryptsetup|sqlite3|readlink|lsblk|stat|sha256sum|find|od|wc|unlink|env|bash)(\047)?$/) {
|
|
bad = 1
|
|
next
|
|
}
|
|
if (value ~ /^readonly[[:space:]]+[A-Za-z_][A-Za-z0-9_]*=(\047[^\047]*\047|[A-Za-z0-9_.:+\/-]+)$/) next
|
|
}
|
|
if (kind == "helper") {
|
|
if (guard == 0 && value == "if [[ \"${BASH_SOURCE[0]}\" == \"$0\" ]]; then") { guard = 1; next }
|
|
if (guard == 1 && value == "_k3slra1h_direct_signal_rc=0") { guard = 2; next }
|
|
if (guard == 2 && value == "_k3slra1h_direct_rc=0") { guard = 3; next }
|
|
if (guard == 3 && value == "trap \047_k3slra1h_direct_signal_rc=130; if (( _k3slra1h_direct_rc == 0 )); then _k3slra1h_direct_rc=130; fi\047 INT") { guard = 4; next }
|
|
if (guard == 4 && value == "trap \047_k3slra1h_direct_signal_rc=143; if (( _k3slra1h_direct_rc == 0 )); then _k3slra1h_direct_rc=143; fi\047 TERM") { guard = 5; next }
|
|
if (guard == 5 && value == "if k3slra1_check_main \"$@\"; then") { guard = 6; next }
|
|
if (guard == 6 && value == "_k3slra1h_direct_rc=0") { guard = 7; next }
|
|
if (guard == 7 && value == "else") { guard = 8; next }
|
|
if (guard == 8 && value == "_k3slra1h_direct_rc=$?") { guard = 9; next }
|
|
if (guard == 9 && value == "fi") { guard = 10; next }
|
|
if (guard == 10 && value == "if (( _k3slra1h_direct_signal_rc != 0 && _k3slra1h_direct_rc == 0 )); then") { guard = 11; next }
|
|
if (guard == 11 && value == "_k3slra1h_direct_rc=$_k3slra1h_direct_signal_rc") { guard = 12; next }
|
|
if (guard == 12 && value == "fi") { guard = 13; next }
|
|
if (guard == 13 && value == "exit \"$_k3slra1h_direct_rc\"") { guard = 14; next }
|
|
if (guard == 14 && value == "fi") { guard = 15; next }
|
|
}
|
|
bad = 1
|
|
}
|
|
END {
|
|
if (in_function || bad) exit 1
|
|
if (kind == "library" && (guard != 0 || sudo_count != 1)) exit 1
|
|
if (kind == "helper" && (guard != 15 || shared_source != 1 ||
|
|
a1_source != 1 || contract_call != 1 || shared_path < 1 ||
|
|
a1_path < 1 || contract_path < 1)) exit 1
|
|
}
|
|
' "$source_path"
|
|
}
|
|
|
|
a1_production_static_preflight() {
|
|
# No production source operation occurs until both files pass this scanner.
|
|
a1_static_source_is_safe "$A1_LIBRARY_PATH" library || return 41
|
|
a1_static_source_is_safe "$A1_HELPER_PATH" helper || return 42
|
|
}
|
|
|
|
# No production API or source operation is allowed before this conservative
|
|
# external-call preflight succeeds.
|
|
preflight_rc=0
|
|
if a1_production_static_preflight; then
|
|
:
|
|
else
|
|
preflight_rc=$?
|
|
fi
|
|
case "$preflight_rc" in
|
|
0) ;;
|
|
41) fail 'A1 library static preflight failed' ;;
|
|
42) fail 'A1 helper static preflight failed' ;;
|
|
*) fail 'A1 production static preflight failed' ;;
|
|
esac
|
|
|
|
# shellcheck source=/dev/null
|
|
source "$A1_LIBRARY_PATH"
|
|
|
|
require_function() {
|
|
local name="${1-}"
|
|
(( $# == 1 )) || fail 'invalid require_function invocation'
|
|
declare -F "$name" >/dev/null || fail "A1 API missing: ${name}"
|
|
}
|
|
|
|
for required_api in \
|
|
_k3slra1_verify_payload \
|
|
_k3slra1_verify_payload_root \
|
|
_k3slra1_select_pre_bundle \
|
|
_k3slra1_verify_manifest \
|
|
_k3slra1_capture_manifest_hex \
|
|
_k3slra1_select_sqlite_layout \
|
|
_k3slra1_sqlite_quick_check; do
|
|
require_function "$required_api"
|
|
done
|
|
|
|
readonly A1_TARGET_COUNT=80
|
|
readonly A1_TARGET_BYTES=22857829
|
|
readonly A1_ANCHOR_BYTES=22851685
|
|
readonly A1_BUNDLE_NAME='k3s-secrets-encryption-20260808T000000Z'
|
|
readonly A1_MAIN_MAPPER='/dev/mapper/k3s-recovery'
|
|
readonly A1_PROOF_MAPPER='/dev/mapper/k3s-recovery-proof'
|
|
readonly A1_MAIN_CANONICAL_MAPPER='/dev/dm-8'
|
|
readonly A1_PROOF_CANONICAL_MAPPER='/dev/dm-7'
|
|
readonly A1_MAIN_MAJOR_MINOR='253:8'
|
|
readonly A1_PROOF_MAJOR_MINOR='253:7'
|
|
readonly A1_MOUNT_OPTIONS='ro,nosuid,nodev,noexec,relatime,noload'
|
|
|
|
fixture_root="$(mktemp -d "${TMPDIR:-/tmp}/k3s-local-recovery-a1-test.XXXXXXXX")"
|
|
cleanup() {
|
|
case "$fixture_root" in
|
|
/tmp/k3s-local-recovery-a1-test.*|"${TMPDIR:-/tmp}"/k3s-local-recovery-a1-test.*)
|
|
rm -rf -- "$fixture_root"
|
|
;;
|
|
*)
|
|
printf 'TEST CLEANUP FAILURE: unexpected fixture root\n' >&2
|
|
return 1
|
|
;;
|
|
esac
|
|
}
|
|
trap cleanup EXIT
|
|
|
|
assert_index=0
|
|
assert_succeeds() {
|
|
local description="${1-}" stdout_file stderr_file rc=0
|
|
shift || fail 'assert_succeeds requires a description'
|
|
(( assert_index += 1 ))
|
|
stdout_file="${fixture_root}/assert-${assert_index}.stdout"
|
|
stderr_file="${fixture_root}/assert-${assert_index}.stderr"
|
|
if "$@" >"$stdout_file" 2>"$stderr_file"; then
|
|
rc=0
|
|
else
|
|
rc=$?
|
|
fi
|
|
(( rc == 0 )) || fail "$description returned rc ${rc}"
|
|
[[ ! -s "$stdout_file" && ! -s "$stderr_file" ]] ||
|
|
fail "$description leaked public output"
|
|
rm -- "$stdout_file" "$stderr_file"
|
|
}
|
|
|
|
assert_fails() {
|
|
local description="${1-}" stdout_file stderr_file rc=0
|
|
shift || fail 'assert_fails requires a description'
|
|
(( assert_index += 1 ))
|
|
stdout_file="${fixture_root}/assert-${assert_index}.stdout"
|
|
stderr_file="${fixture_root}/assert-${assert_index}.stderr"
|
|
if "$@" >"$stdout_file" 2>"$stderr_file"; then
|
|
rc=0
|
|
else
|
|
rc=$?
|
|
fi
|
|
(( rc != 0 )) || fail "$description unexpectedly succeeded"
|
|
(( rc != 97 )) || fail "$description returned fixture/model rc 97"
|
|
[[ ! -s "$stdout_file" && ! -s "$stderr_file" ]] ||
|
|
fail "$description leaked public output"
|
|
rm -- "$stdout_file" "$stderr_file"
|
|
}
|
|
|
|
fixture_path_is_owned() {
|
|
local path="${1-}"
|
|
(( $# == 1 )) || return 97
|
|
case "$path" in
|
|
"$case_root"|"$case_root"/*) return 0 ;;
|
|
*) return 97 ;;
|
|
esac
|
|
}
|
|
|
|
expected_mapper_for_role() {
|
|
local role="${1-}"
|
|
(( $# == 1 )) || return 97
|
|
case "$role" in
|
|
proof) printf '%s\n' "$A1_PROOF_MAPPER" ;;
|
|
original-baseline|original-final) printf '%s\n' "$A1_MAIN_MAPPER" ;;
|
|
*) return 97 ;;
|
|
esac
|
|
}
|
|
|
|
expected_canonical_mapper_for_role() {
|
|
local role="${1-}"
|
|
(( $# == 1 )) || return 97
|
|
case "$role" in
|
|
proof) printf '%s\n' "$A1_PROOF_CANONICAL_MAPPER" ;;
|
|
original-baseline|original-final) printf '%s\n' "$A1_MAIN_CANONICAL_MAPPER" ;;
|
|
*) return 97 ;;
|
|
esac
|
|
}
|
|
|
|
expected_major_minor_for_role() {
|
|
local role="${1-}"
|
|
(( $# == 1 )) || return 97
|
|
case "$role" in
|
|
proof) printf '%s\n' "$A1_PROOF_MAJOR_MINOR" ;;
|
|
original-baseline|original-final) printf '%s\n' "$A1_MAIN_MAJOR_MINOR" ;;
|
|
*) return 97 ;;
|
|
esac
|
|
}
|
|
|
|
canonical_mapper_for_alias() {
|
|
local mapper_alias="${1-}"
|
|
(( $# == 1 )) || return 97
|
|
if [[ "$current_case" == canonical_node_wrong_namespace ]]; then
|
|
case "$mapper_alias" in
|
|
"$A1_PROOF_MAPPER"|"$A1_MAIN_MAPPER") printf '/dev/loop7\n'; return 0 ;;
|
|
esac
|
|
fi
|
|
case "$mapper_alias" in
|
|
"$A1_PROOF_MAPPER") printf '%s\n' "$A1_PROOF_CANONICAL_MAPPER" ;;
|
|
"$A1_MAIN_MAPPER") printf '%s\n' "$A1_MAIN_CANONICAL_MAPPER" ;;
|
|
/dev/mapper/a1-source-mismatch) printf '/dev/dm-99\n' ;;
|
|
/dev/mapper/not-owned) printf '/dev/dm-98\n' ;;
|
|
*) return 97 ;;
|
|
esac
|
|
}
|
|
|
|
declare -a fixture_targets=()
|
|
case_root=''
|
|
mount_root=''
|
|
bundle_root=''
|
|
pre_root=''
|
|
capture_root=''
|
|
manifest_path=''
|
|
anchor_path=''
|
|
fixture_database_relative=''
|
|
current_case=''
|
|
current_role=''
|
|
findmnt_calls=0
|
|
readlink_calls=0
|
|
lsblk_calls=0
|
|
blockdev_calls=0
|
|
sqlite_calls=0
|
|
sqlite_unlink_stdout_calls=0
|
|
sqlite_unlink_stderr_calls=0
|
|
command_trace=''
|
|
hostile_capture_surface=''
|
|
hostile_capture_mode=''
|
|
hostile_capture_file=''
|
|
hostile_capture_valid=''
|
|
hostile_capture_family=''
|
|
hostile_violation_trace=''
|
|
helper_modeled_target=''
|
|
helper_modeled_mutation=''
|
|
readonly A1_METADATA_FORMAT='%d:%i|%h|%s|%y|%F|%u:%g|%a'
|
|
|
|
trace_stage() {
|
|
local stage="${1-}"
|
|
(( $# == 1 )) || return 97
|
|
[[ "$stage" =~ ^[a-z0-9-]+$ && -n "$command_trace" ]] || return 97
|
|
printf '%s\n' "$stage" >>"$command_trace"
|
|
}
|
|
|
|
trace_count() {
|
|
local stage="${1-}" count
|
|
(( $# == 1 )) || return 97
|
|
count="$(/usr/bin/awk -v wanted="$stage" '$0 == wanted { count++ } END { print count + 0 }' \
|
|
"$command_trace")" || return 97
|
|
printf '%s\n' "$count"
|
|
}
|
|
|
|
emit_hostile_capture() {
|
|
local valid="${1-}"
|
|
(( $# == 1 )) || return 97
|
|
case "$hostile_capture_mode" in
|
|
stdout-nonzero)
|
|
printf '%s\n' "$valid"
|
|
return 23
|
|
;;
|
|
raw-nul)
|
|
printf '%s\0\n' "$valid"
|
|
;;
|
|
extra-trailing-lf)
|
|
printf '%s\n\n' "$valid"
|
|
;;
|
|
nonempty-stderr)
|
|
printf 'hostile-capture-stderr\n' >&2
|
|
printf '%s\n' "$valid"
|
|
;;
|
|
*) return 97 ;;
|
|
esac
|
|
}
|
|
|
|
build_manifest() {
|
|
local target
|
|
: >"$manifest_path"
|
|
(
|
|
cd -- "$pre_root"
|
|
for target in "${fixture_targets[@]}"; do
|
|
/usr/bin/sha256sum -- "$target"
|
|
done
|
|
) >"$manifest_path"
|
|
}
|
|
|
|
build_payload() {
|
|
local layout="${1-}" index suffix database_relative
|
|
(( $# == 1 )) || return 97
|
|
case "$layout" in
|
|
canonical) database_relative='./datastore/state.db' ;;
|
|
legacy) database_relative='./rootfs/var/lib/rancher/k3s/server/db/state.db' ;;
|
|
*) return 97 ;;
|
|
esac
|
|
|
|
mount_root="${case_root}/mount"
|
|
bundle_root="${mount_root}/${A1_BUNDLE_NAME}"
|
|
pre_root="${bundle_root}/pre"
|
|
capture_root="${case_root}/capture"
|
|
manifest_path="${pre_root}/verification.manifest"
|
|
anchor_path="${pre_root}/payload/anchor.bin"
|
|
fixture_database_relative="$database_relative"
|
|
mkdir -p -- \
|
|
"${pre_root}/$(dirname -- "${database_relative#./}")" \
|
|
"${pre_root}/payload" \
|
|
"${capture_root}/home"
|
|
chmod 0700 -- "$mount_root" "$bundle_root" "$pre_root" "$capture_root" "${capture_root}/home"
|
|
|
|
fixture_targets=(
|
|
"$database_relative"
|
|
"${database_relative}-wal"
|
|
"${database_relative}-shm"
|
|
'./payload/anchor.bin'
|
|
'./payload/plus+allowed'
|
|
)
|
|
/usr/bin/truncate --size=4096 -- "${pre_root}/${database_relative#./}"
|
|
/usr/bin/truncate --size=1024 -- "${pre_root}/${database_relative#./}-wal"
|
|
/usr/bin/truncate --size=1024 -- "${pre_root}/${database_relative#./}-shm"
|
|
/usr/bin/truncate --size="$A1_ANCHOR_BYTES" -- "$anchor_path"
|
|
: >"${pre_root}/payload/plus+allowed"
|
|
for (( index = 1; index <= 75; index += 1 )); do
|
|
printf -v suffix '%02d' "$index"
|
|
: >"${pre_root}/payload/dummy-${suffix}"
|
|
fixture_targets+=("./payload/dummy-${suffix}")
|
|
done
|
|
(( ${#fixture_targets[@]} == A1_TARGET_COUNT )) || return 97
|
|
build_manifest
|
|
(
|
|
cd -- "$pre_root"
|
|
chmod 0600 -- "${fixture_targets[@]}" verification.manifest
|
|
)
|
|
/usr/bin/find "$mount_root" -type d -exec chmod 0700 -- '{}' +
|
|
}
|
|
|
|
model_test_owned_stat() {
|
|
local requested_format="${1-}" requested_path="${2-}" physical_path
|
|
local actual_dev actual_inode actual_nlink actual_size actual_mtime actual_type actual_mode
|
|
local modeled_dev=4242 modeled_inode modeled_nlink modeled_uid=0 modeled_gid=0 modeled_mode
|
|
local modeled_type
|
|
local sqlite_trace_count=0
|
|
(( $# == 2 )) || return 97
|
|
physical_path="$requested_path"
|
|
case "$requested_path" in
|
|
/proc/[0-9]*/fd/[0-9]*)
|
|
physical_path="$(/usr/bin/readlink --canonicalize-existing -- "$requested_path")" || return 97
|
|
;;
|
|
esac
|
|
fixture_path_is_owned "$physical_path" || return 97
|
|
IFS='|' read -r actual_dev actual_inode actual_nlink actual_size actual_mtime actual_type actual_mode < <(
|
|
/usr/bin/stat --format='%d|%i|%h|%s|%y|%F|%a' -- "$physical_path"
|
|
) || return 97
|
|
modeled_inode="$actual_inode"
|
|
modeled_nlink="$actual_nlink"
|
|
modeled_mode="$actual_mode"
|
|
modeled_type="$actual_type"
|
|
[[ "$current_case" == bundle_wrong_owner && "$physical_path" == "$bundle_root" ]] && modeled_uid=1000
|
|
[[ "$current_case" == manifest_wrong_mode && "$physical_path" == "$manifest_path" ]] && modeled_mode=644
|
|
[[ "$current_case" == payload_wrong_device && "$physical_path" == "$anchor_path" ]] && modeled_dev=4243
|
|
if [[ "$current_case" == heterogeneous_target_metadata ]]; then
|
|
case "$physical_path" in
|
|
"${pre_root}/payload/dummy-01")
|
|
modeled_uid=1000
|
|
modeled_gid=1001
|
|
modeled_mode=640
|
|
;;
|
|
"${pre_root}/payload/dummy-02")
|
|
modeled_uid=65534
|
|
modeled_gid=65534
|
|
modeled_mode=400
|
|
;;
|
|
"${pre_root}/payload/dummy-03")
|
|
modeled_uid=0
|
|
modeled_gid=44
|
|
modeled_mode=644
|
|
;;
|
|
esac
|
|
fi
|
|
if [[ "$current_case" == manifest_duplicate_inode &&
|
|
"$physical_path" == "${pre_root}/payload/dummy-05" ]]; then
|
|
modeled_inode="$(/usr/bin/stat --format='%i' -- "${pre_root}/payload/dummy-04")" || return 97
|
|
modeled_nlink=1
|
|
fi
|
|
if [[ "$current_case" == sqlite_capture_wrong_owner &&
|
|
"$physical_path" == "${capture_root}/sqlite.stdout" ]]; then
|
|
modeled_uid=1000
|
|
fi
|
|
[[ "$current_case" == sqlite_capture_wrong_mode &&
|
|
"$physical_path" == "${capture_root}/sqlite.stdout" ]] && modeled_mode=644
|
|
if [[ "$current_case" == sqlite_wal_drift_after_child &&
|
|
"$physical_path" == "${pre_root}/${fixture_database_relative#./}-wal" ]]; then
|
|
sqlite_trace_count="$(trace_count sqlite)" || return 97
|
|
if (( sqlite_trace_count >= 1 )); then
|
|
actual_mtime='2099-01-01 00:00:00.000000000 +0000'
|
|
fi
|
|
fi
|
|
if [[ "$current_case" == sqlite_capture_parent_drift &&
|
|
"$requested_path" == "$capture_root" ]]; then
|
|
sqlite_trace_count="$(trace_count sqlite)" || return 97
|
|
if (( sqlite_trace_count >= 1 )); then
|
|
modeled_inode="$((modeled_inode + 1000000))"
|
|
fi
|
|
fi
|
|
case "$current_case" in
|
|
sqlite_layout_db_identity_drift)
|
|
if [[ "$physical_path" == "${pre_root}/${fixture_database_relative#./}" ]]; then
|
|
trace_stage layout-db-stat || return 97
|
|
modeled_inode="$((modeled_inode + 2000000))"
|
|
fi
|
|
;;
|
|
sqlite_layout_wal_identity_drift)
|
|
if [[ "$physical_path" == "${pre_root}/${fixture_database_relative#./}-wal" ]]; then
|
|
trace_stage layout-wal-stat || return 97
|
|
modeled_inode="$((modeled_inode + 2000000))"
|
|
fi
|
|
;;
|
|
sqlite_layout_shm_identity_drift)
|
|
if [[ "$physical_path" == "${pre_root}/${fixture_database_relative#./}-shm" ]]; then
|
|
trace_stage layout-shm-stat || return 97
|
|
modeled_inode="$((modeled_inode + 2000000))"
|
|
fi
|
|
;;
|
|
esac
|
|
if [[ -n "$helper_modeled_target" && "$physical_path" == "$helper_modeled_target" ]]; then
|
|
case "$helper_modeled_mutation" in
|
|
wrong-owner) modeled_uid=1000 ;;
|
|
wrong-mode) modeled_mode=755 ;;
|
|
wrong-device) modeled_dev=4243 ;;
|
|
nlink-two) modeled_nlink=2 ;;
|
|
wrong-type) modeled_type='directory' ;;
|
|
'') ;;
|
|
*) return 97 ;;
|
|
esac
|
|
fi
|
|
case "$current_case" in
|
|
helper-lib-wrong-owner)
|
|
[[ "$physical_path" != "${capture_root}/lib" ]] || modeled_uid=1000
|
|
;;
|
|
helper-libexec-wrong-owner)
|
|
[[ "$physical_path" != "${capture_root}/libexec" ]] || modeled_uid=1000
|
|
;;
|
|
helper-contract-wrong-owner)
|
|
[[ "$physical_path" != "${capture_root}/contract" ]] || modeled_uid=1000
|
|
;;
|
|
helper-lib-wrong-mode)
|
|
[[ "$physical_path" != "${capture_root}/lib" ]] || modeled_mode=755
|
|
;;
|
|
helper-libexec-wrong-mode)
|
|
[[ "$physical_path" != "${capture_root}/libexec" ]] || modeled_mode=755
|
|
;;
|
|
helper-contract-wrong-mode)
|
|
[[ "$physical_path" != "${capture_root}/contract" ]] || modeled_mode=755
|
|
;;
|
|
esac
|
|
|
|
case "$requested_format" in
|
|
"$A1_METADATA_FORMAT")
|
|
printf '%s:%s|%s|%s|%s|%s|%s:%s|%s\n' "$modeled_dev" "$modeled_inode" \
|
|
"$modeled_nlink" "$actual_size" "$actual_mtime" "$modeled_type" \
|
|
"$modeled_uid" "$modeled_gid" "$modeled_mode"
|
|
;;
|
|
'%d:%i') printf '%s:%s\n' "$modeled_dev" "$modeled_inode" ;;
|
|
'%i') printf '%s\n' "$modeled_inode" ;;
|
|
'%h') printf '%s\n' "$modeled_nlink" ;;
|
|
'%s') printf '%s\n' "$actual_size" ;;
|
|
'%y') printf '%s\n' "$actual_mtime" ;;
|
|
'%F') printf '%s\n' "$modeled_type" ;;
|
|
'%u:%g') printf '%s:%s\n' "$modeled_uid" "$modeled_gid" ;;
|
|
'%a') printf '%s\n' "$modeled_mode" ;;
|
|
*) return 97 ;;
|
|
esac
|
|
}
|
|
|
|
replace_manifest_path() {
|
|
local old_path="${1-}" new_path="${2-}" line record_path temporary
|
|
(( $# == 2 )) || return 97
|
|
temporary="${manifest_path}.rewrite"
|
|
: >"$temporary"
|
|
while IFS= read -r line; do
|
|
record_path="${line:66}"
|
|
if [[ "$record_path" == "$old_path" ]]; then
|
|
printf '%s %s\n' "${line:0:64}" "$new_path" >>"$temporary"
|
|
else
|
|
printf '%s\n' "$line" >>"$temporary"
|
|
fi
|
|
done <"$manifest_path"
|
|
mv -- "$temporary" "$manifest_path"
|
|
}
|
|
|
|
replace_fixture_target() {
|
|
local old_path="${1-}" new_path="${2-}" index
|
|
(( $# == 2 )) || return 97
|
|
for index in "${!fixture_targets[@]}"; do
|
|
if [[ "${fixture_targets[$index]}" == "$old_path" ]]; then
|
|
fixture_targets[$index]="$new_path"
|
|
return 0
|
|
fi
|
|
done
|
|
return 97
|
|
}
|
|
|
|
mutate_fixture() {
|
|
local mutation="${1-}" first_line first_hash first_path upper_hash
|
|
local long_a long_b long_tail long_path old_wal replacement other_database
|
|
(( $# == 1 )) || return 97
|
|
case "$mutation" in
|
|
canonical_pass|legacy_pass|original_baseline_pass|manifest_plus_path_pass|\
|
|
mount_missing_noload|mapping_not_readonly|\
|
|
mount_wrong_source|mount_wrong_target|mount_wrong_fstype|mount_wrong_major_minor|\
|
|
mount_missing_vfs_ro|mount_forbidden_vfs_rw|mount_evidence_drift|\
|
|
mapper_canonical_mismatch|source_expected_canonical_mismatch|mapper_not_block|mapper_major_minor_mismatch|\
|
|
mapping_evidence_drift|mount_third_evidence_drift|mapping_third_evidence_drift|\
|
|
payload_write_attempt|manifest_drift_after_sqlite|sqlite_wal_drift_after_child|\
|
|
sqlite_quick_check_extra_line|sqlite_quick_check_error|\
|
|
sqlite_quick_check_stderr|sqlite_quick_check_nul|\
|
|
sqlite_capture_path_swap|sqlite_capture_parent_drift|\
|
|
sqlite_capture_wrong_owner|sqlite_capture_wrong_mode|sqlite_capture_cleanup_failure|\
|
|
sqlite_capture_int_cleanup|sqlite_capture_term_cleanup|\
|
|
bundle_wrong_owner|manifest_wrong_mode|payload_wrong_device|manifest_duplicate_inode|\
|
|
proof_mapper_swap|baseline_mapper_swap|final_mapper_swap|\
|
|
mount_evidence_multiline|mount_evidence_cr|mount_evidence_nul|mount_evidence_stderr|\
|
|
findmnt_valid_record_stderr|readlink_valid_record_stderr|lsblk_valid_record_stderr|\
|
|
blockdev_multiline|blockdev_cr|blockdev_nul|blockdev_stderr|blockdev_valid_record_stderr)
|
|
;;
|
|
unrelated_lost_found_pass)
|
|
mkdir -- "${mount_root}/lost+found"
|
|
;;
|
|
preexisting_unmanifested_file)
|
|
: >"${pre_root}/payload/preexisting-unmanifested"
|
|
;;
|
|
manifest_path_512_bytes_pass)
|
|
printf -v long_a '%0250d' 0
|
|
printf -v long_b '%0250d' 0
|
|
long_a="${long_a//0/a}"
|
|
long_b="${long_b//0/b}"
|
|
long_tail='12345678'
|
|
long_path="./${long_a}/${long_b}/${long_tail}"
|
|
[[ ${#long_path} -eq 512 ]] || return 97
|
|
mkdir -p -- "${pre_root}/${long_a}/${long_b}"
|
|
mv -- "${pre_root}/payload/plus+allowed" "${pre_root}/${long_path#./}"
|
|
replace_fixture_target './payload/plus+allowed' "$long_path"
|
|
build_manifest
|
|
;;
|
|
bundle_none)
|
|
rm -rf -- "$bundle_root"
|
|
;;
|
|
bundle_two)
|
|
cp -a -- "$bundle_root" "${mount_root}/k3s-secrets-encryption-20260808T000001Z"
|
|
;;
|
|
matching_bundle_symlink)
|
|
ln -s -- "$A1_BUNDLE_NAME" \
|
|
"${mount_root}/k3s-secrets-encryption-20260808T000001Z"
|
|
;;
|
|
bundle_bad_name)
|
|
mv -- "$bundle_root" "${mount_root}/k3s-secrets-encryption-20260808t000000z"
|
|
bundle_root="${mount_root}/k3s-secrets-encryption-20260808t000000z"
|
|
pre_root="${bundle_root}/pre"
|
|
manifest_path="${pre_root}/verification.manifest"
|
|
anchor_path="${pre_root}/payload/anchor.bin"
|
|
;;
|
|
manifest_missing)
|
|
rm -- "$manifest_path"
|
|
;;
|
|
manifest_no_final_lf)
|
|
/usr/bin/truncate --size=-1 -- "$manifest_path"
|
|
;;
|
|
manifest_cr)
|
|
IFS= read -r first_line <"$manifest_path"
|
|
{
|
|
printf '%s\r\n' "$first_line"
|
|
tail -n +2 -- "$manifest_path"
|
|
} >"${manifest_path}.rewrite"
|
|
mv -- "${manifest_path}.rewrite" "$manifest_path"
|
|
;;
|
|
manifest_uppercase_hash)
|
|
IFS= read -r first_line <"$manifest_path"
|
|
first_hash="${first_line:0:64}"
|
|
first_path="${first_line:66}"
|
|
upper_hash="${first_hash^^}"
|
|
{
|
|
printf '%s %s\n' "$upper_hash" "$first_path"
|
|
tail -n +2 -- "$manifest_path"
|
|
} >"${manifest_path}.rewrite"
|
|
mv -- "${manifest_path}.rewrite" "$manifest_path"
|
|
;;
|
|
manifest_one_space_separator)
|
|
IFS= read -r first_line <"$manifest_path"
|
|
{
|
|
printf '%s %s\n' "${first_line:0:64}" "${first_line:66}"
|
|
tail -n +2 -- "$manifest_path"
|
|
} >"${manifest_path}.rewrite"
|
|
mv -- "${manifest_path}.rewrite" "$manifest_path"
|
|
;;
|
|
manifest_tab_separator)
|
|
IFS= read -r first_line <"$manifest_path"
|
|
{
|
|
printf '%s\t%s\n' "${first_line:0:64}" "${first_line:66}"
|
|
tail -n +2 -- "$manifest_path"
|
|
} >"${manifest_path}.rewrite"
|
|
mv -- "${manifest_path}.rewrite" "$manifest_path"
|
|
;;
|
|
manifest_parent_escape)
|
|
replace_manifest_path './payload/anchor.bin' './payload/../payload/anchor.bin'
|
|
;;
|
|
manifest_absolute_path)
|
|
replace_manifest_path './payload/anchor.bin' '/payload/anchor.bin'
|
|
;;
|
|
manifest_double_slash)
|
|
replace_manifest_path './payload/anchor.bin' './payload//anchor.bin'
|
|
;;
|
|
manifest_dot_segment)
|
|
replace_manifest_path './payload/anchor.bin' './payload/./anchor.bin'
|
|
;;
|
|
manifest_trailing_slash)
|
|
replace_manifest_path './payload/anchor.bin' './payload/anchor.bin/'
|
|
;;
|
|
manifest_invalid_character)
|
|
replace_manifest_path './payload/anchor.bin' './payload/anchor:bin'
|
|
;;
|
|
manifest_path_513_bytes)
|
|
printf -v long_a '%0250d' 0
|
|
printf -v long_b '%0250d' 0
|
|
long_a="${long_a//0/a}"
|
|
long_b="${long_b//0/b}"
|
|
long_tail='123456789'
|
|
long_path="./${long_a}/${long_b}/${long_tail}"
|
|
[[ ${#long_path} -eq 513 ]] || return 97
|
|
mkdir -p -- "${pre_root}/${long_a}/${long_b}"
|
|
mv -- "${pre_root}/payload/plus+allowed" "${pre_root}/${long_path#./}"
|
|
replace_fixture_target './payload/plus+allowed' "$long_path"
|
|
build_manifest
|
|
;;
|
|
manifest_too_large|manifest_65537_bytes)
|
|
/usr/bin/truncate --size=65537 -- "$manifest_path"
|
|
;;
|
|
manifest_nul)
|
|
/usr/bin/truncate --size=-1 -- "$manifest_path"
|
|
printf '\0\n' >>"$manifest_path"
|
|
;;
|
|
manifest_duplicate_path)
|
|
replace_manifest_path './payload/dummy-02' './payload/dummy-01'
|
|
;;
|
|
manifest_self_target)
|
|
replace_manifest_path './payload/dummy-01' './verification.manifest'
|
|
;;
|
|
manifest_hash_mismatch)
|
|
printf x >>"$anchor_path"
|
|
;;
|
|
manifest_target_symlink)
|
|
rm -- "${pre_root}/payload/dummy-01"
|
|
ln -s -- plus+allowed "${pre_root}/payload/dummy-01"
|
|
;;
|
|
manifest_target_hardlink_alias)
|
|
rm -- "${pre_root}/payload/dummy-02"
|
|
ln -- "${pre_root}/payload/dummy-01" "${pre_root}/payload/dummy-02"
|
|
;;
|
|
manifest_target_nlink_two)
|
|
ln -- "${pre_root}/payload/dummy-03" "${bundle_root}/unlisted-hardlink"
|
|
;;
|
|
target_count_79)
|
|
head -n 79 -- "$manifest_path" >"${manifest_path}.rewrite"
|
|
mv -- "${manifest_path}.rewrite" "$manifest_path"
|
|
;;
|
|
target_count_81)
|
|
: >"${pre_root}/payload/dummy-81"
|
|
fixture_targets+=('./payload/dummy-81')
|
|
build_manifest
|
|
;;
|
|
target_bytes_22857828)
|
|
/usr/bin/truncate --size=$((A1_ANCHOR_BYTES - 1)) -- "$anchor_path"
|
|
build_manifest
|
|
;;
|
|
target_bytes_22857830)
|
|
/usr/bin/truncate --size=$((A1_ANCHOR_BYTES + 1)) -- "$anchor_path"
|
|
build_manifest
|
|
;;
|
|
sqlite_mixed_layout)
|
|
if [[ "$current_case" == legacy* ]]; then
|
|
other_database="${pre_root}/datastore/state.db"
|
|
else
|
|
other_database="${pre_root}/rootfs/var/lib/rancher/k3s/server/db/state.db"
|
|
fi
|
|
mkdir -p -- "$(dirname -- "$other_database")"
|
|
: >"$other_database"
|
|
: >"${other_database}-wal"
|
|
: >"${other_database}-shm"
|
|
;;
|
|
sqlite_alternate_partial_layout)
|
|
if [[ "$fixture_database_relative" == './datastore/state.db' ]]; then
|
|
other_database="${pre_root}/rootfs/var/lib/rancher/k3s/server/db/state.db"
|
|
else
|
|
other_database="${pre_root}/datastore/state.db"
|
|
fi
|
|
mkdir -p -- "$(dirname -- "$other_database")"
|
|
: >"$other_database"
|
|
;;
|
|
sqlite_missing_wal)
|
|
old_wal="${fixture_database_relative}-wal"
|
|
replacement='./payload/sqlite-wal-replacement'
|
|
rm -- "${pre_root}/${old_wal#./}"
|
|
/usr/bin/truncate --size=1024 -- "${pre_root}/${replacement#./}"
|
|
replace_fixture_target "$old_wal" "$replacement"
|
|
build_manifest
|
|
;;
|
|
sqlite_missing_shm)
|
|
old_wal="${fixture_database_relative}-shm"
|
|
replacement='./payload/sqlite-shm-replacement'
|
|
rm -- "${pre_root}/${old_wal#./}"
|
|
/usr/bin/truncate --size=1024 -- "${pre_root}/${replacement#./}"
|
|
replace_fixture_target "$old_wal" "$replacement"
|
|
build_manifest
|
|
;;
|
|
sqlite_not_in_manifest)
|
|
replacement='./payload/sqlite-db-replacement'
|
|
/usr/bin/truncate --size=4096 -- "${pre_root}/${replacement#./}"
|
|
replace_fixture_target "$fixture_database_relative" "$replacement"
|
|
build_manifest
|
|
;;
|
|
sqlite_inode_not_manifest_target)
|
|
replacement="${bundle_root}/unlisted-sqlite-inode"
|
|
cp -- "${pre_root}/${fixture_database_relative#./}" "$replacement"
|
|
rm -- "${pre_root}/${fixture_database_relative#./}"
|
|
ln -- "$replacement" "${pre_root}/${fixture_database_relative#./}"
|
|
;;
|
|
sqlite_sidecar_symlink)
|
|
replacement="${pre_root}/payload/sqlite-sidecar-target"
|
|
/usr/bin/truncate --size=1024 -- "$replacement"
|
|
rm -- "${pre_root}/${fixture_database_relative#./}-wal"
|
|
ln -s -- "$replacement" \
|
|
"${pre_root}/${fixture_database_relative#./}-wal"
|
|
;;
|
|
sqlite_sidecar_hardlink)
|
|
replacement="${pre_root}/payload/sqlite-sidecar-target"
|
|
/usr/bin/truncate --size=1024 -- "$replacement"
|
|
rm -- "${pre_root}/${fixture_database_relative#./}-wal"
|
|
ln -- "$replacement" "${pre_root}/${fixture_database_relative#./}-wal"
|
|
;;
|
|
sqlite_capture_alias)
|
|
ln -s -- sqlite.stderr "${capture_root}/sqlite.stdout"
|
|
;;
|
|
sqlite_capture_preexisting)
|
|
: >"${capture_root}/sqlite.stdout"
|
|
chmod 0600 -- "${capture_root}/sqlite.stdout"
|
|
;;
|
|
sqlite_capture_stderr_preexisting)
|
|
# Injected at the stderr-open boundary, after stdout is created and pinned.
|
|
;;
|
|
sqlite_capture_stderr_fifo)
|
|
mkfifo -- "${capture_root}/sqlite.stderr"
|
|
;;
|
|
sqlite_capture_stderr_symlink)
|
|
: >"${capture_root}/home/sqlite-stderr-target"
|
|
chmod 0600 -- "${capture_root}/home/sqlite-stderr-target"
|
|
ln -s -- home/sqlite-stderr-target "${capture_root}/sqlite.stderr"
|
|
;;
|
|
sqlite_layout_unmanifested_symlink|sqlite_layout_unmanifested_fifo|sqlite_layout_unmanifested_directory)
|
|
old_wal="${fixture_database_relative}-wal"
|
|
replacement='./payload/sqlite-layout-wal-replacement'
|
|
mv -- "${pre_root}/${old_wal#./}" "${pre_root}/${replacement#./}"
|
|
replace_fixture_target "$old_wal" "$replacement"
|
|
build_manifest
|
|
case "$mutation" in
|
|
sqlite_layout_unmanifested_symlink)
|
|
ln -s -- "${pre_root}/${replacement#./}" "${pre_root}/${old_wal#./}"
|
|
;;
|
|
sqlite_layout_unmanifested_fifo)
|
|
mkfifo -- "${pre_root}/${old_wal#./}"
|
|
;;
|
|
sqlite_layout_unmanifested_directory)
|
|
mkdir -- "${pre_root}/${old_wal#./}"
|
|
;;
|
|
esac
|
|
;;
|
|
payload_component_symlink)
|
|
mv -- "${pre_root}/payload" "${pre_root}/payload.real"
|
|
ln -s -- payload.real "${pre_root}/payload"
|
|
anchor_path="${pre_root}/payload/anchor.bin"
|
|
;;
|
|
*)
|
|
return 97
|
|
;;
|
|
esac
|
|
}
|
|
|
|
# The only command model installed for payload verification. It delegates
|
|
# read-only parsing of test-owned files, models mount/mapping observations, and
|
|
# rejects every unregistered binary with rc 97.
|
|
install_payload_command_model() {
|
|
_k3slra1_stderr_open_boundary() {
|
|
trace_stage stderr-open-attempt
|
|
if [[ "$current_case" == sqlite_capture_stderr_preexisting ]]; then
|
|
: >"${capture_root}/sqlite.stderr"
|
|
chmod 0600 -- "${capture_root}/sqlite.stderr"
|
|
fi
|
|
}
|
|
|
|
_k3slra1_command() {
|
|
local binary="${1-}" expected_mapper expected_canonical expected_mm modeled_source
|
|
local option path observed_canonical
|
|
(( $# >= 1 )) || return 97
|
|
expected_mapper="$(expected_mapper_for_role "$current_role")" || return 97
|
|
expected_canonical="$(expected_canonical_mapper_for_role "$current_role")" || return 97
|
|
expected_mm="$(expected_major_minor_for_role "$current_role")" || return 97
|
|
[[ "$current_case" != canonical_node_wrong_namespace ]] || expected_canonical='/dev/loop7'
|
|
modeled_source="$expected_mapper"
|
|
case "$current_case" in
|
|
proof_mapper_swap) modeled_source="$A1_MAIN_MAPPER" ;;
|
|
baseline_mapper_swap|final_mapper_swap) modeled_source="$A1_PROOF_MAPPER" ;;
|
|
source_expected_canonical_mismatch) modeled_source='/dev/mapper/a1-source-mismatch' ;;
|
|
mount_wrong_source) modeled_source='/dev/mapper/not-owned' ;;
|
|
esac
|
|
case "$binary" in
|
|
/usr/bin/findmnt)
|
|
(( $# == 7 )) && [[ "${2-}" == --noheadings && "${3-}" == --raw &&
|
|
"${4-}" == --mountpoint && "${5-}" == "$mount_root" &&
|
|
"${6-}" == --output &&
|
|
"${7-}" == 'SOURCE,TARGET,FSTYPE,VFS-OPTIONS,FS-OPTIONS,MAJ:MIN' ]] || return 97
|
|
trace_stage findmnt || return 97
|
|
findmnt_calls="$(trace_count findmnt)" || return 97
|
|
local observed_source="$modeled_source" observed_fstype=ext4
|
|
local observed_target="$mount_root" observed_vfs='ro,nosuid,nodev,noexec,relatime'
|
|
local observed_fs=noload observed_mm="$expected_mm"
|
|
[[ "$current_case" == mount_missing_noload ]] &&
|
|
observed_fs='errors=remount-ro'
|
|
[[ "$current_case" == mount_wrong_source ]] && observed_source='/dev/mapper/not-owned'
|
|
[[ "$current_case" == mount_wrong_target ]] && observed_target="${mount_root}-other"
|
|
[[ "$current_case" == mount_wrong_fstype ]] && observed_fstype=xfs
|
|
[[ "$current_case" == mount_wrong_major_minor ]] && observed_mm='253:99'
|
|
[[ "$current_case" == mount_missing_vfs_ro ]] &&
|
|
observed_vfs='nosuid,nodev,noexec,relatime'
|
|
[[ "$current_case" == mount_forbidden_vfs_rw ]] &&
|
|
observed_vfs='rw,nosuid,nodev,noexec,relatime'
|
|
[[ "$current_case" == mount_evidence_drift && $findmnt_calls -ge 2 ]] &&
|
|
observed_mm='253:8'
|
|
[[ "$current_case" == mount_third_evidence_drift && $findmnt_calls -ge 3 ]] &&
|
|
observed_mm='253:8'
|
|
case "$current_case" in
|
|
mount_evidence_multiline)
|
|
printf '%s %s %s %s %s %s\n%s %s %s %s %s %s\n' \
|
|
"$observed_source" "$observed_target" "$observed_fstype" "$observed_vfs" "$observed_fs" "$observed_mm" \
|
|
"$observed_source" "$observed_target" "$observed_fstype" "$observed_vfs" "$observed_fs" "$observed_mm"
|
|
return 0
|
|
;;
|
|
mount_evidence_cr)
|
|
printf '%s %s %s %s %s %s\r\n' "$observed_source" "$observed_target" \
|
|
"$observed_fstype" "$observed_vfs" "$observed_fs" "$observed_mm"
|
|
return 0
|
|
;;
|
|
mount_evidence_nul)
|
|
printf '%s %s %s %s %s %s\0\n' "$observed_source" "$observed_target" \
|
|
"$observed_fstype" "$observed_vfs" "$observed_fs" "$observed_mm"
|
|
return 0
|
|
;;
|
|
mount_evidence_stderr)
|
|
printf 'mount-canary\n' >&2
|
|
;;
|
|
findmnt_valid_record_stderr)
|
|
printf '%s %s %s %s %s %s\n' "$observed_source" "$observed_target" \
|
|
"$observed_fstype" "$observed_vfs" "$observed_fs" "$observed_mm" >&2
|
|
return 0
|
|
;;
|
|
esac
|
|
if [[ "$hostile_capture_surface" == findmnt ]]; then
|
|
hostile_model_output findmnt \
|
|
"$observed_source $observed_target $observed_fstype $observed_vfs $observed_fs $observed_mm"
|
|
return $?
|
|
fi
|
|
printf '%s %s %s %s %s %s\n' "$observed_source" "$observed_target" \
|
|
"$observed_fstype" "$observed_vfs" "$observed_fs" "$observed_mm"
|
|
;;
|
|
/usr/sbin/blockdev)
|
|
(( $# == 3 )) && [[ "${2-}" == --getro && "${3-}" == "$expected_canonical" ]] || return 97
|
|
trace_stage blockdev || return 97
|
|
blockdev_calls="$(trace_count blockdev)" || return 97
|
|
if [[ "$hostile_capture_surface" == blockdev ]]; then
|
|
hostile_model_output blockdev 1
|
|
return $?
|
|
fi
|
|
if [[ "$current_case" == mapping_not_readonly ||
|
|
( "$current_case" == mapping_evidence_drift && $blockdev_calls -ge 2 ) ||
|
|
( "$current_case" == mapping_third_evidence_drift && $blockdev_calls -ge 3 ) ]]; then
|
|
printf '0\n'
|
|
else
|
|
case "$current_case" in
|
|
blockdev_multiline) printf '1\n1\n' ;;
|
|
blockdev_cr) printf '1\r\n' ;;
|
|
blockdev_nul) printf '1\0\n' ;;
|
|
blockdev_stderr) printf 'block-canary\n' >&2; printf '1\n' ;;
|
|
blockdev_valid_record_stderr) printf '1\n' >&2 ;;
|
|
*) printf '1\n' ;;
|
|
esac
|
|
fi
|
|
;;
|
|
/usr/bin/readlink)
|
|
if (( $# == 4 )) && [[ "${2-}" == --canonicalize-existing && "${3-}" == -- ]] &&
|
|
[[ "${4-}" == "$modeled_source" || "${4-}" == "$expected_mapper" ]]; then
|
|
trace_stage readlink || return 97
|
|
readlink_calls="$(trace_count readlink)" || return 97
|
|
observed_canonical="$(canonical_mapper_for_alias "${4-}")" || return 97
|
|
if [[ "$current_case" == mapper_canonical_mismatch ]] &&
|
|
(( readlink_calls % 2 == 0 )); then
|
|
observed_canonical='/dev/dm-99'
|
|
fi
|
|
if [[ "$current_case" == readlink_valid_record_stderr ]]; then
|
|
printf '%s\n' "$observed_canonical" >&2
|
|
return 0
|
|
fi
|
|
printf '%s\n' "$observed_canonical"
|
|
return 0
|
|
fi
|
|
(( $# == 4 )) && [[ "${2-}" == --canonicalize-existing && "${3-}" == -- ]] || return 97
|
|
fixture_path_is_owned "${4-}" || return 97
|
|
/usr/bin/readlink --canonicalize-existing -- "${4-}"
|
|
;;
|
|
/usr/bin/lsblk)
|
|
(( $# == 8 )) && [[ "${2-}" == --noheadings && "${3-}" == --nodeps &&
|
|
"${4-}" == --raw && "${5-}" == --output && "${6-}" == 'MAJ:MIN' &&
|
|
"${7-}" == -- && "${8-}" == "$expected_canonical" ]] || return 97
|
|
trace_stage lsblk || return 97
|
|
lsblk_calls="$(trace_count lsblk)" || return 97
|
|
case "$current_case" in
|
|
mapper_major_minor_mismatch) printf '253:99\n' ;;
|
|
lsblk_valid_record_stderr) printf '%s\n' "$expected_mm" >&2 ;;
|
|
*)
|
|
if [[ "$hostile_capture_surface" == lsblk ]]; then
|
|
hostile_model_output lsblk "$expected_mm"
|
|
return $?
|
|
fi
|
|
printf '%s\n' "$expected_mm"
|
|
;;
|
|
esac
|
|
;;
|
|
/usr/bin/stat)
|
|
if [[ "${!#}" == "$expected_canonical" ]]; then
|
|
trace_stage mapper-stat || return 97
|
|
case "${2-}" in
|
|
--format='%F|%Hr:%Lr')
|
|
if [[ "$hostile_capture_surface" == mapper-stat ]]; then
|
|
emit_hostile_capture "block special file|${expected_mm}"
|
|
return $?
|
|
else
|
|
[[ "$current_case" == mapper_not_block ]] &&
|
|
printf 'regular file|%s\n' "$expected_mm" || printf 'block special file|%s\n' "$expected_mm"
|
|
fi
|
|
;;
|
|
--format='%F')
|
|
[[ "$current_case" == mapper_not_block ]] &&
|
|
printf 'regular file\n' || printf 'block special file\n'
|
|
;;
|
|
--format='%Hr:%Lr') printf '%s\n' "$expected_mm" ;;
|
|
*) return 97 ;;
|
|
esac
|
|
return 0
|
|
fi
|
|
if [[ "${!#}" == /proc/[0-9]*/fd/[0-9]* ]]; then
|
|
(( $# == 5 )) && [[ "${2-}" == --dereference && "${3-}" == --format=* &&
|
|
"${4-}" == -- ]] || return 97
|
|
case "${5-}" in
|
|
*/fd/6) trace_stage fd6-stat || return 97 ;;
|
|
*/fd/7) trace_stage fd7-stat || return 97 ;;
|
|
*/fd/8) trace_stage fd8-stat || return 97 ;;
|
|
*) return 97 ;;
|
|
esac
|
|
model_test_owned_stat "${3#--format=}" "${5-}"
|
|
return 0
|
|
fi
|
|
(( $# == 4 )) && [[ "${2-}" == --format=* && "${3-}" == -- ]] || return 97
|
|
model_test_owned_stat "${2#--format=}" "${4-}"
|
|
;;
|
|
/usr/bin/sha256sum)
|
|
(( $# == 3 )) && [[ "${2-}" == -- ]] || return 97
|
|
fixture_path_is_owned "${3-}" || return 97
|
|
/usr/bin/sha256sum -- "${3-}"
|
|
;;
|
|
/usr/bin/od)
|
|
(( $# == 6 )) && [[ "${2-}" == -An && "${3-}" == -v && "${4-}" == -tx1 &&
|
|
"${5-}" == -- ]] || return 97
|
|
fixture_path_is_owned "${6-}" || return 97
|
|
/usr/bin/od -An -v -tx1 -- "${6-}"
|
|
;;
|
|
/usr/bin/wc)
|
|
(( $# == 4 )) && [[ "${2-}" == -c && "${3-}" == -- ]] || return 97
|
|
fixture_path_is_owned "${4-}" || return 97
|
|
/usr/bin/wc -c -- "${4-}"
|
|
;;
|
|
/usr/bin/find)
|
|
if (( $# == 7 )) && [[ "${2-}" == "$mount_root" && "${3-}" == -mindepth &&
|
|
"${4-}" == 1 && "${5-}" == -maxdepth && "${6-}" == 1 && "${7-}" == -print0 ]]; then
|
|
if [[ "$hostile_capture_surface" == bundle-find ]]; then
|
|
hostile_bundle_find_output
|
|
return $?
|
|
fi
|
|
/usr/bin/find "$mount_root" -mindepth 1 -maxdepth 1 -print0
|
|
elif (( $# == 6 )) && [[ "${2-}" == "$pre_root" && "${3-}" == -xdev &&
|
|
"${4-}" == -type && "${5-}" == f && "${6-}" == -print0 ]]; then
|
|
if [[ "$hostile_capture_surface" == inventory-find ]]; then
|
|
hostile_inventory_find_output
|
|
return $?
|
|
fi
|
|
/usr/bin/find "$pre_root" -xdev -type f -print0
|
|
else
|
|
return 97
|
|
fi
|
|
;;
|
|
/usr/bin/env)
|
|
local expected_database="${pre_root}/${fixture_database_relative#./}"
|
|
(( $# == 16 )) && [[ "${2-}" == -i &&
|
|
"${3-}" == 'PATH=/usr/sbin:/usr/bin:/sbin:/bin' && "${4-}" == 'LC_ALL=C' &&
|
|
"${5-}" == "HOME=${capture_root}/home" && "${6-}" == /usr/bin/sqlite3 &&
|
|
"${7-}" == -safe && "${8-}" == -nofollow && "${9-}" == -readonly &&
|
|
"${10-}" == -batch && "${11-}" == -bail && "${12-}" == -noheader &&
|
|
"${13-}" == -init && "${14-}" == /dev/null &&
|
|
"${15-}" == "$expected_database" &&
|
|
"${16-}" == 'PRAGMA query_only=ON; PRAGMA quick_check;' ]] || return 97
|
|
trace_stage sqlite || return 97
|
|
sqlite_calls="$(trace_count sqlite)" || return 97
|
|
case "$current_case" in
|
|
sqlite_capture_int_cleanup)
|
|
kill -INT "$BASHPID"
|
|
return 130
|
|
;;
|
|
sqlite_capture_term_cleanup)
|
|
kill -TERM "$BASHPID"
|
|
return 143
|
|
;;
|
|
sqlite_capture_term_cleanup_failure)
|
|
kill -TERM "$BASHPID"
|
|
return 143
|
|
;;
|
|
esac
|
|
case "$current_case" in
|
|
sqlite_capture_path_swap)
|
|
mv -- "${capture_root}/sqlite.stdout" "${capture_root}/sqlite.stdout.detached"
|
|
: >"${capture_root}/sqlite.stdout"
|
|
chmod 0600 -- "${capture_root}/sqlite.stdout"
|
|
;;
|
|
sqlite_capture_stdout_hardlink_swap)
|
|
ln -- "${capture_root}/sqlite.stdout" "${capture_root}/sqlite.stdout.detached"
|
|
;;
|
|
sqlite_capture_stderr_hardlink_swap)
|
|
ln -- "${capture_root}/sqlite.stderr" "${capture_root}/sqlite.stderr.detached"
|
|
;;
|
|
manifest_drift_after_sqlite)
|
|
/usr/bin/truncate --size=$((A1_ANCHOR_BYTES + 1)) -- "$anchor_path"
|
|
;;
|
|
payload_write_attempt)
|
|
: >"${pre_root}/payload/unexpected-write"
|
|
;;
|
|
esac
|
|
case "$current_case" in
|
|
sqlite_quick_check_extra_line) printf 'ok\nextra\n' ;;
|
|
sqlite_quick_check_stderr) printf 'sqlite-canary\n' >&2; printf 'ok\n' ;;
|
|
sqlite_quick_check_nul) printf 'ok\0\n' ;;
|
|
sqlite_quick_check_error) return 23 ;;
|
|
*) printf 'ok\n' ;;
|
|
esac
|
|
;;
|
|
/usr/bin/unlink)
|
|
(( $# == 2 )) || return 97
|
|
case "${2-}" in
|
|
"${capture_root}/sqlite.stdout") trace_stage unlink-stdout || return 97 ;;
|
|
"${capture_root}/sqlite.stderr") trace_stage unlink-stderr || return 97 ;;
|
|
*) return 97 ;;
|
|
esac
|
|
if [[ "$current_case" == sqlite_capture_cleanup_failure ||
|
|
"$current_case" == sqlite_capture_term_cleanup_failure ]]; then
|
|
return 31
|
|
fi
|
|
if [[ "$current_case" == sqlite_partial_unlink_noop ]]; then
|
|
return 0
|
|
fi
|
|
/usr/bin/unlink "${2-}"
|
|
;;
|
|
*)
|
|
return 97
|
|
;;
|
|
esac
|
|
}
|
|
}
|
|
|
|
hostile_model_output() {
|
|
local key="${1-}" valid="${2-}"
|
|
(( $# == 2 )) || return 97
|
|
if [[ "$hostile_capture_surface" == "$key" ]]; then
|
|
trace_stage "hostile-${key}" || return 97
|
|
emit_hostile_capture "$valid"
|
|
else
|
|
printf '%s\n' "$valid"
|
|
fi
|
|
}
|
|
|
|
hostile_inventory_find_output() {
|
|
trace_stage hostile-inventory-find || return 97
|
|
case "$hostile_capture_mode" in
|
|
stdout-nonzero)
|
|
/usr/bin/find "$pre_root" -xdev -type f -print0
|
|
return 23
|
|
;;
|
|
raw-nul)
|
|
/usr/bin/find "$pre_root" -xdev -type f -print0
|
|
printf '%s\0' "$manifest_path"
|
|
;;
|
|
extra-trailing-lf)
|
|
/usr/bin/find "$pre_root" -xdev -type f -print0
|
|
printf '%s\n\0' "${pre_root}/payload/not-an-inventory-path"
|
|
;;
|
|
nonempty-stderr)
|
|
/usr/bin/find "$pre_root" -xdev -type f -print0
|
|
printf 'hostile-inventory-stderr\n' >&2
|
|
;;
|
|
*) return 97 ;;
|
|
esac
|
|
}
|
|
|
|
hostile_bundle_find_output() {
|
|
trace_stage hostile-bundle-find || return 97
|
|
case "$hostile_capture_mode" in
|
|
stdout-nonzero)
|
|
/usr/bin/find "$mount_root" -mindepth 1 -maxdepth 1 -print0
|
|
return 23
|
|
;;
|
|
raw-nul)
|
|
/usr/bin/find "$mount_root" -mindepth 1 -maxdepth 1 -print0
|
|
printf '%s\0' "$bundle_root"
|
|
;;
|
|
extra-trailing-lf)
|
|
printf '%s\n\0' "$bundle_root"
|
|
;;
|
|
nonempty-stderr)
|
|
/usr/bin/find "$mount_root" -mindepth 1 -maxdepth 1 -print0
|
|
printf 'hostile-bundle-stderr\n' >&2
|
|
;;
|
|
*) return 97 ;;
|
|
esac
|
|
}
|
|
|
|
install_hostile_library_command_model() {
|
|
_k3slra1_command() {
|
|
local valid='' key=''
|
|
case "${1-}" in
|
|
/usr/bin/stat)
|
|
if (( $# == 5 )) && [[ "${2-}" == --dereference &&
|
|
"${3-}" == "--format=${A1_METADATA_FORMAT}" && "${4-}" == -- ]]; then
|
|
[[ "${5-}" == /proc/[0-9]*/fd/[0-9]* ]] || return 97
|
|
valid="$(model_test_owned_stat "$A1_METADATA_FORMAT" "${5-}")" || return 97
|
|
hostile_model_output fd-stat "$valid"
|
|
return
|
|
fi
|
|
(( $# == 4 )) && [[ "${3-}" == -- && "${4-}" == "$hostile_capture_file" ]] || return 97
|
|
case "${2-}" in
|
|
"--format=${A1_METADATA_FORMAT}")
|
|
valid="$(model_test_owned_stat "$A1_METADATA_FORMAT" "${4-}")" || return 97
|
|
key=metadata-stat
|
|
;;
|
|
--format='%s')
|
|
valid="$(/usr/bin/stat --format='%s' -- "${4-}")" || return 97
|
|
key="${hostile_capture_family}-stat"
|
|
;;
|
|
*) return 97 ;;
|
|
esac
|
|
hostile_model_output "$key" "$valid"
|
|
;;
|
|
/usr/bin/readlink)
|
|
(( $# == 4 )) && [[ "${2-}" == --canonicalize-existing && "${3-}" == -- &&
|
|
"${4-}" == "$hostile_capture_file" ]] || return 97
|
|
valid="$(/usr/bin/readlink --canonicalize-existing -- "${4-}")" || return 97
|
|
hostile_model_output canonical-readlink "$valid"
|
|
;;
|
|
/usr/bin/sha256sum)
|
|
(( $# == 3 )) && [[ "${2-}" == -- && "${3-}" == "$hostile_capture_file" ]] || return 97
|
|
valid="$(/usr/bin/sha256sum -- "${3-}")" || return 97
|
|
hostile_model_output sha256 "$valid"
|
|
;;
|
|
/usr/bin/wc)
|
|
(( $# == 4 )) && [[ "${2-}" == -c && "${3-}" == -- &&
|
|
"${4-}" == "$hostile_capture_file" ]] || return 97
|
|
valid="$(/usr/bin/wc -c -- "${4-}")" || return 97
|
|
hostile_model_output "${hostile_capture_family}-wc" "$valid"
|
|
;;
|
|
/usr/bin/od)
|
|
(( $# == 6 )) && [[ "${2-}" == -An && "${3-}" == -v && "${4-}" == -tx1 &&
|
|
"${5-}" == -- && "${6-}" == "$hostile_capture_file" ]] || return 97
|
|
valid="$(/usr/bin/od -An -v -tx1 -- "${6-}")" || return 97
|
|
hostile_model_output "${hostile_capture_family}-od" "$valid"
|
|
;;
|
|
*) return 97 ;;
|
|
esac
|
|
}
|
|
}
|
|
|
|
install_hostile_helper_command_model() {
|
|
_k3slra1h_command() {
|
|
local valid=''
|
|
case "${1-}" in
|
|
/usr/bin/stat)
|
|
(( $# == 4 )) && [[ "${2-}" == "--format=${A1_METADATA_FORMAT}" &&
|
|
"${3-}" == -- && "${4-}" == "$hostile_capture_file" ]] || return 97
|
|
valid="$(model_test_owned_stat "$A1_METADATA_FORMAT" "${4-}")" || return 97
|
|
hostile_model_output helper-stat "$valid"
|
|
;;
|
|
/usr/bin/readlink)
|
|
(( $# == 4 )) && [[ "${2-}" == --canonicalize-existing && "${3-}" == -- &&
|
|
"${4-}" == "$hostile_capture_file" ]] || return 97
|
|
valid="$(/usr/bin/readlink --canonicalize-existing -- "${4-}")" || return 97
|
|
hostile_model_output helper-readlink "$valid"
|
|
;;
|
|
*) return 97 ;;
|
|
esac
|
|
}
|
|
}
|
|
|
|
hostile_capture_api_rejects() {
|
|
local surface="${1-}" mode="${2-}" result='sentinel' rc=0 before=0 after=0
|
|
local trace_key=''
|
|
local identity='sentinel' nlink='sentinel' size='sentinel' mtime='sentinel'
|
|
local type='sentinel' owner='sentinel' permissions='sentinel' fd_path=''
|
|
local count='count-sentinel' bytes='bytes-sentinel' snapshot='snapshot-sentinel'
|
|
local stdout_sink="${case_root}/hostile-api.stdout" stderr_sink="${case_root}/hostile-api.stderr"
|
|
(( $# == 2 )) || return 97
|
|
current_case=hostile-capture
|
|
current_role=proof
|
|
hostile_capture_surface="$surface"
|
|
hostile_capture_mode="$mode"
|
|
hostile_capture_family=''
|
|
case "$surface" in
|
|
metadata-stat|canonical-readlink|sha256) hostile_capture_file="$anchor_path" ;;
|
|
fd-stat) hostile_capture_file="$anchor_path" ;;
|
|
manifest-stat|manifest-wc|manifest-od)
|
|
hostile_capture_file="$manifest_path"
|
|
hostile_capture_family=manifest
|
|
;;
|
|
file-stat|file-wc|file-od)
|
|
hostile_capture_file="${capture_root}/hex-probe"
|
|
hostile_capture_family=file
|
|
;;
|
|
helper-stat|helper-readlink) hostile_capture_file="$anchor_path" ;;
|
|
mapper-stat|findmnt|lsblk|blockdev|bundle-find|inventory-find) ;;
|
|
*) return 97 ;;
|
|
esac
|
|
trace_key="hostile-${surface}"
|
|
[[ "$surface" != mapper-stat ]] || trace_key=mapper-stat
|
|
: >"$stdout_sink"
|
|
: >"$stderr_sink"
|
|
before="$(trace_count "$trace_key")" || return 97
|
|
case "$surface" in
|
|
metadata-stat)
|
|
install_hostile_library_command_model
|
|
if _k3slra1_metadata "$hostile_capture_file" result >"$stdout_sink" 2>"$stderr_sink"; then rc=0; else rc=$?; fi
|
|
[[ "$result" == sentinel ]] || return 1
|
|
;;
|
|
canonical-readlink)
|
|
install_hostile_library_command_model
|
|
if _k3slra1_canonical_path "$hostile_capture_file" result >"$stdout_sink" 2>"$stderr_sink"; then rc=0; else rc=$?; fi
|
|
[[ "$result" == sentinel ]] || return 1
|
|
;;
|
|
fd-stat)
|
|
install_hostile_library_command_model
|
|
exec 9<"$hostile_capture_file"
|
|
fd_path="/proc/${BASHPID}/fd/9"
|
|
if _k3slra1_fd_metadata_fields "$fd_path" identity nlink size mtime type owner permissions >"$stdout_sink" 2>"$stderr_sink"; then rc=0; else rc=$?; fi
|
|
exec 9>&-
|
|
[[ "$identity|$nlink|$size|$mtime|$type|$owner|$permissions" == \
|
|
'sentinel|sentinel|sentinel|sentinel|sentinel|sentinel|sentinel' ]] || return 1
|
|
;;
|
|
manifest-stat|manifest-wc|manifest-od)
|
|
install_hostile_library_command_model
|
|
if _k3slra1_capture_manifest_hex "$hostile_capture_file" result >"$stdout_sink" 2>"$stderr_sink"; then rc=0; else rc=$?; fi
|
|
[[ "$result" == sentinel ]] || return 1
|
|
;;
|
|
sha256)
|
|
install_hostile_library_command_model
|
|
if _k3slra1_sha256_record "$hostile_capture_file" result >"$stdout_sink" 2>"$stderr_sink"; then rc=0; else rc=$?; fi
|
|
[[ "$result" == sentinel ]] || return 1
|
|
;;
|
|
file-stat|file-wc|file-od)
|
|
install_hostile_library_command_model
|
|
if _k3slra1_capture_file_hex "$hostile_capture_file" 16 result >"$stdout_sink" 2>"$stderr_sink"; then rc=0; else rc=$?; fi
|
|
[[ "$result" == sentinel ]] || return 1
|
|
;;
|
|
helper-stat)
|
|
source "$A1_HELPER_PATH"
|
|
install_hostile_helper_command_model
|
|
if _k3slra1h_capture_metadata "$hostile_capture_file" result >"$stdout_sink" 2>"$stderr_sink"; then rc=0; else rc=$?; fi
|
|
[[ "$result" == sentinel ]] || return 1
|
|
;;
|
|
helper-readlink)
|
|
source "$A1_HELPER_PATH"
|
|
install_hostile_helper_command_model
|
|
if _k3slra1h_capture_canonical "$hostile_capture_file" result >"$stdout_sink" 2>"$stderr_sink"; then rc=0; else rc=$?; fi
|
|
[[ "$result" == sentinel ]] || return 1
|
|
;;
|
|
mapper-stat|findmnt|lsblk|blockdev)
|
|
current_case=hostile-mapper-stat
|
|
current_role=proof
|
|
install_payload_command_model
|
|
if _k3slra1_capture_mount_mapping_evidence "$mount_root" proof result >"$stdout_sink" 2>"$stderr_sink"; then rc=0; else rc=$?; fi
|
|
[[ "$result" == sentinel ]] || return 1
|
|
;;
|
|
inventory-find)
|
|
current_case=hostile-inventory-find
|
|
current_role=proof
|
|
install_payload_command_model
|
|
if _k3slra1_analyze_manifest "$bundle_root" count bytes snapshot >"$stdout_sink" 2>"$stderr_sink"; then rc=0; else rc=$?; fi
|
|
[[ "$count|$bytes|$snapshot" == \
|
|
'count-sentinel|bytes-sentinel|snapshot-sentinel' ]] || return 1
|
|
;;
|
|
bundle-find)
|
|
current_case=hostile-bundle-find
|
|
current_role=proof
|
|
install_payload_command_model
|
|
if _k3slra1_select_pre_bundle "$mount_root" result >"$stdout_sink" 2>"$stderr_sink"; then rc=0; else rc=$?; fi
|
|
[[ "$result" == sentinel ]] || return 1
|
|
;;
|
|
esac
|
|
after="$(trace_count "$trace_key")" || return 97
|
|
(( after == before + 1 )) || return 97
|
|
(( rc != 0 && rc != 97 )) || return 1
|
|
[[ ! -s "$stdout_sink" && ! -s "$stderr_sink" ]]
|
|
}
|
|
|
|
test_command_capture_hostile_matrix() (
|
|
local surface mode violation_count=0
|
|
case_root="${fixture_root}/hostile-capture-${BASHPID}"
|
|
mkdir -- "$case_root"
|
|
current_case=hostile-capture
|
|
current_role=proof
|
|
command_trace="${case_root}/command.trace"
|
|
hostile_violation_trace="${case_root}/violations"
|
|
: >"$command_trace"
|
|
: >"$hostile_violation_trace"
|
|
build_payload canonical
|
|
printf 'ok\n' >"${capture_root}/hex-probe"
|
|
chmod 0600 -- "${capture_root}/hex-probe"
|
|
for surface in metadata-stat canonical-readlink mapper-stat findmnt lsblk blockdev bundle-find inventory-find fd-stat manifest-stat manifest-wc manifest-od sha256 file-stat file-wc file-od helper-stat helper-readlink; do
|
|
for mode in stdout-nonzero raw-nul extra-trailing-lf nonempty-stderr; do
|
|
if hostile_capture_api_rejects "$surface" "$mode"; then
|
|
:
|
|
else
|
|
printf '%s:%s\n' "$surface" "$mode" >>"$hostile_violation_trace"
|
|
(( violation_count += 1 ))
|
|
fi
|
|
done
|
|
done
|
|
(( violation_count == 0 ))
|
|
)
|
|
|
|
test_dynamic_output_aliases_are_closed() (
|
|
local rc=0 mount_record='mount-sentinel'
|
|
local path='path-sentinel' mf_nlink='nlink-sentinel' mf_size='size-sentinel'
|
|
local mf_mtime='mtime-sentinel' mf_type='type-sentinel'
|
|
local mf_owner='owner-sentinel' mf_mode='mode-sentinel'
|
|
local metadata='metadata-sentinel' ff_nlink='fd-nlink-sentinel'
|
|
local ff_size='fd-size-sentinel' ff_mtime='fd-mtime-sentinel'
|
|
local ff_type='fd-type-sentinel' ff_owner='fd-owner-sentinel'
|
|
local ff_mode='fd-mode-sentinel' canonical_mount='bundle-sentinel'
|
|
local manifest='manifest-sentinel' am_bytes='analyze-bytes-sentinel'
|
|
local am_snapshot='analyze-snapshot-sentinel' bundle='count-sentinel'
|
|
local vm_bytes='verify-bytes-sentinel' record='helper-sentinel'
|
|
local size='capture-sentinel' target='helper-canonical-sentinel'
|
|
local _selected_value='selected-internal-sentinel'
|
|
local _count_value='count-internal-sentinel'
|
|
local _bytes_value='bytes-internal-sentinel'
|
|
local _snapshot_value='snapshot-internal-sentinel'
|
|
local _count_result='count-result-sentinel'
|
|
local _bytes_result='bytes-result-sentinel'
|
|
local _snapshot_result='snapshot-result-sentinel'
|
|
local alias_name='' expected_alias=''
|
|
case_root="${fixture_root}/dynamic-alias-${BASHPID}"
|
|
mkdir -- "$case_root"
|
|
current_case=dynamic-output-alias
|
|
current_role=proof
|
|
command_trace="${case_root}/command.trace"
|
|
: >"$command_trace"
|
|
build_payload canonical
|
|
install_payload_command_model
|
|
|
|
if _k3slra1_capture_mount_mapping_evidence "$mount_root" proof mount_record; then rc=0; else rc=$?; fi
|
|
(( rc != 0 && rc != 97 )) || return 1
|
|
[[ "$mount_record" == mount-sentinel && ! -s "$command_trace" ]] || return 1
|
|
|
|
: >"$command_trace"
|
|
if _k3slra1_metadata_fields "$anchor_path" path mf_nlink mf_size mf_mtime mf_type mf_owner mf_mode; then rc=0; else rc=$?; fi
|
|
(( rc != 0 && rc != 97 )) || return 1
|
|
[[ "$path|$mf_nlink|$mf_size|$mf_mtime|$mf_type|$mf_owner|$mf_mode" == \
|
|
'path-sentinel|nlink-sentinel|size-sentinel|mtime-sentinel|type-sentinel|owner-sentinel|mode-sentinel' ]] || return 1
|
|
[[ ! -s "$command_trace" ]] || return 1
|
|
|
|
: >"$command_trace"
|
|
exec 9<"$anchor_path"
|
|
if _k3slra1_fd_metadata_fields "/proc/${BASHPID}/fd/9" metadata ff_nlink ff_size ff_mtime ff_type ff_owner ff_mode; then rc=0; else rc=$?; fi
|
|
exec 9>&-
|
|
(( rc != 0 && rc != 97 )) || return 1
|
|
[[ "$metadata|$ff_nlink|$ff_size|$ff_mtime|$ff_type|$ff_owner|$ff_mode" == \
|
|
'metadata-sentinel|fd-nlink-sentinel|fd-size-sentinel|fd-mtime-sentinel|fd-type-sentinel|fd-owner-sentinel|fd-mode-sentinel' ]] || return 1
|
|
[[ ! -s "$command_trace" ]] || return 1
|
|
|
|
: >"$command_trace"
|
|
if _k3slra1_select_pre_bundle "$mount_root" canonical_mount; then rc=0; else rc=$?; fi
|
|
(( rc != 0 && rc != 97 )) || return 1
|
|
[[ "$canonical_mount" == bundle-sentinel && ! -s "$command_trace" ]] || return 1
|
|
|
|
: >"$command_trace"
|
|
if _k3slra1_analyze_manifest "$bundle_root" manifest am_bytes am_snapshot; then rc=0; else rc=$?; fi
|
|
(( rc != 0 && rc != 97 )) || return 1
|
|
[[ "$manifest|$am_bytes|$am_snapshot" == \
|
|
'manifest-sentinel|analyze-bytes-sentinel|analyze-snapshot-sentinel' ]] || return 1
|
|
[[ ! -s "$command_trace" ]] || return 1
|
|
|
|
for alias_name in _selected_value _count_value _bytes_value _snapshot_value; do
|
|
: >"$command_trace"
|
|
expected_alias="${!alias_name}"
|
|
if _k3slra1_analyze_manifest "$bundle_root" "$alias_name" am_bytes am_snapshot; then rc=0; else rc=$?; fi
|
|
(( rc != 0 && rc != 97 )) || return 1
|
|
[[ "${!alias_name}" == "$expected_alias" && ! -s "$command_trace" ]] || return 1
|
|
done
|
|
|
|
: >"$command_trace"
|
|
if _k3slra1_verify_manifest "$bundle_root" bundle vm_bytes; then rc=0; else rc=$?; fi
|
|
(( rc != 0 && rc != 97 )) || return 1
|
|
[[ "$bundle|$vm_bytes" == 'count-sentinel|verify-bytes-sentinel' && ! -s "$command_trace" ]] || return 1
|
|
|
|
for alias_name in _count_result _bytes_result _snapshot_result; do
|
|
: >"$command_trace"
|
|
expected_alias="${!alias_name}"
|
|
if _k3slra1_verify_manifest "$bundle_root" "$alias_name" vm_bytes; then rc=0; else rc=$?; fi
|
|
(( rc != 0 && rc != 97 )) || return 1
|
|
[[ "${!alias_name}" == "$expected_alias" && ! -s "$command_trace" ]] || return 1
|
|
done
|
|
|
|
: >"$command_trace"
|
|
path='layout-sentinel'
|
|
if _k3slra1_select_sqlite_layout "$bundle_root" path; then rc=0; else rc=$?; fi
|
|
(( rc != 0 && rc != 97 )) || return 1
|
|
[[ "$path" == layout-sentinel && ! -s "$command_trace" ]] || return 1
|
|
|
|
: >"$command_trace"
|
|
path='guard-sentinel'
|
|
if _k3slra1_metadata "$anchor_path" path; then rc=0; else rc=$?; fi
|
|
(( rc != 0 && rc != 97 )) || return 1
|
|
[[ "$path" == guard-sentinel && ! -s "$command_trace" ]] || return 1
|
|
if _k3slra1_canonical_path "$anchor_path" path; then rc=0; else rc=$?; fi
|
|
(( rc != 0 && rc != 97 )) || return 1
|
|
[[ "$path" == guard-sentinel && ! -s "$command_trace" ]] || return 1
|
|
if _k3slra1_capture_manifest_hex "$manifest_path" size; then rc=0; else rc=$?; fi
|
|
(( rc != 0 && rc != 97 )) || return 1
|
|
[[ "$size" == capture-sentinel && ! -s "$command_trace" ]] || return 1
|
|
printf 'ok\n' >"${capture_root}/alias-probe"
|
|
chmod 0600 -- "${capture_root}/alias-probe"
|
|
if _k3slra1_capture_file_hex "${capture_root}/alias-probe" 16 size; then rc=0; else rc=$?; fi
|
|
(( rc != 0 && rc != 97 )) || return 1
|
|
[[ "$size" == capture-sentinel && ! -s "$command_trace" ]] || return 1
|
|
|
|
source "$A1_HELPER_PATH"
|
|
install_helper_command_model
|
|
if _k3slra1h_capture_metadata "$anchor_path" record; then rc=0; else rc=$?; fi
|
|
(( rc != 0 && rc != 97 )) || return 1
|
|
[[ "$record" == helper-sentinel && ! -s "$command_trace" ]] || return 1
|
|
if _k3slra1h_capture_canonical "$anchor_path" target; then rc=0; else rc=$?; fi
|
|
(( rc != 0 && rc != 97 )) || return 1
|
|
[[ "$target" == helper-canonical-sentinel && ! -s "$command_trace" ]]
|
|
)
|
|
|
|
test_mapper_stat_precedes_lsblk() (
|
|
local evidence='unchanged' rc=0
|
|
case_root="${fixture_root}/mapper-stat-order-${BASHPID}"
|
|
mkdir -- "$case_root"
|
|
current_case=mapper_not_block
|
|
current_role=proof
|
|
command_trace="${case_root}/command.trace"
|
|
: >"$command_trace"
|
|
build_payload canonical
|
|
install_payload_command_model
|
|
if _k3slra1_capture_mount_mapping_evidence "$mount_root" proof evidence; then rc=0; else rc=$?; fi
|
|
(( rc != 0 && rc != 97 )) || return 1
|
|
[[ "$evidence" == unchanged ]] || return 1
|
|
[[ "$(trace_count findmnt)" == 1 && "$(trace_count readlink)" == 2 &&
|
|
"$(trace_count mapper-stat)" == 1 && "$(trace_count lsblk)" == 0 &&
|
|
"$(trace_count blockdev)" == 0 ]]
|
|
)
|
|
|
|
test_canonical_mapper_namespace_is_closed() (
|
|
local evidence='unchanged' rc=0
|
|
case_root="${fixture_root}/mapper-namespace-${BASHPID}"
|
|
mkdir -- "$case_root"
|
|
current_case=canonical_node_wrong_namespace
|
|
current_role=proof
|
|
command_trace="${case_root}/command.trace"
|
|
: >"$command_trace"
|
|
build_payload canonical
|
|
install_payload_command_model
|
|
if _k3slra1_capture_mount_mapping_evidence "$mount_root" proof evidence; then rc=0; else rc=$?; fi
|
|
(( rc != 0 && rc != 97 )) || return 1
|
|
[[ "$evidence" == unchanged ]] || return 1
|
|
[[ "$(trace_count findmnt)" == 1 && "$(trace_count readlink)" == 2 &&
|
|
"$(trace_count mapper-stat)" == 0 && "$(trace_count lsblk)" == 0 &&
|
|
"$(trace_count blockdev)" == 0 ]]
|
|
)
|
|
|
|
run_case() (
|
|
local case_name="${1-}" layout=canonical mutation verify_rc=0
|
|
local direct_count='' direct_bytes='' direct_layout='unchanged'
|
|
local actual_wal_path='' actual_wal_before='' actual_wal_after=''
|
|
(( $# == 1 )) || return 97
|
|
umask 022
|
|
current_case="$case_name"
|
|
current_role=proof
|
|
case "$case_name" in
|
|
legacy_pass|legacy_sqlite_mixed_layout) layout=legacy ;;
|
|
manifest_plus_path_pass|manifest_path_512_bytes_pass|unrelated_lost_found_pass)
|
|
current_role=original-final
|
|
;;
|
|
baseline_mapper_swap) current_role=original-baseline ;;
|
|
final_mapper_swap) current_role=original-final ;;
|
|
original_baseline_pass) current_role=original-baseline ;;
|
|
esac
|
|
case_root="${fixture_root}/case-${case_name}-${BASHPID}"
|
|
mkdir -- "$case_root"
|
|
command_trace="${case_root}/command.trace"
|
|
: >"$command_trace"
|
|
build_payload "$layout"
|
|
mutation="$case_name"
|
|
[[ "$case_name" == legacy_sqlite_mixed_layout ]] && mutation=sqlite_mixed_layout
|
|
mutate_fixture "$mutation"
|
|
install_payload_command_model
|
|
|
|
case "$case_name" in
|
|
sqlite_layout_unmanifested_symlink|sqlite_layout_unmanifested_fifo|sqlite_layout_unmanifested_directory)
|
|
_k3slra1_verify_manifest "$bundle_root" direct_count direct_bytes || return 97
|
|
[[ "$direct_count" == "$A1_TARGET_COUNT" && "$direct_bytes" == "$A1_TARGET_BYTES" ]] || return 97
|
|
_k3slra1_select_sqlite_layout "$bundle_root" direct_layout && return 97
|
|
[[ "$direct_layout" == unchanged ]] || return 97
|
|
;;
|
|
esac
|
|
|
|
if [[ "$case_name" == sqlite_wal_drift_after_child ]]; then
|
|
actual_wal_path="${pre_root}/${fixture_database_relative#./}-wal"
|
|
actual_wal_before="$(
|
|
/usr/bin/stat --format='%s|%y|%i' -- "$actual_wal_path"
|
|
/usr/bin/sha256sum -- "$actual_wal_path"
|
|
)" || return 97
|
|
fi
|
|
|
|
if _k3slra1_verify_payload_root "$mount_root" "$current_role" "$capture_root"; then
|
|
verify_rc=0
|
|
else
|
|
verify_rc=$?
|
|
fi
|
|
|
|
if [[ "$case_name" == sqlite_wal_drift_after_child ]]; then
|
|
actual_wal_after="$(
|
|
/usr/bin/stat --format='%s|%y|%i' -- "$actual_wal_path"
|
|
/usr/bin/sha256sum -- "$actual_wal_path"
|
|
)" || return 97
|
|
[[ "$actual_wal_after" == "$actual_wal_before" ]] || return 97
|
|
fi
|
|
|
|
findmnt_calls="$(trace_count findmnt)" || return 97
|
|
readlink_calls="$(trace_count readlink)" || return 97
|
|
lsblk_calls="$(trace_count lsblk)" || return 97
|
|
blockdev_calls="$(trace_count blockdev)" || return 97
|
|
sqlite_calls="$(trace_count sqlite)" || return 97
|
|
sqlite_unlink_stdout_calls="$(trace_count unlink-stdout)" || return 97
|
|
sqlite_unlink_stderr_calls="$(trace_count unlink-stderr)" || return 97
|
|
|
|
case "$case_name" in
|
|
canonical_pass|legacy_pass|original_baseline_pass|manifest_plus_path_pass|manifest_path_512_bytes_pass|\
|
|
unrelated_lost_found_pass)
|
|
(( findmnt_calls == 3 )) || return 97
|
|
(( readlink_calls == 6 )) || return 97
|
|
(( lsblk_calls == 3 )) || return 97
|
|
(( blockdev_calls == 3 )) || return 97
|
|
(( sqlite_calls == 1 )) || return 97
|
|
(( sqlite_unlink_stdout_calls == 1 && sqlite_unlink_stderr_calls == 1 )) || return 97
|
|
[[ "$(trace_count fd6-stat)" == 2 && "$(trace_count fd7-stat)" == 2 &&
|
|
"$(trace_count fd8-stat)" == 2 ]] || return 97
|
|
;;
|
|
mount_missing_noload|mount_wrong_target|mount_wrong_fstype|\
|
|
mount_wrong_major_minor|mount_missing_vfs_ro|mount_forbidden_vfs_rw|\
|
|
mount_evidence_multiline|mount_evidence_cr|mount_evidence_nul|mount_evidence_stderr|\
|
|
mapper_not_block|mapper_major_minor_mismatch|blockdev_multiline|blockdev_cr|\
|
|
blockdev_nul|blockdev_stderr)
|
|
(( findmnt_calls >= 1 )) || return 97
|
|
;;
|
|
findmnt_valid_record_stderr)
|
|
(( findmnt_calls == 1 && readlink_calls == 0 && lsblk_calls == 0 &&
|
|
blockdev_calls == 0 )) || return 97
|
|
;;
|
|
readlink_valid_record_stderr)
|
|
(( findmnt_calls == 1 && readlink_calls == 1 )) || return 97
|
|
(( lsblk_calls == 0 && blockdev_calls == 0 )) || return 97
|
|
;;
|
|
lsblk_valid_record_stderr)
|
|
(( findmnt_calls == 1 && readlink_calls == 2 && lsblk_calls == 1 )) || return 97
|
|
(( blockdev_calls == 0 )) || return 97
|
|
;;
|
|
blockdev_valid_record_stderr)
|
|
(( findmnt_calls == 1 && readlink_calls == 2 && lsblk_calls == 1 &&
|
|
blockdev_calls == 1 )) || return 97
|
|
;;
|
|
mount_wrong_source|proof_mapper_swap|baseline_mapper_swap|final_mapper_swap|mapper_canonical_mismatch|\
|
|
source_expected_canonical_mismatch)
|
|
(( findmnt_calls == 1 && readlink_calls == 2 )) || return 97
|
|
(( lsblk_calls == 0 && blockdev_calls == 0 && sqlite_calls == 0 )) || return 97
|
|
;;
|
|
mapping_not_readonly)
|
|
(( blockdev_calls >= 1 )) || return 97
|
|
;;
|
|
mount_evidence_drift)
|
|
(( findmnt_calls >= 2 )) || return 97
|
|
;;
|
|
mapping_evidence_drift)
|
|
(( blockdev_calls >= 2 )) || return 97
|
|
;;
|
|
mount_third_evidence_drift)
|
|
(( findmnt_calls == 3 && sqlite_calls == 1 )) || return 97
|
|
(( sqlite_unlink_stdout_calls == 1 && sqlite_unlink_stderr_calls == 1 )) || return 97
|
|
;;
|
|
mapping_third_evidence_drift)
|
|
(( blockdev_calls == 3 && sqlite_calls == 1 )) || return 97
|
|
(( sqlite_unlink_stdout_calls == 1 && sqlite_unlink_stderr_calls == 1 )) || return 97
|
|
;;
|
|
payload_write_attempt|manifest_drift_after_sqlite|sqlite_wal_drift_after_child)
|
|
(( findmnt_calls == 2 && readlink_calls == 4 && lsblk_calls == 2 &&
|
|
blockdev_calls == 2 )) || return 97
|
|
(( sqlite_calls == 1 )) || return 97
|
|
(( sqlite_unlink_stdout_calls == 1 && sqlite_unlink_stderr_calls == 1 )) || return 97
|
|
;;
|
|
sqlite_quick_check_extra_line|sqlite_quick_check_error|sqlite_quick_check_stderr|\
|
|
sqlite_quick_check_nul|sqlite_capture_cleanup_failure)
|
|
(( sqlite_calls == 1 )) || return 97
|
|
(( sqlite_unlink_stdout_calls == 1 && sqlite_unlink_stderr_calls == 1 )) || return 97
|
|
;;
|
|
sqlite_capture_wrong_owner|sqlite_capture_wrong_mode)
|
|
(( sqlite_calls == 0 )) || return 97
|
|
(( sqlite_unlink_stdout_calls == 1 && sqlite_unlink_stderr_calls == 1 )) || return 97
|
|
;;
|
|
sqlite_capture_alias|sqlite_capture_preexisting)
|
|
(( sqlite_calls == 0 && sqlite_unlink_stdout_calls == 0 &&
|
|
sqlite_unlink_stderr_calls == 0 )) || return 97
|
|
;;
|
|
sqlite_capture_stderr_preexisting)
|
|
(( sqlite_calls == 0 && sqlite_unlink_stdout_calls == 1 &&
|
|
sqlite_unlink_stderr_calls == 0 )) || return 97
|
|
[[ "$(trace_count fd6-stat)" == 1 && "$(trace_count fd7-stat)" == 1 &&
|
|
"$(trace_count fd8-stat)" == 0 ]] || return 97
|
|
[[ "$(/usr/bin/tail -n 4 -- "$command_trace")" == \
|
|
$'fd6-stat\nfd7-stat\nstderr-open-attempt\nunlink-stdout' ]] || return 97
|
|
[[ ! -e "${capture_root}/sqlite.stdout" && ! -L "${capture_root}/sqlite.stdout" &&
|
|
-f "${capture_root}/sqlite.stderr" && ! -L "${capture_root}/sqlite.stderr" ]] || return 97
|
|
;;
|
|
sqlite_capture_stderr_fifo|sqlite_capture_stderr_symlink)
|
|
(( findmnt_calls == 2 && readlink_calls == 4 && lsblk_calls == 2 &&
|
|
blockdev_calls == 2 && sqlite_calls == 0 )) || return 97
|
|
(( sqlite_unlink_stdout_calls == 0 && sqlite_unlink_stderr_calls == 0 )) || return 97
|
|
[[ "$(trace_count stderr-open-attempt)" == 0 && "$(trace_count fd6-stat)" == 0 &&
|
|
"$(trace_count fd7-stat)" == 0 && "$(trace_count fd8-stat)" == 0 ]] || return 97
|
|
[[ ! -e "${capture_root}/sqlite.stdout" && ! -L "${capture_root}/sqlite.stdout" ]] || return 97
|
|
[[ -e "${capture_root}/sqlite.stderr" || -L "${capture_root}/sqlite.stderr" ]] || return 97
|
|
;;
|
|
sqlite_capture_path_swap)
|
|
(( sqlite_calls == 1 && sqlite_unlink_stdout_calls == 0 &&
|
|
sqlite_unlink_stderr_calls == 1 )) || return 97
|
|
[[ -f "${capture_root}/sqlite.stdout" &&
|
|
-f "${capture_root}/sqlite.stdout.detached" ]] || return 97
|
|
;;
|
|
sqlite_capture_parent_drift)
|
|
(( sqlite_calls == 1 && sqlite_unlink_stdout_calls == 1 &&
|
|
sqlite_unlink_stderr_calls == 1 )) || return 97
|
|
;;
|
|
manifest_duplicate_path)
|
|
(( findmnt_calls == 1 && sqlite_calls == 0 )) || return 97
|
|
;;
|
|
preexisting_unmanifested_file)
|
|
(( findmnt_calls == 1 && readlink_calls == 2 && lsblk_calls == 1 &&
|
|
blockdev_calls == 1 )) || return 97
|
|
(( sqlite_calls == 0 && sqlite_unlink_stdout_calls == 0 &&
|
|
sqlite_unlink_stderr_calls == 0 )) || return 97
|
|
;;
|
|
bundle_*|matching_bundle_symlink|manifest_*|target_*|sqlite_mixed_layout|\
|
|
legacy_sqlite_mixed_layout|sqlite_alternate_partial_layout|sqlite_missing_wal|\
|
|
sqlite_missing_shm|sqlite_not_in_manifest|sqlite_inode_not_manifest_target|\
|
|
sqlite_sidecar_symlink|sqlite_sidecar_hardlink|sqlite_layout_unmanifested_*|\
|
|
bundle_wrong_owner|manifest_wrong_mode|\
|
|
payload_wrong_device|payload_component_symlink)
|
|
(( sqlite_calls == 0 && sqlite_unlink_stdout_calls == 0 &&
|
|
sqlite_unlink_stderr_calls == 0 )) || return 97
|
|
;;
|
|
esac
|
|
return "$verify_rc"
|
|
)
|
|
|
|
test_direct_api_contract() (
|
|
local selected='unchanged' count='unchanged' bytes='unchanged' layout='unchanged'
|
|
local invalid_rc=0
|
|
case_root="${fixture_root}/direct-api-${BASHPID}"
|
|
mkdir -- "$case_root"
|
|
current_case=direct_api
|
|
current_role=proof
|
|
command_trace="${case_root}/command.trace"
|
|
: >"$command_trace"
|
|
build_payload canonical
|
|
install_payload_command_model
|
|
|
|
_k3slra1_select_pre_bundle "$mount_root" selected || return 1
|
|
[[ "$selected" == "$bundle_root" ]] || return 1
|
|
_k3slra1_verify_manifest "$bundle_root" count bytes || return 1
|
|
[[ "$count" == "$A1_TARGET_COUNT" && "$bytes" == "$A1_TARGET_BYTES" ]] || return 1
|
|
_k3slra1_select_sqlite_layout "$bundle_root" layout || return 1
|
|
[[ "$layout" == canonical ]] || return 1
|
|
|
|
selected=unchanged
|
|
if _k3slra1_select_pre_bundle "$mount_root" '_k3slra1_select_bundle'; then
|
|
return 1
|
|
else
|
|
invalid_rc=$?
|
|
fi
|
|
(( invalid_rc == 1 )) || return 1
|
|
[[ "$selected" == unchanged ]] || return 1
|
|
count=unchanged
|
|
if _k3slra1_verify_manifest "$bundle_root" count count; then
|
|
return 1
|
|
else
|
|
invalid_rc=$?
|
|
fi
|
|
(( invalid_rc == 1 )) || return 1
|
|
[[ "$count" == unchanged ]] || return 1
|
|
if _k3slra1_verify_payload "$mount_root" proof; then
|
|
return 1
|
|
else
|
|
invalid_rc=$?
|
|
fi
|
|
(( invalid_rc == 1 )) || return 1
|
|
if _k3slra1_verify_payload "$mount_root" unsupported "$capture_root"; then
|
|
return 1
|
|
else
|
|
invalid_rc=$?
|
|
fi
|
|
(( invalid_rc == 1 )) || return 1
|
|
return 0
|
|
)
|
|
|
|
test_command_model_requires_canonical_argv() (
|
|
local expected_canonical expected_mm fd_path fd_metadata='' observed_mm='' invalid_rc=0
|
|
case_root="${fixture_root}/command-model-${BASHPID}"
|
|
mkdir -- "$case_root"
|
|
current_case=canonical_pass
|
|
current_role=proof
|
|
command_trace="${case_root}/command.trace"
|
|
: >"$command_trace"
|
|
build_payload canonical
|
|
install_payload_command_model
|
|
expected_canonical="$(expected_canonical_mapper_for_role "$current_role")" || return 97
|
|
expected_mm="$(expected_major_minor_for_role "$current_role")" || return 97
|
|
: >"${capture_root}/sqlite.stdout"
|
|
chmod 0600 -- "${capture_root}/sqlite.stdout"
|
|
exec 7<>"${capture_root}/sqlite.stdout"
|
|
fd_path="/proc/${BASHPID}/fd/7"
|
|
if _k3slra1_command /usr/bin/stat --format="$A1_METADATA_FORMAT" -- "$fd_path"; then
|
|
return 1
|
|
else
|
|
invalid_rc=$?
|
|
fi
|
|
(( invalid_rc == 97 )) || return 1
|
|
fd_metadata="$(_k3slra1_command /usr/bin/stat --dereference --format="$A1_METADATA_FORMAT" -- "$fd_path")" || return 1
|
|
[[ "$fd_metadata" == *'|regular file|0:0|600' ||
|
|
"$fd_metadata" == *'|regular empty file|0:0|600' ]] || return 1
|
|
if _k3slra1_command /usr/bin/lsblk --noheadings --nodeps --output 'MAJ:MIN' -- "$expected_canonical"; then
|
|
return 1
|
|
else
|
|
invalid_rc=$?
|
|
fi
|
|
(( invalid_rc == 97 )) || return 1
|
|
observed_mm="$(_k3slra1_command /usr/bin/lsblk --noheadings --nodeps --raw --output 'MAJ:MIN' -- "$expected_canonical")" || return 1
|
|
[[ "$observed_mm" == "$expected_mm" ]] || return 1
|
|
[[ "$(trace_count lsblk)" == 1 ]] || return 1
|
|
exec 7>&-
|
|
)
|
|
|
|
test_sqlite_signal_cleanup_contract() (
|
|
local signal_name="${1-}" expected_case='' quick_rc=0
|
|
(( $# == 1 )) || return 97
|
|
case "$signal_name" in
|
|
INT) expected_case=sqlite_capture_int_cleanup ;;
|
|
TERM) expected_case=sqlite_capture_term_cleanup ;;
|
|
*) return 97 ;;
|
|
esac
|
|
case_root="${fixture_root}/sqlite-signal-${signal_name}-${BASHPID}"
|
|
mkdir -- "$case_root"
|
|
current_case="$expected_case"
|
|
current_role=proof
|
|
command_trace="${case_root}/command.trace"
|
|
: >"$command_trace"
|
|
build_payload canonical
|
|
install_payload_command_model
|
|
if _k3slra1_sqlite_quick_check "$bundle_root" canonical "$capture_root"; then
|
|
quick_rc=0
|
|
else
|
|
quick_rc=$?
|
|
fi
|
|
(( quick_rc != 0 && quick_rc != 97 )) || return 1
|
|
[[ "$(trace_count sqlite)" == 1 ]] || return 1
|
|
[[ "$(trace_count unlink-stdout)" == 1 ]] || return 1
|
|
[[ "$(trace_count unlink-stderr)" == 1 ]] || return 1
|
|
[[ ! -e "${capture_root}/sqlite.stdout" && ! -e "${capture_root}/sqlite.stderr" ]]
|
|
)
|
|
|
|
test_root_signal_status_contract() (
|
|
local case_name="${1-}" expected_rc="${2-}" root_rc=0
|
|
(( $# == 2 )) || return 97
|
|
[[ "$case_name" == sqlite_capture_term_cleanup ||
|
|
"$case_name" == sqlite_capture_term_cleanup_failure ]] || return 97
|
|
[[ "$expected_rc" == 1 || "$expected_rc" == 143 ]] || return 97
|
|
case_root="${fixture_root}/root-signal-${case_name}-${BASHPID}"
|
|
mkdir -- "$case_root"
|
|
current_case="$case_name"
|
|
current_role=proof
|
|
command_trace="${case_root}/command.trace"
|
|
: >"$command_trace"
|
|
build_payload canonical
|
|
install_payload_command_model
|
|
if _k3slra1_verify_payload_root "$mount_root" proof "$capture_root"; then
|
|
root_rc=0
|
|
else
|
|
root_rc=$?
|
|
fi
|
|
(( root_rc == expected_rc )) || return 1
|
|
[[ "$(trace_count sqlite)" == 1 ]] || return 1
|
|
[[ "$(trace_count unlink-stdout)" == 1 ]] || return 1
|
|
[[ "$(trace_count unlink-stderr)" == 1 ]] || return 1
|
|
[[ "$(trace_count findmnt)" == 2 ]] || return 1
|
|
if (( expected_rc == 143 )); then
|
|
[[ ! -e "${capture_root}/sqlite.stdout" && ! -e "${capture_root}/sqlite.stderr" ]]
|
|
else
|
|
[[ -f "${capture_root}/sqlite.stdout" && -f "${capture_root}/sqlite.stderr" ]]
|
|
fi
|
|
)
|
|
|
|
test_sqlite_preserves_shell_state() (
|
|
local case_name expected_success rc=0 before_umask after_umask
|
|
local before_int before_term after_int after_term
|
|
local state_trace="${fixture_root}/sqlite-shell-state-${BASHPID}.trace"
|
|
: >"$state_trace"
|
|
for case_name in canonical_pass sqlite_quick_check_error sqlite_capture_int_cleanup sqlite_capture_term_cleanup; do
|
|
case_root="${fixture_root}/sqlite-shell-state-${case_name}-${BASHPID}"
|
|
mkdir -- "$case_root"
|
|
current_case="$case_name"
|
|
current_role=proof
|
|
command_trace="${case_root}/command.trace"
|
|
: >"$command_trace"
|
|
build_payload canonical
|
|
install_payload_command_model
|
|
umask 027
|
|
trap 'printf "caller-int\n" >>"$state_trace"' INT
|
|
trap 'printf "caller-term\n" >>"$state_trace"' TERM
|
|
before_umask="$(umask)"
|
|
before_int="$(trap -p INT)"
|
|
before_term="$(trap -p TERM)"
|
|
if _k3slra1_sqlite_quick_check "$bundle_root" canonical "$capture_root"; then rc=0; else rc=$?; fi
|
|
after_umask="$(umask)"
|
|
after_int="$(trap -p INT)"
|
|
after_term="$(trap -p TERM)"
|
|
[[ "$before_umask" == "$after_umask" && "$before_int" == "$after_int" &&
|
|
"$before_term" == "$after_term" ]] || return 1
|
|
expected_success=0
|
|
[[ "$case_name" != canonical_pass ]] || expected_success=1
|
|
if (( expected_success == 1 )); then
|
|
(( rc == 0 )) || return 1
|
|
else
|
|
(( rc != 0 && rc != 97 )) || return 1
|
|
fi
|
|
trap - INT TERM
|
|
done
|
|
[[ ! -s "$state_trace" ]]
|
|
)
|
|
|
|
test_sqlite_hardlink_swap_is_rejected() (
|
|
local stream="${1-}" rc=0 detached=''
|
|
(( $# == 1 )) || return 97
|
|
case "$stream" in
|
|
stdout) current_case=sqlite_capture_stdout_hardlink_swap ;;
|
|
stderr) current_case=sqlite_capture_stderr_hardlink_swap ;;
|
|
*) return 97 ;;
|
|
esac
|
|
case_root="${fixture_root}/sqlite-hardlink-${stream}-${BASHPID}"
|
|
mkdir -- "$case_root"
|
|
current_role=proof
|
|
command_trace="${case_root}/command.trace"
|
|
: >"$command_trace"
|
|
build_payload canonical
|
|
install_payload_command_model
|
|
if _k3slra1_sqlite_quick_check "$bundle_root" canonical "$capture_root"; then rc=0; else rc=$?; fi
|
|
(( rc != 0 && rc != 97 )) || return 1
|
|
[[ "$(trace_count sqlite)" == 1 && "$(trace_count fd6-stat)" == 2 &&
|
|
"$(trace_count fd7-stat)" == 2 && "$(trace_count fd8-stat)" == 2 &&
|
|
"$(trace_count unlink-stdout)" == 1 &&
|
|
"$(trace_count unlink-stderr)" == 1 ]] || return 1
|
|
[[ ! -e "${capture_root}/sqlite.stdout" && ! -e "${capture_root}/sqlite.stderr" ]] || return 1
|
|
detached="${capture_root}/sqlite.${stream}.detached"
|
|
if [[ "$stream" == stdout ]]; then
|
|
detached="${capture_root}/sqlite.stdout.detached"
|
|
else
|
|
detached="${capture_root}/sqlite.stderr.detached"
|
|
fi
|
|
[[ -f "$detached" && "$(/usr/bin/stat --format='%h' -- "$detached")" == 1 ]]
|
|
)
|
|
|
|
test_sqlite_layout_identity_binding() (
|
|
local member suffix trace_name selected_path before after before_hash after_hash
|
|
local snapshot='' count='' bytes='' layout='unchanged' rc=0
|
|
for member in db wal shm; do
|
|
case_root="${fixture_root}/sqlite-layout-binding-${member}-${BASHPID}"
|
|
mkdir -- "$case_root"
|
|
current_case=canonical_pass
|
|
current_role=proof
|
|
command_trace="${case_root}/command.trace"
|
|
: >"$command_trace"
|
|
build_payload canonical
|
|
install_payload_command_model
|
|
_k3slra1_analyze_manifest "$bundle_root" count bytes snapshot || return 1
|
|
[[ "$count" == "$A1_TARGET_COUNT" && "$bytes" == "$A1_TARGET_BYTES" ]] || return 1
|
|
case "$member" in
|
|
db) suffix=''; trace_name=layout-db-stat ;;
|
|
wal) suffix=-wal; trace_name=layout-wal-stat ;;
|
|
shm) suffix=-shm; trace_name=layout-shm-stat ;;
|
|
*) return 97 ;;
|
|
esac
|
|
selected_path="${pre_root}/${fixture_database_relative#./}${suffix}"
|
|
before="$(/usr/bin/stat --format='%d:%i|%h|%s|%y|%F|%u:%g|%a' -- "$selected_path")" || return 97
|
|
before_hash="$(/usr/bin/sha256sum -- "$selected_path")" || return 97
|
|
[[ "$before" == *'|1|'* ]] || return 97
|
|
current_case="sqlite_layout_${member}_identity_drift"
|
|
layout=unchanged
|
|
if _k3slra1_select_sqlite_layout_from_snapshot "$bundle_root" "$snapshot" layout; then rc=0; else rc=$?; fi
|
|
after="$(/usr/bin/stat --format='%d:%i|%h|%s|%y|%F|%u:%g|%a' -- "$selected_path")" || return 97
|
|
after_hash="$(/usr/bin/sha256sum -- "$selected_path")" || return 97
|
|
(( rc != 0 && rc != 97 )) || return 1
|
|
[[ "$layout" == unchanged && "$before" == "$after" &&
|
|
"$before_hash" == "$after_hash" ]] || return 1
|
|
[[ "$(trace_count "$trace_name")" == 1 ]] || return 1
|
|
done
|
|
)
|
|
|
|
test_manifest_accepts_heterogeneous_target_metadata() (
|
|
local count='unchanged' bytes='unchanged'
|
|
local identity='' nlink='' size='' mtime='' type='' owner='' mode=''
|
|
case_root="${fixture_root}/heterogeneous-targets-${BASHPID}"
|
|
mkdir -- "$case_root"
|
|
current_case=heterogeneous_target_metadata
|
|
current_role=proof
|
|
command_trace="${case_root}/command.trace"
|
|
: >"$command_trace"
|
|
build_payload canonical
|
|
install_payload_command_model
|
|
_k3slra1_verify_manifest "$bundle_root" count bytes || return 1
|
|
[[ "$count" == "$A1_TARGET_COUNT" && "$bytes" == "$A1_TARGET_BYTES" ]] || return 1
|
|
_k3slra1_metadata_fields "${pre_root}/payload/dummy-01" \
|
|
identity nlink size mtime type owner mode || return 1
|
|
[[ "$nlink|$type|$owner|$mode" == '1|regular file|1000:1001|640' ]] || return 1
|
|
_k3slra1_metadata_fields "${pre_root}/payload/dummy-02" \
|
|
identity nlink size mtime type owner mode || return 1
|
|
[[ "$nlink|$type|$owner|$mode" == '1|regular file|65534:65534|400' ]] || return 1
|
|
_k3slra1_metadata_fields "${pre_root}/payload/dummy-03" \
|
|
identity nlink size mtime type owner mode || return 1
|
|
[[ "$nlink|$type|$owner|$mode" == '1|regular file|0:44|644' ]]
|
|
)
|
|
|
|
test_partial_stdout_unlink_requires_absence() (
|
|
local stdout_path identity='' nlink='' size='' mtime='' type='' owner='' mode='' rc=0
|
|
case_root="${fixture_root}/partial-unlink-${BASHPID}"
|
|
mkdir -- "$case_root"
|
|
current_case=sqlite_partial_unlink_noop
|
|
current_role=proof
|
|
command_trace="${case_root}/command.trace"
|
|
: >"$command_trace"
|
|
build_payload canonical
|
|
install_payload_command_model
|
|
stdout_path="${capture_root}/sqlite.stdout"
|
|
: >"$stdout_path"
|
|
chmod 0600 -- "$stdout_path"
|
|
_k3slra1_metadata_fields "$stdout_path" identity nlink size mtime type owner mode || return 1
|
|
if _k3slra1_remove_partial_capture "$stdout_path" "${identity%%:*}" "$identity"; then
|
|
rc=0
|
|
else
|
|
rc=$?
|
|
fi
|
|
(( rc != 0 && rc != 97 )) || return 1
|
|
[[ "$(trace_count unlink-stdout)" == 1 ]] || return 1
|
|
[[ -f "$stdout_path" && ! -L "$stdout_path" ]]
|
|
)
|
|
|
|
bounded_manifest_reader_accepts_65536_bytes() (
|
|
local boundary_root="${fixture_root}/manifest-boundary-${BASHPID}"
|
|
local boundary_file="${boundary_root}/manifest" captured='unchanged'
|
|
mkdir -- "$boundary_root"
|
|
printf '%65535s\n' '' | tr ' ' a >"$boundary_file"
|
|
[[ "$(/usr/bin/stat --format='%s' -- "$boundary_file")" == 65536 ]] || return 97
|
|
_k3slra1_capture_manifest_hex "$boundary_file" captured || return 1
|
|
[[ ${#captured} -eq 131072 && "${captured: -2}" == 0a ]]
|
|
)
|
|
|
|
bounded_manifest_reader_rejects_65537_bytes() (
|
|
local boundary_root="${fixture_root}/manifest-overflow-${BASHPID}" rc=0
|
|
local boundary_file="${boundary_root}/manifest" captured='unchanged'
|
|
mkdir -- "$boundary_root"
|
|
printf '%65536s\n' '' | tr ' ' a >"$boundary_file"
|
|
[[ "$(/usr/bin/stat --format='%s' -- "$boundary_file")" == 65537 ]] || return 97
|
|
if _k3slra1_capture_manifest_hex "$boundary_file" captured; then rc=0; else rc=$?; fi
|
|
(( rc != 0 && rc != 97 )) || return 1
|
|
[[ "$captured" == unchanged ]]
|
|
)
|
|
|
|
fixed_mount_name_for_role() {
|
|
local role="${1-}"
|
|
(( $# == 1 )) || return 97
|
|
case "$role" in
|
|
proof) printf 'proof-mount\n' ;;
|
|
original-baseline) printf 'original-baseline-mount\n' ;;
|
|
original-final) printf 'original-final-mount\n' ;;
|
|
*) return 97 ;;
|
|
esac
|
|
}
|
|
|
|
test_parent_helper_dispatch() (
|
|
local dispatch_count=0 dispatch_failure=0 role mount_name modeled_mount modeled_capture
|
|
local modeled_helper invalid_rc=0
|
|
modeled_capture="${fixture_root}/parent-dispatch-${BASHPID}"
|
|
modeled_helper="${modeled_capture}/libexec/k3s-local-recovery-a1-check.sh"
|
|
mkdir -p -- "${modeled_capture}/libexec"
|
|
: >"$modeled_helper"
|
|
|
|
_k3slra1_command() {
|
|
(( $# == 15 )) || return 97
|
|
[[ "${1-}" == /usr/bin/sudo && "${2-}" == --non-interactive && "${3-}" == -- &&
|
|
"${4-}" == /usr/bin/env && "${5-}" == -i &&
|
|
"${6-}" == 'PATH=/usr/sbin:/usr/bin:/sbin:/bin' && "${7-}" == 'LC_ALL=C' &&
|
|
"${8-}" == /usr/bin/bash && "${9-}" == --noprofile && "${10-}" == --norc &&
|
|
"${11-}" == -- && "${12-}" == "$modeled_helper" &&
|
|
"${13-}" == "$modeled_mount" && "${14-}" == "$role" &&
|
|
"${15-}" == "$modeled_capture" ]] || return 97
|
|
(( dispatch_count += 1 ))
|
|
(( dispatch_failure == 0 ))
|
|
}
|
|
|
|
export BASH_ENV='/fixture/must-not-cross-env-i'
|
|
export -f fixture_path_is_owned
|
|
for role in original-baseline proof original-final; do
|
|
mount_name="$(fixed_mount_name_for_role "$role")" || return 1
|
|
modeled_mount="${modeled_capture}/${mount_name}"
|
|
mkdir -- "$modeled_mount"
|
|
_k3slra1_verify_payload "$modeled_mount" "$role" "$modeled_capture" || return 1
|
|
done
|
|
(( dispatch_count == 3 )) || return 1
|
|
|
|
if _k3slra1_verify_payload "$modeled_mount" unsupported "$modeled_capture"; then
|
|
return 1
|
|
else
|
|
invalid_rc=$?
|
|
fi
|
|
(( invalid_rc == 1 )) || return 1
|
|
if _k3slra1_verify_payload "$modeled_mount" proof; then
|
|
return 1
|
|
else
|
|
invalid_rc=$?
|
|
fi
|
|
(( invalid_rc == 1 )) || return 1
|
|
(( dispatch_count == 3 )) || return 1
|
|
dispatch_failure=1
|
|
role=proof
|
|
modeled_mount="${modeled_capture}/proof-mount"
|
|
if _k3slra1_verify_payload "$modeled_mount" "$role" "$modeled_capture"; then
|
|
return 1
|
|
else
|
|
invalid_rc=$?
|
|
fi
|
|
(( invalid_rc == 1 )) || return 1
|
|
(( dispatch_count == 4 )) || return 1
|
|
)
|
|
|
|
test_parent_helper_output_is_suppressed() (
|
|
local modeled_capture="${fixture_root}/parent-quiet-${BASHPID}"
|
|
local modeled_mount="${modeled_capture}/proof-mount"
|
|
local modeled_helper="${modeled_capture}/libexec/k3s-local-recovery-a1-check.sh"
|
|
local public_stdout="${modeled_capture}/public.stdout"
|
|
local public_stderr="${modeled_capture}/public.stderr"
|
|
local dispatch_count=0 rc=0
|
|
mkdir -p -- "${modeled_capture}/libexec" "$modeled_mount"
|
|
: >"$modeled_helper"
|
|
_k3slra1_command() {
|
|
(( $# == 15 )) || return 97
|
|
[[ "${1-}" == /usr/bin/sudo && "${2-}" == --non-interactive && "${3-}" == -- &&
|
|
"${4-}" == /usr/bin/env && "${5-}" == -i &&
|
|
"${6-}" == 'PATH=/usr/sbin:/usr/bin:/sbin:/bin' && "${7-}" == 'LC_ALL=C' &&
|
|
"${8-}" == /usr/bin/bash && "${9-}" == --noprofile && "${10-}" == --norc &&
|
|
"${11-}" == -- && "${12-}" == "$modeled_helper" &&
|
|
"${13-}" == "$modeled_mount" && "${14-}" == proof &&
|
|
"${15-}" == "$modeled_capture" ]] || return 97
|
|
(( dispatch_count += 1 ))
|
|
printf 'hostile-sudo-stdout\n'
|
|
printf 'hostile-sudo-stderr\n' >&2
|
|
return 23
|
|
}
|
|
if _k3slra1_verify_payload "$modeled_mount" proof "$modeled_capture" \
|
|
>"$public_stdout" 2>"$public_stderr"; then
|
|
rc=0
|
|
else
|
|
rc=$?
|
|
fi
|
|
(( rc == 23 && dispatch_count == 1 )) || return 1
|
|
[[ ! -s "$public_stdout" && ! -s "$public_stderr" ]]
|
|
)
|
|
|
|
test_helper_source_guard() (
|
|
local source_stdout="${fixture_root}/helper-source.stdout"
|
|
local source_stderr="${fixture_root}/helper-source.stderr"
|
|
local source_trace="${fixture_root}/helper-source.trace"
|
|
: >"$source_trace"
|
|
if (
|
|
set -Eeuo pipefail
|
|
# shellcheck source=/dev/null
|
|
source "$A1_HELPER_PATH"
|
|
declare -F k3slra1_check_main >/dev/null
|
|
printf 'source-only\n' >>"$source_trace"
|
|
) >"$source_stdout" 2>"$source_stderr"; then
|
|
:
|
|
else
|
|
return 1
|
|
fi
|
|
[[ ! -s "$source_stdout" && ! -s "$source_stderr" ]] || return 1
|
|
[[ "$(<"$source_trace")" == source-only ]] || return 1
|
|
)
|
|
|
|
test_helper_source_preserves_signal_traps() (
|
|
local trap_trace="${fixture_root}/helper-source-traps-${BASHPID}.trace"
|
|
local before_int before_term after_int after_term
|
|
: >"$trap_trace"
|
|
trap 'printf "caller-int\n" >>"$trap_trace"' INT
|
|
trap 'printf "caller-term\n" >>"$trap_trace"' TERM
|
|
before_int="$(trap -p INT)"
|
|
before_term="$(trap -p TERM)"
|
|
source "$A1_HELPER_PATH"
|
|
after_int="$(trap -p INT)"
|
|
after_term="$(trap -p TERM)"
|
|
trap - INT TERM
|
|
[[ "$before_int" == "$after_int" && "$before_term" == "$after_term" ]] || return 1
|
|
[[ ! -s "$trap_trace" ]]
|
|
)
|
|
|
|
write_direct_term_helper_fixture() {
|
|
local staged_root="${1-}" nested_rc="${2-}" helper temporary a1
|
|
(( $# == 2 )) || return 97
|
|
[[ "$nested_rc" == 0 || "$nested_rc" == 1 ]] || return 97
|
|
write_staged_helper_dependencies "$staged_root"
|
|
helper="${staged_root}/libexec/k3s-local-recovery-a1-check.sh"
|
|
temporary="${helper}.rewrite"
|
|
a1="${staged_root}/lib/k3s-local-recovery-a1.sh"
|
|
/usr/bin/awk '
|
|
$0 == "if [[ \"${BASH_SOURCE[0]}\" == \"$0\" ]]; then" {
|
|
print "_k3slra1h_euid_is_root() {"
|
|
print " :"
|
|
print "}"
|
|
print "_k3slra1h_load_staged_dependencies() {"
|
|
print " (( $# == 1 )) || return 97"
|
|
print " source \"${1}/lib/k3s-local-recovery-a1.sh\""
|
|
print "}"
|
|
}
|
|
{ print }
|
|
' "$helper" >"$temporary"
|
|
mv -- "$temporary" "$helper"
|
|
chmod 0600 -- "$helper"
|
|
printf '%s\n' \
|
|
'readonly K3SLRA1_MAIN_MAPPING_NAME=k3s-recovery' \
|
|
'_k3slra1_verify_payload_root() {' \
|
|
' (( $# == 3 )) || return 97' \
|
|
' printf "nested-check-ready\n" >>"$K3SLRA1_TERM_TRACE"' \
|
|
' /usr/bin/sleep 0.35' \
|
|
' printf "nested-check-cleanup\n" >>"$K3SLRA1_TERM_TRACE"' \
|
|
" return ${nested_rc}" \
|
|
'}' >"$a1"
|
|
chmod 0600 -- "$a1"
|
|
}
|
|
|
|
test_helper_direct_term_waits_for_nested_cleanup() (
|
|
local nested_rc="${1-}" expected_rc="${2-}"
|
|
local modeled_capture="${fixture_root}/helper-direct-term-${nested_rc}-${BASHPID}"
|
|
local modeled_mount="${modeled_capture}/proof-mount"
|
|
local helper="${modeled_capture}/libexec/k3s-local-recovery-a1-check.sh"
|
|
local term_trace="${modeled_capture}/term.trace"
|
|
local public_stdout="${modeled_capture}/public.stdout"
|
|
local public_stderr="${modeled_capture}/public.stderr"
|
|
local helper_pid='' watchdog_pid='' ready=0 rc=0 iteration
|
|
(( $# == 2 )) || return 97
|
|
[[ "$nested_rc" == 0 || "$nested_rc" == 1 ]] || return 97
|
|
[[ "$expected_rc" == 1 || "$expected_rc" == 143 ]] || return 97
|
|
write_direct_term_helper_fixture "$modeled_capture" "$nested_rc"
|
|
mkdir -p -- "$modeled_mount"
|
|
chmod 0700 -- "$modeled_mount"
|
|
: >"$term_trace"
|
|
export K3SLRA1_TERM_TRACE="$term_trace"
|
|
/usr/bin/bash --noprofile --norc -- "$helper" "$modeled_mount" proof "$modeled_capture" \
|
|
>"$public_stdout" 2>"$public_stderr" &
|
|
helper_pid=$!
|
|
for (( iteration = 0; iteration < 100; iteration += 1 )); do
|
|
if [[ "$(<"$term_trace")" == nested-check-ready ]]; then
|
|
ready=1
|
|
break
|
|
fi
|
|
/usr/bin/sleep 0.01
|
|
done
|
|
if (( ready != 1 )); then
|
|
kill -KILL "$helper_pid" 2>/dev/null || :
|
|
wait "$helper_pid" 2>/dev/null || :
|
|
return 1
|
|
fi
|
|
(
|
|
/usr/bin/sleep 3
|
|
kill -KILL "$helper_pid" 2>/dev/null || :
|
|
) &
|
|
watchdog_pid=$!
|
|
kill -TERM "$helper_pid" || return 1
|
|
if wait "$helper_pid"; then rc=0; else rc=$?; fi
|
|
kill -TERM "$watchdog_pid" 2>/dev/null || :
|
|
wait "$watchdog_pid" 2>/dev/null || :
|
|
(( rc == expected_rc )) || return 1
|
|
[[ "$(<"$term_trace")" == $'nested-check-ready\nnested-check-cleanup' ]] || return 1
|
|
[[ ! -s "$public_stdout" && ! -s "$public_stderr" ]]
|
|
)
|
|
|
|
test_helper_nonroot_main_is_closed() (
|
|
local helper_stdout="${fixture_root}/helper-nonroot.stdout"
|
|
local helper_stderr="${fixture_root}/helper-nonroot.stderr"
|
|
local downstream_trace="${fixture_root}/helper-nonroot.downstream"
|
|
# shellcheck source=/dev/null
|
|
source "$A1_HELPER_PATH"
|
|
# This is not a successful skip: an EUID 0 runner must provide a separate
|
|
# non-root/user-namespace fixture instead of silently passing this contract.
|
|
(( EUID != 0 )) || return 77
|
|
: >"$downstream_trace"
|
|
_k3slra1h_load_staged_dependencies() {
|
|
printf 'loader\n' >>"$downstream_trace"
|
|
return 97
|
|
}
|
|
_k3slra1_verify_payload_root() {
|
|
printf 'transaction\n' >>"$downstream_trace"
|
|
return 97
|
|
}
|
|
local helper_rc=0
|
|
if k3slra1_check_main /fixture/mount proof /fixture/capture \
|
|
>"$helper_stdout" 2>"$helper_stderr"; then
|
|
return 1
|
|
else
|
|
helper_rc=$?
|
|
fi
|
|
(( helper_rc == 1 )) || return 1
|
|
[[ ! -s "$helper_stdout" && ! -s "$helper_stderr" &&
|
|
! -s "$downstream_trace" ]]
|
|
)
|
|
|
|
write_staged_helper_dependencies() {
|
|
local staged_root="${1-}" mode="${2-success}" shared a1 contract
|
|
local shared_failure='' a1_failure='' contract_failure='' mapping_name=k3s-recovery
|
|
(( $# == 1 || $# == 2 )) || return 97
|
|
case "$mode" in
|
|
success) ;;
|
|
shared-failure) shared_failure='return 23' ;;
|
|
a1-failure) a1_failure='return 23' ;;
|
|
contract-failure) contract_failure=' return 23' ;;
|
|
mapping-mismatch) mapping_name='unexpected-mapping' ;;
|
|
*) return 97 ;;
|
|
esac
|
|
shared="${staged_root}/lib/k3s-local-recovery.sh"
|
|
a1="${staged_root}/lib/k3s-local-recovery-a1.sh"
|
|
contract="${staged_root}/contract/local-recovery.env"
|
|
mkdir -p -- "${staged_root}/lib" "${staged_root}/libexec" "${staged_root}/contract"
|
|
printf '%s\n' \
|
|
'printf "shared\n" >>"$K3SLRA1_STAGE_TRACE"' \
|
|
"$shared_failure" \
|
|
'_k3slr_load_contract() {' \
|
|
' (( $# == 1 )) || return 97' \
|
|
' [[ "$1" == "$K3SLRA1_EXPECTED_CONTRACT" ]] || return 97' \
|
|
' printf "contract\n" >>"$K3SLRA1_STAGE_TRACE"' \
|
|
"$contract_failure" \
|
|
" K3SLR_MAPPING_NAME=${mapping_name}" \
|
|
' K3SLR_PROOF_MAPPING_NAME=k3s-recovery-proof' \
|
|
'}' >"$shared"
|
|
printf '%s\n' \
|
|
'printf "a1\n" >>"$K3SLRA1_STAGE_TRACE"' \
|
|
"$a1_failure" \
|
|
'readonly K3SLRA1_MAIN_MAPPING_NAME=k3s-recovery' \
|
|
'_k3slra1_verify_payload_root() {' \
|
|
' (( $# == 3 )) || return 97' \
|
|
' [[ "$1" == "$K3SLRA1_EXPECTED_MOUNT" && "$2" == proof &&' \
|
|
' "$3" == "$K3SLRA1_EXPECTED_CAPTURE" ]] || return 97' \
|
|
' printf "transaction\n" >>"$K3SLRA1_STAGE_TRACE"' \
|
|
'}' >"$a1"
|
|
printf 'fixture-contract=1\n' >"$contract"
|
|
cp -- "$A1_HELPER_PATH" "${staged_root}/libexec/k3s-local-recovery-a1-check.sh"
|
|
chmod 0600 -- "$shared" "$a1" "$contract" \
|
|
"${staged_root}/libexec/k3s-local-recovery-a1-check.sh"
|
|
/usr/bin/find "$staged_root" -type d -exec chmod 0700 -- '{}' +
|
|
}
|
|
|
|
install_helper_command_model() {
|
|
_k3slra1h_command() {
|
|
local modeled=''
|
|
case "${1-}" in
|
|
/usr/bin/stat)
|
|
(( $# == 4 )) && [[ "${2-}" == --format=* && "${3-}" == -- ]] || return 97
|
|
modeled="$(model_test_owned_stat "${2#--format=}" "${4-}")" || return 97
|
|
if [[ "$current_case" == helper-staged-file-extra-field &&
|
|
"${4-}" == "${capture_root}/lib/k3s-local-recovery.sh" ]]; then
|
|
modeled="${modeled/|regular file|/|unexpected|regular file|}"
|
|
fi
|
|
printf '%s\n' "$modeled"
|
|
;;
|
|
/usr/bin/readlink)
|
|
(( $# == 4 )) && [[ "${2-}" == --canonicalize-existing && "${3-}" == -- ]] || return 97
|
|
fixture_path_is_owned "${4-}" || return 97
|
|
/usr/bin/readlink --canonicalize-existing -- "${4-}"
|
|
;;
|
|
/usr/bin/sha256sum)
|
|
(( $# == 3 )) && [[ "${2-}" == -- ]] || return 97
|
|
fixture_path_is_owned "${3-}" || return 97
|
|
/usr/bin/sha256sum -- "${3-}"
|
|
;;
|
|
*) return 97 ;;
|
|
esac
|
|
}
|
|
}
|
|
|
|
test_helper_compositional_main() (
|
|
local modeled_capture="${fixture_root}/helper-root-${BASHPID}"
|
|
local modeled_mount="${modeled_capture}/proof-mount"
|
|
local stage_trace="${fixture_root}/helper-root-${BASHPID}.trace"
|
|
case_root="$modeled_capture"
|
|
mount_root="$modeled_mount"
|
|
capture_root="$modeled_capture"
|
|
current_case=helper_composition
|
|
current_role=proof
|
|
write_staged_helper_dependencies "$modeled_capture"
|
|
mkdir -p -- "$modeled_mount" "${modeled_capture}/home"
|
|
: >"$stage_trace"
|
|
chmod 0700 -- "$modeled_mount" "${modeled_capture}/home"
|
|
export K3SLRA1_STAGE_TRACE="$stage_trace"
|
|
export K3SLRA1_EXPECTED_CONTRACT="${modeled_capture}/contract/local-recovery.env"
|
|
export K3SLRA1_EXPECTED_MOUNT="$modeled_mount"
|
|
export K3SLRA1_EXPECTED_CAPTURE="$modeled_capture"
|
|
# shellcheck source=/dev/null
|
|
source "$A1_HELPER_PATH"
|
|
declare -F _k3slra1h_load_staged_dependencies >/dev/null || return 1
|
|
|
|
_k3slra1h_euid_is_root() {
|
|
(( $# == 0 )) || return 97
|
|
printf 'root-gate\n' >>"$stage_trace"
|
|
}
|
|
install_helper_command_model
|
|
|
|
k3slra1_check_main "$modeled_mount" proof "$modeled_capture" || return 1
|
|
[[ "$(<"$stage_trace")" == $'root-gate\nshared\na1\ncontract\ntransaction' ]] || return 1
|
|
: >"$stage_trace"
|
|
local invalid_rc=0
|
|
if k3slra1_check_main "$modeled_mount" proof "$modeled_capture" extra; then
|
|
return 1
|
|
else
|
|
invalid_rc=$?
|
|
fi
|
|
(( invalid_rc == 1 )) || return 1
|
|
[[ ! -s "$stage_trace" ]] || return 1
|
|
)
|
|
|
|
test_helper_staged_failure_is_closed() (
|
|
local mode="${1-}" expected_trace='' failure_rc=0
|
|
local modeled_capture="${fixture_root}/helper-failure-${mode}-${BASHPID}"
|
|
local modeled_mount="${modeled_capture}/proof-mount"
|
|
local stage_trace="${fixture_root}/helper-failure-${mode}-${BASHPID}.trace"
|
|
(( $# == 1 )) || return 97
|
|
case "$mode" in
|
|
shared-failure) expected_trace=$'root-gate\nshared' ;;
|
|
a1-failure) expected_trace=$'root-gate\nshared\na1' ;;
|
|
contract-failure|mapping-mismatch) expected_trace=$'root-gate\nshared\na1\ncontract' ;;
|
|
*) return 97 ;;
|
|
esac
|
|
case_root="$modeled_capture"
|
|
mount_root="$modeled_mount"
|
|
capture_root="$modeled_capture"
|
|
current_case=helper_composition
|
|
current_role=proof
|
|
write_staged_helper_dependencies "$modeled_capture" "$mode"
|
|
mkdir -p -- "$modeled_mount" "${modeled_capture}/home"
|
|
: >"$stage_trace"
|
|
chmod 0700 -- "$modeled_mount" "${modeled_capture}/home"
|
|
export K3SLRA1_STAGE_TRACE="$stage_trace"
|
|
export K3SLRA1_EXPECTED_CONTRACT="${modeled_capture}/contract/local-recovery.env"
|
|
export K3SLRA1_EXPECTED_MOUNT="$modeled_mount"
|
|
export K3SLRA1_EXPECTED_CAPTURE="$modeled_capture"
|
|
source "$A1_HELPER_PATH"
|
|
_k3slra1h_euid_is_root() {
|
|
(( $# == 0 )) || return 97
|
|
printf 'root-gate\n' >>"$stage_trace"
|
|
}
|
|
install_helper_command_model
|
|
if k3slra1_check_main "$modeled_mount" proof "$modeled_capture"; then
|
|
return 1
|
|
else
|
|
failure_rc=$?
|
|
fi
|
|
(( failure_rc == 1 )) || return 1
|
|
[[ "$(<"$stage_trace")" == "$expected_trace" ]]
|
|
)
|
|
|
|
test_helper_intermediate_directories_are_pinned() (
|
|
local directory_kind mutation modeled_capture modeled_mount stage_trace rc=0
|
|
local violations="${fixture_root}/helper-directory-violations-${BASHPID}"
|
|
: >"$violations"
|
|
for directory_kind in lib libexec contract; do
|
|
for mutation in wrong-owner wrong-mode wrong-device symlink; do
|
|
modeled_capture="${fixture_root}/helper-dir-${directory_kind}-${mutation}-${BASHPID}"
|
|
modeled_mount="${modeled_capture}/proof-mount"
|
|
stage_trace="${modeled_capture}.trace"
|
|
case_root="$modeled_capture"
|
|
mount_root="$modeled_mount"
|
|
capture_root="$modeled_capture"
|
|
current_role=proof
|
|
current_case=helper-directory-negative
|
|
helper_modeled_target="${modeled_capture}/${directory_kind}"
|
|
helper_modeled_mutation="$mutation"
|
|
write_staged_helper_dependencies "$modeled_capture"
|
|
mkdir -p -- "$modeled_mount" "${modeled_capture}/home"
|
|
chmod 0700 -- "$modeled_mount" "${modeled_capture}/home"
|
|
if [[ "$mutation" == symlink ]]; then
|
|
helper_modeled_mutation=''
|
|
mv -- "${modeled_capture}/${directory_kind}" "${modeled_capture}/${directory_kind}.real"
|
|
ln -s -- "${directory_kind}.real" "${modeled_capture}/${directory_kind}"
|
|
fi
|
|
: >"$stage_trace"
|
|
export K3SLRA1_STAGE_TRACE="$stage_trace"
|
|
export K3SLRA1_EXPECTED_CONTRACT="${modeled_capture}/contract/local-recovery.env"
|
|
export K3SLRA1_EXPECTED_MOUNT="$modeled_mount"
|
|
export K3SLRA1_EXPECTED_CAPTURE="$modeled_capture"
|
|
source "$A1_HELPER_PATH"
|
|
_k3slra1h_euid_is_root() {
|
|
(( $# == 0 )) || return 97
|
|
printf 'root-gate\n' >>"$stage_trace"
|
|
}
|
|
install_helper_command_model
|
|
if k3slra1_check_main "$modeled_mount" proof "$modeled_capture"; then rc=0; else rc=$?; fi
|
|
if (( rc == 0 || rc == 97 )) || [[ "$(<"$stage_trace")" != root-gate ]]; then
|
|
printf '%s:%s\n' "$directory_kind" "$mutation" >>"$violations"
|
|
fi
|
|
done
|
|
done
|
|
[[ ! -s "$violations" ]]
|
|
)
|
|
|
|
test_helper_staged_files_are_pinned() (
|
|
local file_kind mutation modeled_capture modeled_mount staged_file stage_trace rc=0
|
|
local violations="${fixture_root}/helper-file-violations-${BASHPID}"
|
|
: >"$violations"
|
|
for file_kind in shared a1 contract helper; do
|
|
for mutation in wrong-owner wrong-mode wrong-device nlink-two wrong-type symlink; do
|
|
modeled_capture="${fixture_root}/helper-file-${file_kind}-${mutation}-${BASHPID}"
|
|
modeled_mount="${modeled_capture}/proof-mount"
|
|
stage_trace="${modeled_capture}.trace"
|
|
case "$file_kind" in
|
|
shared) staged_file="${modeled_capture}/lib/k3s-local-recovery.sh" ;;
|
|
a1) staged_file="${modeled_capture}/lib/k3s-local-recovery-a1.sh" ;;
|
|
contract) staged_file="${modeled_capture}/contract/local-recovery.env" ;;
|
|
helper) staged_file="${modeled_capture}/libexec/k3s-local-recovery-a1-check.sh" ;;
|
|
*) return 97 ;;
|
|
esac
|
|
case_root="$modeled_capture"
|
|
mount_root="$modeled_mount"
|
|
capture_root="$modeled_capture"
|
|
current_role=proof
|
|
current_case=helper-file-negative
|
|
helper_modeled_target="$staged_file"
|
|
helper_modeled_mutation="$mutation"
|
|
write_staged_helper_dependencies "$modeled_capture"
|
|
mkdir -p -- "$modeled_mount" "${modeled_capture}/home"
|
|
chmod 0700 -- "$modeled_mount" "${modeled_capture}/home"
|
|
if [[ "$mutation" == symlink ]]; then
|
|
helper_modeled_mutation=''
|
|
mv -- "$staged_file" "${staged_file}.real"
|
|
ln -s -- "$(basename -- "$staged_file").real" "$staged_file"
|
|
fi
|
|
: >"$stage_trace"
|
|
export K3SLRA1_STAGE_TRACE="$stage_trace"
|
|
export K3SLRA1_EXPECTED_CONTRACT="${modeled_capture}/contract/local-recovery.env"
|
|
export K3SLRA1_EXPECTED_MOUNT="$modeled_mount"
|
|
export K3SLRA1_EXPECTED_CAPTURE="$modeled_capture"
|
|
source "$A1_HELPER_PATH"
|
|
_k3slra1h_euid_is_root() {
|
|
(( $# == 0 )) || return 97
|
|
printf 'root-gate\n' >>"$stage_trace"
|
|
}
|
|
install_helper_command_model
|
|
if k3slra1_check_main "$modeled_mount" proof "$modeled_capture"; then rc=0; else rc=$?; fi
|
|
if (( rc == 0 || rc == 97 )) || [[ "$(<"$stage_trace")" != root-gate ]]; then
|
|
printf '%s:%s\n' "$file_kind" "$mutation" >>"$violations"
|
|
fi
|
|
done
|
|
done
|
|
[[ ! -s "$violations" ]]
|
|
)
|
|
|
|
test_helper_staged_file_metadata_is_exact() (
|
|
local modeled_capture="${fixture_root}/helper-file-fields-${BASHPID}"
|
|
local modeled_mount="${modeled_capture}/proof-mount"
|
|
local stage_trace="${modeled_capture}.trace" rc=0
|
|
case_root="$modeled_capture"
|
|
mount_root="$modeled_mount"
|
|
capture_root="$modeled_capture"
|
|
current_case=helper-staged-file-extra-field
|
|
current_role=proof
|
|
write_staged_helper_dependencies "$modeled_capture"
|
|
mkdir -p -- "$modeled_mount" "${modeled_capture}/home"
|
|
chmod 0700 -- "$modeled_mount" "${modeled_capture}/home"
|
|
: >"$stage_trace"
|
|
export K3SLRA1_STAGE_TRACE="$stage_trace"
|
|
export K3SLRA1_EXPECTED_CONTRACT="${modeled_capture}/contract/local-recovery.env"
|
|
export K3SLRA1_EXPECTED_MOUNT="$modeled_mount"
|
|
export K3SLRA1_EXPECTED_CAPTURE="$modeled_capture"
|
|
source "$A1_HELPER_PATH"
|
|
_k3slra1h_euid_is_root() {
|
|
(( $# == 0 )) || return 97
|
|
printf 'root-gate\n' >>"$stage_trace"
|
|
}
|
|
install_helper_command_model
|
|
if k3slra1_check_main "$modeled_mount" proof "$modeled_capture"; then rc=0; else rc=$?; fi
|
|
(( rc != 0 && rc != 97 )) || return 1
|
|
[[ "$(<"$stage_trace")" == root-gate ]]
|
|
)
|
|
|
|
test_helper_xtrace_is_rejected() (
|
|
local public_stdout="${fixture_root}/helper-xtrace.stdout"
|
|
local public_stderr="${fixture_root}/helper-xtrace.stderr"
|
|
local downstream_trace="${fixture_root}/helper-xtrace.downstream"
|
|
local xtrace_sink="${fixture_root}/helper-xtrace.trace" rc=0
|
|
: >"$downstream_trace"
|
|
: >"$xtrace_sink"
|
|
source "$A1_HELPER_PATH"
|
|
_k3slra1h_euid_is_root() {
|
|
(( $# == 0 )) || return 97
|
|
printf 'root-gate\n' >>"$downstream_trace"
|
|
}
|
|
_k3slra1h_load_staged_dependencies() {
|
|
printf 'loader\n' >>"$downstream_trace"
|
|
return 97
|
|
}
|
|
_k3slra1_verify_payload_root() {
|
|
printf 'transaction\n' >>"$downstream_trace"
|
|
return 97
|
|
}
|
|
exec 9>"$xtrace_sink"
|
|
if ( BASH_XTRACEFD=9; set -x; k3slra1_check_main /fixture/proof-mount proof /fixture ) \
|
|
>"$public_stdout" 2>"$public_stderr"; then
|
|
rc=0
|
|
else
|
|
rc=$?
|
|
fi
|
|
exec 9>&-
|
|
(( rc != 0 && rc != 97 )) || return 1
|
|
[[ "$(<"$downstream_trace")" == root-gate ]] || return 1
|
|
[[ -s "$xtrace_sink" ]] || return 1
|
|
[[ ! -s "$public_stdout" && ! -s "$public_stderr" ]]
|
|
)
|
|
|
|
test_static_preflight_adversaries() (
|
|
local scanner_root="${fixture_root}/static-scanner-${BASHPID}"
|
|
local valid_source quoted_absolute_source readonly_command_source indirect_source
|
|
local bare_unknown_source unsafe_umask_source valid_subshell_source
|
|
local generic_subshell_source library_input_process_source library_stderr_process_source
|
|
local helper_input_process_source helper_guard_trap_source
|
|
mkdir -- "$scanner_root"
|
|
valid_source="${scanner_root}/valid-library.sh"
|
|
quoted_absolute_source="${scanner_root}/quoted-absolute.sh"
|
|
readonly_command_source="${scanner_root}/readonly-command.sh"
|
|
indirect_source="${scanner_root}/indirect-command.sh"
|
|
bare_unknown_source="${scanner_root}/bare-unknown-command.sh"
|
|
unsafe_umask_source="${scanner_root}/unsafe-umask.sh"
|
|
valid_subshell_source="${scanner_root}/valid-subshell.sh"
|
|
generic_subshell_source="${scanner_root}/generic-subshell.sh"
|
|
library_input_process_source="${scanner_root}/library-input-process.sh"
|
|
library_stderr_process_source="${scanner_root}/library-stderr-process.sh"
|
|
helper_input_process_source="${scanner_root}/helper-input-process.sh"
|
|
helper_guard_trap_source="${scanner_root}/helper-guard-trap.sh"
|
|
|
|
printf '%s\n' \
|
|
'readonly SAFE=1' \
|
|
'_k3slra1_command() {' \
|
|
' "$@"' \
|
|
'}' \
|
|
'dispatch() {' \
|
|
' _k3slra1_command /usr/bin/sudo --non-interactive -- /usr/bin/env -i /usr/bin/bash' \
|
|
'}' >"$valid_source"
|
|
a1_static_source_is_safe "$valid_source" library || return 1
|
|
|
|
cp -- "$valid_source" "$valid_subshell_source"
|
|
printf '%s\n' \
|
|
'_k3slra1_sqlite_quick_check() (' \
|
|
' return 1' \
|
|
')' >>"$valid_subshell_source"
|
|
a1_static_source_is_safe "$valid_subshell_source" library || return 1
|
|
|
|
cp -- "$valid_source" "$generic_subshell_source"
|
|
printf '%s\n' \
|
|
'generic_subshell() (' \
|
|
' return 1' \
|
|
')' >>"$generic_subshell_source"
|
|
a1_static_source_is_safe "$generic_subshell_source" library && return 1
|
|
|
|
cp -- "$valid_source" "$quoted_absolute_source"
|
|
printf '%s\n' \
|
|
'quoted_bypass() {' \
|
|
' "/usr/bin/sudo" --version' \
|
|
'}' >>"$quoted_absolute_source"
|
|
a1_static_source_is_safe "$quoted_absolute_source" library && return 1
|
|
|
|
{
|
|
printf '%s\n' "readonly SUDO='/usr/bin/sudo'"
|
|
tail -n +2 -- "$valid_source"
|
|
} >"$readonly_command_source"
|
|
a1_static_source_is_safe "$readonly_command_source" library && return 1
|
|
|
|
cp -- "$valid_source" "$indirect_source"
|
|
printf '%s\n' \
|
|
'indirect_bypass() {' \
|
|
' local runner=sudo' \
|
|
' "$runner" --version' \
|
|
'}' >>"$indirect_source"
|
|
a1_static_source_is_safe "$indirect_source" library && return 1
|
|
|
|
cp -- "$valid_source" "$bare_unknown_source"
|
|
printf '%s\n' \
|
|
'unknown_bypass() {' \
|
|
' curl https://invalid.example' \
|
|
'}' >>"$bare_unknown_source"
|
|
a1_static_source_is_safe "$bare_unknown_source" library && return 1
|
|
|
|
cp -- "$valid_source" "$unsafe_umask_source"
|
|
printf '%s\n' \
|
|
'unsafe_umask() {' \
|
|
' umask 000' \
|
|
'}' >>"$unsafe_umask_source"
|
|
a1_static_source_is_safe "$unsafe_umask_source" library && return 1
|
|
|
|
cp -- "$valid_source" "$library_input_process_source"
|
|
printf '%s\n' \
|
|
'input_process_bypass() {' \
|
|
" mapfile -d '' -t records < <(_k3slra1_metadata_packet \"\$producer\")" \
|
|
'}' >>"$library_input_process_source"
|
|
a1_static_source_is_safe "$library_input_process_source" library && return 1
|
|
|
|
cp -- "$valid_source" "$library_stderr_process_source"
|
|
printf '%s\n' \
|
|
'stderr_process_bypass() {' \
|
|
' if _k3slra1_command /usr/bin/stat -- "$path" 2> >(_k3slra1_wrong_marker); then' \
|
|
' :' \
|
|
' fi' \
|
|
'}' >>"$library_stderr_process_source"
|
|
a1_static_source_is_safe "$library_stderr_process_source" library && return 1
|
|
|
|
cp -- "$A1_HELPER_PATH" "$helper_input_process_source"
|
|
printf '%s\n' \
|
|
'helper_process_bypass() {' \
|
|
" mapfile -d '' -t records < <(_k3slra1h_metadata_packet \"\$target\" extra)" \
|
|
'}' >>"$helper_input_process_source"
|
|
a1_static_source_is_safe "$helper_input_process_source" helper && return 1
|
|
|
|
/usr/bin/awk '
|
|
$0 == " trap \047_k3slra1h_direct_signal_rc=130; if (( _k3slra1h_direct_rc == 0 )); then _k3slra1h_direct_rc=130; fi\047 INT" {
|
|
print " trap \047printf hostile-guard\047 INT"
|
|
next
|
|
}
|
|
{ print }
|
|
' "$A1_HELPER_PATH" >"$helper_guard_trap_source"
|
|
a1_static_source_is_safe "$helper_guard_trap_source" helper && return 1
|
|
return 0
|
|
)
|
|
|
|
test_real_env_i_sentinel_probe() (
|
|
local probe_root="${fixture_root}/env-i-${BASHPID}" probe_script bash_env_file sentinel output
|
|
probe_script="${probe_root}/probe.sh"
|
|
bash_env_file="${probe_root}/hostile-bash-env"
|
|
sentinel="${probe_root}/sentinel"
|
|
mkdir -- "$probe_root"
|
|
printf 'printf leak >%q\n' "$sentinel" >"$bash_env_file"
|
|
printf '%s\n' \
|
|
'[[ -z "${BASH_ENV+x}" ]] || exit 91' \
|
|
'declare -F k3slra1_hostile_export >/dev/null && exit 92' \
|
|
'printf "env-clean\n"' >"$probe_script"
|
|
chmod 0700 -- "$probe_script"
|
|
k3slra1_hostile_export() { :; }
|
|
export -f k3slra1_hostile_export
|
|
BASH_ENV="$bash_env_file" output="$(
|
|
/usr/bin/env -i PATH=/usr/sbin:/usr/bin:/sbin:/bin LC_ALL=C \
|
|
/usr/bin/bash --noprofile --norc -- "$probe_script"
|
|
)" || return 1
|
|
[[ "$output" == env-clean && ! -e "$sentinel" ]]
|
|
)
|
|
|
|
# Keep the first RED focused and cheap: this compositional matrix allocates one
|
|
# synthetic payload and cannot reach live commands because every seam is fake.
|
|
assert_succeeds 'all capture APIs preserve status, NUL, LF, stderr, and outputs' \
|
|
test_command_capture_hostile_matrix
|
|
|
|
readonly -a expected_failure_cases=(
|
|
bundle_none
|
|
bundle_two
|
|
bundle_bad_name
|
|
matching_bundle_symlink
|
|
manifest_missing
|
|
manifest_no_final_lf
|
|
manifest_nul
|
|
manifest_cr
|
|
manifest_uppercase_hash
|
|
manifest_one_space_separator
|
|
manifest_tab_separator
|
|
manifest_parent_escape
|
|
manifest_absolute_path
|
|
manifest_double_slash
|
|
manifest_dot_segment
|
|
manifest_trailing_slash
|
|
manifest_invalid_character
|
|
manifest_path_513_bytes
|
|
manifest_65537_bytes
|
|
manifest_duplicate_path
|
|
manifest_self_target
|
|
manifest_hash_mismatch
|
|
manifest_target_symlink
|
|
manifest_target_hardlink_alias
|
|
manifest_target_nlink_two
|
|
manifest_duplicate_inode
|
|
manifest_drift_after_sqlite
|
|
sqlite_wal_drift_after_child
|
|
preexisting_unmanifested_file
|
|
target_count_79
|
|
target_count_81
|
|
target_bytes_22857828
|
|
target_bytes_22857830
|
|
sqlite_mixed_layout
|
|
legacy_sqlite_mixed_layout
|
|
sqlite_alternate_partial_layout
|
|
sqlite_missing_wal
|
|
sqlite_missing_shm
|
|
sqlite_not_in_manifest
|
|
sqlite_inode_not_manifest_target
|
|
sqlite_sidecar_symlink
|
|
sqlite_sidecar_hardlink
|
|
sqlite_layout_unmanifested_symlink
|
|
sqlite_layout_unmanifested_fifo
|
|
sqlite_layout_unmanifested_directory
|
|
sqlite_quick_check_extra_line
|
|
sqlite_quick_check_error
|
|
sqlite_quick_check_stderr
|
|
sqlite_quick_check_nul
|
|
sqlite_capture_alias
|
|
sqlite_capture_preexisting
|
|
sqlite_capture_stderr_preexisting
|
|
sqlite_capture_stderr_fifo
|
|
sqlite_capture_stderr_symlink
|
|
sqlite_capture_path_swap
|
|
sqlite_capture_parent_drift
|
|
sqlite_capture_wrong_owner
|
|
sqlite_capture_wrong_mode
|
|
sqlite_capture_cleanup_failure
|
|
mount_missing_noload
|
|
mount_wrong_source
|
|
mount_wrong_target
|
|
mount_wrong_fstype
|
|
mount_wrong_major_minor
|
|
mount_missing_vfs_ro
|
|
mount_forbidden_vfs_rw
|
|
mount_evidence_multiline
|
|
mount_evidence_cr
|
|
mount_evidence_nul
|
|
mount_evidence_stderr
|
|
findmnt_valid_record_stderr
|
|
readlink_valid_record_stderr
|
|
lsblk_valid_record_stderr
|
|
mount_evidence_drift
|
|
mount_third_evidence_drift
|
|
proof_mapper_swap
|
|
baseline_mapper_swap
|
|
final_mapper_swap
|
|
mapper_canonical_mismatch
|
|
source_expected_canonical_mismatch
|
|
mapper_not_block
|
|
mapper_major_minor_mismatch
|
|
mapping_not_readonly
|
|
mapping_evidence_drift
|
|
mapping_third_evidence_drift
|
|
blockdev_multiline
|
|
blockdev_cr
|
|
blockdev_nul
|
|
blockdev_stderr
|
|
blockdev_valid_record_stderr
|
|
bundle_wrong_owner
|
|
manifest_wrong_mode
|
|
payload_wrong_device
|
|
payload_component_symlink
|
|
payload_write_attempt
|
|
)
|
|
|
|
for fixture_case in "${expected_failure_cases[@]}"; do
|
|
assert_fails "$fixture_case is rejected" run_case "$fixture_case"
|
|
done
|
|
|
|
assert_succeeds 'canonical payload is accepted' run_case canonical_pass
|
|
assert_succeeds 'legacy payload is accepted' run_case legacy_pass
|
|
assert_succeeds 'original baseline role is accepted with the main mapper' \
|
|
run_case original_baseline_pass
|
|
assert_succeeds 'plus path is accepted' run_case manifest_plus_path_pass
|
|
assert_succeeds '512-byte path field is accepted' run_case manifest_path_512_bytes_pass
|
|
assert_succeeds 'unrelated lost+found directory is accepted' run_case unrelated_lost_found_pass
|
|
assert_succeeds 'direct A1 APIs preserve strict output contracts' test_direct_api_contract
|
|
assert_succeeds 'dynamic output aliases fail before mutation or command dispatch' \
|
|
test_dynamic_output_aliases_are_closed
|
|
assert_succeeds 'mapper block-type stat precedes lsblk and blockdev' \
|
|
test_mapper_stat_precedes_lsblk
|
|
assert_succeeds 'canonical mapper nodes are restricted to the dm namespace' \
|
|
test_canonical_mapper_namespace_is_closed
|
|
assert_succeeds 'command model requires dereferenced FD stat and raw canonical lsblk argv' \
|
|
test_command_model_requires_canonical_argv
|
|
assert_succeeds 'INT during SQLite check cleans both owned captures' \
|
|
test_sqlite_signal_cleanup_contract INT
|
|
assert_succeeds 'TERM during SQLite check cleans both owned captures' \
|
|
test_sqlite_signal_cleanup_contract TERM
|
|
assert_succeeds 'TERM status propagates through the real root SQLite path' \
|
|
test_root_signal_status_contract sqlite_capture_term_cleanup 143
|
|
assert_succeeds 'cleanup failure takes precedence over TERM status' \
|
|
test_root_signal_status_contract sqlite_capture_term_cleanup_failure 1
|
|
assert_succeeds 'SQLite quick-check preserves caller umask and signal traps' \
|
|
test_sqlite_preserves_shell_state
|
|
assert_succeeds 'stdout hardlink drift is rejected after child FD re-pin' \
|
|
test_sqlite_hardlink_swap_is_rejected stdout
|
|
assert_succeeds 'stderr hardlink drift is rejected after child FD re-pin' \
|
|
test_sqlite_hardlink_swap_is_rejected stderr
|
|
assert_succeeds 'SQLite layout members remain bound to manifest-time identities' \
|
|
test_sqlite_layout_identity_binding
|
|
assert_succeeds 'manifest targets preserve heterogeneous ownership and modes' \
|
|
test_manifest_accepts_heterogeneous_target_metadata
|
|
assert_succeeds 'partial stdout cleanup verifies unlink postcondition' \
|
|
test_partial_stdout_unlink_requires_absence
|
|
assert_succeeds 'bounded manifest reader accepts exactly 65536 bytes' \
|
|
bounded_manifest_reader_accepts_65536_bytes
|
|
assert_succeeds 'bounded manifest reader rejects 65537 bytes without output mutation' \
|
|
bounded_manifest_reader_rejects_65537_bytes
|
|
assert_succeeds 'parent dispatches only the fixed env-clean root helper' test_parent_helper_dispatch
|
|
assert_succeeds 'parent suppresses hostile root-helper stdout and stderr' \
|
|
test_parent_helper_output_is_suppressed
|
|
assert_succeeds 'sourcing helper never auto-runs main' test_helper_source_guard
|
|
assert_succeeds 'sourcing helper preserves caller signal traps' \
|
|
test_helper_source_preserves_signal_traps
|
|
assert_succeeds 'direct helper TERM waits for nested cleanup and exits 143' \
|
|
test_helper_direct_term_waits_for_nested_cleanup 0 143
|
|
assert_succeeds 'direct helper preserves nested cleanup failure over TERM' \
|
|
test_helper_direct_term_waits_for_nested_cleanup 1 1
|
|
assert_succeeds 'direct non-root helper main is fail-closed' test_helper_nonroot_main_is_closed
|
|
assert_succeeds 'actual helper loader composes staged shared, A1, contract, and transaction' \
|
|
test_helper_compositional_main
|
|
assert_succeeds 'staged shared source failure stops before A1 and transaction' \
|
|
test_helper_staged_failure_is_closed shared-failure
|
|
assert_succeeds 'staged A1 source failure stops before contract and transaction' \
|
|
test_helper_staged_failure_is_closed a1-failure
|
|
assert_succeeds 'staged contract parser failure stops before transaction' \
|
|
test_helper_staged_failure_is_closed contract-failure
|
|
assert_succeeds 'contract main mapping mismatch stops before transaction' \
|
|
test_helper_staged_failure_is_closed mapping-mismatch
|
|
assert_succeeds 'helper pins every staged intermediate directory' \
|
|
test_helper_intermediate_directories_are_pinned
|
|
assert_succeeds 'helper pins owner, mode, device, nlink, type, and path for staged files' \
|
|
test_helper_staged_files_are_pinned
|
|
assert_succeeds 'helper staged metadata parser rejects extra fields' \
|
|
test_helper_staged_file_metadata_is_exact
|
|
assert_succeeds 'helper rejects xtrace before staged loading or transaction' \
|
|
test_helper_xtrace_is_rejected
|
|
assert_succeeds 'static preflight rejects quoted, readonly, indirect, and unknown commands' \
|
|
test_static_preflight_adversaries
|
|
assert_succeeds 'real sudo-free env-i boundary removes hostile shell sentinels' \
|
|
test_real_env_i_sentinel_probe
|
|
|
|
printf 'PASS: strict A1 payload fixtures\n'
|