From: Luca Boccassi Date: Mon, 7 Aug 2023 00:05:18 +0000 (+0100) Subject: stub: add support for dtb addons X-Git-Tag: v255-rc1~289^2~3 X-Git-Url: http://git.ipfire.org/cgi-bin/gitweb.cgi?a=commitdiff_plain;h=68f85761e2eb1fd2243019980a64b174f07432c3;p=thirdparty%2Fsystemd.git stub: add support for dtb addons Same as kernel command line addons. --- diff --git a/docs/TPM2_PCR_MEASUREMENTS.md b/docs/TPM2_PCR_MEASUREMENTS.md index dffe2baaa02..f2893f13445 100644 --- a/docs/TPM2_PCR_MEASUREMENTS.md +++ b/docs/TPM2_PCR_MEASUREMENTS.md @@ -90,6 +90,16 @@ UTF-16. → **Measured hash** covers the literal kernel command line in UTF-16 (without any trailing NUL bytes). +### PCR 12, `EV_EVENT_TAG`, "Devicetrees" + +Devicetree addons are measured individually as a tagged event. + +→ **Event Tag** `0x6c46f751` + +→ **Description** the addon filename. + +→ **Measured hash** covers the content of the Devicetree. + ### PCR 12, `EV_IPL`, "Per-UKI Credentials initrd" → **Description** in the event log record is the constant string "Credentials diff --git a/man/systemd-stub.xml b/man/systemd-stub.xml index 1173db572b5..5650c53f021 100644 --- a/man/systemd-stub.xml +++ b/man/systemd-stub.xml @@ -167,19 +167,20 @@ ukify1 tool will add a SBAT policy by default if none is passed when building addons. For more information on SBAT see Shim's documentation. - Addons are supposed to be used to pass additional kernel command line parameters, regardless of the - kernel image being booted, for example to allow platform vendors to ship platform-specific - configuration. The loaded command line addon files are sorted, loaded, measured into TPM PCR 12 (if a - TPM is present) and appended to the kernel command line. UKI command line options are listed first, - then options from addons in /loader/addons/*.addon.efi are appended next, and - finally UKI-specific addons are appended last. Addons are always loaded in the same order based on the - filename, so that, given the same set of addons, the same set of measurements can be expected in - PCR12, however note that the filename is not protected by the PE signature, and as such an attacker - with write access to the ESP could potentially rename these files to change the order in which they - are loaded, in a way that could alter the functionality of the kernel, as some options might be order - dependent. If you sign such addons, you should pay attention to the PCR12 values and make use of an - attestation service so that improper use of your signed addons can be detected and dealt with using - one of the aforementioned revocation mechanisms. + Addons are supposed to be used to pass additional kernel command line parameters or Devicetree blobs, + regardless of the kernel image being booted, for example to allow platform vendors to ship + platform-specific configuration. The loaded command line addon files are sorted, loaded, and measured + into TPM PCR 12 (if a TPM is present) and appended to the kernel command line. UKI command line options + are listed first, then options from addons in /loader/addons/*.addon.efi, and + finally UKI-specific addons. Device tree blobs are loaded and measured following the same algorithm. + Addons are always loaded in the same order based on the filename, so that, given the same set of + addons, the same set of measurements can be expected in PCR12. However, note that the filename is not + protected by the PE signature, and as such an attacker with write access to the ESP could potentially + rename these files to change the order in which they are loaded, in a way that could alter the + functionality of the kernel, as some options might be order dependent. If you sign such addons, you + should pay attention to the PCR12 values and make use of an attestation service so that improper use + of your signed addons can be detected and dealt with using one of the aforementioned revocation + mechanisms. Files /loader/credentials/*.cred are packed up in a cpio archive and placed in the /.extra/global_credentials/ @@ -188,9 +189,9 @@ measured into TPM PCR 12 (if a TPM is present). Additionally, files /loader/addons/*.addon.efi are loaded and - verified as PE binaries, and a .cmdline section is parsed from them. This is - supposed to be used to pass additional command line parameters to the kernel, regardless of the kernel - being booted. + verified as PE binaries, and .cmdline and/or .dtb sections are + parsed from them. This is supposed to be used to pass additional command line parameters or Devicetree + blobs to the kernel, regardless of the kernel being booted. These mechanisms may be used to parameterize and extend trusted (i.e. signed), immutable initrd diff --git a/src/boot/bootctl-status.c b/src/boot/bootctl-status.c index 033abb05665..a1fe3364331 100644 --- a/src/boot/bootctl-status.c +++ b/src/boot/bootctl-status.c @@ -384,6 +384,7 @@ int verb_status(int argc, char *argv[], void *userdata) { { EFI_STUB_FEATURE_RANDOM_SEED, "Support for passing random seed to OS" }, { EFI_STUB_FEATURE_CMDLINE_ADDONS, "Pick up .cmdline from addons" }, { EFI_STUB_FEATURE_CMDLINE_SMBIOS, "Pick up .cmdline from SMBIOS Type 11" }, + { EFI_STUB_FEATURE_DEVICETREE_ADDONS, "Pick up .dtb from addons" }, }; _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; diff --git a/src/boot/efi/stub.c b/src/boot/efi/stub.c index 0f30f13d921..dd3d017cce8 100644 --- a/src/boot/efi/stub.c +++ b/src/boot/efi/stub.c @@ -26,6 +26,8 @@ DECLARE_NOALLOC_SECTION(".sdmagic", "#### LoaderInfo: systemd-stub " GIT_VERSION DECLARE_SBAT(SBAT_STUB_SECTION_TEXT); +#define ADDON_FILENAME_EVENT_TAG_ID UINT32_C(0x6c46f751) + static EFI_STATUS combine_initrd( EFI_PHYSICAL_ADDRESS initrd_base, size_t initrd_size, const void * const extra_initrds[], const size_t extra_initrd_sizes[], size_t n_extra_initrds, @@ -95,6 +97,7 @@ static void export_variables(EFI_LOADED_IMAGE_PROTOCOL *loaded_image) { EFI_STUB_FEATURE_RANDOM_SEED | /* We pass a random seed to the kernel */ EFI_STUB_FEATURE_CMDLINE_ADDONS | /* We pick up .cmdline addons */ EFI_STUB_FEATURE_CMDLINE_SMBIOS | /* We support extending kernel cmdline from SMBIOS Type #11 */ + EFI_STUB_FEATURE_DEVICETREE_ADDONS | /* We pick up .dtb addons */ 0; assert(loaded_image); @@ -253,29 +256,123 @@ static EFI_STATUS load_addons_from_dir( return EFI_SUCCESS; } -static EFI_STATUS cmdline_append_and_measure_addons( +static void cmdline_append_and_measure_addons( + char16_t *cmdline, + char16_t **cmdline_append, + bool *ret_parameters_measured) { + + _cleanup_free_ char16_t *tmp = NULL; + bool m = false; + + assert(cmdline_append); + assert(ret_parameters_measured); + + mangle_stub_cmdline(cmdline); + + if (isempty(cmdline)) + return; + + (void) tpm_log_load_options(cmdline, &m); + *ret_parameters_measured = m; + + tmp = TAKE_PTR(*cmdline_append); + *cmdline_append = xasprintf("%ls%ls%ls", strempty(tmp), isempty(tmp) ? u"" : u" ", cmdline); +} + +static void dtb_install_addons( + struct devicetree_state *dt_state, + void **dt_bases, + size_t *dt_sizes, + char16_t **dt_filenames, + size_t n_dts, + bool *ret_parameters_measured) { + + int parameters_measured = -1; + EFI_STATUS err; + + assert(dt_state); + assert(n_dts == 0 || (dt_bases && dt_sizes && dt_filenames)); + assert(ret_parameters_measured); + + for (size_t i = 0; i < n_dts; ++i) { + err = devicetree_install_from_memory(dt_state, dt_bases[i], dt_sizes[i]); + if (err != EFI_SUCCESS) + log_error_status(err, "Error loading addon devicetree, ignoring: %m"); + else { + bool m = false; + + err = tpm_log_tagged_event( + TPM2_PCR_KERNEL_CONFIG, + POINTER_TO_PHYSICAL_ADDRESS(dt_bases[i]), + dt_sizes[i], + ADDON_FILENAME_EVENT_TAG_ID, + dt_filenames[i], + &m); + if (err != EFI_SUCCESS) + return (void) log_error_status( + err, + "Unable to add measurement of DTB addon #%zu to PCR %i: %m", + i, + TPM2_PCR_KERNEL_CONFIG); + + parameters_measured = parameters_measured < 0 ? m : (parameters_measured && m); + } + } + + *ret_parameters_measured = parameters_measured; +} + +static void dt_bases_free(void **dt_bases, size_t n_dt) { + assert(dt_bases || n_dt == 0); + + for (size_t i = 0; i < n_dt; ++i) + free(dt_bases[i]); + + free(dt_bases); +} + +static void dt_filenames_free(char16_t **dt_filenames, size_t n_dt) { + assert(dt_filenames || n_dt == 0); + + for (size_t i = 0; i < n_dt; ++i) + free(dt_filenames[i]); + + free(dt_filenames); +} + +static EFI_STATUS load_addons( EFI_HANDLE stub_image, EFI_LOADED_IMAGE_PROTOCOL *loaded_image, const char16_t *prefix, const char *uname, - bool *ret_parameters_measured, - char16_t **cmdline_append) { + char16_t **ret_cmdline, + void ***ret_dt_bases, + size_t **ret_dt_sizes, + char16_t ***ret_dt_filenames, + size_t *ret_n_dt) { + _cleanup_free_ size_t *dt_sizes = NULL; _cleanup_(strv_freep) char16_t **items = NULL; _cleanup_(file_closep) EFI_FILE *root = NULL; - _cleanup_free_ char16_t *buffer = NULL; - size_t n_items = 0, n_allocated = 0; + _cleanup_free_ char16_t *cmdline = NULL; + size_t n_items = 0, n_allocated = 0, n_dt = 0; + char16_t **dt_filenames = NULL; + void **dt_bases = NULL; EFI_STATUS err; assert(stub_image); assert(loaded_image); assert(prefix); - assert(ret_parameters_measured); - assert(cmdline_append); + assert(!!ret_dt_bases == !!ret_dt_sizes); + assert(!!ret_dt_bases == !!ret_n_dt); + assert(!!ret_dt_filenames == !!ret_n_dt); if (!loaded_image->DeviceHandle) return EFI_SUCCESS; + CLEANUP_ARRAY(dt_bases, n_dt, dt_bases_free); + CLEANUP_ARRAY(dt_filenames, n_dt, dt_filenames_free); + err = open_volume(loaded_image->DeviceHandle, &root); if (err == EFI_UNSUPPORTED) /* Error will be unsupported if the bootloader doesn't implement the file system protocol on @@ -325,11 +422,12 @@ static EFI_STATUS cmdline_append_and_measure_addons( return log_error_status(err, "Failed to find protocol in %ls: %m", items[i]); err = pe_memory_locate_sections(loaded_addon->ImageBase, unified_sections, addrs, szs); - if (err != EFI_SUCCESS || szs[UNIFIED_SECTION_CMDLINE] == 0) { + if (err != EFI_SUCCESS || + (szs[UNIFIED_SECTION_CMDLINE] == 0 && szs[UNIFIED_SECTION_DTB] == 0)) { if (err == EFI_SUCCESS) err = EFI_NOT_FOUND; log_error_status(err, - "Unable to locate embedded .cmdline section in %ls, ignoring: %m", + "Unable to locate embedded .cmdline/.dtb sections in %ls, ignoring: %m", items[i]); continue; } @@ -350,22 +448,42 @@ static EFI_STATUS cmdline_append_and_measure_addons( continue; } - _cleanup_free_ char16_t *tmp = TAKE_PTR(buffer), - *extra16 = xstrn8_to_16((char *)loaded_addon->ImageBase + addrs[UNIFIED_SECTION_CMDLINE], - szs[UNIFIED_SECTION_CMDLINE]); - buffer = xasprintf("%ls%ls%ls", strempty(tmp), isempty(tmp) ? u"" : u" ", extra16); - } + if (ret_cmdline && szs[UNIFIED_SECTION_CMDLINE] > 0) { + _cleanup_free_ char16_t *tmp = TAKE_PTR(cmdline), + *extra16 = xstrn8_to_16((char *)loaded_addon->ImageBase + addrs[UNIFIED_SECTION_CMDLINE], + szs[UNIFIED_SECTION_CMDLINE]); + cmdline = xasprintf("%ls%ls%ls", strempty(tmp), isempty(tmp) ? u"" : u" ", extra16); + } + + if (ret_dt_bases && szs[UNIFIED_SECTION_DTB] > 0) { + dt_sizes = xrealloc(dt_sizes, + n_dt * sizeof(size_t), + (n_dt + 1) * sizeof(size_t)); + dt_sizes[n_dt] = szs[UNIFIED_SECTION_DTB]; - mangle_stub_cmdline(buffer); + dt_bases = xrealloc(dt_bases, + n_dt * sizeof(void *), + (n_dt + 1) * sizeof(void *)); + dt_bases[n_dt] = xmemdup((uint8_t*)loaded_addon->ImageBase + addrs[UNIFIED_SECTION_DTB], + dt_sizes[n_dt]); - if (!isempty(buffer)) { - _cleanup_free_ char16_t *tmp = TAKE_PTR(*cmdline_append); - bool m = false; + dt_filenames = xrealloc(dt_filenames, + n_dt * sizeof(char16_t *), + (n_dt + 1) * sizeof(char16_t *)); + dt_filenames[n_dt] = xstrdup16(items[i]); + + ++n_dt; + } + } - (void) tpm_log_load_options(buffer, &m); - *ret_parameters_measured = m; + if (ret_cmdline && !isempty(cmdline)) + *ret_cmdline = TAKE_PTR(cmdline); - *cmdline_append = xasprintf("%ls%ls%ls", strempty(tmp), isempty(tmp) ? u"" : u" ", buffer); + if (ret_n_dt && n_dt > 0) { + *ret_dt_filenames = TAKE_PTR(dt_filenames); + *ret_dt_bases = TAKE_PTR(dt_bases); + *ret_dt_sizes = TAKE_PTR(dt_sizes); + *ret_n_dt = n_dt; } return EFI_SUCCESS; @@ -374,12 +492,15 @@ static EFI_STATUS cmdline_append_and_measure_addons( static EFI_STATUS run(EFI_HANDLE image) { _cleanup_free_ void *credential_initrd = NULL, *global_credential_initrd = NULL, *sysext_initrd = NULL, *pcrsig_initrd = NULL, *pcrpkey_initrd = NULL; size_t credential_initrd_size = 0, global_credential_initrd_size = 0, sysext_initrd_size = 0, pcrsig_initrd_size = 0, pcrpkey_initrd_size = 0; - size_t linux_size, initrd_size, dt_size; + void **dt_bases_addons_global = NULL, **dt_bases_addons_uki = NULL; + char16_t **dt_filenames_addons_global = NULL, **dt_filenames_addons_uki = NULL; + _cleanup_free_ size_t *dt_sizes_addons_global = NULL, *dt_sizes_addons_uki = NULL; + size_t linux_size, initrd_size, dt_size, n_dts_addons_global = 0, n_dts_addons_uki = 0; EFI_PHYSICAL_ADDRESS linux_base, initrd_base, dt_base; _cleanup_(devicetree_cleanup) struct devicetree_state dt_state = {}; EFI_LOADED_IMAGE_PROTOCOL *loaded_image; size_t addrs[_UNIFIED_SECTION_MAX] = {}, szs[_UNIFIED_SECTION_MAX] = {}; - _cleanup_free_ char16_t *cmdline = NULL; + _cleanup_free_ char16_t *cmdline = NULL, *cmdline_addons_global = NULL, *cmdline_addons_uki = NULL; int sections_measured = -1, parameters_measured = -1; _cleanup_free_ char *uname = NULL; bool sysext_measured = false, m; @@ -406,6 +527,40 @@ static EFI_STATUS run(EFI_HANDLE image) { return log_error_status(err, "Unable to locate embedded .linux section: %m"); } + CLEANUP_ARRAY(dt_bases_addons_global, n_dts_addons_global, dt_bases_free); + CLEANUP_ARRAY(dt_bases_addons_uki, n_dts_addons_uki, dt_bases_free); + CLEANUP_ARRAY(dt_filenames_addons_global, n_dts_addons_global, dt_filenames_free); + CLEANUP_ARRAY(dt_filenames_addons_uki, n_dts_addons_uki, dt_filenames_free); + + /* Now that we have the UKI sections loaded, also load global first and then local (per-UKI) + * addons. The data is loaded at once, and then used later. */ + err = load_addons( + image, + loaded_image, + u"\\loader\\addons", + uname, + &cmdline_addons_global, + &dt_bases_addons_global, + &dt_sizes_addons_global, + &dt_filenames_addons_global, + &n_dts_addons_global); + if (err != EFI_SUCCESS) + log_error_status(err, "Error loading global addons, ignoring: %m"); + + _cleanup_free_ char16_t *dropin_dir = get_extra_dir(loaded_image->FilePath); + err = load_addons( + image, + loaded_image, + dropin_dir, + uname, + &cmdline_addons_uki, + &dt_bases_addons_uki, + &dt_sizes_addons_uki, + &dt_filenames_addons_uki, + &n_dts_addons_uki); + if (err != EFI_SUCCESS) + log_error_status(err, "Error loading UKI-specific addons, ignoring: %m"); + /* 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. */ @@ -471,27 +626,10 @@ static EFI_STATUS run(EFI_HANDLE image) { * measure the additions separately, after the embedded options, but before the smbios ones, * so that the order is reversed from "most hardcoded" to "most dynamic". The global addons are * loaded first, and the image-specific ones later, for the same reason. */ - err = cmdline_append_and_measure_addons( - image, - loaded_image, - u"\\loader\\addons", - uname, - &m, - &cmdline); - if (err != EFI_SUCCESS) - log_error_status(err, "Error loading global addons, ignoring: %m"); + cmdline_append_and_measure_addons(cmdline_addons_global, &cmdline, &m); parameters_measured = parameters_measured < 0 ? m : (parameters_measured && m); - _cleanup_free_ char16_t *dropin_dir = get_extra_dir(loaded_image->FilePath); - err = cmdline_append_and_measure_addons( - image, - loaded_image, - dropin_dir, - uname, - &m, - &cmdline); - if (err != EFI_SUCCESS) - log_error_status(err, "Error loading UKI-specific addons, ignoring: %m"); + cmdline_append_and_measure_addons(cmdline_addons_uki, &cmdline, &m); parameters_measured = parameters_measured < 0 ? m : (parameters_measured && m); /* SMBIOS OEM Strings data is controlled by the host admin and not covered @@ -552,6 +690,32 @@ static EFI_STATUS run(EFI_HANDLE image) { &m) == EFI_SUCCESS) sysext_measured = m; + dt_size = szs[UNIFIED_SECTION_DTB]; + dt_base = dt_size != 0 ? POINTER_TO_PHYSICAL_ADDRESS(loaded_image->ImageBase) + addrs[UNIFIED_SECTION_DTB] : 0; + + /* First load the base device tree, then fix it up using addons - global first, then per-UKI. */ + if (dt_size > 0) { + err = devicetree_install_from_memory( + &dt_state, PHYSICAL_ADDRESS_TO_POINTER(dt_base), dt_size); + if (err != EFI_SUCCESS) + log_error_status(err, "Error loading embedded devicetree: %m"); + } + + dtb_install_addons(&dt_state, + dt_bases_addons_global, + dt_sizes_addons_global, + dt_filenames_addons_global, + n_dts_addons_global, + &m); + parameters_measured = parameters_measured < 0 ? m : (parameters_measured && m); + dtb_install_addons(&dt_state, + dt_bases_addons_uki, + dt_sizes_addons_uki, + dt_filenames_addons_uki, + n_dts_addons_uki, + &m); + parameters_measured = parameters_measured < 0 ? m : (parameters_measured && m); + if (parameters_measured > 0) (void) efivar_set_uint_string(MAKE_GUID_PTR(LOADER), u"StubPcrKernelParameters", TPM2_PCR_KERNEL_CONFIG, 0); if (sysext_measured) @@ -600,9 +764,6 @@ static EFI_STATUS run(EFI_HANDLE image) { 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[UNIFIED_SECTION_DTB]; - dt_base = dt_size != 0 ? POINTER_TO_PHYSICAL_ADDRESS(loaded_image->ImageBase) + addrs[UNIFIED_SECTION_DTB] : 0; - _cleanup_pages_ Pages initrd_pages = {}; if (credential_initrd || global_credential_initrd || sysext_initrd || pcrsig_initrd || pcrpkey_initrd) { /* If we have generated initrds dynamically, let's combine them with the built-in initrd. */ @@ -637,13 +798,6 @@ static EFI_STATUS run(EFI_HANDLE image) { pcrpkey_initrd = mfree(pcrpkey_initrd); } - if (dt_size > 0) { - err = devicetree_install_from_memory( - &dt_state, PHYSICAL_ADDRESS_TO_POINTER(dt_base), dt_size); - if (err != EFI_SUCCESS) - log_error_status(err, "Error loading embedded devicetree: %m"); - } - err = linux_exec(image, cmdline, PHYSICAL_ADDRESS_TO_POINTER(linux_base), linux_size, PHYSICAL_ADDRESS_TO_POINTER(initrd_base), initrd_size); diff --git a/src/fundamental/efivars-fundamental.h b/src/fundamental/efivars-fundamental.h index 569f5eeceb1..8c9e7ddef28 100644 --- a/src/fundamental/efivars-fundamental.h +++ b/src/fundamental/efivars-fundamental.h @@ -31,6 +31,7 @@ #define EFI_STUB_FEATURE_RANDOM_SEED (UINT64_C(1) << 4) #define EFI_STUB_FEATURE_CMDLINE_ADDONS (UINT64_C(1) << 5) #define EFI_STUB_FEATURE_CMDLINE_SMBIOS (UINT64_C(1) << 6) +#define EFI_STUB_FEATURE_DEVICETREE_ADDONS (UINT64_C(1) << 7) typedef enum SecureBootMode { SECURE_BOOT_UNSUPPORTED,