From d9c48820a8bac517cb215bdf371afa3ea67a76ff Mon Sep 17 00:00:00 2001 From: DevomB Date: Fri, 2 Oct 2026 02:46:33 -0700 Subject: [PATCH 1/2] An install medium gets its root shell: the test for a medium matched one letter as a whole word, which usb and iso are not, so the display and the serial console asked for a login nobody on a medium can give. The three places that make the test take the whole word now, and a check holds each to the four signed command lines --- build/recipes/console.sh | 2 +- build/service-scripts/testctl.sh | 2 +- build/services/getty-tty1/run | 2 +- tools/tests/medium-shell.sh | 39 ++++++++++++++++++++++++++++++++ 4 files changed, 42 insertions(+), 3 deletions(-) create mode 100755 tools/tests/medium-shell.sh diff --git a/build/recipes/console.sh b/build/recipes/console.sh index 8c9c659d..887ee1e4 100644 --- a/build/recipes/console.sh +++ b/build/recipes/console.sh @@ -51,7 +51,7 @@ if [ -x /usr/sbin/agetty ]; then # serial console is the installer's root shell: -n -l skips login(1). # On an installed system it is an ordinary login prompt; root is locked, # so it admits the first-boot user, not root. - if grep -qw 'kryptik\.media=[a-z]' /proc/cmdline 2>/dev/null; then + if grep -qE '(^| )kryptik\.media=[a-z]+( |$)' /proc/cmdline 2>/dev/null; then exec /usr/sbin/agetty -n -l /usr/bin/bash --keep-baud \ 115200,57600,38400,9600 "$dev" vt220 fi diff --git a/build/service-scripts/testctl.sh b/build/service-scripts/testctl.sh index eb739a18..49916d3c 100755 --- a/build/service-scripts/testctl.sh +++ b/build/service-scripts/testctl.sh @@ -25,7 +25,7 @@ testctl_signed() { # testctl_signed FILE testctl_media() { grep -qs '^media=.\+' /run/kryptik/boot-identity 2>/dev/null && return 0 - grep -qw 'kryptik\.media=[a-z]' /proc/cmdline 2>/dev/null + grep -qE '(^| )kryptik\.media=[a-z]+( |$)' /proc/cmdline 2>/dev/null } testctl_load() { diff --git a/build/services/getty-tty1/run b/build/services/getty-tty1/run index b80c4068..7ccce453 100755 --- a/build/services/getty-tty1/run +++ b/build/services/getty-tty1/run @@ -2,7 +2,7 @@ # Authenticated login on tty1; the desktop session starts from the user's # profile. An install medium's passwordless root shell is here as well as on a # serial console (kryptik-console), since whoever installs may be at either. -if grep -qw 'kryptik\.media=[a-z]' /proc/cmdline 2>/dev/null; then +if grep -qE '(^| )kryptik\.media=[a-z]+( |$)' /proc/cmdline 2>/dev/null; then exec /usr/sbin/agetty -n -l /usr/bin/bash --noclear tty1 linux fi exec /usr/sbin/agetty --noclear tty1 linux diff --git a/tools/tests/medium-shell.sh b/tools/tests/medium-shell.sh new file mode 100755 index 00000000..7146a4c7 --- /dev/null +++ b/tools/tests/medium-shell.sh @@ -0,0 +1,39 @@ +#!/usr/bin/env bash +# The test "this is an install medium", as the console wrapper, the tty1 getty +# and the control-disk reader each make it, against the four command lines +# stage 06 signs: a medium gets the root shell, an installed slot the login +# prompt. Offline. +set -uo pipefail +ROOT="$(cd "$(dirname "${BASH_SOURCE[0]}")/../.." && pwd)" + +PASS=0; FAIL=0 +green() { printf ' PASS %s\n' "$1"; PASS=$((PASS + 1)); } +red() { printf ' FAIL %s\n' "$1"; FAIL=$((FAIL + 1)); } +T="$(mktemp -d)"; trap 'rm -rf "$T"' EXIT + +# As 06-iso.sh writes them; the verity table is one long quoted word. +common='ro rootwait console=tty0 console=ttyS0,115200 panic=10 loglevel=4 mitigations=auto,nosmt pti=on page_alloc.shuffle=1 hash_pointers=always nosmt' +table='0 3145728 verity 1 PARTLABEL=kryptik-media PARTLABEL=kryptik-media 4096 4096 393216 393216 sha256 ab12 cd34 1 panic_on_corruption' +printf 'dm-mod.waitfor=PARTLABEL=kryptik-media dm-mod.create="kroot,,0,ro,%s" root=/dev/dm-0 %s kryptik.media=usb\n' "$table" "$common" > "$T/usb" +printf 'dm-mod.create="kmedia,,0,ro,0 6291456 linear /dev/sr0 1234;kroot,,1,ro,%s" root=/dev/dm-1 %s kryptik.media=iso\n' "$table" "$common" > "$T/iso" +printf 'dm-mod.waitfor=PARTLABEL=kryptik-a dm-mod.create="kroot,,0,ro,%s" root=/dev/dm-0 %s kryptik.slot=a\n' "$table" "$common" > "$T/slot-a" +printf 'dm-mod.waitfor=PARTLABEL=kryptik-b dm-mod.create="kroot,,0,ro,%s" root=/dev/dm-0 %s kryptik.slot=b\n' "$table" "$common" > "$T/slot-b" + +first="" +for f in build/recipes/console.sh build/services/getty-tty1/run build/service-scripts/testctl.sh; do + # The file's own grep, its file argument left off. + cmd="$(sed -n "s|.*\(grep -q[A-Za-z]* '[^']*kryptik[^']*'\) /proc/cmdline.*|\1|p" "${ROOT}/${f}")" + if [[ "$(grep -c . <<<"$cmd")" -ne 1 ]]; then red "${f}: one test of /proc/cmdline for kryptik.media expected, found: ${cmd:-none}"; continue; fi + [[ -z "$first" ]] && first="$cmd" + [[ "$cmd" == "$first" ]] && green "${f}: the same test as the others" || red "${f}: its test differs: ${cmd}" + for line in usb iso; do + if eval "$cmd" '"$T/$line"'; then green "${f}: the ${line} medium's command line is a medium's"; else red "${f}: the ${line} medium is not seen as one"; fi + done + for line in slot-a slot-b; do + if eval "$cmd" '"$T/$line"'; then red "${f}: an installed ${line} is taken for a medium"; else green "${f}: an installed ${line} is not a medium"; fi + done +done + +echo +echo "${PASS} passed, ${FAIL} failed" +[[ "$FAIL" -eq 0 ]] From 49dcde41b5f8dd95098dfaf51bebddf1927e97bf Mon Sep 17 00:00:00 2001 From: DevomB Date: Fri, 2 Oct 2026 02:46:35 -0700 Subject: [PATCH 2/2] Acceptance types at the medium's console: a command runs as root, the installer and the recovery tool are found by name, and poweroff ends the session, on the USB medium and the ISO --- tools/acceptance.sh | 5 +++ tools/image/medium-shell-test.sh | 56 ++++++++++++++++++++++++++++++++ 2 files changed, 61 insertions(+) create mode 100755 tools/image/medium-shell-test.sh diff --git a/tools/acceptance.sh b/tools/acceptance.sh index 55b9beaa..abc733fd 100755 --- a/tools/acceptance.sh +++ b/tools/acceptance.sh @@ -370,6 +370,10 @@ it_ovmf_vars() { "${IMG}/ovmf-vars.sh" --cert "$MEDIUM_CERT" } it_smoke_usb() { "${IMG}/media-smoke.sh" --usb "$MEDIA_USB" --vars clean; } +it_medium_shell() { + local m=(--usb "$MEDIA_USB"); [[ -f "$MEDIA_ISO" ]] && m+=(--iso "$MEDIA_ISO") + "${IMG}/medium-shell-test.sh" "${m[@]}" +} it_smoke_iso() { "${IMG}/media-smoke.sh" --iso "$MEDIA_ISO" --vars clean; } it_smoke_sb() { "${IMG}/media-smoke.sh" --usb "$MEDIA_USB" --vars enrolled; } it_refused() { "${IMG}/media-smoke.sh" --usb "$MEDIA_USB" --vars ms --expect-refused; } @@ -431,6 +435,7 @@ item build licences M host 0 it_licences need_sysroot item build kernel-config M host 0 it_kernel_config need_sources item build support-status M host 0 it_support_status item boot media-smoke-usb M vm 25 it_smoke_usb need_vm +item boot medium-shell M vm 3 it_medium_shell need_vm item boot media-smoke-iso M vm 25 it_smoke_iso need_vm_iso item install install-test M vm 10 it_install need_vm item install state-test M vm 10 it_state need_vm diff --git a/tools/image/medium-shell-test.sh b/tools/image/medium-shell-test.sh new file mode 100755 index 00000000..b63b5527 --- /dev/null +++ b/tools/image/medium-shell-test.sh @@ -0,0 +1,56 @@ +#!/usr/bin/env bash +# The install medium's console is a root shell: whoever boots it types +# kryptik-install there. Every suite installs through a control disk, so this +# is the one that types: a command runs as root, the installer and the +# recovery tool are found by name, and poweroff ends the session. +# +# tools/image/medium-shell-test.sh (--usb IMG | --iso ISO)... [--timeout N] +set -uo pipefail +SELF="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)" +# shellcheck source=/dev/null +source "${SELF}/../../build/lib/common.sh" +trap - ERR; set +e + +MEDIA=(); TIMEOUT=300 +while [[ "$#" -gt 0 ]]; do + case "$1" in + --usb|--iso) MEDIA+=("$1" "${2:?}"); shift 2 ;; + --timeout) TIMEOUT="${2:?}"; shift 2 ;; + -h|--help) sed -n '2,7p' "${BASH_SOURCE[0]}"; exit 0 ;; + *) die "unknown argument: $1" ;; + esac +done +[[ "${#MEDIA[@]}" -gt 0 ]] || die "--usb IMG or --iso ISO is required" +VMDIR="${KRYPTIK_WORK}/vm"; mkdir -p "$VMDIR" + +# shellcheck source=tools/image/suite-lib.sh +source "${SELF}/suite-lib.sh" + +for ((i = 0; i < ${#MEDIA[@]}; i += 2)); do + kind="${MEDIA[i]#--}"; medium="${MEDIA[i+1]}" + [[ -f "$medium" ]] || die "no such medium: ${medium}" + step "the ${kind} medium's console" + out="$("${SELF}/run-ovmf.sh" "--${kind}" "$medium" --mode serve --name "medium-shell-${kind}")" + SER="$(sed -n 's/^serial=//p' <<<"$out")"; PIDF="$(sed -n 's/^pid=//p' <<<"$out")"; LOG="$(sed -n 's/^log=//p' <<<"$out")" + [[ -S "$SER" ]] || die "no serial socket: ${out}" + # The shell's own arithmetic and substitutions answer, never the echo of + # what was typed; a getty drops its first second's input, so the first + # line goes twice. + python3 "$DRV" --serial "$SER" --timeout "$TIMEOUT" \ + "expect:KRYPTIK_SMOKE: END" "sleep:3" \ + 'send:echo shell-$((6 * 7))' "sleep:2" 'send:echo shell-$((6 * 7))' "expect:shell-42" \ + 'send:echo uid-$(id -u)' "expect:uid-0" \ + 'send:echo found-$(command -v kryptik-install)-$(command -v kryptik-recover)' \ + "expect:found-/usr/sbin/kryptik-install-/usr/sbin/kryptik-recover" \ + "send:kryptik-install --help" "expect:usage: kryptik-install --target" \ + "send:poweroff" "expect:Power down" "wait-exit" + drc=$? + stop_vm + [[ "$drc" -eq 0 ]] && green "${kind}: commands typed at the console ran as root, the installer is on its path, and poweroff took" \ + || red "${kind}: the console did not take commands (see above)" + if txt | grep -q 'kryptik login:'; then red "${kind}: the medium's console asked for a login"; else green "${kind}: no login prompt on the medium"; fi + if txt | grep -qE 'KRYPTIK_SMOKE: early_getty_pid=[0-9]+ comm=bash'; then green "${kind}: the console's process is the shell"; else red "${kind}: the console's process is not a shell: $(txt | grep -m1 -o 'early_getty_pid=.*' | cut -c1-80)"; fi +done + +printf '\n%d passed, %d failed\n' "$PASS" "$FAIL" +[[ "$FAIL" -eq 0 ]] || exit 1