From 8d5e4d59f2d1c648e3cd249b55d0825c814f7ad0 Mon Sep 17 00:00:00 2001 From: Lennart Poettering Date: Mon, 25 Jul 2022 17:35:16 +0200 Subject: [PATCH 01/18] efi: optionally report when measuring to TPM whether we actually did the measurement calls can succeed either when they actually measured something, or when they skipped measurement because the local system didn't support TPMs. Let's optionally return a boolean saying which case it is. This is later useful to tell userspace how and if we measured something. --- src/boot/efi/boot.c | 2 +- src/boot/efi/cpio.c | 3 +- src/boot/efi/measure.c | 64 +++++++++++++++++++++++++++++++++--------- src/boot/efi/measure.h | 19 ++++++++++--- src/boot/efi/stub.c | 2 +- 5 files changed, 69 insertions(+), 21 deletions(-) diff --git a/src/boot/efi/boot.c b/src/boot/efi/boot.c index ece1c11cd5e..193eebde0c2 100644 --- a/src/boot/efi/boot.c +++ b/src/boot/efi/boot.c @@ -2339,7 +2339,7 @@ static EFI_STATUS image_start( loaded_image->LoadOptionsSize = strsize16(options); /* Try to log any options to the TPM, especially to catch manually edited options */ - (void) tpm_log_load_options(options); + (void) tpm_log_load_options(options, NULL); } efivar_set_time_usec(LOADER_GUID, L"LoaderTimeExecUSec", 0); diff --git a/src/boot/efi/cpio.c b/src/boot/efi/cpio.c index 6905e07c851..693b3816a49 100644 --- a/src/boot/efi/cpio.c +++ b/src/boot/efi/cpio.c @@ -436,7 +436,8 @@ EFI_STATUS pack_cpio( tpm_pcr[i], POINTER_TO_PHYSICAL_ADDRESS(buffer), buffer_size, - tpm_description); + tpm_description, + NULL); if (err != EFI_SUCCESS) log_error_stall(L"Unable to add initrd TPM measurement for PCR %u (%s), ignoring: %r", tpm_pcr[i], tpm_description, err); } diff --git a/src/boot/efi/measure.c b/src/boot/efi/measure.c index 71085546818..9041896f5b3 100644 --- a/src/boot/efi/measure.c +++ b/src/boot/efi/measure.c @@ -141,43 +141,79 @@ bool tpm_present(void) { return tcg2_interface_check() || tcg1_interface_check(); } -EFI_STATUS tpm_log_event(uint32_t pcrindex, EFI_PHYSICAL_ADDRESS buffer, UINTN buffer_size, const char16_t *description) { - EFI_TCG *tpm1; +EFI_STATUS tpm_log_event(uint32_t pcrindex, EFI_PHYSICAL_ADDRESS buffer, UINTN buffer_size, const char16_t *description, bool *ret_measured) { EFI_TCG2 *tpm2; + EFI_STATUS err; assert(description); - /* PCR disabled */ - if (pcrindex == UINT32_MAX) + /* If EFI_SUCCESS is returned, will initialize ret_measured to true if we actually measured + * something, or false if measurement was turned off. */ + + if (pcrindex == UINT32_MAX) { /* PCR disabled? */ + if (ret_measured) + *ret_measured = false; + return EFI_SUCCESS; + } tpm2 = tcg2_interface_check(); if (tpm2) - return tpm2_measure_to_pcr_and_event_log(tpm2, pcrindex, buffer, buffer_size, description); + err = tpm2_measure_to_pcr_and_event_log(tpm2, pcrindex, buffer, buffer_size, description); + else { + EFI_TCG *tpm1; - tpm1 = tcg1_interface_check(); - if (tpm1) - return tpm1_measure_to_pcr_and_event_log(tpm1, pcrindex, buffer, buffer_size, description); + tpm1 = tcg1_interface_check(); + if (tpm1) + err = tpm1_measure_to_pcr_and_event_log(tpm1, pcrindex, buffer, buffer_size, description); + else { + /* No active TPM found, so don't return an error */ - /* No active TPM found, so don't return an error */ - return EFI_SUCCESS; + if (ret_measured) + *ret_measured = false; + + return EFI_SUCCESS; + } + } + + if (err == EFI_SUCCESS && ret_measured) + *ret_measured = true; + + return err; } -EFI_STATUS tpm_log_load_options(const char16_t *load_options) { +EFI_STATUS tpm_log_event_ascii(uint32_t pcrindex, EFI_PHYSICAL_ADDRESS buffer, UINTN buffer_size, const char *description, bool *ret_measured) { + _cleanup_free_ char16_t *c = NULL; + + if (description) + c = xstra_to_str(description); + + return tpm_log_event(pcrindex, buffer, buffer_size, c, ret_measured); +} + +EFI_STATUS tpm_log_load_options(const char16_t *load_options, bool *ret_measured) { + int measured = -1; EFI_STATUS err; /* Measures a load options string into the TPM2, i.e. the kernel command line */ for (UINTN i = 0; i < 2; i++) { uint32_t pcr = i == 0 ? TPM_PCR_INDEX_KERNEL_PARAMETERS : TPM_PCR_INDEX_KERNEL_PARAMETERS_COMPAT; + bool m; - err = tpm_log_event(pcr, - POINTER_TO_PHYSICAL_ADDRESS(load_options), - strsize16(load_options), load_options); + if (pcr == UINT32_MAX) /* Skip this one, if it's invalid, so that our 'measured' return value is not corrupted by it */ + continue; + + err = tpm_log_event(pcr, POINTER_TO_PHYSICAL_ADDRESS(load_options), strsize16(load_options), load_options, &m); if (err != EFI_SUCCESS) return log_error_status_stall(err, L"Unable to add load options (i.e. kernel command) line measurement to PCR %u: %r", pcr, err); + + measured = measured < 0 ? m : (measured && m); } + if (ret_measured) + *ret_measured = measured < 0 ? false : measured; + return EFI_SUCCESS; } diff --git a/src/boot/efi/measure.h b/src/boot/efi/measure.h index af1cc62cdd0..5da160f8c87 100644 --- a/src/boot/efi/measure.h +++ b/src/boot/efi/measure.h @@ -22,8 +22,9 @@ #if ENABLE_TPM bool tpm_present(void); -EFI_STATUS tpm_log_event(uint32_t pcrindex, EFI_PHYSICAL_ADDRESS buffer, UINTN buffer_size, const char16_t *description); -EFI_STATUS tpm_log_load_options(const char16_t *cmdline); +EFI_STATUS tpm_log_event(uint32_t pcrindex, EFI_PHYSICAL_ADDRESS buffer, UINTN buffer_size, const char16_t *description, bool *ret_measured); +EFI_STATUS tpm_log_event_ascii(uint32_t pcrindex, EFI_PHYSICAL_ADDRESS buffer, UINTN buffer_size, const char *description, bool *ret_measured); +EFI_STATUS tpm_log_load_options(const char16_t *cmdline, bool *ret_measured); #else @@ -31,11 +32,21 @@ static inline bool tpm_present(void) { return false; } -static inline EFI_STATUS tpm_log_event(uint32_t pcrindex, EFI_PHYSICAL_ADDRESS buffer, UINTN buffer_size, const char16_t *description) { +static inline EFI_STATUS tpm_log_event(uint32_t pcrindex, EFI_PHYSICAL_ADDRESS buffer, UINTN buffer_size, const char16_t *description, bool *ret_measured) { + if (ret_measured) + *ret_measured = false; return EFI_SUCCESS; } -static inline EFI_STATUS tpm_log_load_options(const char16_t *cmdline) { +static inline EFI_STATUS tpm_log_event_ascii(uint32_t pcrindex, EFI_PHYSICAL_ADDRESS buffer, UINTN buffer_size, const char *description, bool *ret_measured) { + if (ret_measured) + *ret_measured = false; + return EFI_SUCCESS; +} + +static inline EFI_STATUS tpm_log_load_options(const char16_t *cmdline, bool *ret_measured) { + if (ret_measured) + *ret_measured = false; return EFI_SUCCESS; } diff --git a/src/boot/efi/stub.c b/src/boot/efi/stub.c index 83badd96cda..7f43678cb14 100644 --- a/src/boot/efi/stub.c +++ b/src/boot/efi/stub.c @@ -223,7 +223,7 @@ EFI_STATUS efi_main(EFI_HANDLE image, EFI_SYSTEM_TABLE *sys_table) { * duplicates what we already did in the boot menu, if that was already used. However, since * we want the boot menu to support an EFI binary, and want to this stub to be usable from * any boot menu, let's measure things anyway. */ - (void) tpm_log_load_options(loaded_image->LoadOptions); + (void) tpm_log_load_options(loaded_image->LoadOptions, NULL); } export_variables(loaded_image); From 599fe002a135570406504998dbb7a42dabc963da Mon Sep 17 00:00:00 2001 From: Lennart Poettering Date: Tue, 26 Jul 2022 18:32:21 +0200 Subject: [PATCH 02/18] efi: tell userspace where the stub measured the kernel command line/credentials into This is useful for userspace to know, so that policies can be put together safely, matching what the stub actually measured. --- man/systemd-stub.xml | 10 +++++++- src/boot/efi/cpio.c | 24 +++++++++++++++--- src/boot/efi/cpio.h | 4 ++- src/boot/efi/stub.c | 60 ++++++++++++++++++++++++++------------------ 4 files changed, 69 insertions(+), 29 deletions(-) diff --git a/man/systemd-stub.xml b/man/systemd-stub.xml index 92a20e259ed..955fa6f98f4 100644 --- a/man/systemd-stub.xml +++ b/man/systemd-stub.xml @@ -185,7 +185,7 @@ Credentials (synthesized initrd from companion files) - 12 + 9 + 9 + 12 @@ -239,6 +239,14 @@ bootctl1 to view this data. + + + StubPcrKernelParameters + + The PCR register index the kernel command line and credentials are measured into, + formatted as decimal ASCII string (i.e. 12). This variable is set if a measurement + was successfully completed, and remains unset otherwise. + Note that some of the variables above may also be set by the boot loader. The stub will only set diff --git a/src/boot/efi/cpio.c b/src/boot/efi/cpio.c index 693b3816a49..0d41102d2d8 100644 --- a/src/boot/efi/cpio.c +++ b/src/boot/efi/cpio.c @@ -315,7 +315,8 @@ EFI_STATUS pack_cpio( UINTN n_tpm_pcr, const char16_t *tpm_description, void **ret_buffer, - UINTN *ret_buffer_size) { + UINTN *ret_buffer_size, + bool *ret_measured) { _cleanup_(file_closep) EFI_FILE *root = NULL, *extra_dir = NULL; UINTN dirent_size = 0, buffer_size = 0, n_items = 0, n_allocated = 0; @@ -324,6 +325,7 @@ EFI_STATUS pack_cpio( _cleanup_(strv_freep) char16_t **items = NULL; _cleanup_free_ void *buffer = NULL; uint32_t inode = 1; /* inode counter, so that each item gets a new inode */ + int measured = -1; EFI_STATUS err; assert(loaded_image); @@ -432,24 +434,40 @@ EFI_STATUS pack_cpio( return log_error_status_stall(err, L"Failed to pack cpio trailer: %r"); for (UINTN i = 0; i < n_tpm_pcr; i++) { + bool m; + + if (tpm_pcr[i] == UINT32_MAX) /* Disabled */ + continue; + err = tpm_log_event( tpm_pcr[i], POINTER_TO_PHYSICAL_ADDRESS(buffer), buffer_size, tpm_description, - NULL); - if (err != EFI_SUCCESS) + &m); + if (err != EFI_SUCCESS) { log_error_stall(L"Unable to add initrd TPM measurement for PCR %u (%s), ignoring: %r", tpm_pcr[i], tpm_description, err); + measured = false; + continue; + } + + measured = measured < 0 ? m : (measured && m); } *ret_buffer = TAKE_PTR(buffer); *ret_buffer_size = buffer_size; + if (ret_measured) + *ret_measured = measured; + return EFI_SUCCESS; nothing: *ret_buffer = NULL; *ret_buffer_size = 0; + if (ret_measured) + *ret_measured = true; + return EFI_SUCCESS; } diff --git a/src/boot/efi/cpio.h b/src/boot/efi/cpio.h index e3d206d7e89..672a751825f 100644 --- a/src/boot/efi/cpio.h +++ b/src/boot/efi/cpio.h @@ -2,6 +2,7 @@ #pragma once #include +#include #include EFI_STATUS pack_cpio( @@ -15,4 +16,5 @@ EFI_STATUS pack_cpio( UINTN n_tpm_pcr, const char16_t *tpm_description, void **ret_buffer, - UINTN *ret_buffer_size); + UINTN *ret_buffer_size, + bool *ret_measured); diff --git a/src/boot/efi/stub.c b/src/boot/efi/stub.c index 7f43678cb14..3673d163450 100644 --- a/src/boot/efi/stub.c +++ b/src/boot/efi/stub.c @@ -178,7 +178,9 @@ EFI_STATUS efi_main(EFI_HANDLE image, EFI_SYSTEM_TABLE *sys_table) { UINTN szs[_SECTION_MAX] = {}; char *cmdline = NULL; _cleanup_free_ char *cmdline_owned = NULL; + int parameters_measured = -1; EFI_STATUS err; + bool m; InitializeLib(image, sys_table); debug_hook(L"systemd-stub"); @@ -223,34 +225,40 @@ EFI_STATUS efi_main(EFI_HANDLE image, EFI_SYSTEM_TABLE *sys_table) { * duplicates what we already did in the boot menu, if that was already used. However, since * we want the boot menu to support an EFI binary, and want to this stub to be usable from * any boot menu, let's measure things anyway. */ - (void) tpm_log_load_options(loaded_image->LoadOptions, NULL); + m = false; + (void) tpm_log_load_options(loaded_image->LoadOptions, &m); + parameters_measured = m; } export_variables(loaded_image); - (void) pack_cpio(loaded_image, - NULL, - L".cred", - ".extra/credentials", - /* dir_mode= */ 0500, - /* access_mode= */ 0400, - /* tpm_pcr= */ (uint32_t[]) { TPM_PCR_INDEX_KERNEL_PARAMETERS, TPM_PCR_INDEX_KERNEL_PARAMETERS_COMPAT }, - /* n_tpm_pcr= */ 2, - L"Credentials initrd", - &credential_initrd, - &credential_initrd_size); + if (pack_cpio(loaded_image, + NULL, + L".cred", + ".extra/credentials", + /* dir_mode= */ 0500, + /* access_mode= */ 0400, + /* tpm_pcr= */ (uint32_t[]) { TPM_PCR_INDEX_KERNEL_PARAMETERS, TPM_PCR_INDEX_KERNEL_PARAMETERS_COMPAT }, + /* n_tpm_pcr= */ 2, + L"Credentials initrd", + &credential_initrd, + &credential_initrd_size, + &m) == EFI_SUCCESS) + parameters_measured = parameters_measured < 0 ? m : (parameters_measured && m); - (void) pack_cpio(loaded_image, - L"\\loader\\credentials", - L".cred", - ".extra/global_credentials", - /* dir_mode= */ 0500, - /* access_mode= */ 0400, - /* tpm_pcr= */ (uint32_t[]) { TPM_PCR_INDEX_KERNEL_PARAMETERS, TPM_PCR_INDEX_KERNEL_PARAMETERS_COMPAT }, - /* n_tpm_pcr= */ 2, - L"Global credentials initrd", - &global_credential_initrd, - &global_credential_initrd_size); + if (pack_cpio(loaded_image, + L"\\loader\\credentials", + L".cred", + ".extra/global_credentials", + /* dir_mode= */ 0500, + /* access_mode= */ 0400, + /* tpm_pcr= */ (uint32_t[]) { TPM_PCR_INDEX_KERNEL_PARAMETERS, TPM_PCR_INDEX_KERNEL_PARAMETERS_COMPAT }, + /* n_tpm_pcr= */ 2, + L"Global credentials initrd", + &global_credential_initrd, + &global_credential_initrd_size, + &m) == EFI_SUCCESS) + parameters_measured = parameters_measured < 0 ? m : (parameters_measured && m); (void) pack_cpio(loaded_image, NULL, @@ -262,7 +270,11 @@ EFI_STATUS efi_main(EFI_HANDLE image, EFI_SYSTEM_TABLE *sys_table) { /* n_tpm_pcr= */ 1, L"System extension initrd", &sysext_initrd, - &sysext_initrd_size); + &sysext_initrd_size, + NULL); + + if (parameters_measured > 0) + (void) efivar_set_uint_string(LOADER_GUID, L"StubPcrKernelParameters", TPM_PCR_INDEX_KERNEL_PARAMETERS, 0); linux_size = szs[SECTION_LINUX]; linux_base = POINTER_TO_PHYSICAL_ADDRESS(loaded_image->ImageBase) + addrs[SECTION_LINUX]; From 72c97c19c3865f72ec3e40d31d2a57f40880dbe5 Mon Sep 17 00:00:00 2001 From: Lennart Poettering Date: Mon, 25 Jul 2022 17:44:24 +0200 Subject: [PATCH 03/18] efi: from the stub measure the ELF kernel + built-in initrd and so on into PCR 11 MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Here we grab a new – on Linux so far unused (by my Googling skills, that is) – and measure all static components of the PE kernel image into. This is useful since for the first time we'll have a PCR that contains only a PCR of the booted kernel, nothing else. That allows putting together TPM policies that bind to a specific kernel (+ builtin initrd), without having to have booted that kernel first. PCRs can be pre-calculated. Yay! You might wonder, why we measure just the discovered PE sections we are about to use, instead of the whole PE image. That's because of the next step I have in mind: PE images should also be able to carry an additional section that contains a signature for its own expected, pre-calculated PCR values. This signature data should then be passed into the booted kernel and can be used there in TPM policies. Benefit: TPM policies can now be bound to *signatures* of PCRs, instead of the raw hash values themselves. This makes update management a *lot* easier, as policies don't need to be updated whenever a kernel is updated, as long as the signature is available. Now, if the PCR signature is embedded in the kernel PE image it cannot be of a PCR hash of the kernel PE image itself, because that would be a chicken-and-egg problem. Hence, by only measuring the relavent payload sections (and that means excluding the future section that will contain the PCR hash signature) we avoid this problem, naturally. --- man/systemd-cryptenroll.xml | 5 +++++ man/systemd-stub.xml | 32 ++++++++++++++++++++++-------- src/boot/efi/measure.h | 6 ++++++ src/boot/efi/stub.c | 39 +++++++++++++++++++++++++++++++++++-- 4 files changed, 72 insertions(+), 10 deletions(-) diff --git a/man/systemd-cryptenroll.xml b/man/systemd-cryptenroll.xml index 8e1b9857a72..4a5127b02d1 100644 --- a/man/systemd-cryptenroll.xml +++ b/man/systemd-cryptenroll.xml @@ -296,6 +296,11 @@ The IMA project measures its runtime state into this PCR. + + 11 + systemd-stub7 measures the ELF kernel image, embedded initrd and other payload of the PE image it is placed in into this PCR. Unlike PCR 4 (where the same data should be measured into), this PCR value should be easy to pre-calculate, as this only contains static parts of the PE binary. Use this PCR to bind TPM policies to a specific kernel image, possibly with an embedded initial RAM disk (initrd). + + 12 systemd-boot7 measures any specified kernel command line into this PCR. systemd-stub7 measures any manually specified kernel command line (i.e. a kernel command line that overrides the one embedded in the unified PE image) and loaded credentials into this PCR. (Note that if systemd-boot and systemd-stub are used in combination the command line might be measured twice!) diff --git a/man/systemd-stub.xml b/man/systemd-stub.xml index 955fa6f98f4..df7cabcf503 100644 --- a/man/systemd-stub.xml +++ b/man/systemd-stub.xml @@ -53,6 +53,10 @@ The ELF Linux kernel images will be looked for in the .linux PE section of the executed image. + OS release information, i.e. the + os-release5 file of + the OS the kernel belongs to, in the .osrel PE section. + The initial RAM disk (initrd) will be looked for in the .initrd PE section. @@ -76,6 +80,9 @@ If a DeviceTree is embedded in the .dtb section, it replaces an existing DeviceTree in the corresponding EFI configuration table. systemd-stub will ask the firmware via the EFI_DT_FIXUP_PROTOCOL for hardware specific fixups to the DeviceTree. + + The contents of these six PE sections are measured into TPM PCR 11, that is otherwise not + used. Thus, it can be pre-calculated without too much effort. @@ -133,10 +140,10 @@ core kernel, the embedded initrd and kernel command line (see above for a full list). Also note that the Linux kernel will measure all initrds it receives into TPM PCR 9. This means - every type of initrd will be measured twice: the initrd embedded in the kernel image will be measured to - both PCR 4 and PCR 9; the initrd synthesized from credentials will be measured to both PCR 12 and PCR 9; - the initrd synthesized from system extensions will be measured to both PCR 4 and PCR 9. Let's summarize - the OS resources and the PCRs they are measured to: + every type of initrd will be measured two or three times: the initrd embedded in the kernel image will be + measured to PCR 4, PCR 9 and PCR 11; the initrd synthesized from credentials will be measured to both PCR + 9 and PCR 12; the initrd synthesized from system extensions will be measured to both PCR 4 and PCR + 9. Let's summarize the OS resources and the PCRs they are measured to: OS Resource PCR Summary @@ -160,22 +167,22 @@ Boot splash (embedded in the unified PE binary) - 4 + 4 + 11 Core kernel code (embedded in unified PE binary) - 4 + 4 + 11 Main initrd (embedded in unified PE binary) - 4 + 9 + 4 + 9 + 11 Default kernel command line (embedded in unified PE binary) - 4 + 4 + 11 @@ -240,6 +247,15 @@ this data. + + StubPcrKernelImage + + The PCR register index the ELF kernel image/initial RAM disk image/boot + splash/devicetree database/embedded command line are measured into, formatted as decimal ASCII string + (i.e. 11). This variable is set if a measurement was successfully completed, and + remains unset otherwise. + + StubPcrKernelParameters diff --git a/src/boot/efi/measure.h b/src/boot/efi/measure.h index 5da160f8c87..df7f04c9fbb 100644 --- a/src/boot/efi/measure.h +++ b/src/boot/efi/measure.h @@ -5,6 +5,12 @@ #include #include +/* This TPM PCR is where we extend the sd-stub "payloads" into, before using them. i.e. the kernel ELF image, + * embedded initrd, and so on. In contrast to PCR 4 (which also contains this data, given the whole + * surrounding PE image is measured into it) this should be reasonably pre-calculatable, because it *only* + * consists of static data from the kernel PE image. */ +#define TPM_PCR_INDEX_KERNEL_IMAGE 11U + /* This TPM PCR is where sd-stub extends the kernel command line and any passed credentials into. */ #define TPM_PCR_INDEX_KERNEL_PARAMETERS 12U diff --git a/src/boot/efi/stub.c b/src/boot/efi/stub.c index 3673d163450..9ec01d1baa6 100644 --- a/src/boot/efi/stub.c +++ b/src/boot/efi/stub.c @@ -149,7 +149,7 @@ static void export_variables(EFI_LOADED_IMAGE_PROTOCOL *loaded_image) { EFI_STATUS efi_main(EFI_HANDLE image, EFI_SYSTEM_TABLE *sys_table) { - enum { + enum Section { SECTION_CMDLINE, SECTION_LINUX, SECTION_INITRD, @@ -178,7 +178,7 @@ EFI_STATUS efi_main(EFI_HANDLE image, EFI_SYSTEM_TABLE *sys_table) { UINTN szs[_SECTION_MAX] = {}; char *cmdline = NULL; _cleanup_free_ char *cmdline_owned = NULL; - int parameters_measured = -1; + int sections_measured = -1, parameters_measured = -1; EFI_STATUS err; bool m; @@ -204,6 +204,41 @@ EFI_STATUS efi_main(EFI_HANDLE image, EFI_SYSTEM_TABLE *sys_table) { return log_error_status_stall(err, L"Unable to locate embedded .linux section: %r", err); } + /* Measure all "payload" of this PE image into a separate PCR (i.e. where nothing else is written + * into so far), so that we have one PCR that we can nicely write policies against because it + * contains all static data of this image, and thus can be easily be pre-calculated. */ + for (enum Section section = 0; section < _SECTION_MAX; section++) { + m = false; + + if (szs[section] == 0) /* not found */ + continue; + + /* First measure the name of the section */ + (void) tpm_log_event_ascii( + TPM_PCR_INDEX_KERNEL_IMAGE, + POINTER_TO_PHYSICAL_ADDRESS(sections[section]), + strsize8(sections[section]), /* including NUL byte */ + sections[section], + &m); + + sections_measured = sections_measured < 0 ? m : (sections_measured && m); + + /* Then measure the data of the section */ + (void) tpm_log_event_ascii( + TPM_PCR_INDEX_KERNEL_IMAGE, + POINTER_TO_PHYSICAL_ADDRESS(loaded_image->ImageBase) + addrs[section], + szs[section], + sections[section], + &m); + + sections_measured = sections_measured < 0 ? m : (sections_measured && m); + } + + /* After we are done, set an EFI variable that tells userspace this was done successfully, and encode + * in it which PCR was used. */ + if (sections_measured > 0) + (void) efivar_set_uint_string(LOADER_GUID, L"StubPcrKernelImage", TPM_PCR_INDEX_KERNEL_IMAGE, 0); + /* Show splash screen as early as possible */ graphics_splash((const uint8_t*) loaded_image->ImageBase + addrs[SECTION_SPLASH], szs[SECTION_SPLASH], NULL); From 16700cb85ab2d815dec3f489c3280f264ea23a86 Mon Sep 17 00:00:00 2001 From: Lennart Poettering Date: Tue, 26 Jul 2022 00:13:06 +0200 Subject: [PATCH 04/18] stub: measure PE sections in a defined order, and include all sections --- src/boot/efi/stub.c | 7 +++++-- 1 file changed, 5 insertions(+), 2 deletions(-) diff --git a/src/boot/efi/stub.c b/src/boot/efi/stub.c index 9ec01d1baa6..1afbde32952 100644 --- a/src/boot/efi/stub.c +++ b/src/boot/efi/stub.c @@ -150,8 +150,10 @@ static void export_variables(EFI_LOADED_IMAGE_PROTOCOL *loaded_image) { EFI_STATUS efi_main(EFI_HANDLE image, EFI_SYSTEM_TABLE *sys_table) { enum Section { - SECTION_CMDLINE, + /* This is the canonical order in which we measure the sections. PLEASE DO NOT REORDER! */ SECTION_LINUX, + SECTION_OSREL, + SECTION_CMDLINE, SECTION_INITRD, SECTION_SPLASH, SECTION_DTB, @@ -159,8 +161,9 @@ EFI_STATUS efi_main(EFI_HANDLE image, EFI_SYSTEM_TABLE *sys_table) { }; static const char * const sections[_SECTION_MAX + 1] = { - [SECTION_CMDLINE] = ".cmdline", [SECTION_LINUX] = ".linux", + [SECTION_OSREL] = ".osrel", + [SECTION_CMDLINE] = ".cmdline", [SECTION_INITRD] = ".initrd", [SECTION_SPLASH] = ".splash", [SECTION_DTB] = ".dtb", From de7ad6d4f439f912ab0ba078dd29ef21a0af3623 Mon Sep 17 00:00:00 2001 From: Lennart Poettering Date: Tue, 26 Jul 2022 11:35:57 +0200 Subject: [PATCH 05/18] sd-stub: measure sysext images picked up by sd-stub into PCR 13 Let's grab another so far unused PCR, and measure all sysext images into it that we load from the ESP. Note that this is possibly partly redundant, since sysext images should have dm-verity enabled, and that is hooked up to IMA. However, measuring this explicitly has the benefit that we can measure filenames too, easily, and that all without need for IMA or anything like that. This means: when booting a unified sd-stub kernel through sd-boot we'll now have: 1. PCR 11: unified kernel image payload (i.e. kernel, initrd, boot splash, dtb, osrelease) 2. PCR 12: kernel command line (i.e. the one embedded in the image, plus optionally an overriden one) + any credential files picked up by sd-stub 3. PCR 13: sysext images picked up by sd-stub And each of these three PCRs should carry just the above, and start from zero, thus be pre-calculatable. Thus, all components and parameters of the OS boot process (i.e. everything after the boot loader) is now nicely pre-calculable. NOTE: this actually replaces previous measuring of the syext images into PCR 4. I added this back in 845707aae23b3129db635604edb95c4048a5922a, following the train of thought, that sysext images for the initrd should be measured like the initrd itself they are for, and according to my thinking that would be a unified kernel which is measured by firmware into PCR 4 like any other UEFI executables. However, I think we should depart from that idea. First and foremost that makes it harder to pre-calculate PCR 4 (since we actually measured quite incompatible records to the TPM event log), but also I think there's great value in being able to write policies that bind to the used sysexts independently of the earlier boot chain (i.e. shim, boot loader, unified kernel), hence a separate PCR makes more sense. Strictly speaking, this is a compatibility break, but I think one we can get away with, simply because the initrd sysext images are currently not picked up by systemd-sysext yet in the initrd, and because of that we can be reasonably sure noone uses this yet, and hence relies on the PCR register used. Hence, let's clean this up before people actually do start relying on this. --- man/systemd-cryptenroll.xml | 5 +++++ man/systemd-stub.xml | 13 +++++++++++-- src/boot/efi/measure.h | 4 ++-- src/boot/efi/stub.c | 29 ++++++++++++++++------------- 4 files changed, 34 insertions(+), 17 deletions(-) diff --git a/man/systemd-cryptenroll.xml b/man/systemd-cryptenroll.xml index 4a5127b02d1..2aa396e3004 100644 --- a/man/systemd-cryptenroll.xml +++ b/man/systemd-cryptenroll.xml @@ -306,6 +306,11 @@ systemd-boot7 measures any specified kernel command line into this PCR. systemd-stub7 measures any manually specified kernel command line (i.e. a kernel command line that overrides the one embedded in the unified PE image) and loaded credentials into this PCR. (Note that if systemd-boot and systemd-stub are used in combination the command line might be measured twice!) + + 13 + systemd-stub7 measures any systemd-sysext8 images it loads and passed to the booted kernel into this PCR. + + 14 The shim project measures its "MOK" certificates and hashes into this PCR. diff --git a/man/systemd-stub.xml b/man/systemd-stub.xml index df7cabcf503..1e9bb5d631b 100644 --- a/man/systemd-stub.xml +++ b/man/systemd-stub.xml @@ -115,7 +115,7 @@ images to the initrd. See systemd-sysext8 for details on system extension images. The generated cpio archive containing these - system extension images is measured into TPM PCR 4 (if a TPM is present). + system extension images is measured into TPM PCR 13 (if a TPM is present). Files /loader/credentials/*.cred are packed up in a cpio archive and placed in the /.extra/global_credentials/ @@ -197,7 +197,7 @@ System Extensions (synthesized initrd from companion files) - 4 + 9 + 9 + 13 @@ -263,6 +263,15 @@ formatted as decimal ASCII string (i.e. 12). This variable is set if a measurement was successfully completed, and remains unset otherwise. + + + StubPcrInitRDSysExts + + The PCR register index the systemd extensions for the initial RAM disk image, which + are picked up from the file system the kernel image is located on. Formatted as decimal ASCII string + (i.e. 13). This variable is set if a measurement was successfully completed, and + remains unset otherwise. + Note that some of the variables above may also be set by the boot loader. The stub will only set diff --git a/src/boot/efi/measure.h b/src/boot/efi/measure.h index df7f04c9fbb..141d44aa79c 100644 --- a/src/boot/efi/measure.h +++ b/src/boot/efi/measure.h @@ -22,8 +22,8 @@ #define TPM_PCR_INDEX_KERNEL_PARAMETERS_COMPAT UINT32_MAX #endif -/* This TPM PCR is where most Linux infrastructure extends the initrd binary images into, and so do we. */ -#define TPM_PCR_INDEX_INITRD 4U +/* This TPM PCR is where we extend the initrd sysext images into which we pass to the booted kernel */ +#define TPM_PCR_INDEX_INITRD_SYSEXTS 13U #if ENABLE_TPM diff --git a/src/boot/efi/stub.c b/src/boot/efi/stub.c index 1afbde32952..3b74647a18c 100644 --- a/src/boot/efi/stub.c +++ b/src/boot/efi/stub.c @@ -182,8 +182,8 @@ EFI_STATUS efi_main(EFI_HANDLE image, EFI_SYSTEM_TABLE *sys_table) { char *cmdline = NULL; _cleanup_free_ char *cmdline_owned = NULL; int sections_measured = -1, parameters_measured = -1; + bool sysext_measured = false, m; EFI_STATUS err; - bool m; InitializeLib(image, sys_table); debug_hook(L"systemd-stub"); @@ -298,21 +298,24 @@ EFI_STATUS efi_main(EFI_HANDLE image, EFI_SYSTEM_TABLE *sys_table) { &m) == EFI_SUCCESS) parameters_measured = parameters_measured < 0 ? m : (parameters_measured && m); - (void) pack_cpio(loaded_image, - NULL, - L".raw", - ".extra/sysext", - /* dir_mode= */ 0555, - /* access_mode= */ 0444, - /* tpm_pcr= */ (uint32_t[]) { TPM_PCR_INDEX_INITRD }, - /* n_tpm_pcr= */ 1, - L"System extension initrd", - &sysext_initrd, - &sysext_initrd_size, - NULL); + if (pack_cpio(loaded_image, + NULL, + L".raw", + ".extra/sysext", + /* dir_mode= */ 0555, + /* access_mode= */ 0444, + /* tpm_pcr= */ (uint32_t[]) { TPM_PCR_INDEX_INITRD_SYSEXTS }, + /* n_tpm_pcr= */ 1, + L"System extension initrd", + &sysext_initrd, + &sysext_initrd_size, + &m) == EFI_SUCCESS) + sysext_measured = m; if (parameters_measured > 0) (void) efivar_set_uint_string(LOADER_GUID, L"StubPcrKernelParameters", TPM_PCR_INDEX_KERNEL_PARAMETERS, 0); + if (sysext_measured) + (void) efivar_set_uint_string(LOADER_GUID, L"StubPcrInitRDSysExts", TPM_PCR_INDEX_INITRD_SYSEXTS, 0); linux_size = szs[SECTION_LINUX]; linux_base = POINTER_TO_PHYSICAL_ADDRESS(loaded_image->ImageBase) + addrs[SECTION_LINUX]; From aa323c090750408fc008bfcdc538d8cf54d15180 Mon Sep 17 00:00:00 2001 From: Lennart Poettering Date: Wed, 27 Jul 2022 10:43:34 +0200 Subject: [PATCH 06/18] boot: split out TPM PCR defines into header file we can share between userspace and kernel space --- src/boot/efi/measure.c | 1 + src/boot/efi/measure.h | 20 -------------------- src/boot/efi/stub.c | 1 + src/fundamental/meson.build | 1 + src/fundamental/tpm-pcr.h | 24 ++++++++++++++++++++++++ 5 files changed, 27 insertions(+), 20 deletions(-) create mode 100644 src/fundamental/tpm-pcr.h diff --git a/src/boot/efi/measure.c b/src/boot/efi/measure.c index 9041896f5b3..9a16920787b 100644 --- a/src/boot/efi/measure.c +++ b/src/boot/efi/measure.c @@ -5,6 +5,7 @@ #include #include +#include "tpm-pcr.h" #include "macro-fundamental.h" #include "measure.h" #include "missing_efi.h" diff --git a/src/boot/efi/measure.h b/src/boot/efi/measure.h index 141d44aa79c..19a50f47e7d 100644 --- a/src/boot/efi/measure.h +++ b/src/boot/efi/measure.h @@ -5,26 +5,6 @@ #include #include -/* This TPM PCR is where we extend the sd-stub "payloads" into, before using them. i.e. the kernel ELF image, - * embedded initrd, and so on. In contrast to PCR 4 (which also contains this data, given the whole - * surrounding PE image is measured into it) this should be reasonably pre-calculatable, because it *only* - * consists of static data from the kernel PE image. */ -#define TPM_PCR_INDEX_KERNEL_IMAGE 11U - -/* This TPM PCR is where sd-stub extends the kernel command line and any passed credentials into. */ -#define TPM_PCR_INDEX_KERNEL_PARAMETERS 12U - -/* sd-stub used to write the kernel command line/credentials into PCR 8, in systemd <= 250. Let's provide for - * some compatibility. (Remove in 2023!) */ -#if EFI_TPM_PCR_COMPAT -#define TPM_PCR_INDEX_KERNEL_PARAMETERS_COMPAT 8U -#else -#define TPM_PCR_INDEX_KERNEL_PARAMETERS_COMPAT UINT32_MAX -#endif - -/* This TPM PCR is where we extend the initrd sysext images into which we pass to the booted kernel */ -#define TPM_PCR_INDEX_INITRD_SYSEXTS 13U - #if ENABLE_TPM bool tpm_present(void); diff --git a/src/boot/efi/stub.c b/src/boot/efi/stub.c index 3b74647a18c..549f3ce1d5b 100644 --- a/src/boot/efi/stub.c +++ b/src/boot/efi/stub.c @@ -12,6 +12,7 @@ #include "pe.h" #include "secure-boot.h" #include "splash.h" +#include "tpm-pcr.h" #include "util.h" /* magic string to find in the binary image */ diff --git a/src/fundamental/meson.build b/src/fundamental/meson.build index 2ec4a28433d..a4383f6a271 100644 --- a/src/fundamental/meson.build +++ b/src/fundamental/meson.build @@ -8,6 +8,7 @@ fundamental_headers = files( 'macro-fundamental.h', 'sha256.h', 'string-util-fundamental.h', + 'tpm-pcr.h', ) # for sd-boot diff --git a/src/fundamental/tpm-pcr.h b/src/fundamental/tpm-pcr.h new file mode 100644 index 00000000000..6e8b25934b2 --- /dev/null +++ b/src/fundamental/tpm-pcr.h @@ -0,0 +1,24 @@ +/* SPDX-License-Identifier: LGPL-2.1-or-later */ +#pragma once + +/* The various TPM PCRs we measure into from sd-stub and sd-boot. */ + +/* This TPM PCR is where we extend the sd-stub "payloads" into, before using them. i.e. the kernel ELF image, + * embedded initrd, and so on. In contrast to PCR 4 (which also contains this data, given the whole + * surrounding PE image is measured into it) this should be reasonably pre-calculatable, because it *only* + * consists of static data from the kernel PE image. */ +#define TPM_PCR_INDEX_KERNEL_IMAGE 11U + +/* This TPM PCR is where sd-stub extends the kernel command line and any passed credentials into. */ +#define TPM_PCR_INDEX_KERNEL_PARAMETERS 12U + +/* sd-stub used to write the kernel command line/credentials into PCR 8, in systemd <= 250. Let's provide for + * some compatibility. (Remove in 2023!) */ +#if EFI_TPM_PCR_COMPAT +#define TPM_PCR_INDEX_KERNEL_PARAMETERS_COMPAT 8U +#else +#define TPM_PCR_INDEX_KERNEL_PARAMETERS_COMPAT UINT32_MAX +#endif + +/* This TPM PCR is where we extend the initrd sysext images into which we pass to the booted kernel */ +#define TPM_PCR_INDEX_INITRD_SYSEXTS 13U From 6017eee9ce495d0b12cfe2f6f7569a0808cd672d Mon Sep 17 00:00:00 2001 From: Lennart Poettering Date: Wed, 27 Jul 2022 10:58:29 +0200 Subject: [PATCH 07/18] boot: move unified kernel PE section definitions into 'fundamental' code Le's share this code between userspace and uefispace. This is useful later when pre-measuring expected PCRs from userspace. --- meson.build | 2 ++ src/boot/efi/stub.c | 59 +++++++++++-------------------------- src/fundamental/meson.build | 1 + src/fundamental/tpm-pcr.c | 15 ++++++++++ src/fundamental/tpm-pcr.h | 14 +++++++++ 5 files changed, 50 insertions(+), 41 deletions(-) create mode 100644 src/fundamental/tpm-pcr.c diff --git a/meson.build b/meson.build index 6bbb2db55d1..bfe6b31b959 100644 --- a/meson.build +++ b/meson.build @@ -310,6 +310,8 @@ conf.set_quoted('STATUS_UNIT_FORMAT_DEFAULT_STR', status_unit_format conf.set10('FIRST_BOOT_FULL_PRESET', get_option('first-boot-full-preset')) +conf.set10('EFI_TPM_PCR_COMPAT', get_option('efi-tpm-pcr-compat')) + ##################################################################### cc = meson.get_compiler('c') diff --git a/src/boot/efi/stub.c b/src/boot/efi/stub.c index 549f3ce1d5b..c35ee38749b 100644 --- a/src/boot/efi/stub.c +++ b/src/boot/efi/stub.c @@ -149,28 +149,6 @@ static void export_variables(EFI_LOADED_IMAGE_PROTOCOL *loaded_image) { } EFI_STATUS efi_main(EFI_HANDLE image, EFI_SYSTEM_TABLE *sys_table) { - - enum Section { - /* This is the canonical order in which we measure the sections. PLEASE DO NOT REORDER! */ - SECTION_LINUX, - SECTION_OSREL, - SECTION_CMDLINE, - SECTION_INITRD, - SECTION_SPLASH, - SECTION_DTB, - _SECTION_MAX, - }; - - static const char * const sections[_SECTION_MAX + 1] = { - [SECTION_LINUX] = ".linux", - [SECTION_OSREL] = ".osrel", - [SECTION_CMDLINE] = ".cmdline", - [SECTION_INITRD] = ".initrd", - [SECTION_SPLASH] = ".splash", - [SECTION_DTB] = ".dtb", - NULL, - }; - UINTN cmdline_len = 0, linux_size, initrd_size, dt_size; UINTN credential_initrd_size = 0, global_credential_initrd_size = 0, sysext_initrd_size = 0; _cleanup_free_ void *credential_initrd = NULL, *global_credential_initrd = NULL; @@ -178,8 +156,7 @@ EFI_STATUS efi_main(EFI_HANDLE image, EFI_SYSTEM_TABLE *sys_table) { EFI_PHYSICAL_ADDRESS linux_base, initrd_base, dt_base; _cleanup_(devicetree_cleanup) struct devicetree_state dt_state = {}; EFI_LOADED_IMAGE_PROTOCOL *loaded_image; - UINTN addrs[_SECTION_MAX] = {}; - UINTN szs[_SECTION_MAX] = {}; + UINTN addrs[_UNIFIED_SECTION_MAX] = {}, szs[_UNIFIED_SECTION_MAX] = {}; char *cmdline = NULL; _cleanup_free_ char *cmdline_owned = NULL; int sections_measured = -1, parameters_measured = -1; @@ -201,8 +178,8 @@ EFI_STATUS efi_main(EFI_HANDLE image, EFI_SYSTEM_TABLE *sys_table) { if (err != EFI_SUCCESS) return log_error_status_stall(err, L"Error getting a LoadedImageProtocol handle: %r", err); - err = pe_memory_locate_sections(loaded_image->ImageBase, sections, addrs, szs); - if (err != EFI_SUCCESS || szs[SECTION_LINUX] == 0) { + err = pe_memory_locate_sections(loaded_image->ImageBase, unified_sections, addrs, szs); + if (err != EFI_SUCCESS || szs[UNIFIED_SECTION_LINUX] == 0) { if (err == EFI_SUCCESS) err = EFI_NOT_FOUND; return log_error_status_stall(err, L"Unable to locate embedded .linux section: %r", err); @@ -211,7 +188,7 @@ EFI_STATUS efi_main(EFI_HANDLE image, EFI_SYSTEM_TABLE *sys_table) { /* Measure all "payload" of this PE image into a separate PCR (i.e. where nothing else is written * into so far), so that we have one PCR that we can nicely write policies against because it * contains all static data of this image, and thus can be easily be pre-calculated. */ - for (enum Section section = 0; section < _SECTION_MAX; section++) { + for (UnifiedSection section = 0; section < _UNIFIED_SECTION_MAX; section++) { m = false; if (szs[section] == 0) /* not found */ @@ -220,9 +197,9 @@ EFI_STATUS efi_main(EFI_HANDLE image, EFI_SYSTEM_TABLE *sys_table) { /* First measure the name of the section */ (void) tpm_log_event_ascii( TPM_PCR_INDEX_KERNEL_IMAGE, - POINTER_TO_PHYSICAL_ADDRESS(sections[section]), - strsize8(sections[section]), /* including NUL byte */ - sections[section], + POINTER_TO_PHYSICAL_ADDRESS(unified_sections[section]), + strsize8(unified_sections[section]), /* including NUL byte */ + unified_sections[section], &m); sections_measured = sections_measured < 0 ? m : (sections_measured && m); @@ -232,7 +209,7 @@ EFI_STATUS efi_main(EFI_HANDLE image, EFI_SYSTEM_TABLE *sys_table) { TPM_PCR_INDEX_KERNEL_IMAGE, POINTER_TO_PHYSICAL_ADDRESS(loaded_image->ImageBase) + addrs[section], szs[section], - sections[section], + unified_sections[section], &m); sections_measured = sections_measured < 0 ? m : (sections_measured && m); @@ -244,11 +221,11 @@ EFI_STATUS efi_main(EFI_HANDLE image, EFI_SYSTEM_TABLE *sys_table) { (void) efivar_set_uint_string(LOADER_GUID, L"StubPcrKernelImage", TPM_PCR_INDEX_KERNEL_IMAGE, 0); /* Show splash screen as early as possible */ - graphics_splash((const uint8_t*) loaded_image->ImageBase + addrs[SECTION_SPLASH], szs[SECTION_SPLASH], NULL); + graphics_splash((const uint8_t*) loaded_image->ImageBase + addrs[UNIFIED_SECTION_SPLASH], szs[UNIFIED_SECTION_SPLASH], NULL); - if (szs[SECTION_CMDLINE] > 0) { - cmdline = (char *) loaded_image->ImageBase + addrs[SECTION_CMDLINE]; - cmdline_len = szs[SECTION_CMDLINE]; + if (szs[UNIFIED_SECTION_CMDLINE] > 0) { + cmdline = (char *) loaded_image->ImageBase + addrs[UNIFIED_SECTION_CMDLINE]; + cmdline_len = szs[UNIFIED_SECTION_CMDLINE]; } /* if we are not in secure boot mode, or none was provided, accept a custom command line and replace the built-in one */ @@ -318,14 +295,14 @@ EFI_STATUS efi_main(EFI_HANDLE image, EFI_SYSTEM_TABLE *sys_table) { if (sysext_measured) (void) efivar_set_uint_string(LOADER_GUID, L"StubPcrInitRDSysExts", TPM_PCR_INDEX_INITRD_SYSEXTS, 0); - linux_size = szs[SECTION_LINUX]; - linux_base = POINTER_TO_PHYSICAL_ADDRESS(loaded_image->ImageBase) + addrs[SECTION_LINUX]; + linux_size = szs[UNIFIED_SECTION_LINUX]; + linux_base = POINTER_TO_PHYSICAL_ADDRESS(loaded_image->ImageBase) + addrs[UNIFIED_SECTION_LINUX]; - initrd_size = szs[SECTION_INITRD]; - initrd_base = initrd_size != 0 ? POINTER_TO_PHYSICAL_ADDRESS(loaded_image->ImageBase) + addrs[SECTION_INITRD] : 0; + initrd_size = szs[UNIFIED_SECTION_INITRD]; + initrd_base = initrd_size != 0 ? POINTER_TO_PHYSICAL_ADDRESS(loaded_image->ImageBase) + addrs[UNIFIED_SECTION_INITRD] : 0; - dt_size = szs[SECTION_DTB]; - dt_base = dt_size != 0 ? POINTER_TO_PHYSICAL_ADDRESS(loaded_image->ImageBase) + addrs[SECTION_DTB] : 0; + dt_size = szs[UNIFIED_SECTION_DTB]; + dt_base = dt_size != 0 ? POINTER_TO_PHYSICAL_ADDRESS(loaded_image->ImageBase) + addrs[UNIFIED_SECTION_DTB] : 0; if (credential_initrd || global_credential_initrd || sysext_initrd) { /* If we have generated initrds dynamically, let's combine them with the built-in initrd. */ diff --git a/src/fundamental/meson.build b/src/fundamental/meson.build index a4383f6a271..3810d6b456d 100644 --- a/src/fundamental/meson.build +++ b/src/fundamental/meson.build @@ -17,6 +17,7 @@ fundamental_source_paths = files( 'efivars-fundamental.c', 'sha256.c', 'string-util-fundamental.c', + 'tpm-pcr.c', ) # for libbasic diff --git a/src/fundamental/tpm-pcr.c b/src/fundamental/tpm-pcr.c new file mode 100644 index 00000000000..97b3c7b9d2a --- /dev/null +++ b/src/fundamental/tpm-pcr.c @@ -0,0 +1,15 @@ +/* SPDX-License-Identifier: LGPL-2.1-or-later */ + +#include + +#include "tpm-pcr.h" + +const char* const unified_sections[_UNIFIED_SECTION_MAX + 1] = { + [UNIFIED_SECTION_LINUX] = ".linux", + [UNIFIED_SECTION_OSREL] = ".osrel", + [UNIFIED_SECTION_CMDLINE] = ".cmdline", + [UNIFIED_SECTION_INITRD] = ".initrd", + [UNIFIED_SECTION_SPLASH] = ".splash", + [UNIFIED_SECTION_DTB] = ".dtb", + NULL, +}; diff --git a/src/fundamental/tpm-pcr.h b/src/fundamental/tpm-pcr.h index 6e8b25934b2..fb0774f70d1 100644 --- a/src/fundamental/tpm-pcr.h +++ b/src/fundamental/tpm-pcr.h @@ -22,3 +22,17 @@ /* This TPM PCR is where we extend the initrd sysext images into which we pass to the booted kernel */ #define TPM_PCR_INDEX_INITRD_SYSEXTS 13U + +/* List of PE sections that have special meaning for us in unified kernels. This is the canonical order in + * which we measure the sections into TPM PCR 11 (see above). PLEASE DO NOT REORDER! */ +typedef enum UnifiedSection { + UNIFIED_SECTION_LINUX, + UNIFIED_SECTION_OSREL, + UNIFIED_SECTION_CMDLINE, + UNIFIED_SECTION_INITRD, + UNIFIED_SECTION_SPLASH, + UNIFIED_SECTION_DTB, + _UNIFIED_SECTION_MAX, +} UnifiedSection; + +extern const char* const unified_sections[_UNIFIED_SECTION_MAX + 1]; From 46d336722da20e1f0e6734dffe203bb358ce2485 Mon Sep 17 00:00:00 2001 From: Lennart Poettering Date: Wed, 27 Jul 2022 15:25:59 +0200 Subject: [PATCH 08/18] stub: introduce StubFeatures, similar to LoaderFeatures systemd-boot reports its features via the LoaderFeatures EFI variable. Let's add something similar for stub features, given they have been growing. For starters only define four feature flags. One is a baseline feature we pretty much always supported (see comment in code), two are features added in one of the most recently released systemd version, and the final one, is a feature we added a few commits ago. This is useful for userspace to figure out what is supported and what not. --- src/boot/efi/stub.c | 10 ++++++++++ src/fundamental/efivars-fundamental.h | 7 +++++++ 2 files changed, 17 insertions(+) diff --git a/src/boot/efi/stub.c b/src/boot/efi/stub.c index c35ee38749b..0bd60431495 100644 --- a/src/boot/efi/stub.c +++ b/src/boot/efi/stub.c @@ -103,6 +103,13 @@ static EFI_STATUS combine_initrd( } static void export_variables(EFI_LOADED_IMAGE_PROTOCOL *loaded_image) { + static const uint64_t stub_features = + EFI_STUB_FEATURE_REPORT_BOOT_PARTITION | /* We set LoaderDevicePartUUID */ + EFI_STUB_FEATURE_PICK_UP_CREDENTIALS | /* We pick up credentials from the boot partition */ + EFI_STUB_FEATURE_PICK_UP_SYSEXTS | /* We pick up system extensions from the boot partition */ + EFI_STUB_FEATURE_THREE_PCRS | /* We can measure kernel image, parameters and sysext */ + 0; + char16_t uuid[37]; assert(loaded_image); @@ -143,9 +150,12 @@ static void export_variables(EFI_LOADED_IMAGE_PROTOCOL *loaded_image) { efivar_set(LOADER_GUID, L"LoaderFirmwareType", s, 0); } + /* add StubInfo (this is one is owned by the stub, hence we unconditionally override this with our * own data) */ (void) efivar_set(LOADER_GUID, L"StubInfo", L"systemd-stub " GIT_VERSION, 0); + + (void) efivar_set_uint64_le(LOADER_GUID, L"StubFeatures", stub_features, 0); } EFI_STATUS efi_main(EFI_HANDLE image, EFI_SYSTEM_TABLE *sys_table) { diff --git a/src/fundamental/efivars-fundamental.h b/src/fundamental/efivars-fundamental.h index d1b70eafdce..5d15e19a0e1 100644 --- a/src/fundamental/efivars-fundamental.h +++ b/src/fundamental/efivars-fundamental.h @@ -4,6 +4,7 @@ #include #include "string-util-fundamental.h" +/* Features of the loader, i.e. systemd-boot */ #define EFI_LOADER_FEATURE_CONFIG_TIMEOUT (UINT64_C(1) << 0) #define EFI_LOADER_FEATURE_CONFIG_TIMEOUT_ONE_SHOT (UINT64_C(1) << 1) #define EFI_LOADER_FEATURE_ENTRY_DEFAULT (UINT64_C(1) << 2) @@ -13,6 +14,12 @@ #define EFI_LOADER_FEATURE_RANDOM_SEED (UINT64_C(1) << 6) #define EFI_LOADER_FEATURE_LOAD_DRIVER (UINT64_C(1) << 7) +/* Features of the stub, i.e. systemd-stub */ +#define EFI_STUB_FEATURE_REPORT_BOOT_PARTITION (UINT64_C(1) << 0) +#define EFI_STUB_FEATURE_PICK_UP_CREDENTIALS (UINT64_C(1) << 1) +#define EFI_STUB_FEATURE_PICK_UP_SYSEXTS (UINT64_C(1) << 2) +#define EFI_STUB_FEATURE_THREE_PCRS (UINT64_C(1) << 3) + typedef enum SecureBootMode { SECURE_BOOT_UNSUPPORTED, SECURE_BOOT_DISABLED, From 03d5449f3ab0d136eeab20c767cf7f6c3790869e Mon Sep 17 00:00:00 2001 From: Lennart Poettering Date: Thu, 28 Jul 2022 17:11:47 +0200 Subject: [PATCH 09/18] stub: clean up kernel command line when converting to ASCII Let's be a bit more careful when converting the UTF-16 cmdline to ASCII. Let's convert all characters out of the printable ASCII range to spaces, instead of blindly relying on C's downcasting behaviour. --- src/boot/efi/stub.c | 17 +++++++++++------ 1 file changed, 11 insertions(+), 6 deletions(-) diff --git a/src/boot/efi/stub.c b/src/boot/efi/stub.c index 0bd60431495..49ece419619 100644 --- a/src/boot/efi/stub.c +++ b/src/boot/efi/stub.c @@ -238,14 +238,19 @@ EFI_STATUS efi_main(EFI_HANDLE image, EFI_SYSTEM_TABLE *sys_table) { cmdline_len = szs[UNIFIED_SECTION_CMDLINE]; } - /* if we are not in secure boot mode, or none was provided, accept a custom command line and replace the built-in one */ - if ((!secure_boot_enabled() || cmdline_len == 0) && loaded_image->LoadOptionsSize > 0 && - *(char16_t *) loaded_image->LoadOptions > 0x1F) { + /* if we are not in secure boot mode, or none was provided, accept a custom command line and replace + * the built-in one. We also do a superficial check whether first chararacter of passed command line + * is printable character (for compat with some Dell systems which fill in garbage?). */ + if ((!secure_boot_enabled() || cmdline_len == 0) && + loaded_image->LoadOptionsSize > 0 && + ((char16_t *) loaded_image->LoadOptions)[0] > 0x1F) { cmdline_len = (loaded_image->LoadOptionsSize / sizeof(char16_t)) * sizeof(char); - cmdline = cmdline_owned = xmalloc(cmdline_len); + cmdline = cmdline_owned = xnew(char, cmdline_len); - for (UINTN i = 0; i < cmdline_len; i++) - cmdline[i] = ((char16_t *) loaded_image->LoadOptions)[i]; + for (UINTN i = 0; i < cmdline_len; i++) { + char16_t c = ((char16_t *) loaded_image->LoadOptions)[i]; + cmdline[i] = c > 0x1F && c < 0x7F ? c : ' '; /* convert non-printable and non_ASCII characters to spaces. */ + } /* Let's measure the passed kernel command line into the TPM. Note that this possibly * duplicates what we already did in the boot menu, if that was already used. However, since From 6977ea99755efb5d2f3f8a8801199c37a521f1e2 Mon Sep 17 00:00:00 2001 From: Lennart Poettering Date: Wed, 27 Jul 2022 15:28:55 +0200 Subject: [PATCH 10/18] bootctl: show stub feature set in status output --- src/boot/bootctl.c | 23 +++++++++++++++----- src/shared/efi-loader.c | 48 +++++++++++++++++++++++++++++++++++++++++ src/shared/efi-loader.h | 5 +++++ 3 files changed, 71 insertions(+), 5 deletions(-) diff --git a/src/boot/bootctl.c b/src/boot/bootctl.c index 7470157ecd5..e53328ca39e 100644 --- a/src/boot/bootctl.c +++ b/src/boot/bootctl.c @@ -1714,7 +1714,7 @@ static int verb_status(int argc, char *argv[], void *userdata) { static const struct { uint64_t flag; const char *name; - } flags[] = { + } loader_flags[] = { { EFI_LOADER_FEATURE_BOOT_COUNTING, "Boot counting" }, { EFI_LOADER_FEATURE_CONFIG_TIMEOUT, "Menu timeout control" }, { EFI_LOADER_FEATURE_CONFIG_TIMEOUT_ONE_SHOT, "One-shot menu timeout control" }, @@ -1724,9 +1724,18 @@ static int verb_status(int argc, char *argv[], void *userdata) { { EFI_LOADER_FEATURE_RANDOM_SEED, "Support for passing random seed to OS" }, { EFI_LOADER_FEATURE_LOAD_DRIVER, "Load drop-in drivers" }, }; + static const struct { + uint64_t flag; + const char *name; + } stub_flags[] = { + { EFI_STUB_FEATURE_REPORT_BOOT_PARTITION, "Stub sets ESP information" }, + { EFI_STUB_FEATURE_PICK_UP_CREDENTIALS, "Picks up credentials from boot partition" }, + { EFI_STUB_FEATURE_PICK_UP_SYSEXTS, "Picks up system extension images from boot partition" }, + { EFI_STUB_FEATURE_THREE_PCRS, "Measures kernel+command line+sysexts" }, + }; _cleanup_free_ char *fw_type = NULL, *fw_info = NULL, *loader = NULL, *loader_path = NULL, *stub = NULL; sd_id128_t loader_part_uuid = SD_ID128_NULL; - uint64_t loader_features = 0; + uint64_t loader_features = 0, stub_features = 0; Tpm2Support s; int have; @@ -1736,6 +1745,7 @@ static int verb_status(int argc, char *argv[], void *userdata) { read_efi_var(EFI_LOADER_VARIABLE(StubInfo), &stub); read_efi_var(EFI_LOADER_VARIABLE(LoaderImageIdentifier), &loader_path); (void) efi_loader_get_features(&loader_features); + (void) efi_stub_get_features(&stub_features); if (loader_path) efi_tilt_backslashes(loader_path); @@ -1777,8 +1787,8 @@ static int verb_status(int argc, char *argv[], void *userdata) { printf("Current Boot Loader:\n"); printf(" Product: %s%s%s\n", ansi_highlight(), strna(loader), ansi_normal()); - for (size_t i = 0; i < ELEMENTSOF(flags); i++) - print_yes_no_line(i == 0, FLAGS_SET(loader_features, flags[i].flag), flags[i].name); + for (size_t i = 0; i < ELEMENTSOF(loader_flags); i++) + print_yes_no_line(i == 0, FLAGS_SET(loader_features, loader_flags[i].flag), loader_flags[i].name); sd_id128_t bootloader_esp_uuid; bool have_bootloader_esp_uuid = efi_loader_get_device_part_uuid(&bootloader_esp_uuid) >= 0; @@ -1787,8 +1797,11 @@ static int verb_status(int argc, char *argv[], void *userdata) { if (have_bootloader_esp_uuid && !sd_id128_equal(esp_uuid, bootloader_esp_uuid)) printf("WARNING: The boot loader reports a different ESP UUID than detected!\n"); - if (stub) + if (stub) { printf(" Stub: %s\n", stub); + for (size_t i = 0; i < ELEMENTSOF(stub_flags); i++) + print_yes_no_line(i == 0, FLAGS_SET(stub_features, stub_flags[i].flag), stub_flags[i].name); + } if (!sd_id128_is_null(loader_part_uuid)) printf(" ESP: /dev/disk/by-partuuid/" SD_ID128_UUID_FORMAT_STR "\n", SD_ID128_FORMAT_VAL(loader_part_uuid)); diff --git a/src/shared/efi-loader.c b/src/shared/efi-loader.c index e114d5ea2c6..3eed44a533b 100644 --- a/src/shared/efi-loader.c +++ b/src/shared/efi-loader.c @@ -187,6 +187,54 @@ int efi_loader_get_features(uint64_t *ret) { return 0; } +int efi_stub_get_features(uint64_t *ret) { + _cleanup_free_ void *v = NULL; + size_t s; + int r; + + assert(ret); + + if (!is_efi_boot()) { + *ret = 0; + return 0; + } + + r = efi_get_variable(EFI_LOADER_VARIABLE(StubFeatures), NULL, &v, &s); + if (r == -ENOENT) { + _cleanup_free_ char *info = NULL; + + /* The new (v252+) StubFeatures variable is not supported, let's see if it's systemd-stub at all */ + r = efi_get_variable_string(EFI_LOADER_VARIABLE(StubInfo), &info); + if (r < 0) { + if (r != -ENOENT) + return r; + + /* Variable not set, definitely means not systemd-stub */ + + } else if (first_word(info, "systemd-stub")) { + + /* An older systemd-stub version. Let's hardcode the feature set, since it was pretty + * static in all its versions. */ + + *ret = EFI_STUB_FEATURE_REPORT_BOOT_PARTITION; + return 0; + } + + /* No features supported */ + *ret = 0; + return 0; + } + if (r < 0) + return r; + + if (s != sizeof(uint64_t)) + return log_debug_errno(SYNTHETIC_ERRNO(EINVAL), + "StubFeatures EFI variable doesn't have the right size."); + + memcpy(ret, v, sizeof(uint64_t)); + return 0; +} + int efi_loader_get_config_timeout_one_shot(usec_t *ret) { _cleanup_free_ char *v = NULL; static struct stat cache_stat = {}; diff --git a/src/shared/efi-loader.h b/src/shared/efi-loader.h index 507160e50de..84968869abe 100644 --- a/src/shared/efi-loader.h +++ b/src/shared/efi-loader.h @@ -16,6 +16,7 @@ int efi_loader_get_boot_usec(usec_t *ret_firmware, usec_t *ret_loader); int efi_loader_get_entries(char ***ret); int efi_loader_get_features(uint64_t *ret); +int efi_stub_get_features(uint64_t *ret); int efi_loader_get_config_timeout_one_shot(usec_t *ret); int efi_loader_update_entry_one_shot_cache(char **cache, struct stat *cache_stat); @@ -38,6 +39,10 @@ static inline int efi_loader_get_features(uint64_t *ret) { return -EOPNOTSUPP; } +static inline int efi_stub_get_features(uint64_t *ret) { + return -EOPNOTSUPP; +} + static inline int efi_loader_get_config_timeout_one_shot(usec_t *ret) { return -EOPNOTSUPP; } From a67f22c4a73a22312a61dd9352504cd2a960b8a8 Mon Sep 17 00:00:00 2001 From: Lennart Poettering Date: Wed, 27 Jul 2022 16:39:13 +0200 Subject: [PATCH 11/18] boot: add three new boot loader feature flags Report whether the devicetree + sort-key boot loader spec type #1 fields are supported, and whether the "@saved" pseudo-entry is supported. Strictly speaking, thes features have been added in versions that are already released (250+), so by adding this those version even though they support the features will be considered not supporting them, but that should be OK (the opposite would be a problem though, i.e. if we'd assume a boot loader had a feature it actually does not). These three features are features relevant to userspace, as it allows userspace to tweak/genereate BLS entries or set EFI vars correctly. Other features (i.e. that have no impliciations to userspace) are not reported. --- src/boot/bootctl.c | 3 +++ src/boot/efi/boot.c | 3 +++ src/fundamental/efivars-fundamental.h | 3 +++ 3 files changed, 9 insertions(+) diff --git a/src/boot/bootctl.c b/src/boot/bootctl.c index e53328ca39e..8d71b4c40a9 100644 --- a/src/boot/bootctl.c +++ b/src/boot/bootctl.c @@ -1723,6 +1723,9 @@ static int verb_status(int argc, char *argv[], void *userdata) { { EFI_LOADER_FEATURE_XBOOTLDR, "Support for XBOOTLDR partition" }, { EFI_LOADER_FEATURE_RANDOM_SEED, "Support for passing random seed to OS" }, { EFI_LOADER_FEATURE_LOAD_DRIVER, "Load drop-in drivers" }, + { EFI_LOADER_FEATURE_SORT_KEY, "Support Type #1 sort-key field" }, + { EFI_LOADER_FEATURE_SAVED_ENTRY, "Support @saved pseudo-entry" }, + { EFI_LOADER_FEATURE_DEVICETREE, "Support Type #1 devicetree field" }, }; static const struct { uint64_t flag; diff --git a/src/boot/efi/boot.c b/src/boot/efi/boot.c index 193eebde0c2..b3f424d8ba0 100644 --- a/src/boot/efi/boot.c +++ b/src/boot/efi/boot.c @@ -2443,6 +2443,9 @@ static void export_variables( EFI_LOADER_FEATURE_XBOOTLDR | EFI_LOADER_FEATURE_RANDOM_SEED | EFI_LOADER_FEATURE_LOAD_DRIVER | + EFI_LOADER_FEATURE_SORT_KEY | + EFI_LOADER_FEATURE_SAVED_ENTRY | + EFI_LOADER_FEATURE_DEVICETREE | 0; _cleanup_free_ char16_t *infostr = NULL, *typestr = NULL; diff --git a/src/fundamental/efivars-fundamental.h b/src/fundamental/efivars-fundamental.h index 5d15e19a0e1..fe34e6c714a 100644 --- a/src/fundamental/efivars-fundamental.h +++ b/src/fundamental/efivars-fundamental.h @@ -13,6 +13,9 @@ #define EFI_LOADER_FEATURE_XBOOTLDR (UINT64_C(1) << 5) #define EFI_LOADER_FEATURE_RANDOM_SEED (UINT64_C(1) << 6) #define EFI_LOADER_FEATURE_LOAD_DRIVER (UINT64_C(1) << 7) +#define EFI_LOADER_FEATURE_SORT_KEY (UINT64_C(1) << 8) +#define EFI_LOADER_FEATURE_SAVED_ENTRY (UINT64_C(1) << 9) +#define EFI_LOADER_FEATURE_DEVICETREE (UINT64_C(1) << 10) /* Features of the stub, i.e. systemd-stub */ #define EFI_STUB_FEATURE_REPORT_BOOT_PARTITION (UINT64_C(1) << 0) From 257bc3ef60c7a35171aa5ed87891a19b8c67654b Mon Sep 17 00:00:00 2001 From: Lennart Poettering Date: Thu, 28 Jul 2022 10:47:05 +0200 Subject: [PATCH 12/18] boot: add hexdump helper call This is not actually used (or even supposed to be used) in clean codepaths, but is tremendously useful when verifying things work correctly, as a debugging tool. --- src/boot/efi/util.c | 25 +++++++++++++++++++++++++ src/boot/efi/util.h | 4 ++++ 2 files changed, 29 insertions(+) diff --git a/src/boot/efi/util.c b/src/boot/efi/util.c index 7603eb8f534..6fcf9b31211 100644 --- a/src/boot/efi/util.c +++ b/src/boot/efi/util.c @@ -637,6 +637,31 @@ __attribute__((noinline)) void debug_break(void) { } #endif + +#ifdef EFI_DEBUG +void hexdump(const char16_t *prefix, const void *data, UINTN size) { + static const char hex[16] = "0123456789abcdef"; + _cleanup_free_ char16_t *buf = NULL; + const uint8_t *d = data; + + assert(prefix); + assert(data || size == 0); + + /* Debugging helper — please keep this around, even if not used */ + + buf = xnew(char16_t, size*2+1); + + for (UINTN i = 0; i < size; i++) { + buf[i*2] = hex[d[i] >> 4]; + buf[i*2+1] = hex[d[i] & 0x0F]; + } + + buf[size*2] = 0; + + log_error_stall(L"%s[%" PRIuN "]: %s", prefix, size, buf); +} +#endif + #if defined(__i386__) || defined(__x86_64__) static inline uint8_t inb(uint16_t port) { uint8_t value; diff --git a/src/boot/efi/util.h b/src/boot/efi/util.h index 4de799ea0e4..bb4bb64e0e2 100644 --- a/src/boot/efi/util.h +++ b/src/boot/efi/util.h @@ -167,6 +167,10 @@ extern uint8_t _text, _data; # define debug_hook(identity) #endif +#ifdef EFI_DEBUG +void hexdump(const char16_t *prefix, const void *data, UINTN size); +#endif + #if defined(__i386__) || defined(__x86_64__) void beep(UINTN beep_count); #else From cb180369f150fe11d8853a1ee54bbc873fdc64bd Mon Sep 17 00:00:00 2001 From: Lennart Poettering Date: Wed, 27 Jul 2022 17:07:12 +0200 Subject: [PATCH 13/18] bootctl: underline sections in status output Let's give the output a bit more structure, inspired by the way our newer --help texts use underlining to separate out sections a bit. --- src/boot/bootctl.c | 20 +++++++++++--------- 1 file changed, 11 insertions(+), 9 deletions(-) diff --git a/src/boot/bootctl.c b/src/boot/bootctl.c index 8d71b4c40a9..20faa260981 100644 --- a/src/boot/bootctl.c +++ b/src/boot/bootctl.c @@ -504,7 +504,7 @@ static int enumerate_binaries(const char *esp_path, const char *path, const char static int status_binaries(const char *esp_path, sd_id128_t partition) { int r; - printf("Available Boot Loaders on ESP:\n"); + printf("%sAvailable Boot Loaders on ESP:%s\n", ansi_underline(), ansi_normal()); if (!esp_path) { printf(" ESP: Cannot find or access mount point of ESP.\n\n"); @@ -582,7 +582,7 @@ static int status_variables(void) { return log_error_errno(n_order, "Failed to read EFI boot order: %m"); /* print entries in BootOrder first */ - printf("Boot Loaders Listed in EFI Variables:\n"); + printf("%sBoot Loaders Listed in EFI Variables:%s\n", ansi_underline(), ansi_normal()); for (int i = 0; i < n_order; i++) print_efi_option(order[i], true); @@ -655,8 +655,8 @@ static int status_entries( dollar_boot_partition_uuid = esp_partition_uuid; } - printf("Boot Loader Entries:\n" - " $BOOT: %s", dollar_boot_path); + printf("%sBoot Loader Entries:%s\n" + " $BOOT: %s", ansi_underline(), ansi_normal(), dollar_boot_path); if (!sd_id128_is_null(dollar_boot_partition_uuid)) printf(" (/dev/disk/by-partuuid/" SD_ID128_UUID_FORMAT_STR ")", SD_ID128_FORMAT_VAL(dollar_boot_partition_uuid)); @@ -665,7 +665,7 @@ static int status_entries( if (config->default_entry < 0) printf("%zu entries, no entry could be determined as default.\n", config->n_entries); else { - printf("Default Boot Loader Entry:\n"); + printf("%sDefault Boot Loader Entry:%s\n", ansi_underline(), ansi_normal()); r = show_boot_entry( boot_config_default_entry(config), @@ -1758,7 +1758,7 @@ static int verb_status(int argc, char *argv[], void *userdata) { r = log_warning_errno(k, "Failed to read EFI variable LoaderDevicePartUUID: %m"); SecureBootMode secure = efi_get_secure_boot_mode(); - printf("System:\n"); + printf("%sSystem:%s\n", ansi_underline(), ansi_normal()); printf(" Firmware: %s%s (%s)%s\n", ansi_highlight(), strna(fw_type), strna(fw_info), ansi_normal()); printf(" Firmware Arch: %s\n", get_efi_arch()); printf(" Secure Boot: %sd (%s)\n", @@ -1787,7 +1787,7 @@ static int verb_status(int argc, char *argv[], void *userdata) { } printf("\n"); - printf("Current Boot Loader:\n"); + printf("%sCurrent Boot Loader:%s\n", ansi_underline(), ansi_normal()); printf(" Product: %s%s%s\n", ansi_highlight(), strna(loader), ansi_normal()); for (size_t i = 0; i < ELEMENTSOF(loader_flags); i++) @@ -1813,7 +1813,7 @@ static int verb_status(int argc, char *argv[], void *userdata) { printf(" File: %s%s\n", special_glyph(SPECIAL_GLYPH_TREE_RIGHT), strna(loader_path)); printf("\n"); - printf("Random Seed:\n"); + printf("%sRandom Seed:%s\n", ansi_underline(), ansi_normal()); have = access(EFIVAR_PATH(EFI_LOADER_VARIABLE(LoaderRandomSeed)), F_OK) >= 0; printf(" Passed to OS: %s\n", yes_no(have)); have = access(EFIVAR_PATH(EFI_LOADER_VARIABLE(LoaderSystemToken)), F_OK) >= 0; @@ -1832,7 +1832,9 @@ static int verb_status(int argc, char *argv[], void *userdata) { printf("\n"); } else - printf("System:\n Not booted with EFI\n\n"); + printf("%sSystem:%s\n" + "Not booted with EFI\n\n", + ansi_underline(), ansi_normal()); if (arg_esp_path) { k = status_binaries(arg_esp_path, esp_uuid); From 84be9b63ebac1d320b1e39ccf6e0895e621af887 Mon Sep 17 00:00:00 2001 From: Lennart Poettering Date: Wed, 27 Jul 2022 17:23:25 +0200 Subject: [PATCH 14/18] bootctl: prettify tree output of discovered boot loaders a bit MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Previously, the tree output of discovered boot laoders in the ESP would look like this: Available Boot Loaders on ESP: ESP: /efi (/dev/disk/by-partuuid/0c6f41ed-2573-4723-8c84-23681f9d1c28) File: └─/EFI/systemd/systemd-bootx64.efi (systemd-boot v250.7-1.fc36) File: └─/EFI/BOOT/BOOTX64.EFI (systemd-boot v250.7-1.fc36) With this change the tree branches are corrected to look like this: Available Boot Loaders on ESP: ESP: /efi (/dev/disk/by-partuuid/0c6f41ed-2573-4723-8c84-23681f9d1c28) File: ├─/EFI/systemd/systemd-bootx64.efi (systemd-boot v250.7-1.fc36) └─/EFI/BOOT/BOOTX64.EFI (systemd-boot v250.7-1.fc36) Ahhh! So much nicer. This incorrect tree drawing has been bugging me for so long. Finally I can sleep at night again! --- src/boot/bootctl.c | 63 ++++++++++++++++++++++++++++++++++------------ 1 file changed, 47 insertions(+), 16 deletions(-) diff --git a/src/boot/bootctl.c b/src/boot/bootctl.c index 20faa260981..5ac742ef293 100644 --- a/src/boot/bootctl.c +++ b/src/boot/bootctl.c @@ -456,13 +456,21 @@ static const char *get_efi_arch(void) { return EFI_MACHINE_TYPE_NAME; } -static int enumerate_binaries(const char *esp_path, const char *path, const char *prefix) { +static int enumerate_binaries( + const char *esp_path, + const char *path, + const char *prefix, + char **previous, + bool *is_first) { + _cleanup_closedir_ DIR *d = NULL; const char *p; int c = 0, r; assert(esp_path); assert(path); + assert(previous); + assert(is_first); p = prefix_roota(esp_path, path); d = opendir(p); @@ -490,10 +498,24 @@ static int enumerate_binaries(const char *esp_path, const char *path, const char r = get_file_version(fd, &v); if (r < 0) return r; + + if (*previous) { /* let's output the previous entry now, since now we know that there will be one more, and can draw the tree glyph properly */ + printf(" %s %s%s\n", + *is_first ? "File:" : " ", + special_glyph(SPECIAL_GLYPH_TREE_BRANCH), *previous); + *is_first = false; + *previous = mfree(*previous); + } + + /* Do not output this entry immediately, but store what should be printed in a state + * variable, because we only will know the tree glyph to print (branch or final edge) once we + * read one more entry */ if (r > 0) - printf(" File: %s/%s/%s (%s%s%s)\n", special_glyph(SPECIAL_GLYPH_TREE_RIGHT), path, de->d_name, ansi_highlight(), v, ansi_normal()); + r = asprintf(previous, "/%s/%s (%s%s%s)", path, de->d_name, ansi_highlight(), v, ansi_normal()); else - printf(" File: %s/%s/%s\n", special_glyph(SPECIAL_GLYPH_TREE_RIGHT), path, de->d_name); + r = asprintf(previous, "/%s/%s", path, de->d_name); + if (r < 0) + return log_oom(); c++; } @@ -502,7 +524,9 @@ static int enumerate_binaries(const char *esp_path, const char *path, const char } static int status_binaries(const char *esp_path, sd_id128_t partition) { - int r; + _cleanup_free_ char *last = NULL; + bool is_first = true; + int r, k; printf("%sAvailable Boot Loaders on ESP:%s\n", ansi_underline(), ansi_normal()); @@ -516,23 +540,30 @@ static int status_binaries(const char *esp_path, sd_id128_t partition) { printf(" (/dev/disk/by-partuuid/" SD_ID128_UUID_FORMAT_STR ")", SD_ID128_FORMAT_VAL(partition)); printf("\n"); - r = enumerate_binaries(esp_path, "EFI/systemd", NULL); - if (r < 0) - goto finish; + r = enumerate_binaries(esp_path, "EFI/systemd", NULL, &last, &is_first); + if (r < 0) { + printf("\n"); + return r; + } + + k = enumerate_binaries(esp_path, "EFI/BOOT", "boot", &last, &is_first); + if (k < 0) { + printf("\n"); + return k; + } + + if (last) /* let's output the last entry now, since now we know that there will be no more, and can draw the tree glyph properly */ + printf(" %s %s%s\n", + is_first ? "File:" : " ", + special_glyph(SPECIAL_GLYPH_TREE_RIGHT), last); + if (r == 0 && !arg_quiet) log_info("systemd-boot not installed in ESP."); - - r = enumerate_binaries(esp_path, "EFI/BOOT", "boot"); - if (r < 0) - goto finish; - if (r == 0 && !arg_quiet) + if (k == 0 && !arg_quiet) log_info("No default/fallback boot loader installed in ESP."); - r = 0; - -finish: printf("\n"); - return r; + return 0; } static int print_efi_option(uint16_t id, bool in_order) { From 51470e1e5601c8fb5371fbc9e1b3ed69ca61c7a0 Mon Sep 17 00:00:00 2001 From: Lennart Poettering Date: Thu, 28 Jul 2022 10:48:16 +0200 Subject: [PATCH 15/18] bootctl: suppress "Boot Loaders Listed in EFI Variables" section header if section empty Some more cosmetics. --- src/boot/bootctl.c | 26 ++++++++++++++++++-------- 1 file changed, 18 insertions(+), 8 deletions(-) diff --git a/src/boot/bootctl.c b/src/boot/bootctl.c index 5ac742ef293..f04463157a5 100644 --- a/src/boot/bootctl.c +++ b/src/boot/bootctl.c @@ -566,23 +566,30 @@ static int status_binaries(const char *esp_path, sd_id128_t partition) { return 0; } -static int print_efi_option(uint16_t id, bool in_order) { +static int print_efi_option(uint16_t id, int *n_printed, bool in_order) { _cleanup_free_ char *title = NULL; _cleanup_free_ char *path = NULL; sd_id128_t partition; bool active; int r; + assert(n_printed); + r = efi_get_boot_option(id, &title, &partition, &path, &active); if (r < 0) - return r; + return log_error_errno(r, "Failed to read boot option %u: %m", id); /* print only configured entries with partition information */ - if (!path || sd_id128_is_null(partition)) + if (!path || sd_id128_is_null(partition)) { + log_debug("Ignoring boot entry %u without partition information.", id); return 0; + } efi_tilt_backslashes(path); + if (*n_printed == 0) /* Print section title before first entry */ + printf("%sBoot Loaders Listed in EFI Variables:%s\n", ansi_underline(), ansi_normal()); + printf(" Title: %s%s%s\n", ansi_highlight(), strna(title), ansi_normal()); printf(" ID: 0x%04X\n", id); printf(" Status: %sactive%s\n", active ? "" : "in", in_order ? ", boot-order" : ""); @@ -591,12 +598,13 @@ static int print_efi_option(uint16_t id, bool in_order) { printf(" File: %s%s\n", special_glyph(SPECIAL_GLYPH_TREE_RIGHT), path); printf("\n"); - return 0; + (*n_printed)++; + return 1; } static int status_variables(void) { _cleanup_free_ uint16_t *options = NULL, *order = NULL; - int n_options, n_order; + int n_options, n_order, n_printed = 0; n_options = efi_get_boot_options(&options); if (n_options == -ENOENT) @@ -613,9 +621,8 @@ static int status_variables(void) { return log_error_errno(n_order, "Failed to read EFI boot order: %m"); /* print entries in BootOrder first */ - printf("%sBoot Loaders Listed in EFI Variables:%s\n", ansi_underline(), ansi_normal()); for (int i = 0; i < n_order; i++) - print_efi_option(order[i], true); + (void) print_efi_option(order[i], &n_printed, /* in_order= */ true); /* print remaining entries */ for (int i = 0; i < n_options; i++) { @@ -623,12 +630,15 @@ static int status_variables(void) { if (options[i] == order[j]) goto next_option; - print_efi_option(options[i], false); + (void) print_efi_option(options[i], &n_printed, /* in_order= */ false); next_option: continue; } + if (n_printed == 0) + printf("No boot loaders listed in EFI Variables.\n\n"); + return 0; } From ca1092dc15ce49d2b677aa326836588839bb6fe1 Mon Sep 17 00:00:00 2001 From: Lennart Poettering Date: Tue, 26 Jul 2022 00:13:16 +0200 Subject: [PATCH 16/18] measure: add new tool to precalculate PCR values for a kernel image For now, this simply outputs the PCR hash values expected for a kernel image, if it's measured like sd-stub would do it. (Later on, we can extend the tool, to optionally sign these pre-calculated measurements, in order to implement signed PCR policies for disk encryption.) --- man/rules/meson.build | 1 + man/systemd-measure.xml | 154 ++++++++++++ meson.build | 12 + src/boot/measure.c | 533 ++++++++++++++++++++++++++++++++++++++++ 4 files changed, 700 insertions(+) create mode 100644 man/systemd-measure.xml create mode 100644 src/boot/measure.c diff --git a/man/rules/meson.build b/man/rules/meson.build index 277ab5e5933..4f3fe0da7c7 100644 --- a/man/rules/meson.build +++ b/man/rules/meson.build @@ -953,6 +953,7 @@ manpages = [ 'systemd-makefs', 'systemd-mkswap@.service'], ''], + ['systemd-measure', '1', [], 'HAVE_GNU_EFI'], ['systemd-modules-load.service', '8', ['systemd-modules-load'], 'HAVE_KMOD'], ['systemd-mount', '1', ['systemd-umount'], ''], ['systemd-network-generator.service', '8', ['systemd-network-generator'], ''], diff --git a/man/systemd-measure.xml b/man/systemd-measure.xml new file mode 100644 index 00000000000..e6b8d31a881 --- /dev/null +++ b/man/systemd-measure.xml @@ -0,0 +1,154 @@ + + + + + + + + systemd-measure + systemd + + + + systemd-measure + 1 + + + + systemd-measure + Pre-calculate expected TPM2 PCR values for booted unified kernel images + + + + + /usr/lib/systemd/systemd-measure OPTIONS + + + + + Description + + Note: this command is experimental for now. While it is likely to become a regular component of + systemd, it might still change in behaviour and interface. + + systemd-measure is a tool that may be used to pre-calculate the expected TPM2 + PCR 11 values that should be seen when a unified Linux kernel image based on + systemd-stub7 is + booted up. It accepts paths to the ELF kernel image file, initial ram disk image file, devicetree file, + kernel command line file, + os-release5 file, and + boot splash file that make up the unified kernel image, and determines the PCR values expected to be in + place after booting the image. Calculation starts with a zero-initialized PCR 11, and is executed in a + fashion compatible with what systemd-stub does at boot. + + + + Commands + + The following commands are understood: + + + + status + + This is the default command if none is specified. This queries the local system's + TPM2 PCR 11+12+13 values and displays them. The data is written in a similar format as the + calculate command below, and may be used to quickly compare expectation with + reality. + + + + calculate + + Pre-calculate the expected value seen in PCR register 11 after boot-up of a unified + kernel image consisting of the components specified with , + , , , + , , see below. Only is + mandatory. + + + + + + Options + + The following options are understood: + + + + + + + + + + + When used with the calculate verb, configures the files to read + the unified kernel image components from. Each option corresponds with the equally named section in + the unified kernel PE file. The switch expects the path to the ELF kernel + file that the unified PE kernel will wrap. All switches except are + optional. Each option may be used at most once. + + + + + + Controls the PCR banks to pre-calculate the PCR values for – in case + calculate is invoked –, or the banks to show in the status + output. May be used more then once to specify multiple banks. If not specified, defaults to the four + banks sha1, sha256, sha384, + sha512. + + + + + + + + + Examples + + + Generate a unified kernel image, and calculate the expected TPM PCR 11 value + + # objcopy \ + --add-section .linux=vmlinux --change-section-vma .linux=0x2000000 \ + --add-section .osrel=os-release.txt --change-section-vma .osrel=0x20000 \ + --add-section .cmdline=cmdline.txt --change-section-vma .cmdline=0x30000 \ + --add-section .initrd=initrd.cpio --change-section-vma .initrd=0x3000000 \ + --add-section .splash=splash.bmp --change-section-vma .splash=0x100000 \ + --add-section .dtb=devicetree.dtb --change-section-vma .dtb=0x40000 \ + /usr/lib/systemd/boot/efi/linuxx64.efi.stub \ + foo.efi +# systemd-measure calculate \ + --linux=vmlinux \ + --osrel=os-release \ + --cmdline=cmdline.txt \ + --initrd=initrd.cpio \ + --splash=splash.bmp \ + --dtb=devicetree.dtb +11:sha1=d775a7b4482450ac77e03ee19bda90bd792d6ec7 +11:sha256=bc6170f9ce28eb051ab465cd62be8cf63985276766cf9faf527ffefb66f45651 +11:sha384=1cf67dff4757e61e5a73d2a21a6694d668629bbc3761747d493f7f49ad720be02fd07263e1f93061243aec599d1ee4b4 +11:sha512=8e79acd3ddbbc8282e98091849c3530f996303c8ac8e87a3b2378b71c8b3a6e86d5c4f41ecea9e1517090c3e8ec0c714821032038f525f744960bcd082d937da + + + + + + Exit status + + On success, 0 is returned, a non-zero failure code otherwise. + + + + See Also + + systemd1, + systemd-stub7, + objcopy1 + + + + diff --git a/meson.build b/meson.build index bfe6b31b959..cf3aa2b4964 100644 --- a/meson.build +++ b/meson.build @@ -2541,6 +2541,18 @@ if conf.get('HAVE_BLKID') == 1 and conf.get('HAVE_GNU_EFI') == 1 install_rpath : rootpkglibdir, install : true, install_dir : systemgeneratordir) + + if conf.get('HAVE_OPENSSL') == 1 + executable( + 'systemd-measure', + 'src/boot/measure.c', + include_directories : includes, + link_with : [libshared], + dependencies : [libopenssl], + install_rpath : rootpkglibdir, + install : true, + install_dir : rootlibexecdir) + endif endif executable( diff --git a/src/boot/measure.c b/src/boot/measure.c new file mode 100644 index 00000000000..29568c5b0d1 --- /dev/null +++ b/src/boot/measure.c @@ -0,0 +1,533 @@ +/* SPDX-License-Identifier: LGPL-2.1-or-later */ + +#include +#include + +#include "alloc-util.h" +#include "efi-loader.h" +#include "fd-util.h" +#include "fileio.h" +#include "hexdecoct.h" +#include "main-func.h" +#include "openssl-util.h" +#include "parse-argument.h" +#include "parse-util.h" +#include "pretty-print.h" +#include "terminal-util.h" +#include "tpm-pcr.h" +#include "tpm2-util.h" +#include "verbs.h" + +/* Tool for pre-calculating expected TPM PCR values based on measured resources. This is intended to be used + * to pre-calculate suitable values for PCR 11, the way sd-stub measures into it. */ + +static char *arg_sections[_UNIFIED_SECTION_MAX] = {}; +static char **arg_banks = NULL; + +STATIC_DESTRUCTOR_REGISTER(arg_banks, strv_freep); + +static inline void free_sections(char*(*sections)[_UNIFIED_SECTION_MAX]) { + for (UnifiedSection c = 0; c < _UNIFIED_SECTION_MAX; c++) + free((*sections)[c]); +} + +STATIC_DESTRUCTOR_REGISTER(arg_sections, free_sections); + +static int help(int argc, char *argv[], void *userdata) { + _cleanup_free_ char *link = NULL; + int r; + + r = terminal_urlify_man("systemd-measure", "1", &link); + if (r < 0) + return log_oom(); + + printf("%1$s [OPTIONS...] COMMAND ...\n" + "\n%5$sPre-calculate PCR hash for kernel image.%6$s\n" + "\n%3$sCommands:%4$s\n" + " status Show current PCR values\n" + " calculate Calculate expected PCR values\n" + "\n%3$sOptions:%4$s\n" + " -h --help Show this help\n" + " --version Print version\n" + " --linux=PATH Path Linux kernel ELF image\n" + " --osrel=PATH Path to os-release file\n" + " --cmdline=PATH Path to file with kernel command line\n" + " --initrd=PATH Path to initrd image\n" + " --splash=PATH Path to splash bitmap\n" + " --dtb=PATH Path to Devicetree file\n" + " --bank=DIGEST Select TPM bank (SHA1, SHA256)\n" + "\nSee the %2$s for details.\n", + program_invocation_short_name, + link, + ansi_underline(), + ansi_normal(), + ansi_highlight(), + ansi_normal()); + + return 0; +} + +static int parse_argv(int argc, char *argv[]) { + enum { + ARG_VERSION = 0x100, + _ARG_SECTION_FIRST, + ARG_LINUX = _ARG_SECTION_FIRST, + ARG_OSREL, + ARG_CMDLINE, + ARG_INITRD, + ARG_SPLASH, + _ARG_SECTION_LAST, + ARG_DTB = _ARG_SECTION_LAST, + ARG_BANK, + }; + + static const struct option options[] = { + { "help", no_argument, NULL, 'h' }, + { "version", no_argument, NULL, ARG_VERSION }, + { "linux", required_argument, NULL, ARG_LINUX }, + { "osrel", required_argument, NULL, ARG_OSREL }, + { "cmdline", required_argument, NULL, ARG_CMDLINE }, + { "initrd", required_argument, NULL, ARG_INITRD }, + { "splash", required_argument, NULL, ARG_SPLASH }, + { "dtb", required_argument, NULL, ARG_DTB }, + { "bank", required_argument, NULL, ARG_BANK }, + {} + }; + + int c, r; + + assert(argc >= 0); + assert(argv); + + /* Make sure the arguments list and the section list, stays in sync */ + assert_cc(_ARG_SECTION_FIRST + _UNIFIED_SECTION_MAX == _ARG_SECTION_LAST + 1); + + while ((c = getopt_long(argc, argv, "h", options, NULL)) >= 0) + switch (c) { + + case 'h': + help(0, NULL, NULL); + return 0; + + case ARG_VERSION: + return version(); + + case _ARG_SECTION_FIRST..._ARG_SECTION_LAST: { + UnifiedSection section = c - _ARG_SECTION_FIRST; + + r = parse_path_argument(optarg, /* suppress_root= */ false, arg_sections + section); + if (r < 0) + return r; + break; + } + + case ARG_BANK: { + const EVP_MD *implementation; + + implementation = EVP_get_digestbyname(optarg); + if (!implementation) + return log_error_errno(SYNTHETIC_ERRNO(EINVAL), "Unknown bank '%s', refusing.", optarg); + + if (strv_extend(&arg_banks, EVP_MD_name(implementation)) < 0) + return log_oom(); + + break; + } + + case '?': + return -EINVAL; + + default: + assert_not_reached(); + } + + if (strv_isempty(arg_banks)) { + /* If no banks are specifically selected, pick all known banks */ + arg_banks = strv_new("SHA1", "SHA256", "SHA384", "SHA512"); + if (!arg_banks) + return log_oom(); + } + + strv_sort(arg_banks); + strv_uniq(arg_banks); + + return 1; +} + +typedef struct PcrState { + const EVP_MD *md; + void *value; + size_t value_size; +} PcrState; + +static void pcr_state_free_all(PcrState **pcr_state) { + assert(pcr_state); + + if (!*pcr_state) + return; + + for (size_t i = 0; (*pcr_state)[i].value; i++) + free((*pcr_state)[i].value); + + *pcr_state = mfree(*pcr_state); +} + +static void evp_md_ctx_free_all(EVP_MD_CTX **md[]) { + assert(md); + + if (!*md) + return; + + for (size_t i = 0; (*md)[i]; i++) + EVP_MD_CTX_free((*md)[i]); + + *md = mfree(*md); +} + +static int pcr_state_extend(PcrState *pcr_state, const void *data, size_t sz) { + _cleanup_(EVP_MD_CTX_freep) EVP_MD_CTX *mc = NULL; + unsigned value_size; + + assert(pcr_state); + assert(data || sz == 0); + assert(pcr_state->md); + assert(pcr_state->value); + assert(pcr_state->value_size > 0); + + /* Extends a (virtual) PCR by the given data */ + + mc = EVP_MD_CTX_new(); + if (!mc) + return log_oom(); + + if (EVP_DigestInit_ex(mc, pcr_state->md, NULL) != 1) + return log_error_errno(SYNTHETIC_ERRNO(EINVAL), "Failed to initialize %s context.", EVP_MD_name(pcr_state->md)); + + /* First thing we do, is hash the old PCR value */ + if (EVP_DigestUpdate(mc, pcr_state->value, pcr_state->value_size) != 1) + return log_error_errno(SYNTHETIC_ERRNO(EINVAL), "Failed to run digest."); + + /* Then, we hash the new data */ + if (EVP_DigestUpdate(mc, data, sz) != 1) + return log_error_errno(SYNTHETIC_ERRNO(EINVAL), "Failed to run digest."); + + if (EVP_DigestFinal_ex(mc, pcr_state->value, &value_size) != 1) + return log_error_errno(SYNTHETIC_ERRNO(EINVAL), "Failed to finalize hash context."); + + assert(value_size == pcr_state->value_size); + return 0; +} + +#define BUFFER_SIZE (16U * 1024U) + +static int measure_pcr(PcrState *pcr_states, size_t n) { + _cleanup_free_ void *buffer = NULL; + int r; + + assert(n > 0); + assert(pcr_states); + + buffer = malloc(BUFFER_SIZE); + if (!buffer) + return log_oom(); + + for (UnifiedSection c = 0; c < _UNIFIED_SECTION_MAX; c++) { + _cleanup_(evp_md_ctx_free_all) EVP_MD_CTX **mdctx = NULL; + _cleanup_close_ int fd = -1; + uint64_t m = 0; + + if (!arg_sections[c]) + continue; + + fd = open(arg_sections[c], O_RDONLY|O_CLOEXEC); + if (fd < 0) + return log_error_errno(errno, "Failed to open '%s': %m", arg_sections[c]); + + /* Allocate one message digest context per bank (NULL terminated) */ + mdctx = new0(EVP_MD_CTX*, n + 1); + if (!mdctx) + return log_oom(); + + for (size_t i = 0; i < n; i++) { + mdctx[i] = EVP_MD_CTX_new(); + if (!mdctx[i]) + return log_oom(); + + if (EVP_DigestInit_ex(mdctx[i], pcr_states[i].md, NULL) != 1) + return log_error_errno(SYNTHETIC_ERRNO(EINVAL), "Failed to initialize data %s context.", EVP_MD_name(pcr_states[i].md)); + } + + for (;;) { + ssize_t sz; + + sz = read(fd, buffer, BUFFER_SIZE); + if (sz < 0) + return log_error_errno(errno, "Failed to read '%s': %m", arg_sections[c]); + if (sz == 0) /* EOF */ + break; + + 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 */ + continue; + + for (size_t i = 0; i < n; i++) { + _cleanup_free_ void *data_hash = NULL; + unsigned data_hash_size; + + data_hash = malloc(pcr_states[i].value_size); + if (!data_hash) + return log_oom(); + + /* Measure name of section */ + if (EVP_Digest(unified_sections[c], strlen(unified_sections[c]) + 1, data_hash, &data_hash_size, pcr_states[i].md, NULL) != 1) + return log_error_errno(SYNTHETIC_ERRNO(EINVAL), "Failed to hash section name with %s.", EVP_MD_name(pcr_states[i].md)); + + assert(data_hash_size == (unsigned) pcr_states[i].value_size); + + r = pcr_state_extend(pcr_states + i, data_hash, data_hash_size); + if (r < 0) + return r; + + /* Retrieve hash of data an measure it*/ + if (EVP_DigestFinal_ex(mdctx[i], data_hash, &data_hash_size) != 1) + return log_error_errno(SYNTHETIC_ERRNO(EINVAL), "Failed to finalize hash context."); + + assert(data_hash_size == (unsigned) pcr_states[i].value_size); + + r = pcr_state_extend(pcr_states + i, data_hash, data_hash_size); + if (r < 0) + return r; + } + } + + return 0; +} + +static int verb_calculate(int argc, char *argv[], void *userdata) { + _cleanup_(pcr_state_free_all) PcrState *pcr_states = NULL; + size_t n = 0; + int r; + + if (!arg_sections[UNIFIED_SECTION_LINUX]) + return log_error_errno(SYNTHETIC_ERRNO(EINVAL), "--linux= switch must be specified, refusing."); + + pcr_states = new0(PcrState, strv_length(arg_banks) + 1); + if (!pcr_states) + return log_oom(); + + /* Allocate a PCR state structure, one for each bank */ + STRV_FOREACH(d, arg_banks) { + const EVP_MD *implementation; + _cleanup_free_ void *v = NULL; + int sz; + + assert_se(implementation = EVP_get_digestbyname(*d)); /* Must work, we already checked while parsing command line */ + + sz = EVP_MD_size(implementation); + if (sz <= 0 || sz >= INT_MAX) + return log_error_errno(SYNTHETIC_ERRNO(EINVAL), "Unexpected digest size: %i", sz); + + v = malloc0(sz); /* initial PCR state is all zeroes */ + if (!v) + return log_oom(); + + pcr_states[n++] = (struct PcrState) { + .md = implementation, + .value = TAKE_PTR(v), + .value_size = sz, + }; + } + + r = measure_pcr(pcr_states, n); + if (r < 0) + return r; + + for (size_t i = 0; i < n; i++) { + _cleanup_free_ char *hd = NULL, *b = NULL; + + hd = hexmem(pcr_states[i].value, pcr_states[i].value_size); + if (!hd) + return log_oom(); + + b = strdup(EVP_MD_name(pcr_states[i].md)); + if (!b) + return log_oom(); + + printf("%" PRIu32 ":%s=%s\n", TPM_PCR_INDEX_KERNEL_IMAGE, ascii_strlower(b), hd); + } + + return 0; +} + +static int compare_reported_pcr_nr(uint32_t pcr, const char *varname, const char *description) { + _cleanup_free_ char *s = NULL; + uint32_t v; + int r; + + r = efi_get_variable_string(varname, &s); + if (r == -ENOENT) + return 0; + if (r < 0) + return log_error_errno(r, "Failed to read EFI variable '%s': %m", varname); + + r = safe_atou32(s, &v); + if (r < 0) + return log_error_errno(r, "Failed to parse EFI variable '%s': %s", varname, s); + + if (pcr != v) + log_warning("PCR number reported by stub for %s (%" PRIu32 ") different from our expectation (%" PRIu32 ").\n" + "The measurements are likely inconsistent.", description, v, pcr); + + return 0; +} + +static int validate_stub(void) { + uint64_t features; + bool found = false; + int r; + + if (tpm2_support() != TPM2_SUPPORT_FULL) + return log_error_errno(SYNTHETIC_ERRNO(EOPNOTSUPP), "Sorry, system lacks full TPM2 support."); + + r = efi_stub_get_features(&features); + if (r < 0) + return log_error_errno(r, "Unable to get stub features: %m"); + + if (!FLAGS_SET(features, EFI_STUB_FEATURE_THREE_PCRS)) + log_warning("Warning: current kernel image does not support measuring itself, the command line or initrd system extension images.\n" + "The PCR measurements seen are unlikely to be valid."); + + r = compare_reported_pcr_nr(TPM_PCR_INDEX_KERNEL_IMAGE, EFI_LOADER_VARIABLE("StubPcrKernelImage"), "kernel image"); + if (r < 0) + return r; + + r = compare_reported_pcr_nr(TPM_PCR_INDEX_KERNEL_PARAMETERS, EFI_LOADER_VARIABLE("StubPcrKernelParameters"), "kernel parameters"); + if (r < 0) + return r; + + r = compare_reported_pcr_nr(TPM_PCR_INDEX_INITRD_SYSEXTS, EFI_LOADER_VARIABLE("StubPcrInitRDSysExts"), "initrd system extension images"); + if (r < 0) + return r; + + STRV_FOREACH(bank, arg_banks) { + _cleanup_free_ char *b = NULL, *p = NULL; + + b = strdup(*bank); + if (!b) + return log_oom(); + + if (asprintf(&p, "/sys/class/tpm/tpm0/pcr-%s/", ascii_strlower(b)) < 0) + return log_oom(); + + if (access(p, F_OK) < 0) { + if (errno != ENOENT) + return log_error_errno(errno, "Failed to detect if '%s' exists: %m", b); + } else + found = true; + } + + if (!found) + return log_error_errno(SYNTHETIC_ERRNO(EOPNOTSUPP), "None of the select PCR banks appear to exist."); + + return 0; +} + +static int verb_status(int argc, char *argv[], void *userdata) { + + static const struct { + uint32_t nr; + const char *description; + } relevant_pcrs[] = { + { TPM_PCR_INDEX_KERNEL_IMAGE, "Unified Kernel Image" }, + { TPM_PCR_INDEX_KERNEL_PARAMETERS, "Kernel Parameters" }, + { TPM_PCR_INDEX_INITRD_SYSEXTS, "initrd System Extensions" }, + }; + + int r; + + r = validate_stub(); + if (r < 0) + return r; + + for (size_t i = 0; i < ELEMENTSOF(relevant_pcrs); i++) { + + STRV_FOREACH(bank, arg_banks) { + _cleanup_free_ char *b = NULL, *p = NULL, *s = NULL, *f = NULL; + _cleanup_free_ void *h = NULL; + size_t l; + + b = strdup(*bank); + if (!b) + return log_oom(); + + if (asprintf(&p, "/sys/class/tpm/tpm0/pcr-%s/%" PRIu32, ascii_strlower(b), relevant_pcrs[i].nr) < 0) + return log_oom(); + + r = read_virtual_file(p, 4096, &s, NULL); + if (r == -ENOENT) + continue; + if (r < 0) + return log_error_errno(r, "Failed to read '%s': %m", p); + + r = unhexmem(strstrip(s), SIZE_MAX, &h, &l); + if (r < 0) + return log_error_errno(r, "Failed to decode PCR value '%s': %m", s); + + f = hexmem(h, l); + if (!h) + return log_oom(); + + if (bank == arg_banks) { + /* before the first line for each PCR, write a short descriptive text to + * stderr, and leave the primary content on stdout */ + fflush(stdout); + fprintf(stderr, "%s# PCR[%" PRIu32 "] %s%s%s\n", + ansi_grey(), + relevant_pcrs[i].nr, + relevant_pcrs[i].description, + memeqzero(h, l) ? " (NOT SET!)" : "", + ansi_normal()); + fflush(stderr); + } + + printf("%" PRIu32 ":%s=%s\n", relevant_pcrs[i].nr, b, f); + } + } + + return 0; +} + +static int measure_main(int argc, char *argv[]) { + static const Verb verbs[] = { + { "help", VERB_ANY, VERB_ANY, 0, help }, + { "status", VERB_ANY, 1, VERB_DEFAULT, verb_status }, + { "calculate", VERB_ANY, 1, 0, verb_calculate }, + {} + }; + + return dispatch_verb(argc, argv, verbs, NULL); +} + +static int run(int argc, char *argv[]) { + int r; + + log_show_color(true); + log_parse_environment(); + log_open(); + + r = parse_argv(argc, argv); + if (r <= 0) + return r; + + return measure_main(argc, argv); +} + +DEFINE_MAIN_FUNCTION(run); From 984638cde732ae126659630835a590c28c8602b7 Mon Sep 17 00:00:00 2001 From: Lennart Poettering Date: Fri, 29 Jul 2022 18:45:53 +0200 Subject: [PATCH 17/18] test: add trivial test for systemd-measure --- test/units/testsuite-70.sh | 12 ++++++++++++ 1 file changed, 12 insertions(+) diff --git a/test/units/testsuite-70.sh b/test/units/testsuite-70.sh index 09f78a0226b..f0bbee9436c 100755 --- a/test/units/testsuite-70.sh +++ b/test/units/testsuite-70.sh @@ -43,6 +43,18 @@ env PASSWORD=passphrase systemd-cryptenroll --tpm2-device=auto --tpm2-pcrs=0+7 $ tpm2_pcrextend 0:sha256=0000000000000000000000000000000000000000000000000000000000000000 /usr/lib/systemd/systemd-cryptsetup attach test-volume $img - tpm2-device=auto,headless=1 && exit 1 +echo HALLO > /tmp/tpmdata1 +echo foobar > /tmp/tpmdata2 + +cat > /tmp/result </testok exit 0 From c0a74f62860a3e3ce069ae809c29f259bb341d54 Mon Sep 17 00:00:00 2001 From: Lennart Poettering Date: Wed, 27 Jul 2022 15:46:42 +0200 Subject: [PATCH 18/18] update TODO --- TODO | 27 ++++++++++++--------------- 1 file changed, 12 insertions(+), 15 deletions(-) diff --git a/TODO b/TODO index f8bb47d20c7..af50489056b 100644 --- a/TODO +++ b/TODO @@ -115,6 +115,14 @@ Features: on other disks. Always boot into them via NextBoot EFI variable, to not affect PCR values. +* systemd-measure tool: + - pre-calculate PCR 12 (command line) + PCR 13 (sysext) the same way we can precalculate PCR 11 + - sign pre-calculated hashes in a way compatible with TPM2 PCR hash signature + policies, in a way they can be included in unified PE kernel images, and + made available to userspace. There, this should be consumed by + systemd-cryptsetup to implement PCR signature based TPM volume unlock + policies. + * in sd-boot: load EFI drivers from a new PE section. That way, one can have a "supercharged" sd-boot binary, that could carry ext4 drivers built-in. @@ -381,12 +389,6 @@ Features: case the same wd is reused multiple times before we start processing IN_IGNORED again) -* sd-stub: set efi var indicating stub features, i.e. whether they pick up - creds, sysexts and so on. similar to existing variable of sd-boot - -* sd-stub: set efi vars declaring TPM PCRs we measured creds/cmdline + sysext - into (even if we hardcode them) - * systemd-fstab-generator: support addition mount specifications via kernel cmdline. Usecase: invoke a VM, and mount a host homedir into it via virtio-fs. @@ -409,10 +411,6 @@ Features: - sd-stub: automatically pick up microcode from ESP (/loader/microcode/*) and synthesize initrd from it, and measure it. Signing is not necessary, as microcode does that on its own. Pass as first initrd to kernel. - - sd-stub should measure the kernel/initrd/… into a separate PCR, so that we - have one PCR we can bind the encrypted creds to that is not effected by - anything else but what we drop in via kernel-install, i.e. by earlier EFI - code running (i.e. like PCR 4) * Add a new service type very similar to Type=notify, that goes one step further and extends the protocol to cover reloads. Specifically, SIGHUP will @@ -656,7 +654,7 @@ Features: dep in the base OS image) * sysext: automatically activate sysext images dropped in via new sd-stub - sysext pickup logic. + sysext pickup logic. (must insist on verity + signature on those though) * add concept for "exitrd" as inverse of "initrd", that we can transition to at shutdown, and has similar security semantics. This should then take the place @@ -704,9 +702,9 @@ Features: what must be read-only, what requires encryption, and what requires authentication. -* in uefi stub: query firmware regarding which PCRs are being used, store that - in EFI var. then use this when enrolling TPM2 in cryptsetup to verify that - the selected PCRs actually are used by firmware. +* in uefi stub: query firmware regarding which PCR banks are being used, store + that in EFI var. then use this when enrolling TPM2 in cryptsetup to verify + that the selected PCRs actually are used by firmware. * rework recursive read-only remount to use new mount API @@ -1602,7 +1600,6 @@ Features: - show whether UEFI audit mode is available - teach it to prepare an ESP wholesale, i.e. with mkfs.vfat invocation - teach it to copy in unified kernel images and maybe type #1 boot loader spec entries from host - - make it operate on loopback files, dissecting enough to find ESP to operate on - bootspec: properly support boot attempt counters when parsing entry file names * kernel-install: