From e7ae4479258241dfe6204a9d49770c875fe262a6 Mon Sep 17 00:00:00 2001 From: DevomB Date: Thu, 8 Oct 2026 19:55:28 -0700 Subject: [PATCH 1/4] A commit whose committed-slot record cannot be written is a failed commit: the trial stays, and the next boot commits again boot-success's commit_slot returned 0 once BOOTX64.EFI was the slot's kernel, whatever became of the committed-slot record. With the record unwritten, the caller cleared the trial, recorded "commit b", forgot the entries and raised the clock's floor while the ESP still named a: the next boot found b running uncommitted and rebooted once for nothing, and kryptik-update refused every apply from b until kryptik-recover --commit-slot b from the medium. Now the record is part of the commit. Failing it keeps the trial, and since BOOTX64.EFI already boots b and kryptik-efiboot's entries come after it in BootOrder, the next boot finds BOOTX64.EFI already b and writes only the record. kryptik-recover's put already dies the same way. kryptik-update says which ESP write failed when the staged kernel's flush or the version record fails, instead of leaving set -e to exit without a word. --- build/service-scripts/boot-success.sh | 8 +++++--- tools/tests/boot-success.sh | 4 ++++ tools/update/kryptik-update | 9 +++++---- 3 files changed, 14 insertions(+), 7 deletions(-) diff --git a/build/service-scripts/boot-success.sh b/build/service-scripts/boot-success.sh index cd7c7cb9..66663f95 100755 --- a/build/service-scripts/boot-success.sh +++ b/build/service-scripts/boot-success.sh @@ -83,8 +83,10 @@ commit_slot() { # commit_slot : make BOOTX64.EFI this slot's kernel cp "$src" "$dst.new" && sync -f "$dst.new" && mv -f "$dst.new" "$dst" && sync -f "$dst" && rc=0 [ "$rc" -eq 0 ] && say "committed: BOOTX64.EFI is now slot $1" fi - printf '%s\n' "$1" > "$ESP_MNT/kryptik/committed-slot.new" && sync -f "$ESP_MNT/kryptik/committed-slot.new" && \ - mv -f "$ESP_MNT/kryptik/committed-slot.new" "$ESP_MNT/kryptik/committed-slot" + # The record is part of the commit: kryptik-update applies only from the slot it names. + { printf '%s\n' "$1" > "$ESP_MNT/kryptik/committed-slot.new" && sync -f "$ESP_MNT/kryptik/committed-slot.new" && \ + mv -f "$ESP_MNT/kryptik/committed-slot.new" "$ESP_MNT/kryptik/committed-slot"; } \ + || { say "the committed-slot record could not be written; the trial stays, and the next boot commits again"; rc=1; } else say "no kernel for slot $1 on the ESP" fi @@ -149,7 +151,7 @@ if [ -n "$trial" ]; then say "$(kryptikd time committed "$B/release-$slot" 2>&1)" else result "commit-failed $slot" - say "slot $slot is healthy but the commit failed; the committed slot is unchanged" + say "slot $slot is healthy but the commit failed; the trial stays for the next boot" fi else say "trial slot $slot came up UNHEALTHY:" diff --git a/tools/tests/boot-success.sh b/tools/tests/boot-success.sh index 898b6174..76b2bced 100755 --- a/tools/tests/boot-success.sh +++ b/tools/tests/boot-success.sh @@ -67,6 +67,7 @@ EOF cat > "$T/bin/sync" <<'EOF' #!/bin/sh echo "sync $*" >> "$KTEST/syncs" +case "$*" in *committed-slot.new) [ ! -e "$KTEST/sync_fails" ] ;; esac EOF chmod +x "$T"/bin/* @@ -129,6 +130,9 @@ check "committed-slot records b" "$(cat "$KTEST/esp/kryptik/committed-slot")" "b check "the new committed-slot record is fsynced under its temporary name" "$(grep -c -x "sync -f $KTEST/run/esp/kryptik/committed-slot.new" "$KTEST/syncs" 2>/dev/null)" "1" check "the trial record is gone" "$([[ -e "$KTEST/boot/trial" ]] && echo present || echo gone)" "gone" check "the trial's firmware entries and BootNext are forgotten after the commit, the committed slot gets its own, and its release goes to the clock's floor" "$CALLS" "mount -t vfat -o rw,nosuid,nodev,noexec /dev/vda1 $KTEST/run/esp umount $KTEST/run/esp efiboot forget efiboot ensure b kryptikd time committed $KTEST/boot/release-b " +run_case recfail b "" persistent 'b\narmed=1\n' $ALL; : > "$KTEST/sync_fails"; go +check "a committed-slot record that cannot be written fails the commit: the trial stays, for the next boot to commit again" \ + "$RESULT|$(cat "$KTEST/esp/kryptik/committed-slot")|$([[ -e "$KTEST/boot/trial" ]] && echo kept || echo gone)|$(grep -c 'efiboot forget' "$KTEST/calls" 2>/dev/null)" "commit-failed b|a|kept|0" run_case commit0 b "" persistent 'b\narmed=0\n' $ALL; go check "a trial that booted before its armed=1 line was written is still a trial: committed" "$RESULT" "commit b" diff --git a/tools/update/kryptik-update b/tools/update/kryptik-update index 8c9390d7..6dc0ed90 100755 --- a/tools/update/kryptik-update +++ b/tools/update/kryptik-update @@ -363,7 +363,7 @@ cmd_apply() { mount_esp mkdir -p "$ESP_MNT/EFI/kryptik" "$ESP_MNT/kryptik" cp "$(payload_path "kryptik-$target.efi")" "$ESP_MNT/EFI/kryptik/kryptik-$target.efi.new" || die "staging the kernel on the ESP failed" - sync -f "$ESP_MNT/EFI/kryptik/kryptik-$target.efi.new" + sync -f "$ESP_MNT/EFI/kryptik/kryptik-$target.efi.new" || die "the kernel staged on the ESP could not be flushed" # Check the staged kernel before it gets the name rollback and recovery use. want_k="$H_KA"; [ "$target" = b ] && want_k="$H_KB" if [ "$(sha256sum "$ESP_MNT/EFI/kryptik/kryptik-$target.efi.new" | cut -c1-64)" != "$want_k" ]; then @@ -371,9 +371,10 @@ cmd_apply() { die "the kernel staged on the ESP does not hash to the manifest's kryptik-$target.efi" fi mv -f "$ESP_MNT/EFI/kryptik/kryptik-$target.efi.new" "$ESP_MNT/EFI/kryptik/kryptik-$target.efi" - printf '%s\n' "$VERSION" > "$ESP_MNT/kryptik/version-$target.new" - sync -f "$ESP_MNT/kryptik/version-$target.new" - mv -f "$ESP_MNT/kryptik/version-$target.new" "$ESP_MNT/kryptik/version-$target" + { printf '%s\n' "$VERSION" > "$ESP_MNT/kryptik/version-$target.new" \ + && sync -f "$ESP_MNT/kryptik/version-$target.new" \ + && mv -f "$ESP_MNT/kryptik/version-$target.new" "$ESP_MNT/kryptik/version-$target"; } \ + || die "slot $target's version could not be recorded on the ESP" sync umount_esp say "kernel for slot $target installed on the ESP (version $VERSION)" From c346847d25662d86c68986b6284417c6feda8e97 Mon Sep 17 00:00:00 2001 From: DevomB Date: Thu, 8 Oct 2026 19:57:28 -0700 Subject: [PATCH 2/4] The committed-slot record is written only once BOOTX64.EFI boots the slot: a boot file that could not be replaced left the ESP naming a slot the firmware does not boot From a non-author read: when the copy, flush or rename of BOOTX64.EFI failed, commit_slot still rewrote committed-slot to the trial's slot. The next boot took BOOTX64.EFI, the old slot, whose applies kryptik-update then refused as not committed. The fixture fails that step and expects the record unchanged. --- build/service-scripts/boot-success.sh | 11 +++++++---- tools/tests/boot-success.sh | 8 +++++++- 2 files changed, 14 insertions(+), 5 deletions(-) diff --git a/build/service-scripts/boot-success.sh b/build/service-scripts/boot-success.sh index 66663f95..2ba424d7 100755 --- a/build/service-scripts/boot-success.sh +++ b/build/service-scripts/boot-success.sh @@ -83,10 +83,13 @@ commit_slot() { # commit_slot : make BOOTX64.EFI this slot's kernel cp "$src" "$dst.new" && sync -f "$dst.new" && mv -f "$dst.new" "$dst" && sync -f "$dst" && rc=0 [ "$rc" -eq 0 ] && say "committed: BOOTX64.EFI is now slot $1" fi - # The record is part of the commit: kryptik-update applies only from the slot it names. - { printf '%s\n' "$1" > "$ESP_MNT/kryptik/committed-slot.new" && sync -f "$ESP_MNT/kryptik/committed-slot.new" && \ - mv -f "$ESP_MNT/kryptik/committed-slot.new" "$ESP_MNT/kryptik/committed-slot"; } \ - || { say "the committed-slot record could not be written; the trial stays, and the next boot commits again"; rc=1; } + # The record is part of the commit, and follows the boot file: kryptik-update + # applies only from the slot it names, so it must not name one that does not boot. + if [ "$rc" -eq 0 ]; then + { printf '%s\n' "$1" > "$ESP_MNT/kryptik/committed-slot.new" && sync -f "$ESP_MNT/kryptik/committed-slot.new" && \ + mv -f "$ESP_MNT/kryptik/committed-slot.new" "$ESP_MNT/kryptik/committed-slot"; } \ + || { say "the committed-slot record could not be written; the trial stays, and the next boot commits again"; rc=1; } + fi else say "no kernel for slot $1 on the ESP" fi diff --git a/tools/tests/boot-success.sh b/tools/tests/boot-success.sh index 76b2bced..c283ace6 100755 --- a/tools/tests/boot-success.sh +++ b/tools/tests/boot-success.sh @@ -67,7 +67,10 @@ EOF cat > "$T/bin/sync" <<'EOF' #!/bin/sh echo "sync $*" >> "$KTEST/syncs" -case "$*" in *committed-slot.new) [ ! -e "$KTEST/sync_fails" ] ;; esac +case "$*" in + *committed-slot.new) [ ! -e "$KTEST/sync_fails" ] ;; + *BOOTX64.EFI.new) [ ! -e "$KTEST/sync_fails_boot" ] ;; +esac EOF chmod +x "$T"/bin/* @@ -133,6 +136,9 @@ check "the trial's firmware entries and BootNext are forgotten after the commit, run_case recfail b "" persistent 'b\narmed=1\n' $ALL; : > "$KTEST/sync_fails"; go check "a committed-slot record that cannot be written fails the commit: the trial stays, for the next boot to commit again" \ "$RESULT|$(cat "$KTEST/esp/kryptik/committed-slot")|$([[ -e "$KTEST/boot/trial" ]] && echo kept || echo gone)|$(grep -c 'efiboot forget' "$KTEST/calls" 2>/dev/null)" "commit-failed b|a|kept|0" +run_case bootfail b "" persistent 'b\narmed=1\n' $ALL; : > "$KTEST/sync_fails_boot"; go +check "a boot file that cannot be replaced fails the commit, and the record goes on naming the slot BOOTX64.EFI boots" \ + "$RESULT|$(cat "$KTEST/esp/EFI/BOOT/BOOTX64.EFI")|$(cat "$KTEST/esp/kryptik/committed-slot")|$([[ -e "$KTEST/boot/trial" ]] && echo kept || echo gone)" "commit-failed b|kernel-a|a|kept" run_case commit0 b "" persistent 'b\narmed=0\n' $ALL; go check "a trial that booted before its armed=1 line was written is still a trial: committed" "$RESULT" "commit b" From 5688e3d624011c26e1d5330c3e3ee56f077695e5 Mon Sep 17 00:00:00 2001 From: DevomB Date: Thu, 8 Oct 2026 21:21:12 -0700 Subject: [PATCH 3/4] A kernel copied onto the ESP takes its name only once it reads back as its source: boot-success's commit and kryptik-recover's commit and restore now compare, as the installer and kryptik-update already did boot-success copied the trial slot's kernel to BOOTX64.EFI.new, fsynced and renamed it without looking at what landed, and kryptik-recover did the same for the boot file and for the medium's kernel. A copy that read back wrong became a boot file Secure Boot refuses. Now the copy is compared with its source before the rename: a failed compare fails the commit and the trial stays, as a failed write already does, and recover stops before anything is committed. The installer compares BOOTX64.EFI and kryptik-update hashes its staged kernel. The boot-success fixture makes the copy read back wrong and expects the old boot file and record and the trial kept. --- build/service-scripts/boot-success.sh | 5 +++-- docs/design/boot-and-updates.md | 26 +++++++++++++------------- tools/tests/boot-success.sh | 10 ++++++++++ tools/update/kryptik-recover | 3 +++ 4 files changed, 29 insertions(+), 15 deletions(-) diff --git a/build/service-scripts/boot-success.sh b/build/service-scripts/boot-success.sh index 2ba424d7..3492eb37 100755 --- a/build/service-scripts/boot-success.sh +++ b/build/service-scripts/boot-success.sh @@ -79,8 +79,9 @@ commit_slot() { # commit_slot : make BOOTX64.EFI this slot's kernel if cmp -s "$src" "$dst"; then say "BOOTX64.EFI already is slot $1"; rc=0 else - # Copy, fsync, then rename: on FAT only the rename is not atomic. - cp "$src" "$dst.new" && sync -f "$dst.new" && mv -f "$dst.new" "$dst" && sync -f "$dst" && rc=0 + # Copy, fsync, compare, then rename: on FAT only the rename is not atomic, and a + # copy that reads back wrong never becomes the boot file. + cp "$src" "$dst.new" && sync -f "$dst.new" && cmp -s "$src" "$dst.new" && mv -f "$dst.new" "$dst" && sync -f "$dst" && rc=0 [ "$rc" -eq 0 ] && say "committed: BOOTX64.EFI is now slot $1" fi # The record is part of the commit, and follows the boot file: kryptik-update diff --git a/docs/design/boot-and-updates.md b/docs/design/boot-and-updates.md index fe35d3ae..e1496bc1 100644 --- a/docs/design/boot-and-updates.md +++ b/docs/design/boot-and-updates.md @@ -150,19 +150,19 @@ payloads come from [the update channel](update-channel.md) or by hand. 5. `boot-success` judges the trial slot: state persistent; eudev, seatd, the launch daemon, the net zone and the login getty up; kryptikd finding kernel support and the zones; an unambiguous ESP. Healthy: it copies the kernel - over `BOOTX64.EFI` (`.new`, fsync, rename), updates `committed-slot` alike, - clears the trial and forgets the entries, and the slot's kept manifest - raises the clock's floor if it is the newest. Unhealthy: it records that, - forgets the entries and reboots into the committed slot, `BootNext` being - spent. On a degraded state the trial record is out of reach, so - `committed-slot` says whether the boot is a trial. A trial that never comes - up also lands on the committed slot; the fallback records `trial.failed` - and forgets the entries, and the updater will not re-arm that payload - without `--retry`, which root gives (`su` from the administration login - on tty2; the refusal prints the command). Only a trial reboots; an - unhealthy committed slot is reported and left running, and so is a trial - on a degraded state whose ESP cannot be read, since nothing then says it - is one. + over `BOOTX64.EFI` (`.new`, fsync, compare, rename), updates + `committed-slot` alike, clears the trial and forgets the entries, and the + slot's kept manifest raises the clock's floor if it is the newest. + Unhealthy: it records that, forgets the entries and reboots into the + committed slot, `BootNext` being spent. On a degraded state the trial + record is out of reach, so `committed-slot` says whether the boot is a + trial. A trial that never comes up also lands on the committed slot; the + fallback records `trial.failed` and forgets the entries, and the updater + will not re-arm that payload without `--retry`, which root gives (`su` from + the administration login on tty2; the refusal prints the command). Only a + trial reboots; an unhealthy committed slot is reported and left running, + and so is a trial on a degraded state whose ESP cannot be read, since + nothing then says it is one. The net zone is in the check on purpose: a release whose net zone cannot come up could never fetch the release that fixes it. Whoever can crash diff --git a/tools/tests/boot-success.sh b/tools/tests/boot-success.sh index c283ace6..4bee30d6 100755 --- a/tools/tests/boot-success.sh +++ b/tools/tests/boot-success.sh @@ -72,6 +72,13 @@ case "$*" in *BOOTX64.EFI.new) [ ! -e "$KTEST/sync_fails_boot" ] ;; esac EOF +# cmp: the real one, but a copy of a kernel to BOOTX64.EFI.new can be made to read back wrong. +REAL_CMP="$(command -v cmp)" +cat > "$T/bin/cmp" < "$KTEST/sync_fails_boot"; go check "a boot file that cannot be replaced fails the commit, and the record goes on naming the slot BOOTX64.EFI boots" \ "$RESULT|$(cat "$KTEST/esp/EFI/BOOT/BOOTX64.EFI")|$(cat "$KTEST/esp/kryptik/committed-slot")|$([[ -e "$KTEST/boot/trial" ]] && echo kept || echo gone)" "commit-failed b|kernel-a|a|kept" +run_case copybad b "" persistent 'b\narmed=1\n' $ALL; : > "$KTEST/copy_bad"; go +check "a boot file whose copy reads back wrong is not renamed into place: the commit fails, the trial stays" \ + "$RESULT|$(cat "$KTEST/esp/EFI/BOOT/BOOTX64.EFI")|$(cat "$KTEST/esp/kryptik/committed-slot")|$([[ -e "$KTEST/boot/trial" ]] && echo kept || echo gone)" "commit-failed b|kernel-a|a|kept" run_case commit0 b "" persistent 'b\narmed=0\n' $ALL; go check "a trial that booted before its armed=1 line was written is still a trial: committed" "$RESULT" "commit b" diff --git a/tools/update/kryptik-recover b/tools/update/kryptik-recover index 205a855b..2b407e0f 100755 --- a/tools/update/kryptik-recover +++ b/tools/update/kryptik-recover @@ -91,6 +91,7 @@ commit_slot() { # commit_slot SLOT (ESP mounted rw at /run/kryptik-recover) has_fs "$(slot_dev "$s")" || die "slot $s holds no filesystem; use --restore-slot $s" cp "$k" /run/kryptik-recover/EFI/BOOT/BOOTX64.EFI.new sync -f /run/kryptik-recover/EFI/BOOT/BOOTX64.EFI.new + cmp -s "$k" /run/kryptik-recover/EFI/BOOT/BOOTX64.EFI.new || die "slot $s's kernel did not copy whole; BOOTX64.EFI is unchanged" 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 @@ -148,6 +149,8 @@ if [ -n "$RESTORE" ]; then mount -t vfat -o rw "$ESP" /run/kryptik-recover || die "cannot mount the target ESP" cp "$kdir/kryptik-$RESTORE.efi" "/run/kryptik-recover/EFI/kryptik/kryptik-$RESTORE.efi.new" sync -f "/run/kryptik-recover/EFI/kryptik/kryptik-$RESTORE.efi.new" + cmp -s "$kdir/kryptik-$RESTORE.efi" "/run/kryptik-recover/EFI/kryptik/kryptik-$RESTORE.efi.new" \ + || die "the medium's kernel for slot $RESTORE did not copy whole; nothing was committed" mv -f "/run/kryptik-recover/EFI/kryptik/kryptik-$RESTORE.efi.new" "/run/kryptik-recover/EFI/kryptik/kryptik-$RESTORE.efi" put "/run/kryptik-recover/kryptik/version-$RESTORE" "$ver" || die "cannot record slot ${RESTORE}'s version" commit_slot "$RESTORE" From d0e6f32d68b1b52bc68fc0fcf6e921ad60c5f8bb Mon Sep 17 00:00:00 2001 From: DevomB Date: Thu, 8 Oct 2026 21:27:22 -0700 Subject: [PATCH 4/4] The kernel copies are compared as the device holds them: each is dropped from the page cache after its fsync, so the compare reads the ESP and not the copy still in memory From a non-author read: cmp right after sync -f read the cached pages, which says little more than cp's own status. dd iflag=nocache count=0 drops a file's clean pages, best effort; boot-success's and kryptik-recover's compares and kryptik-update's hash of its staged kernel now read what the ESP holds. --- build/service-scripts/boot-success.sh | 8 +++++--- tools/update/kryptik-recover | 4 ++++ tools/update/kryptik-update | 7 ++++++- 3 files changed, 15 insertions(+), 4 deletions(-) diff --git a/build/service-scripts/boot-success.sh b/build/service-scripts/boot-success.sh index 3492eb37..5252071c 100755 --- a/build/service-scripts/boot-success.sh +++ b/build/service-scripts/boot-success.sh @@ -25,6 +25,8 @@ ident() { sed -n "s/^$1=//p" "$RUN/boot-identity" 2>/dev/null | head -1; } other_slot() { case "$1" in a) echo b ;; b) echo a ;; esac; } slot="$(ident slot)"; media="$(ident media)"; state="$(ident state)" now() { date -Iseconds 2>/dev/null || date; } +# Drop FILE's clean pages, so the next read is what the device holds; best effort. +uncache() { dd if="$1" iflag=nocache count=0 status=none 2>/dev/null || :; } # uncache FILE result() { printf '%s %s\n' "$*" "$(now)" > "$B/last-result.new" && mv -f "$B/last-result.new" "$B/last-result"; } if [ -n "$media" ]; then @@ -79,9 +81,9 @@ commit_slot() { # commit_slot : make BOOTX64.EFI this slot's kernel if cmp -s "$src" "$dst"; then say "BOOTX64.EFI already is slot $1"; rc=0 else - # Copy, fsync, compare, then rename: on FAT only the rename is not atomic, and a - # copy that reads back wrong never becomes the boot file. - cp "$src" "$dst.new" && sync -f "$dst.new" && cmp -s "$src" "$dst.new" && mv -f "$dst.new" "$dst" && sync -f "$dst" && rc=0 + # Copy, fsync, compare as the device holds it, then rename: on FAT only the + # rename is not atomic, and a copy that landed wrong never becomes the boot file. + cp "$src" "$dst.new" && sync -f "$dst.new" && uncache "$dst.new" && cmp -s "$src" "$dst.new" && mv -f "$dst.new" "$dst" && sync -f "$dst" && rc=0 [ "$rc" -eq 0 ] && say "committed: BOOTX64.EFI is now slot $1" fi # The record is part of the commit, and follows the boot file: kryptik-update diff --git a/tools/update/kryptik-recover b/tools/update/kryptik-recover index 2b407e0f..83843912 100755 --- a/tools/update/kryptik-recover +++ b/tools/update/kryptik-recover @@ -59,6 +59,8 @@ slot_dev() { case "$1" in a) echo "$SA" ;; b) echo "$SB" ;; *) die "slot must be has_fs() { [ "$(dd if="$1" bs=1 skip=1080 count=2 status=none | od -An -tx1 | tr -d ' \n')" = "53ef" ]; } # Written, fsynced, renamed, as the kernels are: a power cut leaves the old file whole, not empty. put() { printf '%s\n' "$2" > "$1.new" && sync -f "$1.new" && mv -f "$1.new" "$1"; } # put FILE LINE +# Drop FILE's clean pages, so the next read is what the device holds; best effort. +uncache() { dd if="$1" iflag=nocache count=0 status=none 2>/dev/null || :; } # uncache FILE if [ "$STATUS" = 1 ]; then mount -t vfat -o ro "$ESP" /run/kryptik-recover || die "cannot mount the ESP" @@ -91,6 +93,7 @@ commit_slot() { # commit_slot SLOT (ESP mounted rw at /run/kryptik-recover) has_fs "$(slot_dev "$s")" || die "slot $s holds no filesystem; use --restore-slot $s" cp "$k" /run/kryptik-recover/EFI/BOOT/BOOTX64.EFI.new sync -f /run/kryptik-recover/EFI/BOOT/BOOTX64.EFI.new + uncache /run/kryptik-recover/EFI/BOOT/BOOTX64.EFI.new cmp -s "$k" /run/kryptik-recover/EFI/BOOT/BOOTX64.EFI.new || die "slot $s's kernel did not copy whole; BOOTX64.EFI is unchanged" 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" @@ -149,6 +152,7 @@ if [ -n "$RESTORE" ]; then mount -t vfat -o rw "$ESP" /run/kryptik-recover || die "cannot mount the target ESP" cp "$kdir/kryptik-$RESTORE.efi" "/run/kryptik-recover/EFI/kryptik/kryptik-$RESTORE.efi.new" sync -f "/run/kryptik-recover/EFI/kryptik/kryptik-$RESTORE.efi.new" + uncache "/run/kryptik-recover/EFI/kryptik/kryptik-$RESTORE.efi.new" cmp -s "$kdir/kryptik-$RESTORE.efi" "/run/kryptik-recover/EFI/kryptik/kryptik-$RESTORE.efi.new" \ || die "the medium's kernel for slot $RESTORE did not copy whole; nothing was committed" mv -f "/run/kryptik-recover/EFI/kryptik/kryptik-$RESTORE.efi.new" "/run/kryptik-recover/EFI/kryptik/kryptik-$RESTORE.efi" diff --git a/tools/update/kryptik-update b/tools/update/kryptik-update index 6dc0ed90..2508c005 100755 --- a/tools/update/kryptik-update +++ b/tools/update/kryptik-update @@ -76,6 +76,9 @@ esp_dev() { own_part kryptik-esp; } slot_dev() { own_part "kryptik-$1"; } hdr() { awk -F': ' -v k="$2" '$1==k {print $2; exit}' "$1"; } +# Drop FILE's clean pages, so the next read is what the device holds; best effort. +uncache() { dd if="$1" iflag=nocache count=0 status=none 2>/dev/null || :; } # uncache FILE + mount_esp() { dev="$(esp_dev)" || exit 1 mkdir -p "$ESP_MNT" @@ -364,7 +367,9 @@ cmd_apply() { mkdir -p "$ESP_MNT/EFI/kryptik" "$ESP_MNT/kryptik" cp "$(payload_path "kryptik-$target.efi")" "$ESP_MNT/EFI/kryptik/kryptik-$target.efi.new" || die "staging the kernel on the ESP failed" sync -f "$ESP_MNT/EFI/kryptik/kryptik-$target.efi.new" || die "the kernel staged on the ESP could not be flushed" - # Check the staged kernel before it gets the name rollback and recovery use. + # Check the staged kernel, as the device holds it, before it gets the name + # rollback and recovery use. + uncache "$ESP_MNT/EFI/kryptik/kryptik-$target.efi.new" want_k="$H_KA"; [ "$target" = b ] && want_k="$H_KB" if [ "$(sha256sum "$ESP_MNT/EFI/kryptik/kryptik-$target.efi.new" | cut -c1-64)" != "$want_k" ]; then rm -f "$ESP_MNT/EFI/kryptik/kryptik-$target.efi.new"