Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions build/recipes/boot-check.sh
Original file line number Diff line number Diff line change
Expand Up @@ -88,6 +88,7 @@ s_boot_check() {
chk "zone definitions" /usr/lib/kryptik/zones/work.toml
chk "zone policies" /usr/lib/kryptik/zones/policy/work.seccomp
chk "device helper" /usr/libexec/kryptik/devices.sh x
chk "ESP record reader" /usr/libexec/kryptik/esp-records.sh
chk "boot scripts" /usr/libexec/kryptik/sysinit.sh x
chk "test control helper" /usr/libexec/kryptik/testctl.sh
chk "keyboard helper" /usr/libexec/kryptik/keyboard.sh
Expand Down
31 changes: 31 additions & 0 deletions build/service-scripts/esp-records.sh
Original file line number Diff line number Diff line change
@@ -0,0 +1,31 @@
#!/bin/sh
# The records Kryptik keeps on an ESP (kryptik/committed-slot,
# kryptik/version-a and version-b), read for a terminal or a log. An ESP is
# plain FAT that anyone holding the disk can rewrite, so a record is shown
# only in the shape Kryptik writes it; anything else reads as unknown.
# POSIX sh, sourced.
# esp_slot FILE [ABSENT] a or b
# esp_version FILE [ABSENT] a release's version
# A missing FILE reads as ABSENT (default unknown).

esp_slot() {
[ -e "$1" ] || { echo "${2:-unknown}"; return 0; }
case "$(head -c 8 "$1" 2>/dev/null | sed -n 1p)" in
a) echo a ;;
b) echo b ;;
*) echo unknown ;;
esac
}

# MAJOR.MINOR.PATCH as a release is numbered (build/lib/release-keys.sh),
# then the parts a development build adds: 0.1.20261007.1a2b3c4d.1
esp_version() {
[ -e "$1" ] || { echo "${2:-unknown}"; return 0; }
ev="$(head -c 80 "$1" 2>/dev/null | sed -n 1p)"
if [ "${#ev}" -le 64 ] && printf '%s\n' "$ev" \
| LC_ALL=C grep -E -x -q '(0|[1-9][0-9]*)\.(0|[1-9][0-9]*)\.(0|[1-9][0-9]*)(\.[0-9a-z]+)*'; then
printf '%s\n' "$ev"
else
echo unknown
fi
}
3 changes: 2 additions & 1 deletion build/service-scripts/installer-run.sh
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@
# whose test control disk asks for it (testctl.sh). Users run kryptik-install.
set -u
. /usr/libexec/kryptik/testctl.sh
. /usr/libexec/kryptik/esp-records.sh

say() { echo "KRYPTIK_INSTALL: $*"; }

Expand Down Expand Up @@ -111,7 +112,7 @@ if [ "$rc" -eq 0 ]; then
if [ -n "$esp" ] && mount -t vfat -o ro "$esp" /run/verify 2>/dev/null; then
say "verify: esp_files=$(cd /run/verify && find . -type f | sort | tr '\n' ' ')"
say "verify: bootx64_sha256=$(sha256sum /run/verify/EFI/BOOT/BOOTX64.EFI 2>/dev/null | cut -c1-64)"
say "verify: version_a=$(cat /run/verify/kryptik/version-a 2>/dev/null || echo none)"
say "verify: version_a=$(esp_version /run/verify/kryptik/version-a none)"
umount /run/verify
else
say "verify: could not mount the ESP read-only"
Expand Down
6 changes: 6 additions & 0 deletions docs/design/boot-and-updates.md
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,12 @@ Partitions are found by GPT label, never by device name.
| 3 | `kryptik-b` | slot B, empty after install |
| 4 | `kryptik-state` | LUKS2 with ext4 inside, rest of disk: `/var`, the `/etc` upper layer, `/home`, zone volumes, update staging |

Nothing on the ESP is signed but the kernels. Its records reach a terminal
and `update.log` (`kryptik-update status` and `rollback`,
`kryptik-recover --status`) only in the shape Kryptik writes them, a slot
`a` or `b` and a release's version (`build/service-scripts/esp-records.sh`);
anything else reads as `unknown`.

The USB image holds `kryptik-esp` and `kryptik-media`; the ISO holds the ESP
as its El Torito image and the root image at a sector offset (a `linear` dm
target over `/dev/sr0`, verity on top). The root image is byte-identical on
Expand Down
43 changes: 43 additions & 0 deletions tools/tests/esp-records.sh
Original file line number Diff line number Diff line change
@@ -0,0 +1,43 @@
#!/usr/bin/env bash
# The ESP's records as the tools show them (build/service-scripts/esp-records.sh):
# in the shape Kryptik writes them, and as unknown otherwise.
set -uo pipefail
ROOT="$(cd "$(dirname "${BASH_SOURCE[0]}")/../.." && pwd)"
# shellcheck source=/dev/null
. "$ROOT/build/service-scripts/esp-records.sh"
PASS=0; FAIL=0
check() {
if [[ "$2" == "$3" ]]; then printf ' PASS %s\n' "$1"; PASS=$((PASS + 1))
else printf ' FAIL %s (got %q, want %q)\n' "$1" "$2" "$3"; FAIL=$((FAIL + 1)); fi
}
T="$(mktemp -d)"; trap 'rm -rf "$T"' EXIT
rec() { printf '%b' "$1" > "$T/rec"; } # rec BYTES: the record, backslash escapes read

