WIP: Paused over-hardening fix wave (boot checkout verification, double-read package manifest)
This commit is contained in:
@@ -51,7 +51,9 @@ copy_hash_inputs() {
|
||||
find "$repo_dir/setup/provenance" -type f -print0
|
||||
)
|
||||
mkdir -p "$root/setup/lib"
|
||||
cp -- "$repo_dir/setup/lib/artifact-provenance" "$root/setup/lib/artifact-provenance"
|
||||
cp -- "$repo_dir/setup/lib/artifact-provenance" \
|
||||
"$repo_dir/setup/lib/extras-catalog" \
|
||||
"$repo_dir/setup/lib/machine-role" "$root/setup/lib/"
|
||||
}
|
||||
|
||||
# A PANAMA_PATH that looks enough like the real one for install to run, and
|
||||
@@ -61,7 +63,7 @@ build_fixture() {
|
||||
rm -rf "$root"
|
||||
mkdir -p "$root/bin" "$root/setup/scripts" "$root/setup/packages" \
|
||||
"$root/setup/lib" "$root/setup/provenance/keys" \
|
||||
"$root/config/dot/quickshell/scripts"
|
||||
"$root/config/dot/quickshell/scripts" "$root/tmp"
|
||||
|
||||
cp "$installer" "$root/install"
|
||||
: >"$root/bin/ascii"
|
||||
@@ -126,6 +128,46 @@ EOF
|
||||
#!/usr/bin/env bash
|
||||
printf 'dnf-transaction\n' >>"$PANAMA_RAN"
|
||||
exit 0
|
||||
EOF
|
||||
cat >"$root/shim/mv" <<'EOF'
|
||||
#!/usr/bin/env bash
|
||||
set -euo pipefail
|
||||
destination="${!#}"
|
||||
if [[ "${STUB_SIGNAL_PACKAGES_HASH:-0}" == 1 \
|
||||
&& "$destination" == */state/panama/packages-hash ]]; then
|
||||
printf 'signal:packages-receipt\n' >>"$PANAMA_RAN"
|
||||
pgid="$(ps -o pgid= -p $$ | tr -d ' ')"
|
||||
kill -TERM -- "-$pgid"
|
||||
sleep 2
|
||||
fi
|
||||
exec /usr/bin/mv "$@"
|
||||
EOF
|
||||
cat >"$root/shim/mktemp" <<'EOF'
|
||||
#!/usr/bin/env bash
|
||||
set -euo pipefail
|
||||
if [[ "${STUB_SIGNAL_HASH_WORK:-0}" == 1 && "${1:-}" == -d ]]; then
|
||||
directory="$(/usr/bin/mktemp "$@")"
|
||||
printf '%s\n' "$directory"
|
||||
printf 'signal:packages-hash-work\n' >>"$PANAMA_RAN"
|
||||
pgid="$(ps -o pgid= -p $$ | tr -d ' ')"
|
||||
kill -TERM -- "-$pgid"
|
||||
sleep 2
|
||||
fi
|
||||
exec /usr/bin/mktemp "$@"
|
||||
EOF
|
||||
cat >"$root/shim/mkdir" <<'EOF'
|
||||
#!/usr/bin/env bash
|
||||
set -euo pipefail
|
||||
target="${!#}"
|
||||
if [[ "${STUB_SIGNAL_HASH_WORK:-0}" == 1 \
|
||||
&& "$(basename -- "$target")" == panama-packages-hash.* ]]; then
|
||||
/usr/bin/mkdir "$@"
|
||||
printf 'signal:packages-hash-work\n' >>"$PANAMA_RAN"
|
||||
pgid="$(ps -o pgid= -p $$ | tr -d ' ')"
|
||||
kill -TERM -- "-$pgid"
|
||||
sleep 2
|
||||
fi
|
||||
exec /usr/bin/mkdir "$@"
|
||||
EOF
|
||||
for prerequisite in gum lspci mokutil fwupdmgr; do
|
||||
ln -s gsettings "$root/shim/$prerequisite"
|
||||
@@ -139,7 +181,8 @@ run_install() {
|
||||
local status=0
|
||||
: >"$root/ran"
|
||||
PATH="$root/shim:$PATH" PANAMA_PATH="$root" PANAMA_RAN="$root/ran" \
|
||||
XDG_STATE_HOME="$root/state" bash "$root/install" "$@" \
|
||||
XDG_STATE_HOME="$root/state" TMPDIR="$root/tmp" \
|
||||
/usr/bin/setsid bash "$root/install" "$@" \
|
||||
>"$root/out" 2>&1 || status=$?
|
||||
cat "$root/ran"
|
||||
return "$status"
|
||||
@@ -147,7 +190,8 @@ run_install() {
|
||||
|
||||
run_hash() {
|
||||
local root="$1"
|
||||
sed -n '/^hash_packages() {/,/^}$/p' "$root/install" >"$root/hash-only"
|
||||
sed -n '/^_collect_package_inputs() {/,/^PACKAGE_START_HASH=/p' \
|
||||
"$root/install" >"$root/hash-only"
|
||||
printf 'set -uo pipefail\nhash_packages\n' >>"$root/hash-only"
|
||||
PANAMA_PATH="$root" bash "$root/hash-only" 2>"$root/hash-only.err"
|
||||
}
|
||||
@@ -255,7 +299,8 @@ grep -qx 'install-packages' <<<"$ran_forced" \
|
||||
# Dynamically discovering them makes this fail when a new reviewed input is
|
||||
# added but omitted from hash_packages.
|
||||
for relative in "${package_inputs[@]}" "${provenance_inputs[@]}" \
|
||||
'setup/scripts/install-packages' 'setup/lib/artifact-provenance'; do
|
||||
'setup/scripts/install-packages' 'setup/lib/artifact-provenance' \
|
||||
'setup/lib/extras-catalog' 'setup/lib/machine-role'; do
|
||||
printf 'changed %s\n' "$relative" >>"$tmp/a/$relative"
|
||||
install_status=0
|
||||
ran_input_changed="$(run_install "$tmp/a" --upgrade)" || install_status=$?
|
||||
@@ -282,7 +327,8 @@ done
|
||||
# Fixed hash inputs must not silently disappear or degrade into a directory or
|
||||
# link. An unreadable package input also proves a failed content read cannot be
|
||||
# hidden by the final digest command.
|
||||
for fixed_input in setup/scripts/install-packages setup/lib/artifact-provenance; do
|
||||
for fixed_input in setup/scripts/install-packages setup/lib/artifact-provenance \
|
||||
setup/lib/extras-catalog setup/lib/machine-role; do
|
||||
for case_name in missing directory symlink unreadable; do
|
||||
case_root="$tmp/hash-${fixed_input//\//-}-$case_name"
|
||||
build_fixture "$case_root"
|
||||
@@ -305,6 +351,27 @@ build_fixture "$read_failure_root"
|
||||
chmod 000 "$read_failure_root/${package_inputs[0]}"
|
||||
assert_hash_failure "$read_failure_root" "${package_inputs[0]} unreadable"
|
||||
|
||||
# Discovery must reject a symlink instead of silently dropping it from the
|
||||
# receipt while a later consumer follows it.
|
||||
for discovered_root in setup/packages setup/provenance; do
|
||||
case_root="$tmp/hash-${discovered_root//\//-}-symlink"
|
||||
build_fixture "$case_root"
|
||||
printf 'linked installer input\n' >"$case_root/symlink-target"
|
||||
ln -s "$case_root/symlink-target" "$case_root/$discovered_root/symlink-input"
|
||||
assert_hash_failure "$case_root" "$discovered_root symlink input"
|
||||
done
|
||||
|
||||
# Discovery roots are behavior inputs too. GNU find -P treats a symlink passed
|
||||
# as its starting path as an empty traversal, so checking only descendants can
|
||||
# silently erase a whole package or provenance tree from the receipt.
|
||||
for discovered_root in setup/packages setup/provenance; do
|
||||
case_root="$tmp/hash-${discovered_root//\//-}-root-symlink"
|
||||
build_fixture "$case_root"
|
||||
mv -- "$case_root/$discovered_root" "$case_root/$discovered_root.real"
|
||||
ln -s "$case_root/$discovered_root.real" "$case_root/$discovered_root"
|
||||
assert_hash_failure "$case_root" "$discovered_root discovery-root symlink"
|
||||
done
|
||||
|
||||
# A hash failure is an installer failure, not a reason to skip the package
|
||||
# stage and retain a stale stamp.
|
||||
build_fixture "$tmp/hash-failure"
|
||||
@@ -320,6 +387,61 @@ grep -qx 'install-packages' <<<"$ran_hash_failure" \
|
||||
cmp -s -- "$tmp/hash-failure/stamp-before" "$tmp/hash-failure/state/panama/packages-hash" \
|
||||
|| note 'a failed package-state hash wrote a new packages-hash stamp'
|
||||
|
||||
# The stage may race its own input receipt. A successful stage that changes a
|
||||
# sourced behavior file must not stamp the new digest as though it were the
|
||||
# bytes used to decide this run.
|
||||
build_fixture "$tmp/hash-drift"
|
||||
cat >"$tmp/hash-drift/setup/scripts/install-packages" <<'EOF'
|
||||
#!/usr/bin/env bash
|
||||
if [[ "${1:-}" == --trust-preflight ]]; then
|
||||
printf 'trust-preflight\n' >>"$PANAMA_RAN"
|
||||
exit 0
|
||||
fi
|
||||
printf 'install-packages\n' >>"$PANAMA_RAN"
|
||||
printf '# changed during package stage\n' >>"$PANAMA_PATH/setup/lib/machine-role"
|
||||
EOF
|
||||
chmod +x "$tmp/hash-drift/setup/scripts/install-packages"
|
||||
install_status=0
|
||||
run_install "$tmp/hash-drift" --upgrade >/dev/null || install_status=$?
|
||||
[[ "$install_status" -ne 0 ]] \
|
||||
|| note 'mid-stage package input drift returned success'
|
||||
[[ ! -e "$tmp/hash-drift/state/panama/packages-hash" ]] \
|
||||
|| note 'mid-stage package input drift stamped bytes the stage did not start with'
|
||||
|
||||
# The hash workspace exists before command substitution publishes its pathname.
|
||||
# A process-group signal in that window must still remove the private tree.
|
||||
build_fixture "$tmp/hash-work-signal"
|
||||
install_status=0
|
||||
signal_run="$(STUB_SIGNAL_HASH_WORK=1 \
|
||||
run_install "$tmp/hash-work-signal" --upgrade)" || install_status=$?
|
||||
[[ "$install_status" -eq 143 ]] \
|
||||
|| note "package hash workspace signal returned $install_status instead of 143"
|
||||
grep -qx 'signal:packages-hash-work' <<<"$signal_run" \
|
||||
|| note 'package hash workspace adapter did not deliver a real process-group signal'
|
||||
[[ -z "$(find "$tmp/hash-work-signal/tmp" -mindepth 1 -print -quit)" ]] \
|
||||
|| note 'package hash workspace signal left a private temporary directory'
|
||||
|
||||
# A real process-group signal at the final receipt rename must preserve the
|
||||
# prior stamp and remove the private temporary receipt.
|
||||
build_fixture "$tmp/hash-receipt-signal"
|
||||
run_install "$tmp/hash-receipt-signal" --upgrade >/dev/null
|
||||
cp -- "$tmp/hash-receipt-signal/state/panama/packages-hash" \
|
||||
"$tmp/hash-receipt-signal/stamp-before"
|
||||
install_status=0
|
||||
signal_run="$(STUB_SIGNAL_PACKAGES_HASH=1 \
|
||||
run_install "$tmp/hash-receipt-signal" --upgrade --packages)" \
|
||||
|| install_status=$?
|
||||
[[ "$install_status" -eq 143 ]] \
|
||||
|| note "package receipt signal returned $install_status instead of 143"
|
||||
grep -qx 'signal:packages-receipt' <<<"$signal_run" \
|
||||
|| note 'package receipt signal adapter did not deliver a real process-group signal'
|
||||
cmp -s -- "$tmp/hash-receipt-signal/stamp-before" \
|
||||
"$tmp/hash-receipt-signal/state/panama/packages-hash" \
|
||||
|| note 'package receipt signal replaced the prior hash stamp'
|
||||
[[ -z "$(find "$tmp/hash-receipt-signal/state/panama" \
|
||||
-name '.packages-hash.*' -print -quit)" ]] \
|
||||
|| note 'package receipt signal left a temporary hash stamp'
|
||||
|
||||
# A failing stage must not record the hash, or the failure is hidden forever.
|
||||
build_fixture "$tmp/c" 1
|
||||
install_status=0
|
||||
@@ -368,6 +490,64 @@ for suppressed in link-dotfiles link-skills link-user change-settings install-ha
|
||||
&& note "stage-time Terra trust failure still ran $suppressed"
|
||||
done
|
||||
|
||||
# Exercise the complete real package entrypoint at the second boundary. The
|
||||
# outer preflight sees no Terra repository; the same DNF adapter exposes an
|
||||
# unsafe enabled Terra identity to the package stage's own preflight. Removing
|
||||
# that production call would reach the transaction marker below.
|
||||
real_preflight_root="$tmp/real-second-preflight"
|
||||
build_fixture "$real_preflight_root"
|
||||
cp -- "$repo_dir/setup/scripts/install-packages" \
|
||||
"$real_preflight_root/setup/scripts/install-packages"
|
||||
chmod +x "$real_preflight_root/setup/scripts/install-packages"
|
||||
mkdir -p "$real_preflight_root/state/panama"
|
||||
printf 'server\n' >"$real_preflight_root/state/panama/role"
|
||||
cat >"$real_preflight_root/shim/dnf" <<'EOF'
|
||||
#!/usr/bin/env bash
|
||||
set -euo pipefail
|
||||
if [[ "$*" == '--quiet --no-plugins --dump-repo-config=*' ]]; then
|
||||
count=0
|
||||
[[ ! -f "$PANAMA_DNF_DUMP_COUNT" ]] || read -r count <"$PANAMA_DNF_DUMP_COUNT"
|
||||
count=$((count + 1))
|
||||
printf '%s\n' "$count" >"$PANAMA_DNF_DUMP_COUNT"
|
||||
printf 'dnf-dump\n' >>"$PANAMA_RAN"
|
||||
printf '======== "fedora" repository configuration: ========\n'
|
||||
printf 'baseurl = \nenabled = 1\ngpgcheck = 1\n'
|
||||
printf 'gpgkey = file:///etc/pki/rpm-gpg/RPM-GPG-KEY-fedora-44-primary\n'
|
||||
printf 'metalink = https://mirrors.fedoraproject.org/metalink\nmirrorlist\n'
|
||||
printf 'pkg_gpgcheck = 0\nrepo_gpgcheck = 0\n'
|
||||
if (( count == 2 )); then
|
||||
printf '======== "terra" repository configuration: ========\n'
|
||||
printf 'baseurl = https://evil.invalid/terra44\nenabled = 1\ngpgcheck = 0\n'
|
||||
printf 'gpgkey = https://evil.invalid/key\n'
|
||||
printf 'metalink = \nmirrorlist = \npkg_gpgcheck = 0\nrepo_gpgcheck = 0\n'
|
||||
fi
|
||||
exit 0
|
||||
fi
|
||||
printf 'dnf-transaction\n' >>"$PANAMA_RAN"
|
||||
exit 0
|
||||
EOF
|
||||
chmod +x "$real_preflight_root/shim/dnf"
|
||||
printf '0\n' >"$real_preflight_root/dnf-dump-count"
|
||||
: >"$real_preflight_root/ran"
|
||||
real_preflight_status=0
|
||||
PATH="$real_preflight_root/shim:$PATH" \
|
||||
PANAMA_PATH="$real_preflight_root" PANAMA_RAN="$real_preflight_root/ran" \
|
||||
PANAMA_DNF_DUMP_COUNT="$real_preflight_root/dnf-dump-count" \
|
||||
XDG_STATE_HOME="$real_preflight_root/state" \
|
||||
bash "$real_preflight_root/install" --upgrade --packages \
|
||||
>"$real_preflight_root/out" 2>&1 || real_preflight_status=$?
|
||||
[[ "$real_preflight_status" -eq 78 ]] \
|
||||
|| note "real second repository preflight returned $real_preflight_status instead of 78"
|
||||
[[ "$(<"$real_preflight_root/dnf-dump-count")" == 2 ]] \
|
||||
|| note "real package entrypoint executed $(<"$real_preflight_root/dnf-dump-count") repository preflights instead of two: $(tr '\n' ' ' <"$real_preflight_root/out")"
|
||||
[[ "$(grep -c '^dnf-dump$' "$real_preflight_root/ran")" -eq 2 ]] \
|
||||
|| note "real second preflight fixture log was: $(tr '\n' ',' <"$real_preflight_root/ran")"
|
||||
for suppressed in dnf-transaction link-dotfiles link-skills link-user change-settings \
|
||||
install-hardware; do
|
||||
grep -qx "$suppressed" "$real_preflight_root/ran" \
|
||||
&& note "real second repository preflight still ran $suppressed"
|
||||
done
|
||||
|
||||
# A full install always runs the stage, whatever any recorded hash says.
|
||||
build_fixture "$tmp/d"
|
||||
install_status=0
|
||||
|
||||
Reference in New Issue
Block a user