From 0005411352f9bda0d9887c37b9e75a2bce6c1133 Mon Sep 17 00:00:00 2001 From: Daan De Meyer Date: Fri, 4 Oct 2024 10:22:37 +0200 Subject: [PATCH 1/5] measure: Take SizeOfImage into account as well for .linux section Same change as https://github.com/systemd/systemd/pull/34583 but for systemd-measure. Otherwise we end up with PCR policy digest mismatches as systemd-stub will measure the full virtual size of the kernel image after it has been loaded while systemd-measure will disregard the extra size introduced by SizeOfImage. While ideally the stub would only measure the data that's actually on disk and not the uninitialized data introduced by VirtualSize > SizeOfRawData, we want newer systemd-measure to work with older stubs so we have to fix systemd-measure and can't fix this in the stub. --- src/boot/measure.c | 33 +++++++++++++++++++++++++++++++++ 1 file changed, 33 insertions(+) diff --git a/src/boot/measure.c b/src/boot/measure.c index 36d42147a1..f2d644ff03 100644 --- a/src/boot/measure.c +++ b/src/boot/measure.c @@ -16,6 +16,7 @@ #include "openssl-util.h" #include "parse-argument.h" #include "parse-util.h" +#include "pe-binary.h" #include "pretty-print.h" #include "sha256.h" #include "strv.h" @@ -517,6 +518,38 @@ static int measure_kernel(PcrState *pcr_states, size_t n) { m += sz; } + if (c == UNIFIED_SECTION_LINUX) { + _cleanup_free_ PeHeader *pe_header = NULL; + + r = pe_load_headers(fd, /*ret_dos_header=*/ NULL, &pe_header); + if (r < 0) + log_warning_errno(r, "Failed to parse kernel image file '%s', ignoring: %m", arg_sections[c]); + else if (m < pe_header->optional.SizeOfImage) { + memzero(buffer, BUFFER_SIZE); + + /* Our EFI stub measures VirtualSize bytes of the .linux section into PCR 11. + * Notably, VirtualSize can be larger than the section's size on disk. In + * that case the extra space is initialized with zeros, so the stub ends up + * measuring a bunch of zeros. To accomodate this, we have to measure the + * same number of zeros here. We opt to measure extra zeros here instead of + * modifying the stub to only measure the number of bytes on disk as we want + * newer ukify + systemd-measure to work with older versions of the stub and + * as of 6.12 the kernel image's VirtualSize won't be larger than its size on + * disk anymore (see https://github.com/systemd/systemd/issues/34578#issuecomment-2382459515). + */ + + while (m < pe_header->optional.SizeOfImage) { + uint64_t sz = MIN(BUFFER_SIZE, pe_header->optional.SizeOfImage - m); + + for (size_t i = 0; i < n; i++) + if (EVP_DigestUpdate(mdctx[i], buffer, sz) != 1) + return log_error_errno(SYNTHETIC_ERRNO(EINVAL), "Failed to run digest."); + + m += sz; + } + } + } + fd = safe_close(fd); if (m == 0) /* We skip over empty files, the stub does so too */ From b53f2d5ed8ad0e537e9086daf84f9c2bf69fb72b Mon Sep 17 00:00:00 2001 From: Daan De Meyer Date: Mon, 7 Oct 2024 17:39:27 +0200 Subject: [PATCH 2/5] pcrlock: Take VirtualSize > SizeOfRawData into account If VirtualSize > SizeOfRawData, measure extra zeros to take into account the extra zeros also measured by the stub. --- src/pcrlock/pehash.c | 16 +++++++++++++++- 1 file changed, 15 insertions(+), 1 deletion(-) diff --git a/src/pcrlock/pehash.c b/src/pcrlock/pehash.c index 06d1f6afc7..7e9dade1f7 100644 --- a/src/pcrlock/pehash.c +++ b/src/pcrlock/pehash.c @@ -216,10 +216,24 @@ int uki_hash(int fd, if (EVP_DigestInit_ex(mdctx, md, NULL) != 1) return log_debug_errno(SYNTHETIC_ERRNO(ENOTRECOVERABLE), "Failed to allocate message digest."); - r = hash_file(fd, mdctx, section->PointerToRawData, section->VirtualSize); + r = hash_file(fd, mdctx, section->PointerToRawData, MIN(section->VirtualSize, section->SizeOfRawData)); if (r < 0) return r; + if (section->SizeOfRawData < section->VirtualSize) { + uint8_t zeroes[1024] = {}; + size_t remaining = section->VirtualSize - section->SizeOfRawData; + + while (remaining > 0) { + size_t sz = MIN(sizeof(zeroes), remaining); + + if (EVP_DigestUpdate(mdctx, zeroes, sz) != 1) + return log_debug_errno(SYNTHETIC_ERRNO(ENOTRECOVERABLE), "Unable to hash data."); + + remaining -= sz; + } + } + hashes[i] = malloc(hsz); if (!hashes[i]) return log_oom_debug(); From 88d9ca6d8a6cfe733bcded4ef9490d1f0937acbc Mon Sep 17 00:00:00 2001 From: Daan De Meyer Date: Sun, 20 Oct 2024 16:31:23 +0200 Subject: [PATCH 3/5] cryptenroll: Remove faulty assert() We can break out of the preceeding for loop in certain scenarios which would trigger the assert so let's drop it. --- src/cryptenroll/cryptenroll-tpm2.c | 2 -- 1 file changed, 2 deletions(-) diff --git a/src/cryptenroll/cryptenroll-tpm2.c b/src/cryptenroll/cryptenroll-tpm2.c index d58194fb85..ca163ef3c2 100644 --- a/src/cryptenroll/cryptenroll-tpm2.c +++ b/src/cryptenroll/cryptenroll-tpm2.c @@ -78,8 +78,6 @@ static int search_policy_hash( j++; } - assert(j == n_policy_hash); - if (match) /* Found entry with the exact same set of hashes */ return keyslot; } From 922fe8b91df9ab240dbf6608b22889feb2b5d939 Mon Sep 17 00:00:00 2001 From: Daan De Meyer Date: Mon, 21 Oct 2024 15:01:59 +0200 Subject: [PATCH 4/5] TEST-70-TPM2: Disable public key enrollment explicitly Otherwise, when the test is executed on a system with signed PCRs, cryptenroll will automatically pick up the public key from the UKI which results in a volume that can't be unlocked because the pcrextend tests appends extra things to pcr 11. --- test/units/TEST-70-TPM2.pcrlock.sh | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/test/units/TEST-70-TPM2.pcrlock.sh b/test/units/TEST-70-TPM2.pcrlock.sh index 0bf32e2eac..19af3a774d 100755 --- a/test/units/TEST-70-TPM2.pcrlock.sh +++ b/test/units/TEST-70-TPM2.pcrlock.sh @@ -86,7 +86,7 @@ echo -n hoho >/tmp/pcrlockpwd chmod 0600 /tmp/pcrlockpwd cryptsetup luksFormat -q --pbkdf pbkdf2 --pbkdf-force-iterations 1000 --use-urandom "$img" /tmp/pcrlockpwd -systemd-cryptenroll --unlock-key-file=/tmp/pcrlockpwd --tpm2-device=auto --tpm2-pcrlock=/var/lib/systemd/pcrlock.json --wipe-slot=tpm2 "$img" +systemd-cryptenroll --unlock-key-file=/tmp/pcrlockpwd --tpm2-device=auto --tpm2-pcrlock=/var/lib/systemd/pcrlock.json --tpm2-public-key= --wipe-slot=tpm2 "$img" systemd-cryptsetup attach pcrlock "$img" - tpm2-device=auto,tpm2-pcrlock=/var/lib/systemd/pcrlock.json,headless systemd-cryptsetup detach pcrlock @@ -136,7 +136,7 @@ systemd-cryptenroll --unlock-tpm2-device=auto --tpm2-device=auto --tpm2-pcrlock= "$SD_MEASURE" sign --current --bank=sha256 --private-key="$img".private.pem --public-key="$img".public.pem --phase=: | tee "$img".pcrsign SYSTEMD_CRYPTSETUP_USE_TOKEN_MODULE=0 systemd-cryptsetup attach pcrlock "$img" - "tpm2-device=auto,tpm2-pcrlock=/var/lib/systemd/pcrlock.json,tpm2-signature=$img.pcrsign,headless" systemd-cryptsetup detach pcrlock -systemd-cryptenroll --unlock-key-file=/tmp/pcrlockpwd --tpm2-device=auto --tpm2-pcrlock=/var/lib/systemd/pcrlock.json --wipe-slot=tpm2 "$img" +systemd-cryptenroll --unlock-key-file=/tmp/pcrlockpwd --tpm2-device=auto --tpm2-pcrlock=/var/lib/systemd/pcrlock.json --tpm2-public-key= --wipe-slot=tpm2 "$img" rm "$img".public.pem "$img".private.pem "$img".pcrsign # Now use the root fs support, i.e. make the tool write a copy of the pcrlock From 977fc93603b189141c4717691ee8194753989ff1 Mon Sep 17 00:00:00 2001 From: Daan De Meyer Date: Fri, 4 Oct 2024 16:46:16 +0200 Subject: [PATCH 5/5] Rework TEST-86-MULTI-PROFILE-UKI Now that mkosi supports generating UKI profiles, let's make use of that to generate the UKI profiles required for the test instead of doing it within the test itself. --- mkosi.conf | 3 +- mkosi.uki-profiles/profile1.conf | 7 +++ mkosi.uki-profiles/profile2.conf | 7 +++ test/TEST-86-MULTI-PROFILE-UKI/meson.build | 1 - test/units/TEST-86-MULTI-PROFILE-UKI.sh | 65 +++++++++------------- 5 files changed, 40 insertions(+), 43 deletions(-) create mode 100644 mkosi.uki-profiles/profile1.conf create mode 100644 mkosi.uki-profiles/profile2.conf diff --git a/mkosi.conf b/mkosi.conf index b76cefe0df..f2389b7f01 100644 --- a/mkosi.conf +++ b/mkosi.conf @@ -33,9 +33,8 @@ CacheDirectory=build/mkosi.cache BuildSourcesEphemeral=yes Incremental=yes -# TODO: Remove when TEST-70-TPM doesn't fail in an image with signed PCRs anymore. [Validation] -SignExpectedPcr=no +SignExpectedPcr=yes [Content] ExtraTrees= diff --git a/mkosi.uki-profiles/profile1.conf b/mkosi.uki-profiles/profile1.conf new file mode 100644 index 0000000000..3dc39d2534 --- /dev/null +++ b/mkosi.uki-profiles/profile1.conf @@ -0,0 +1,7 @@ +# SPDX-License-Identifier: LGPL-2.1-or-later + +[UKIProfile] +Profile= + ID=profile1 + TITLE=Profile Two +Cmdline=testprofile1=1 diff --git a/mkosi.uki-profiles/profile2.conf b/mkosi.uki-profiles/profile2.conf new file mode 100644 index 0000000000..d5bd4b6b6a --- /dev/null +++ b/mkosi.uki-profiles/profile2.conf @@ -0,0 +1,7 @@ +# SPDX-License-Identifier: LGPL-2.1-or-later + +[UKIProfile] +Profile= + ID=profile2 + TITLE=Profile Two +Cmdline=testprofile2=1 diff --git a/test/TEST-86-MULTI-PROFILE-UKI/meson.build b/test/TEST-86-MULTI-PROFILE-UKI/meson.build index 10d5957d8f..53042884cc 100644 --- a/test/TEST-86-MULTI-PROFILE-UKI/meson.build +++ b/test/TEST-86-MULTI-PROFILE-UKI/meson.build @@ -6,6 +6,5 @@ integration_tests += [ 'storage' : 'persistent', 'vm' : true, 'firmware' : 'auto', - 'enabled' : false, }, ] diff --git a/test/units/TEST-86-MULTI-PROFILE-UKI.sh b/test/units/TEST-86-MULTI-PROFILE-UKI.sh index 042cc59419..1af0788d5e 100755 --- a/test/units/TEST-86-MULTI-PROFILE-UKI.sh +++ b/test/units/TEST-86-MULTI-PROFILE-UKI.sh @@ -25,57 +25,42 @@ fi echo "CURRENT EVENT LOG + PCRS:" /usr/lib/systemd/systemd-pcrlock -if test ! -f /run/systemd/stub/profile; then - openssl genpkey -algorithm RSA -pkeyopt rsa_keygen_bits:2048 -out /root/pcrsign.private.pem - openssl rsa -pubout -in /root/pcrsign.private.pem -out /root/pcrsign.public.pem +test -f /run/systemd/stub/profile - ukify build --extend="$CURRENT_UKI" --output=/tmp/extended0.efi --profile='ID=profile0 -TITLE="Profile Zero"' --measure-base="$CURRENT_UKI" --pcr-private-key=/root/pcrsign.private.pem --pcr-public-key=/root/pcrsign.public.pem --pcr-banks=sha256,sha384,sha512 +# shellcheck source=/dev/null +. /run/systemd/stub/profile - ukify build --extend=/tmp/extended0.efi --output=/tmp/extended1.efi --profile='ID=profile1 -TITLE="Profile One"' --measure-base=/tmp/extended0.efi --cmdline="testprofile1=1 $(cat /proc/cmdline)" --pcr-private-key=/root/pcrsign.private.pem --pcr-public-key=/root/pcrsign.public.pem --pcr-banks=sha256,sha384,sha512 +if [[ "$ID" == "main" ]]; then + if [[ -f /root/encrypted.raw ]]; then + exit 1 + fi - ukify build --extend=/tmp/extended1.efi --output=/tmp/extended2.efi --profile='ID=profile2 -TITLE="Profile Two"' --measure-base=/tmp/extended1.efi --cmdline="testprofile2=1 $(cat /proc/cmdline)" --pcr-private-key=/root/pcrsign.private.pem --pcr-public-key=/root/pcrsign.public.pem --pcr-banks=sha256,sha384,sha512 - - echo "EXTENDED UKI:" - ukify inspect /tmp/extended2.efi - rm /tmp/extended0.efi /tmp/extended1.efi - mv /tmp/extended2.efi "$CURRENT_UKI" - - # Prepare a disk image, locked to the PCR measurements of the UKI we just generated + # Prepare a disk image, locked to the PCR measurements of the current UKI truncate -s 32M /root/encrypted.raw echo -n "geheim" >/root/encrypted.secret cryptsetup luksFormat -q --pbkdf pbkdf2 --pbkdf-force-iterations 1000 --use-urandom /root/encrypted.raw --key-file=/root/encrypted.secret - systemd-cryptenroll --tpm2-device=auto --tpm2-pcrs= --tpm2-public-key=/root/pcrsign.public.pem --unlock-key-file=/root/encrypted.secret /root/encrypted.raw + systemd-cryptenroll --tpm2-device=auto --tpm2-pcrs= --unlock-key-file=/root/encrypted.secret /root/encrypted.raw rm -f /root/encrypted.secret +fi +# Validate that with the current profile we can fulfill the PCR 11 policy +systemd-cryptsetup attach multiprof /root/encrypted.raw - tpm2-device=auto,headless=1 +systemd-cryptsetup detach multiprof + +if [[ "$ID" == "main" ]]; then + bootctl set-default "$(basename "$CURRENT_UKI")@profile1" reboot exit 0 +elif [[ "$ID" == "profile1" ]]; then + grep testprofile1=1 /proc/cmdline + bootctl set-default "$(basename "$CURRENT_UKI")@profile2" + reboot + exit 0 +elif [[ "$ID" == "profile2" ]]; then + grep testprofile2=1 /proc/cmdline + rm /root/encrypted.raw else - # shellcheck source=/dev/null - . /run/systemd/stub/profile - - # Validate that with the current profile we can fulfill the PCR 11 policy - systemd-cryptsetup attach multiprof /root/encrypted.raw - tpm2-device=auto,headless=1 - systemd-cryptsetup detach multiprof - - if [ "$ID" = "profile0" ]; then - grep -v testprofile /proc/cmdline - echo "default $(basename "$CURRENT_UKI")@profile1" >"$(bootctl -p)/loader/loader.conf" - reboot - exit 0 - elif [ "$ID" = "profile1" ]; then - grep testprofile1=1 /proc/cmdline - echo "default $(basename "$CURRENT_UKI")@profile2" >"$(bootctl -p)/loader/loader.conf" - reboot - exit 0 - elif [ "$ID" = "profile2" ]; then - grep testprofile2=1 /proc/cmdline - rm /root/encrypted.raw - else - exit 1 - fi + exit 1 fi touch /testok