echo "-- committed-slot"
rec 'a\n'; check "slot a" "$(esp_slot "$T/rec")" a
rec 'b'; check "slot b, written without a newline" "$(esp_slot "$T/rec")" b
rec 'c\n'; check "a letter that is no slot" "$(esp_slot "$T/rec")" unknown
rec '\033]0;owned\007a\n'; check "a terminal sequence before the slot" "$(esp_slot "$T/rec")" unknown
rec 'a\r\n'; check "a carriage return after it" "$(esp_slot "$T/rec")" unknown
rec 'a; reboot\n'; check "more on the line" "$(esp_slot "$T/rec")" unknown
rec ''; check "an empty record" "$(esp_slot "$T/rec")" unknown
check "no record" "$(esp_slot "$T/absent")" unknown
check "no record, read as the caller says" "$(esp_slot "$T/absent" none)" none

echo "-- version-a and version-b"
rec '1.0.0\n'; check "a release" "$(esp_version "$T/rec")" 1.0.0
rec '1.0.10'; check "a release, written without a newline" "$(esp_version "$T/rec")" 1.0.10
rec '0.1.20261007.1a2b3c4d\n'; check "a development build" "$(esp_version "$T/rec")" 0.1.20261007.1a2b3c4d
rec '0.1.20261007.1a2b3c4d.1\n'; check "the update suite's second build" "$(esp_version "$T/rec")" 0.1.20261007.1a2b3c4d.1
rec '1.0\n'; check "two parts" "$(esp_version "$T/rec")" unknown
rec '01.0.0\n'; check "a leading zero" "$(esp_version "$T/rec")" unknown
rec '9.9.9 (newest)\n'; check "words after a version" "$(esp_version "$T/rec")" unknown
rec '1.0.0\033[2J\n'; check "a terminal sequence after a version" "$(esp_version "$T/rec")" unknown
rec '\n1.0.0\n'; check "a version on the second line" "$(esp_version "$T/rec")" unknown
rec "$(printf '1.%.0s' $(seq 40))0\n"; check "a version longer than any release's" "$(esp_version "$T/rec")" unknown
rec ''; check "an empty record" "$(esp_version "$T/rec")" unknown
check "no record" "$(esp_version "$T/absent")" unknown
check "no record, read as the caller says" "$(esp_version "$T/absent" none)" none

printf '\n%d passed, %d failed\n' "$PASS" "$FAIL"
[[ "$FAIL" -eq 0 ]]
9 changes: 9 additions & 0 deletions tools/tests/update-esp.sh
Original file line number Diff line number Diff line change
Expand Up @@ -25,6 +25,7 @@ echo b > "$T/esp/kryptik/committed-slot"
echo 'sync() { :; }'
echo 'running_slot() { echo a; }; running_version() { echo 1.0; }; kryptik-efiboot() { :; }'
echo "ESP_MNT='$T/esp'; LOCK='$T/lock'; B='$T/boot'; DEGRADED='$T/degraded'"
echo ". '$ROOT/build/service-scripts/esp-records.sh'"
sed -n '/^mount_esp() {/,/^}/p; /^umount_esp() {/,/^}/p; /^ESP_MINE=/p; /^trap .*umount_esp/p; /^cmd_status() {/,/^}/p;
/^other_slot() /p; /^unlist_slot() {/,/^}/p; /^cmd_rollback() {/,/^}/p' "$TOOL"
} > "$T/esp.sh"
Expand All @@ -48,6 +49,14 @@ sleep 0.5
out="$(bash -c "source '$T/esp.sh'; cmd_status" 2>&1)"
wait "$holder"
[[ "$out" == *"being applied"* && -z "$(calls)" ]] && ok "status leaves the ESP alone while an apply holds the lock" || bad "status under an apply: $(calls) / $out"
# Records rewritten by whoever held the disk: shown as unknown, never as written.
printf '\033]0;owned\007b\n' > "$T/esp/kryptik/committed-slot"
printf '9.9.9 (newest)\n' > "$T/esp/kryptik/version-a"; printf '1.0.0\n' > "$T/esp/kryptik/version-b"
fresh; out="$(bash -c "source '$T/esp.sh'; cmd_status" 2>&1)"
[[ "$out" == *"committed slot: unknown"* && "$out" == *"slot a: version unknown"* \
&& "$out" == *"slot b: version 1.0.0"* && "$out" != *$'\033'* ]] \
&& ok "status shows the ESP's records only in the shape Kryptik writes them" || bad "status of rewritten records: $out"
echo b > "$T/esp/kryptik/committed-slot"; rm -f "$T/esp/kryptik/version-a" "$T/esp/kryptik/version-b"

