diff options
Diffstat (limited to 'src/boot/efi/stub.c')
-rw-r--r-- | src/boot/efi/stub.c | 816 |
1 files changed, 816 insertions, 0 deletions
diff --git a/src/boot/efi/stub.c b/src/boot/efi/stub.c new file mode 100644 index 0000000..7ef3e76 --- /dev/null +++ b/src/boot/efi/stub.c @@ -0,0 +1,816 @@ +/* SPDX-License-Identifier: LGPL-2.1-or-later */ + +#include "cpio.h" +#include "device-path-util.h" +#include "devicetree.h" +#include "graphics.h" +#include "linux.h" +#include "measure.h" +#include "memory-util-fundamental.h" +#include "part-discovery.h" +#include "pe.h" +#include "proto/shell-parameters.h" +#include "random-seed.h" +#include "sbat.h" +#include "secure-boot.h" +#include "shim.h" +#include "splash.h" +#include "tpm2-pcr.h" +#include "uki.h" +#include "util.h" +#include "version.h" +#include "vmm.h" + +/* magic string to find in the binary image */ +DECLARE_NOALLOC_SECTION(".sdmagic", "#### LoaderInfo: systemd-stub " GIT_VERSION " ####"); + +DECLARE_SBAT(SBAT_STUB_SECTION_TEXT); + +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, + Pages *ret_initr_pages, size_t *ret_initrd_size) { + + size_t n; + + assert(ret_initr_pages); + assert(ret_initrd_size); + + /* Combines four initrds into one, by simple concatenation in memory */ + + n = ALIGN4(initrd_size); /* main initrd might not be padded yet */ + + for (size_t i = 0; i < n_extra_initrds; i++) { + if (!extra_initrds[i]) + continue; + + if (n > SIZE_MAX - extra_initrd_sizes[i]) + return EFI_OUT_OF_RESOURCES; + + n += extra_initrd_sizes[i]; + } + + _cleanup_pages_ Pages pages = xmalloc_pages( + AllocateMaxAddress, + EfiLoaderData, + EFI_SIZE_TO_PAGES(n), + UINT32_MAX /* Below 4G boundary. */); + uint8_t *p = PHYSICAL_ADDRESS_TO_POINTER(pages.addr); + if (initrd_base != 0) { + size_t pad; + + /* Order matters, the real initrd must come first, since it might include microcode updates + * which the kernel only looks for in the first cpio archive */ + p = mempcpy(p, PHYSICAL_ADDRESS_TO_POINTER(initrd_base), initrd_size); + + pad = ALIGN4(initrd_size) - initrd_size; + if (pad > 0) { + memzero(p, pad); + p += pad; + } + } + + for (size_t i = 0; i < n_extra_initrds; i++) { + if (!extra_initrds[i]) + continue; + + p = mempcpy(p, extra_initrds[i], extra_initrd_sizes[i]); + } + + assert(PHYSICAL_ADDRESS_TO_POINTER(pages.addr + n) == p); + + *ret_initr_pages = pages; + *ret_initrd_size = n; + pages.n_pages = 0; + + return EFI_SUCCESS; +} + +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 */ + 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); + + /* Export the device path this image is started from, if it's not set yet */ + if (efivar_get_raw(MAKE_GUID_PTR(LOADER), u"LoaderDevicePartUUID", NULL, NULL) != EFI_SUCCESS) { + _cleanup_free_ char16_t *uuid = disk_get_part_uuid(loaded_image->DeviceHandle); + if (uuid) + efivar_set(MAKE_GUID_PTR(LOADER), u"LoaderDevicePartUUID", uuid, 0); + } + + /* If LoaderImageIdentifier is not set, assume the image with this stub was loaded directly from the + * UEFI firmware without any boot loader, and hence set the LoaderImageIdentifier ourselves. Note + * that some boot chain loaders neither set LoaderImageIdentifier nor make FilePath available to us, + * in which case there's simple nothing to set for us. (The UEFI spec doesn't really say who's wrong + * here, i.e. whether FilePath may be NULL or not, hence handle this gracefully and check if FilePath + * is non-NULL explicitly.) */ + if (efivar_get_raw(MAKE_GUID_PTR(LOADER), u"LoaderImageIdentifier", NULL, NULL) != EFI_SUCCESS && + loaded_image->FilePath) { + _cleanup_free_ char16_t *s = NULL; + if (device_path_to_str(loaded_image->FilePath, &s) == EFI_SUCCESS) + efivar_set(MAKE_GUID_PTR(LOADER), u"LoaderImageIdentifier", s, 0); + } + + /* if LoaderFirmwareInfo is not set, let's set it */ + if (efivar_get_raw(MAKE_GUID_PTR(LOADER), u"LoaderFirmwareInfo", NULL, NULL) != EFI_SUCCESS) { + _cleanup_free_ char16_t *s = NULL; + s = xasprintf("%ls %u.%02u", ST->FirmwareVendor, ST->FirmwareRevision >> 16, ST->FirmwareRevision & 0xffff); + efivar_set(MAKE_GUID_PTR(LOADER), u"LoaderFirmwareInfo", s, 0); + } + + /* ditto for LoaderFirmwareType */ + if (efivar_get_raw(MAKE_GUID_PTR(LOADER), u"LoaderFirmwareType", NULL, NULL) != EFI_SUCCESS) { + _cleanup_free_ char16_t *s = NULL; + s = xasprintf("UEFI %u.%02u", ST->Hdr.Revision >> 16, ST->Hdr.Revision & 0xffff); + efivar_set(MAKE_GUID_PTR(LOADER), u"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(MAKE_GUID_PTR(LOADER), u"StubInfo", u"systemd-stub " GIT_VERSION, 0); + + (void) efivar_set_uint64_le(MAKE_GUID_PTR(LOADER), u"StubFeatures", stub_features, 0); +} + +static bool use_load_options( + EFI_HANDLE stub_image, + EFI_LOADED_IMAGE_PROTOCOL *loaded_image, + bool have_cmdline, + char16_t **ret) { + + assert(stub_image); + assert(loaded_image); + assert(ret); + + /* We only allow custom command lines if we aren't in secure boot or if no cmdline was baked into + * the stub image. + * We also don't allow it if we are in confidential vms and secureboot is on. */ + if (secure_boot_enabled() && (have_cmdline || is_confidential_vm())) + return false; + + /* We also do a superficial check whether first character of passed command line + * is printable character (for compat with some Dell systems which fill in garbage?). */ + if (loaded_image->LoadOptionsSize < sizeof(char16_t) || ((char16_t *) loaded_image->LoadOptions)[0] <= 0x1F) + return false; + + /* The UEFI shell registers EFI_SHELL_PARAMETERS_PROTOCOL onto images it runs. This lets us know that + * LoadOptions starts with the stub binary path which we want to strip off. */ + EFI_SHELL_PARAMETERS_PROTOCOL *shell; + if (BS->HandleProtocol(stub_image, MAKE_GUID_PTR(EFI_SHELL_PARAMETERS_PROTOCOL), (void **) &shell) + != EFI_SUCCESS) { + /* Not running from EFI shell, use entire LoadOptions. Note that LoadOptions is a void*, so + * it could be anything! */ + *ret = xstrndup16(loaded_image->LoadOptions, loaded_image->LoadOptionsSize / sizeof(char16_t)); + mangle_stub_cmdline(*ret); + return true; + } + + if (shell->Argc < 2) + /* No arguments were provided? Then we fall back to built-in cmdline. */ + return false; + + /* Assemble the command line ourselves without our stub path. */ + *ret = xstrdup16(shell->Argv[1]); + for (size_t i = 2; i < shell->Argc; i++) { + _cleanup_free_ char16_t *old = *ret; + *ret = xasprintf("%ls %ls", old, shell->Argv[i]); + } + + mangle_stub_cmdline(*ret); + return true; +} + +static EFI_STATUS load_addons_from_dir( + EFI_FILE *root, + const char16_t *prefix, + char16_t ***items, + size_t *n_items, + size_t *n_allocated) { + + _cleanup_(file_closep) EFI_FILE *extra_dir = NULL; + _cleanup_free_ EFI_FILE_INFO *dirent = NULL; + size_t dirent_size = 0; + EFI_STATUS err; + + assert(root); + assert(prefix); + assert(items); + assert(n_items); + assert(n_allocated); + + err = open_directory(root, prefix, &extra_dir); + if (err == EFI_NOT_FOUND) + /* No extra subdir, that's totally OK */ + return EFI_SUCCESS; + if (err != EFI_SUCCESS) + return log_error_status(err, "Failed to open addons directory '%ls': %m", prefix); + + for (;;) { + _cleanup_free_ char16_t *d = NULL; + + err = readdir(extra_dir, &dirent, &dirent_size); + if (err != EFI_SUCCESS) + return log_error_status(err, "Failed to read addons directory of loaded image: %m"); + if (!dirent) /* End of directory */ + break; + + if (dirent->FileName[0] == '.') + continue; + if (FLAGS_SET(dirent->Attribute, EFI_FILE_DIRECTORY)) + continue; + if (!is_ascii(dirent->FileName)) + continue; + if (strlen16(dirent->FileName) > 255) /* Max filename size on Linux */ + continue; + if (!endswith_no_case(dirent->FileName, u".addon.efi")) + continue; + + d = xstrdup16(dirent->FileName); + + if (*n_items + 2 > *n_allocated) { + /* We allocate 16 entries at a time, as a matter of optimization */ + if (*n_items > (SIZE_MAX / sizeof(uint16_t)) - 16) /* Overflow check, just in case */ + return log_oom(); + + size_t m = *n_items + 16; + *items = xrealloc(*items, *n_allocated * sizeof(uint16_t *), m * sizeof(uint16_t *)); + *n_allocated = m; + } + + (*items)[(*n_items)++] = TAKE_PTR(d); + (*items)[*n_items] = NULL; /* Let's always NUL terminate, to make freeing via strv_free() easy */ + } + + return EFI_SUCCESS; +} + +static void cmdline_append_and_measure_addons( + char16_t *cmdline_global, + char16_t *cmdline_uki, + char16_t **cmdline_append, + bool *ret_parameters_measured) { + + _cleanup_free_ char16_t *tmp = NULL, *merged = NULL; + bool m = false; + + assert(cmdline_append); + assert(ret_parameters_measured); + + if (isempty(cmdline_global) && isempty(cmdline_uki)) + return; + + merged = xasprintf("%ls%ls%ls", + strempty(cmdline_global), + isempty(cmdline_global) || isempty(cmdline_uki) ? u"" : u" ", + strempty(cmdline_uki)); + + mangle_stub_cmdline(merged); + + if (isempty(merged)) + return; + + (void) tpm_log_load_options(merged, &m); + *ret_parameters_measured = m; + + tmp = TAKE_PTR(*cmdline_append); + *cmdline_append = xasprintf("%ls%ls%ls", strempty(tmp), isempty(tmp) ? u"" : u" ", merged); +} + +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], + DEVICETREE_ADDON_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, + 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 *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_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 + * its file handles. */ + return EFI_SUCCESS; + if (err != EFI_SUCCESS) + return log_error_status(err, "Unable to open root directory: %m"); + + err = load_addons_from_dir(root, prefix, &items, &n_items, &n_allocated); + if (err != EFI_SUCCESS) + return err; + + if (n_items == 0) + return EFI_SUCCESS; /* Empty directory */ + + /* Now, sort the files we found, to make this uniform and stable (and to ensure the TPM measurements + * are not dependent on read order) */ + sort_pointer_array((void**) items, n_items, (compare_pointer_func_t) strcmp16); + + for (size_t i = 0; i < n_items; i++) { + size_t addrs[_UNIFIED_SECTION_MAX] = {}, szs[_UNIFIED_SECTION_MAX] = {}; + _cleanup_free_ EFI_DEVICE_PATH *addon_path = NULL; + _cleanup_(unload_imagep) EFI_HANDLE addon = NULL; + EFI_LOADED_IMAGE_PROTOCOL *loaded_addon = NULL; + _cleanup_free_ char16_t *addon_spath = NULL; + + addon_spath = xasprintf("%ls\\%ls", prefix, items[i]); + err = make_file_device_path(loaded_image->DeviceHandle, addon_spath, &addon_path); + if (err != EFI_SUCCESS) + return log_error_status(err, "Error making device path for %ls: %m", addon_spath); + + /* By using shim_load_image, we cover both the case where the PE files are signed with MoK + * and with DB, and running with or without shim. */ + err = shim_load_image(stub_image, addon_path, &addon); + if (err != EFI_SUCCESS) { + log_error_status(err, + "Failed to read '%ls' from '%ls', ignoring: %m", + items[i], + addon_spath); + continue; + } + + err = BS->HandleProtocol(addon, + MAKE_GUID_PTR(EFI_LOADED_IMAGE_PROTOCOL), + (void **) &loaded_addon); + if (err != EFI_SUCCESS) + 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 && szs[UNIFIED_SECTION_DTB] == 0)) { + if (err == EFI_SUCCESS) + err = EFI_NOT_FOUND; + log_error_status(err, + "Unable to locate embedded .cmdline/.dtb sections in %ls, ignoring: %m", + items[i]); + continue; + } + + /* We want to enforce that addons are not UKIs, i.e.: they must not embed a kernel. */ + if (szs[UNIFIED_SECTION_LINUX] > 0) { + log_error_status(EFI_INVALID_PARAMETER, "%ls is a UKI, not an addon, ignoring: %m", items[i]); + continue; + } + + /* Also enforce that, in case it is specified, .uname matches as a quick way to allow + * enforcing compatibility with a specific UKI only */ + if (uname && szs[UNIFIED_SECTION_UNAME] > 0 && + !strneq8(uname, + (char *)loaded_addon->ImageBase + addrs[UNIFIED_SECTION_UNAME], + szs[UNIFIED_SECTION_UNAME])) { + log_error(".uname mismatch between %ls and UKI, ignoring", items[i]); + continue; + } + + 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]; + + 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]); + + 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; + } + } + + if (ret_cmdline && !isempty(cmdline)) + *ret_cmdline = TAKE_PTR(cmdline); + + 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; +} + +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; + 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, *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; + uint64_t loader_features = 0; + EFI_STATUS err; + + err = BS->HandleProtocol(image, MAKE_GUID_PTR(EFI_LOADED_IMAGE_PROTOCOL), (void **) &loaded_image); + if (err != EFI_SUCCESS) + return log_error_status(err, "Error getting a LoadedImageProtocol handle: %m"); + + if (loaded_image->DeviceHandle && /* Handle case, where bootloader doesn't support DeviceHandle. */ + (efivar_get_uint64_le(MAKE_GUID_PTR(LOADER), u"LoaderFeatures", &loader_features) != EFI_SUCCESS || + !FLAGS_SET(loader_features, EFI_LOADER_FEATURE_RANDOM_SEED))) { + _cleanup_(file_closep) EFI_FILE *esp_dir = NULL; + + err = partition_open(MAKE_GUID_PTR(ESP), loaded_image->DeviceHandle, NULL, &esp_dir); + if (err == EFI_SUCCESS) /* Non-fatal on failure, so that we still boot without it. */ + (void) process_random_seed(esp_dir); + } + + 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(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"); + + /* Some bootloaders always pass NULL in FilePath, so we need to check for it here. */ + _cleanup_free_ char16_t *dropin_dir = get_extra_dir(loaded_image->FilePath); + if (dropin_dir) { + 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. */ + for (UnifiedSection section = 0; section < _UNIFIED_SECTION_MAX; section++) { + + if (!unified_section_measure(section)) /* shall not measure? */ + continue; + + if (szs[section] == 0) /* not found */ + continue; + + m = false; + + /* First measure the name of the section */ + (void) tpm_log_event_ascii( + TPM2_PCR_KERNEL_BOOT, + 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); + + /* Then measure the data of the section */ + (void) tpm_log_event_ascii( + TPM2_PCR_KERNEL_BOOT, + POINTER_TO_PHYSICAL_ADDRESS(loaded_image->ImageBase) + addrs[section], + szs[section], + unified_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(MAKE_GUID_PTR(LOADER), u"StubPcrKernelImage", TPM2_PCR_KERNEL_BOOT, 0); + + /* Show splash screen as early as possible */ + graphics_splash((const uint8_t*) loaded_image->ImageBase + addrs[UNIFIED_SECTION_SPLASH], szs[UNIFIED_SECTION_SPLASH]); + + if (szs[UNIFIED_SECTION_UNAME] > 0) + uname = xstrndup8((char *)loaded_image->ImageBase + addrs[UNIFIED_SECTION_UNAME], + szs[UNIFIED_SECTION_UNAME]); + + if (use_load_options(image, loaded_image, szs[UNIFIED_SECTION_CMDLINE] > 0, &cmdline)) { + /* 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 + * 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. */ + m = false; + (void) tpm_log_load_options(cmdline, &m); + parameters_measured = m; + } else if (szs[UNIFIED_SECTION_CMDLINE] > 0) { + cmdline = xstrn8_to_16( + (char *) loaded_image->ImageBase + addrs[UNIFIED_SECTION_CMDLINE], + szs[UNIFIED_SECTION_CMDLINE]); + mangle_stub_cmdline(cmdline); + } + + /* If we have any extra command line to add via PE addons, load them now and append, and + * measure the additions together, 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. */ + cmdline_append_and_measure_addons(cmdline_addons_global, 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 + * by the VM attestation, so MUST NOT be trusted when in a confidential VM */ + if (!is_confidential_vm()) { + const char *extra = smbios_find_oem_string("io.systemd.stub.kernel-cmdline-extra"); + if (extra) { + _cleanup_free_ char16_t *tmp = TAKE_PTR(cmdline), *extra16 = xstr8_to_16(extra); + cmdline = xasprintf("%ls %ls", tmp, extra16); + + /* SMBIOS strings are measured in PCR1, but we also want to measure them in our specific + * PCR12, as firmware-owned PCRs are very difficult to use as they'll contain unpredictable + * measurements that are not under control of the machine owner. */ + m = false; + (void) tpm_log_load_options(extra16, &m); + parameters_measured = parameters_measured < 0 ? m : (parameters_measured && m); + } + } + + export_variables(loaded_image); + + if (pack_cpio(loaded_image, + NULL, + u".cred", + ".extra/credentials", + /* dir_mode= */ 0500, + /* access_mode= */ 0400, + /* tpm_pcr= */ TPM2_PCR_KERNEL_CONFIG, + u"Credentials initrd", + &credential_initrd, + &credential_initrd_size, + &m) == EFI_SUCCESS) + parameters_measured = parameters_measured < 0 ? m : (parameters_measured && m); + + if (pack_cpio(loaded_image, + u"\\loader\\credentials", + u".cred", + ".extra/global_credentials", + /* dir_mode= */ 0500, + /* access_mode= */ 0400, + /* tpm_pcr= */ TPM2_PCR_KERNEL_CONFIG, + u"Global credentials initrd", + &global_credential_initrd, + &global_credential_initrd_size, + &m) == EFI_SUCCESS) + parameters_measured = parameters_measured < 0 ? m : (parameters_measured && m); + + if (pack_cpio(loaded_image, + NULL, + u".raw", + ".extra/sysext", + /* dir_mode= */ 0555, + /* access_mode= */ 0444, + /* tpm_pcr= */ TPM2_PCR_SYSEXTS, + u"System extension initrd", + &sysext_initrd, + &sysext_initrd_size, + &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) + (void) efivar_set_uint_string(MAKE_GUID_PTR(LOADER), u"StubPcrInitRDSysExts", TPM2_PCR_SYSEXTS, 0); + + /* If the PCR signature was embedded in the PE image, then let's wrap it in a cpio and also pass it + * to the kernel, so that it can be read from /.extra/tpm2-pcr-signature.json. Note that this section + * is not measured, neither as raw section (see above), nor as cpio (here), because it is the + * signature of expected PCR values, i.e. its input are PCR measurements, and hence it shouldn't + * itself be input for PCR measurements. */ + if (szs[UNIFIED_SECTION_PCRSIG] > 0) + (void) pack_cpio_literal( + (uint8_t*) loaded_image->ImageBase + addrs[UNIFIED_SECTION_PCRSIG], + szs[UNIFIED_SECTION_PCRSIG], + ".extra", + u"tpm2-pcr-signature.json", + /* dir_mode= */ 0555, + /* access_mode= */ 0444, + /* tpm_pcr= */ UINT32_MAX, + /* tpm_description= */ NULL, + &pcrsig_initrd, + &pcrsig_initrd_size, + /* ret_measured= */ NULL); + + /* If the public key used for the PCR signatures was embedded in the PE image, then let's wrap it in + * a cpio and also pass it to the kernel, so that it can be read from + * /.extra/tpm2-pcr-public-key.pem. This section is already measure above, hence we won't measure the + * cpio. */ + if (szs[UNIFIED_SECTION_PCRPKEY] > 0) + (void) pack_cpio_literal( + (uint8_t*) loaded_image->ImageBase + addrs[UNIFIED_SECTION_PCRPKEY], + szs[UNIFIED_SECTION_PCRPKEY], + ".extra", + u"tpm2-pcr-public-key.pem", + /* dir_mode= */ 0555, + /* access_mode= */ 0444, + /* tpm_pcr= */ UINT32_MAX, + /* tpm_description= */ NULL, + &pcrpkey_initrd, + &pcrpkey_initrd_size, + /* ret_measured= */ NULL); + + linux_size = szs[UNIFIED_SECTION_LINUX]; + linux_base = POINTER_TO_PHYSICAL_ADDRESS(loaded_image->ImageBase) + addrs[UNIFIED_SECTION_LINUX]; + + initrd_size = szs[UNIFIED_SECTION_INITRD]; + initrd_base = initrd_size != 0 ? POINTER_TO_PHYSICAL_ADDRESS(loaded_image->ImageBase) + addrs[UNIFIED_SECTION_INITRD] : 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. */ + err = combine_initrd( + initrd_base, initrd_size, + (const void*const[]) { + credential_initrd, + global_credential_initrd, + sysext_initrd, + pcrsig_initrd, + pcrpkey_initrd, + }, + (const size_t[]) { + credential_initrd_size, + global_credential_initrd_size, + sysext_initrd_size, + pcrsig_initrd_size, + pcrpkey_initrd_size, + }, + 5, + &initrd_pages, &initrd_size); + if (err != EFI_SUCCESS) + return err; + + initrd_base = initrd_pages.addr; + + /* Given these might be large let's free them explicitly, quickly. */ + credential_initrd = mfree(credential_initrd); + global_credential_initrd = mfree(global_credential_initrd); + sysext_initrd = mfree(sysext_initrd); + pcrsig_initrd = mfree(pcrsig_initrd); + pcrpkey_initrd = mfree(pcrpkey_initrd); + } + + err = linux_exec(image, cmdline, + PHYSICAL_ADDRESS_TO_POINTER(linux_base), linux_size, + PHYSICAL_ADDRESS_TO_POINTER(initrd_base), initrd_size); + graphics_mode(false); + return err; +} + +DEFINE_EFI_MAIN_FUNCTION(run, "systemd-stub", /*wait_for_debugger=*/false); |