echo "-- an apply cut short while it writes a slot"
mkdir -p "$T/esp/EFI/kryptik"
Expand Down
8 changes: 5 additions & 3 deletions tools/update/kryptik-recover
Original file line number Diff line number Diff line change
Expand Up @@ -44,6 +44,8 @@ for lblname in kryptik-state kryptik-media; do
case "$(readlink -f "$d")" in "$DISK"*) [ "$lblname" = kryptik-state ] && [ "$d" = "$ST" ] && continue; die "${DISK} is the medium; refusing" ;; esac
done
done
# The disk's ESP records are shown only in the shape Kryptik writes them.
. /usr/libexec/kryptik/esp-records.sh
mountpoint -q /run/kryptik-recover 2>/dev/null && umount /run/kryptik-recover
mkdir -p /run/kryptik-recover
cleanup() { mountpoint -q /run/kryptik-recover 2>/dev/null && umount /run/kryptik-recover; rmdir /run/kryptik-recover 2>/dev/null || true; }
Expand All @@ -57,10 +59,10 @@ put() { printf '%s\n' "$2" > "$1.new" && sync -f "$1.new" && mv -f "$1.new" "$1"
if [ "$STATUS" = 1 ]; then
mount -t vfat -o ro "$ESP" /run/kryptik-recover || die "cannot mount the ESP"
echo "disk $DISK"
echo "committed slot $(cat /run/kryptik-recover/kryptik/committed-slot 2>/dev/null || echo unknown)"
echo "committed slot $(esp_slot /run/kryptik-recover/kryptik/committed-slot)"
for s in a b; do
printf 'slot %s version %s, kernel %s, filesystem %s\n' "$s" \
"$(cat "/run/kryptik-recover/kryptik/version-$s" 2>/dev/null || echo none)" \
"$(esp_version "/run/kryptik-recover/kryptik/version-$s" none)" \
"$([ -f "/run/kryptik-recover/EFI/kryptik/kryptik-$s.efi" ] && echo present || echo absent)" \
"$(has_fs "$(slot_dev "$s")" && echo present || echo NONE)"
done
Expand Down Expand Up @@ -88,7 +90,7 @@ commit_slot() { # commit_slot SLOT (ESP mounted rw at /run/kryptik-recover)
mv -f /run/kryptik-recover/EFI/BOOT/BOOTX64.EFI.new /run/kryptik-recover/EFI/BOOT/BOOTX64.EFI
put /run/kryptik-recover/kryptik/committed-slot "$s" || die "cannot record slot $s as committed"
sync
say "committed: BOOTX64.EFI is now slot $s ($(cat "/run/kryptik-recover/kryptik/version-$s" 2>/dev/null || echo unknown))"
say "committed: BOOTX64.EFI is now slot $s ($(esp_version "/run/kryptik-recover/kryptik/version-$s"))"
# As boot-success does at each commit: Kryptik's firmware entries and
# BootNext go, so the firmware boots BOOTX64.EFI, and the committed slot
# gets its own entry on this disk's ESP, a second way to it.
Expand Down
11 changes: 7 additions & 4 deletions tools/update/kryptik-update
Original file line number Diff line number Diff line change
Expand Up @@ -56,7 +56,10 @@ case "${1:-}" in
check-manifest|check-pointer|check-release) ;;
*)
# shellcheck source=/dev/null
. /usr/libexec/kryptik/devices.sh ;;
. /usr/libexec/kryptik/devices.sh
# The ESP's records, shown only in the shape Kryptik writes them.
# shellcheck source=/dev/null
. /usr/libexec/kryptik/esp-records.sh ;;
esac

running_slot() { sed -n 's/^slot=//p' /run/kryptik/boot-identity 2>/dev/null; }
Expand Down Expand Up @@ -100,9 +103,9 @@ cmd_status() {
exec 9>"$LOCK"
if flock -s -n 9; then
mount_esp
echo "committed slot: $(cat "$ESP_MNT/kryptik/committed-slot" 2>/dev/null || echo unknown)"
echo "committed slot: $(esp_slot "$ESP_MNT/kryptik/committed-slot")"
for s in a b; do
printf 'slot %s: version %s, kernel %s\n' "$s" "$(cat "$ESP_MNT/kryptik/version-$s" 2>/dev/null || echo none)" \
printf 'slot %s: version %s, kernel %s\n' "$s" "$(esp_version "$ESP_MNT/kryptik/version-$s" none)" \
"$([ -f "$ESP_MNT/EFI/kryptik/kryptik-$s.efi" ] && echo present || echo absent)"
done
umount_esp
Expand Down Expand Up @@ -395,7 +398,7 @@ cmd_rollback() {
[ -n "$target" ] || die "cannot tell which slot is running"
mount_esp
[ -f "$ESP_MNT/EFI/kryptik/kryptik-$target.efi" ] || die "slot $target has no kernel on the ESP; nothing to roll back to"
ver="$(cat "$ESP_MNT/kryptik/version-$target" 2>/dev/null || echo unknown)"
ver="$(esp_version "$ESP_MNT/kryptik/version-$target")"
umount_esp
tdev="$(slot_dev "$target")" || exit 1
[ -b "$tdev" ] || die "kryptik-$target ($tdev) is not a block device"
Expand Down
Loading