diff --git a/src/basic/iovec-util.h b/src/basic/iovec-util.h index a499fef9ec..b92257cb0d 100644 --- a/src/basic/iovec-util.h +++ b/src/basic/iovec-util.h @@ -6,6 +6,7 @@ #include #include "alloc-util.h" +#include "iovec-util-fundamental.h" #include "macro.h" extern const struct iovec iovec_nul_byte; /* Points to a single NUL byte */ @@ -15,13 +16,6 @@ size_t iovec_total_size(const struct iovec *iovec, size_t n); bool iovec_increment(struct iovec *iovec, size_t n, size_t k); -/* This accepts both const and non-const pointers */ -#define IOVEC_MAKE(base, len) \ - (struct iovec) { \ - .iov_base = (void*) (base), \ - .iov_len = (len), \ - } - static inline struct iovec* iovec_make_string(struct iovec *iovec, const char *s) { assert(iovec); /* We don't use strlen_ptr() here, because we don't want to include string-util.h for now */ @@ -38,14 +32,6 @@ static inline struct iovec* iovec_make_string(struct iovec *iovec, const char *s .iov_len = STRLEN(s), \ } -static inline void iovec_done(struct iovec *iovec) { - /* A _cleanup_() helper that frees the iov_base in the iovec */ - assert(iovec); - - iovec->iov_base = mfree(iovec->iov_base); - iovec->iov_len = 0; -} - static inline void iovec_done_erase(struct iovec *iovec) { assert(iovec); @@ -53,16 +39,6 @@ static inline void iovec_done_erase(struct iovec *iovec) { iovec->iov_len = 0; } -static inline bool iovec_is_set(const struct iovec *iovec) { - /* Checks if the iovec points to a non-empty chunk of memory */ - return iovec && iovec->iov_len > 0 && iovec->iov_base; -} - -static inline bool iovec_is_valid(const struct iovec *iovec) { - /* Checks if the iovec is either NULL, empty or points to a valid bit of memory */ - return !iovec || (iovec->iov_base || iovec->iov_len == 0); -} - char* set_iovec_string_field(struct iovec *iovec, size_t *n_iovec, const char *field, const char *value); char* set_iovec_string_field_free(struct iovec *iovec, size_t *n_iovec, const char *field, char *value); diff --git a/src/basic/macro.h b/src/basic/macro.h index 19d5039fd3..7e2bc628db 100644 --- a/src/basic/macro.h +++ b/src/basic/macro.h @@ -211,16 +211,10 @@ static inline int __coverity_check_and_return__(int condition) { #define PTR_TO_UINT64(p) ((uint64_t) ((uintptr_t) (p))) #define UINT64_TO_PTR(u) ((void *) ((uintptr_t) (u))) -#define PTR_TO_SIZE(p) ((size_t) ((uintptr_t) (p))) -#define SIZE_TO_PTR(u) ((void *) ((uintptr_t) (u))) - #define CHAR_TO_STR(x) ((char[2]) { x, 0 }) #define char_array_0(x) x[sizeof(x)-1] = 0; -#define sizeof_field(struct_type, member) sizeof(((struct_type *) 0)->member) -#define endoffsetof_field(struct_type, member) (offsetof(struct_type, member) + sizeof_field(struct_type, member)) - /* Maximum buffer size needed for formatting an unsigned integer type as hex, including space for '0x' * prefix and trailing NUL suffix. */ #define HEXADECIMAL_STR_MAX(type) (2 + sizeof(type) * 2 + 1) @@ -266,18 +260,6 @@ static inline int __coverity_check_and_return__(int condition) { /* Pointers range from NULL to POINTER_MAX */ #define POINTER_MAX ((void*) UINTPTR_MAX) -#define _FOREACH_ARRAY(i, array, num, m, end) \ - for (typeof(array[0]) *i = (array), *end = ({ \ - typeof(num) m = (num); \ - (i && m > 0) ? i + m : NULL; \ - }); end && i < end; i++) - -#define FOREACH_ARRAY(i, array, num) \ - _FOREACH_ARRAY(i, array, num, UNIQ_T(m, UNIQ), UNIQ_T(end, UNIQ)) - -#define FOREACH_ELEMENT(i, array) \ - FOREACH_ARRAY(i, array, ELEMENTSOF(array)) - #define _DEFINE_TRIVIAL_REF_FUNC(type, name, scope) \ scope type *name##_ref(type *p) { \ if (!p) \ diff --git a/src/boot/efi/boot.c b/src/boot/efi/boot.c index 79de121f0d..9dbf96f169 100644 --- a/src/boot/efi/boot.c +++ b/src/boot/efi/boot.c @@ -7,6 +7,7 @@ #include "devicetree.h" #include "drivers.h" #include "efivars-fundamental.h" +#include "export-vars.h" #include "graphics.h" #include "initrd.h" #include "linux.h" @@ -1855,23 +1856,24 @@ static void generate_boot_entry_titles(Config *config) { } static bool is_sd_boot(EFI_FILE *root_dir, const char16_t *loader_path) { - EFI_STATUS err; static const char * const sections[] = { ".sdmagic", NULL }; - size_t offset = 0, size = 0, read; _cleanup_free_ char *content = NULL; + PeSectionVector vector = {}; + EFI_STATUS err; + size_t read; assert(root_dir); assert(loader_path); - err = pe_file_locate_sections(root_dir, loader_path, sections, &offset, &size); - if (err != EFI_SUCCESS || size != sizeof(SD_MAGIC)) + err = pe_file_locate_sections(root_dir, loader_path, sections, &vector); + if (err != EFI_SUCCESS || vector.size != sizeof(SD_MAGIC)) return false; - err = file_read(root_dir, loader_path, offset, size, &content, &read); - if (err != EFI_SUCCESS || size != read) + err = file_read(root_dir, loader_path, vector.file_offset, vector.size, &content, &read); + if (err != EFI_SUCCESS || vector.size != read) return false; return memcmp(content, SD_MAGIC, sizeof(SD_MAGIC)) == 0; @@ -2104,7 +2106,7 @@ static void config_load_type2_entries( _SECTION_MAX, }; - static const char * const sections[_SECTION_MAX + 1] = { + static const char * const section_names[_SECTION_MAX + 1] = { [SECTION_CMDLINE] = ".cmdline", [SECTION_OSREL] = ".osrel", NULL, @@ -2114,8 +2116,9 @@ static void config_load_type2_entries( *os_image_version = NULL, *os_version = NULL, *os_version_id = NULL, *os_build_id = NULL; const char16_t *good_name, *good_version, *good_sort_key; _cleanup_free_ char *content = NULL; - size_t offs[_SECTION_MAX] = {}, szs[_SECTION_MAX] = {}, pos = 0; + PeSectionVector sections[_SECTION_MAX] = {}; char *line, *key, *value; + size_t pos = 0; err = readdir(linux_dir, &f, &f_size); if (err != EFI_SUCCESS || !f) @@ -2131,11 +2134,16 @@ static void config_load_type2_entries( continue; /* look for .osrel and .cmdline sections in the .efi binary */ - err = pe_file_locate_sections(linux_dir, f->FileName, sections, offs, szs); - if (err != EFI_SUCCESS || szs[SECTION_OSREL] == 0) + err = pe_file_locate_sections(linux_dir, f->FileName, section_names, sections); + if (err != EFI_SUCCESS || !PE_SECTION_VECTOR_IS_SET(sections + SECTION_OSREL)) continue; - err = file_read(linux_dir, f->FileName, offs[SECTION_OSREL], szs[SECTION_OSREL], &content, NULL); + err = file_read(linux_dir, + f->FileName, + sections[SECTION_OSREL].file_offset, + sections[SECTION_OSREL].size, + &content, + NULL); if (err != EFI_SUCCESS) continue; @@ -2206,14 +2214,19 @@ static void config_load_type2_entries( config_add_entry(config, entry); boot_entry_parse_tries(entry, u"\\EFI\\Linux", f->FileName, u".efi"); - if (szs[SECTION_CMDLINE] == 0) + if (!PE_SECTION_VECTOR_IS_SET(sections + SECTION_CMDLINE)) continue; content = mfree(content); /* read the embedded cmdline file */ size_t cmdline_len; - err = file_read(linux_dir, f->FileName, offs[SECTION_CMDLINE], szs[SECTION_CMDLINE], &content, &cmdline_len); + err = file_read(linux_dir, + f->FileName, + sections[SECTION_CMDLINE].file_offset, + sections[SECTION_CMDLINE].size, + &content, + &cmdline_len); if (err == EFI_SUCCESS) { entry->options = xstrn8_to_16(content, cmdline_len); mangle_stub_cmdline(entry->options); @@ -2526,9 +2539,8 @@ static EFI_STATUS secure_boot_discover_keys(Config *config, EFI_FILE *root_dir) return EFI_SUCCESS; } -static void export_variables( +static void export_loader_variables( EFI_LOADED_IMAGE_PROTOCOL *loaded_image, - const char16_t *loaded_image_path, uint64_t init_usec) { static const uint64_t loader_features = @@ -2548,28 +2560,11 @@ static void export_variables( EFI_LOADER_FEATURE_MENU_DISABLE | 0; - _cleanup_free_ char16_t *infostr = NULL, *typestr = NULL; - assert(loaded_image); - efivar_set_time_usec(MAKE_GUID_PTR(LOADER), u"LoaderTimeInitUSec", init_usec); - efivar_set(MAKE_GUID_PTR(LOADER), u"LoaderInfo", u"systemd-boot " GIT_VERSION, 0); - - infostr = xasprintf("%ls %u.%02u", ST->FirmwareVendor, ST->FirmwareRevision >> 16, ST->FirmwareRevision & 0xffff); - efivar_set(MAKE_GUID_PTR(LOADER), u"LoaderFirmwareInfo", infostr, 0); - - typestr = xasprintf("UEFI %u.%02u", ST->Hdr.Revision >> 16, ST->Hdr.Revision & 0xffff); - efivar_set(MAKE_GUID_PTR(LOADER), u"LoaderFirmwareType", typestr, 0); - + (void) efivar_set_time_usec(MAKE_GUID_PTR(LOADER), u"LoaderTimeInitUSec", init_usec); + (void) efivar_set(MAKE_GUID_PTR(LOADER), u"LoaderInfo", u"systemd-boot " GIT_VERSION, 0); (void) efivar_set_uint64_le(MAKE_GUID_PTR(LOADER), u"LoaderFeatures", loader_features, 0); - - /* the filesystem path to this image, to prevent adding ourselves to the menu */ - efivar_set(MAKE_GUID_PTR(LOADER), u"LoaderImageIdentifier", loaded_image_path, 0); - - /* export the device path this image is started from */ - _cleanup_free_ char16_t *uuid = disk_get_part_uuid(loaded_image->DeviceHandle); - if (uuid) - efivar_set(MAKE_GUID_PTR(LOADER), u"LoaderDevicePartUUID", uuid, 0); } static void config_load_all_entries( @@ -2669,7 +2664,6 @@ static EFI_STATUS run(EFI_HANDLE image) { EFI_LOADED_IMAGE_PROTOCOL *loaded_image; _cleanup_(file_closep) EFI_FILE *root_dir = NULL; _cleanup_(config_free) Config config = {}; - _cleanup_free_ char16_t *loaded_image_path = NULL; EFI_STATUS err; uint64_t init_usec; bool menu = false; @@ -2684,9 +2678,8 @@ static EFI_STATUS run(EFI_HANDLE image) { if (err != EFI_SUCCESS) return log_error_status(err, "Error getting a LoadedImageProtocol handle: %m"); - (void) device_path_to_str(loaded_image->FilePath, &loaded_image_path); - - export_variables(loaded_image, loaded_image_path, init_usec); + export_common_variables(loaded_image); + export_loader_variables(loaded_image, init_usec); err = discover_root_dir(loaded_image, &root_dir); if (err != EFI_SUCCESS) @@ -2694,6 +2687,8 @@ static EFI_STATUS run(EFI_HANDLE image) { (void) load_drivers(image, loaded_image, root_dir); + _cleanup_free_ char16_t *loaded_image_path = NULL; + (void) device_path_to_str(loaded_image->FilePath, &loaded_image_path); config_load_all_entries(&config, loaded_image, loaded_image_path, root_dir); if (config.n_entries == 0) diff --git a/src/boot/efi/cpio.c b/src/boot/efi/cpio.c index a3c98c7b92..ba439740f7 100644 --- a/src/boot/efi/cpio.c +++ b/src/boot/efi/cpio.c @@ -311,8 +311,7 @@ EFI_STATUS pack_cpio( uint32_t access_mode, uint32_t tpm_pcr, const char16_t *tpm_description, - void **ret_buffer, - size_t *ret_buffer_size, + struct iovec *ret_buffer, bool *ret_measured) { _cleanup_(file_closep) EFI_FILE *root = NULL, *extra_dir = NULL; @@ -327,7 +326,6 @@ EFI_STATUS pack_cpio( assert(loaded_image); assert(target_dir_prefix); assert(ret_buffer); - assert(ret_buffer_size); if (!loaded_image->DeviceHandle) goto nothing; @@ -439,14 +437,11 @@ EFI_STATUS pack_cpio( tpm_pcr, tpm_description); - *ret_buffer = TAKE_PTR(buffer); - *ret_buffer_size = buffer_size; - + *ret_buffer = IOVEC_MAKE(TAKE_PTR(buffer), buffer_size); return EFI_SUCCESS; nothing: - *ret_buffer = NULL; - *ret_buffer_size = 0; + *ret_buffer = (struct iovec) {}; if (ret_measured) *ret_measured = false; @@ -463,8 +458,7 @@ EFI_STATUS pack_cpio_literal( uint32_t access_mode, uint32_t tpm_pcr, const char16_t *tpm_description, - void **ret_buffer, - size_t *ret_buffer_size, + struct iovec *ret_buffer, bool *ret_measured) { uint32_t inode = 1; /* inode counter, so that each item gets a new inode */ @@ -476,7 +470,6 @@ EFI_STATUS pack_cpio_literal( assert(target_dir_prefix); assert(target_filename); assert(ret_buffer); - assert(ret_buffer_size); /* Generate the leading directory inodes right before adding the first files, to the * archive. Otherwise the cpio archive cannot be unpacked, since the leading dirs won't exist. */ @@ -508,8 +501,6 @@ EFI_STATUS pack_cpio_literal( tpm_pcr, tpm_description); - *ret_buffer = TAKE_PTR(buffer); - *ret_buffer_size = buffer_size; - + *ret_buffer = IOVEC_MAKE(TAKE_PTR(buffer), buffer_size); return EFI_SUCCESS; } diff --git a/src/boot/efi/cpio.h b/src/boot/efi/cpio.h index 9d14fa15a2..660372c0b7 100644 --- a/src/boot/efi/cpio.h +++ b/src/boot/efi/cpio.h @@ -2,6 +2,7 @@ #pragma once #include "efi.h" +#include "iovec-util-fundamental.h" #include "proto/loaded-image.h" EFI_STATUS pack_cpio( @@ -14,8 +15,7 @@ EFI_STATUS pack_cpio( uint32_t access_mode, uint32_t tpm_pcr, const char16_t *tpm_description, - void **ret_buffer, - size_t *ret_buffer_size, + struct iovec *ret_buffer, bool *ret_measured); EFI_STATUS pack_cpio_literal( @@ -27,6 +27,5 @@ EFI_STATUS pack_cpio_literal( uint32_t access_mode, uint32_t tpm_pcr, const char16_t *tpm_description, - void **ret_buffer, - size_t *ret_buffer_size, + struct iovec *ret_buffer, bool *ret_measured); diff --git a/src/boot/efi/export-vars.c b/src/boot/efi/export-vars.c new file mode 100644 index 0000000000..3926747bba --- /dev/null +++ b/src/boot/efi/export-vars.c @@ -0,0 +1,44 @@ +/* SPDX-License-Identifier: LGPL-2.1-or-later */ + +#include "device-path-util.h" +#include "export-vars.h" +#include "part-discovery.h" +#include "util.h" + +void export_common_variables(EFI_LOADED_IMAGE_PROTOCOL *loaded_image) { + 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); + } +} diff --git a/src/boot/efi/export-vars.h b/src/boot/efi/export-vars.h new file mode 100644 index 0000000000..a925d09827 --- /dev/null +++ b/src/boot/efi/export-vars.h @@ -0,0 +1,5 @@ +/* SPDX-License-Identifier: LGPL-2.1-or-later */ + +#include "proto/loaded-image.h" + +void export_common_variables(EFI_LOADED_IMAGE_PROTOCOL *loaded_image); diff --git a/src/boot/efi/linux.c b/src/boot/efi/linux.c index 65bc176df7..706648b49d 100644 --- a/src/boot/efi/linux.c +++ b/src/boot/efi/linux.c @@ -93,19 +93,17 @@ static EFI_STATUS load_image(EFI_HANDLE parent, const void *source, size_t len, EFI_STATUS linux_exec( EFI_HANDLE parent, const char16_t *cmdline, - const void *linux_buffer, - size_t linux_length, - const void *initrd_buffer, - size_t initrd_length) { + const struct iovec *kernel, + const struct iovec *initrd) { uint32_t compat_address; EFI_STATUS err; assert(parent); - assert(linux_buffer && linux_length > 0); - assert(initrd_buffer || initrd_length == 0); + assert(iovec_is_set(kernel)); + assert(iovec_is_valid(initrd)); - err = pe_kernel_info(linux_buffer, &compat_address); + err = pe_kernel_info(kernel->iov_base, &compat_address); #if defined(__i386__) || defined(__x86_64__) if (err == EFI_UNSUPPORTED) /* Kernel is too old to support LINUX_INITRD_MEDIA_GUID, try the deprecated EFI handover @@ -113,16 +111,14 @@ EFI_STATUS linux_exec( return linux_exec_efi_handover( parent, cmdline, - linux_buffer, - linux_length, - initrd_buffer, - initrd_length); + kernel, + initrd); #endif if (err != EFI_SUCCESS) return log_error_status(err, "Bad kernel image: %m"); _cleanup_(unload_imagep) EFI_HANDLE kernel_image = NULL; - err = load_image(parent, linux_buffer, linux_length, &kernel_image); + err = load_image(parent, kernel->iov_base, kernel->iov_len, &kernel_image); if (err != EFI_SUCCESS) return log_error_status(err, "Error loading kernel image: %m"); @@ -138,7 +134,7 @@ EFI_STATUS linux_exec( } _cleanup_(cleanup_initrd) EFI_HANDLE initrd_handle = NULL; - err = initrd_register(initrd_buffer, initrd_length, &initrd_handle); + err = initrd_register(initrd->iov_base, initrd->iov_len, &initrd_handle); if (err != EFI_SUCCESS) return log_error_status(err, "Error registering initrd: %m"); diff --git a/src/boot/efi/linux.h b/src/boot/efi/linux.h index 46b5f4f4d7..1bee8907c4 100644 --- a/src/boot/efi/linux.h +++ b/src/boot/efi/linux.h @@ -2,18 +2,15 @@ #pragma once #include "efi.h" +#include "iovec-util-fundamental.h" EFI_STATUS linux_exec( EFI_HANDLE parent, const char16_t *cmdline, - const void *linux_buffer, - size_t linux_length, - const void *initrd_buffer, - size_t initrd_length); + const struct iovec *kernel, + const struct iovec *initrd); EFI_STATUS linux_exec_efi_handover( EFI_HANDLE parent, const char16_t *cmdline, - const void *linux_buffer, - size_t linux_length, - const void *initrd_buffer, - size_t initrd_length); + const struct iovec *kernel, + const struct iovec *initrd); diff --git a/src/boot/efi/linux_x86.c b/src/boot/efi/linux_x86.c index 757902daac..c456209831 100644 --- a/src/boot/efi/linux_x86.c +++ b/src/boot/efi/linux_x86.c @@ -123,19 +123,17 @@ static void linux_efi_handover(EFI_HANDLE parent, uintptr_t kernel, BootParams * EFI_STATUS linux_exec_efi_handover( EFI_HANDLE parent, const char16_t *cmdline, - const void *linux_buffer, - size_t linux_length, - const void *initrd_buffer, - size_t initrd_length) { + const struct iovec *kernel, + const struct iovec *initrd) { assert(parent); - assert(linux_buffer); - assert(initrd_buffer || initrd_length == 0); + assert(iovec_is_set(kernel)); + assert(iovec_is_valid(initrd)); - if (linux_length < sizeof(BootParams)) + if (kernel->iov_len < sizeof(BootParams)) return EFI_LOAD_ERROR; - const BootParams *image_params = (const BootParams *) linux_buffer; + const BootParams *image_params = (const BootParams *) kernel->iov_base; if (image_params->hdr.header != SETUP_MAGIC || image_params->hdr.boot_flag != BOOT_FLAG_MAGIC) return log_error_status(EFI_UNSUPPORTED, "Unsupported kernel image."); if (image_params->hdr.version < SETUP_VERSION_2_11) @@ -155,22 +153,26 @@ EFI_STATUS linux_exec_efi_handover( /* There is no way to pass the high bits of code32_start. Newer kernels seems to handle this * just fine, but older kernels will fail even if they otherwise have above 4G boot support. */ _cleanup_pages_ Pages linux_relocated = {}; - if (POINTER_TO_PHYSICAL_ADDRESS(linux_buffer) + linux_length > UINT32_MAX) { + const void *linux_buffer; + if (POINTER_TO_PHYSICAL_ADDRESS(kernel->iov_base) + kernel->iov_len > UINT32_MAX) { linux_relocated = xmalloc_pages( - AllocateMaxAddress, EfiLoaderCode, EFI_SIZE_TO_PAGES(linux_length), UINT32_MAX); + AllocateMaxAddress, EfiLoaderCode, EFI_SIZE_TO_PAGES(kernel->iov_len), UINT32_MAX); linux_buffer = memcpy( - PHYSICAL_ADDRESS_TO_POINTER(linux_relocated.addr), linux_buffer, linux_length); - } + PHYSICAL_ADDRESS_TO_POINTER(linux_relocated.addr), kernel->iov_base, kernel->iov_len); + } else + linux_buffer = kernel->iov_base; _cleanup_pages_ Pages initrd_relocated = {}; - if (!can_4g && POINTER_TO_PHYSICAL_ADDRESS(initrd_buffer) + initrd_length > UINT32_MAX) { + const void *initrd_buffer; + if (!can_4g && POINTER_TO_PHYSICAL_ADDRESS(initrd->iov_base) + initrd->iov_len > UINT32_MAX) { initrd_relocated = xmalloc_pages( - AllocateMaxAddress, EfiLoaderData, EFI_SIZE_TO_PAGES(initrd_length), UINT32_MAX); + AllocateMaxAddress, EfiLoaderData, EFI_SIZE_TO_PAGES(initrd->iov_len), UINT32_MAX); initrd_buffer = memcpy( PHYSICAL_ADDRESS_TO_POINTER(initrd_relocated.addr), - initrd_buffer, - initrd_length); - } + initrd->iov_base, + initrd->iov_len); + } else + initrd_buffer = initrd->iov_base; _cleanup_pages_ Pages boot_params_page = xmalloc_pages( can_4g ? AllocateAnyPages : AllocateMaxAddress, @@ -215,8 +217,8 @@ EFI_STATUS linux_exec_efi_handover( boot_params->hdr.ramdisk_image = (uintptr_t) initrd_buffer; boot_params->ext_ramdisk_image = POINTER_TO_PHYSICAL_ADDRESS(initrd_buffer) >> 32; - boot_params->hdr.ramdisk_size = initrd_length; - boot_params->ext_ramdisk_size = ((uint64_t) initrd_length) >> 32; + boot_params->hdr.ramdisk_size = initrd->iov_len; + boot_params->ext_ramdisk_size = ((uint64_t) initrd->iov_len) >> 32; log_wait(); linux_efi_handover(parent, (uintptr_t) linux_buffer, boot_params); diff --git a/src/boot/efi/measure.c b/src/boot/efi/measure.c index 24cb60e7a3..15f1ba9aff 100644 --- a/src/boot/efi/measure.c +++ b/src/boot/efi/measure.c @@ -195,12 +195,17 @@ static EFI_STATUS tcg2_log_ipl_event(uint32_t pcrindex, EFI_PHYSICAL_ADDRESS buf assert(ret_measured); tpm2 = tcg2_interface_check(); - if (tpm2) - err = tpm2_measure_to_pcr_and_ipl_event_log(tpm2, pcrindex, buffer, buffer_size, description); + if (!tpm2) { + *ret_measured = false; + return EFI_SUCCESS; + } - *ret_measured = tpm2 && (err == EFI_SUCCESS); + err = tpm2_measure_to_pcr_and_ipl_event_log(tpm2, pcrindex, buffer, buffer_size, description); + if (err != EFI_SUCCESS) + return err; - return err; + *ret_measured = true; + return EFI_SUCCESS; } static EFI_STATUS cc_log_event(uint32_t pcrindex, EFI_PHYSICAL_ADDRESS buffer, size_t buffer_size, const char16_t *description, bool *ret_measured) { @@ -210,12 +215,17 @@ static EFI_STATUS cc_log_event(uint32_t pcrindex, EFI_PHYSICAL_ADDRESS buffer, s assert(ret_measured); cc = cc_interface_check(); - if (cc) - err = cc_measure_to_mr_and_ipl_event_log(cc, pcrindex, buffer, buffer_size, description); + if (!cc) { + *ret_measured = false; + return EFI_SUCCESS; + } - *ret_measured = cc && (err == EFI_SUCCESS); + err = cc_measure_to_mr_and_ipl_event_log(cc, pcrindex, buffer, buffer_size, description); + if (err != EFI_SUCCESS) + return err; - return err; + *ret_measured = true; + return EFI_SUCCESS; } EFI_STATUS tpm_log_ipl_event(uint32_t pcrindex, EFI_PHYSICAL_ADDRESS buffer, size_t buffer_size, const char16_t *description, bool *ret_measured) { @@ -240,10 +250,13 @@ EFI_STATUS tpm_log_ipl_event(uint32_t pcrindex, EFI_PHYSICAL_ADDRESS buffer, siz return err; err = tcg2_log_ipl_event(pcrindex, buffer, buffer_size, description, &tpm_ret_measured); - if (err == EFI_SUCCESS && ret_measured) + if (err != EFI_SUCCESS) + return err; + + if (ret_measured) *ret_measured = tpm_ret_measured || cc_ret_measured; - return err; + return EFI_SUCCESS; } EFI_STATUS tpm_log_tagged_event( @@ -272,10 +285,11 @@ EFI_STATUS tpm_log_tagged_event( } err = tpm2_measure_to_pcr_and_tagged_event_log(tpm2, pcrindex, buffer, buffer_size, event_id, description); - if (err == EFI_SUCCESS && ret_measured) - *ret_measured = true; + if (!err) + return err; - return err; + *ret_measured = true; + return EFI_SUCCESS; } EFI_STATUS tpm_log_ipl_event_ascii(uint32_t pcrindex, EFI_PHYSICAL_ADDRESS buffer, size_t buffer_size, const char *description, bool *ret_measured) { diff --git a/src/boot/efi/meson.build b/src/boot/efi/meson.build index 7a60b0ec7e..42a7914bf6 100644 --- a/src/boot/efi/meson.build +++ b/src/boot/efi/meson.build @@ -258,6 +258,7 @@ libefi_sources = files( 'devicetree.c', 'drivers.c', 'efi-string.c', + 'export-vars.c', 'graphics.c', 'initrd.c', 'log.c', diff --git a/src/boot/efi/pe.c b/src/boot/efi/pe.c index 829266b7f5..587e3ef7b1 100644 --- a/src/boot/efi/pe.c +++ b/src/boot/efi/pe.c @@ -101,7 +101,7 @@ typedef struct PeOptionalHeader { } _packed_ PeOptionalHeader; typedef struct PeFileHeader { - uint8_t Magic[4]; + uint8_t Magic[4]; CoffFileHeader FileHeader; PeOptionalHeader OptionalHeader; } _packed_ PeFileHeader; @@ -119,69 +119,134 @@ typedef struct PeSectionHeader { uint32_t Characteristics; } _packed_ PeSectionHeader; +#define SECTION_TABLE_BYTES_MAX (16U * 1024U * 1024U) + static bool verify_dos(const DosFileHeader *dos) { assert(dos); - return memcmp(dos->Magic, DOS_FILE_MAGIC, STRLEN(DOS_FILE_MAGIC)) == 0; + + DISABLE_WARNING_TYPE_LIMITS; + return memcmp(dos->Magic, DOS_FILE_MAGIC, STRLEN(DOS_FILE_MAGIC)) == 0 && + dos->ExeHeader >= sizeof(DosFileHeader) && + (size_t) dos->ExeHeader <= SIZE_MAX - sizeof(PeFileHeader); + REENABLE_WARNING; } -static bool verify_pe(const PeFileHeader *pe, bool allow_compatibility) { +static bool verify_pe( + const DosFileHeader *dos, + const PeFileHeader *pe, + bool allow_compatibility) { + + assert(dos); assert(pe); + return memcmp(pe->Magic, PE_FILE_MAGIC, STRLEN(PE_FILE_MAGIC)) == 0 && - (pe->FileHeader.Machine == TARGET_MACHINE_TYPE || - (allow_compatibility && pe->FileHeader.Machine == TARGET_MACHINE_TYPE_COMPATIBILITY)) && - pe->FileHeader.NumberOfSections > 0 && - pe->FileHeader.NumberOfSections <= MAX_SECTIONS && - IN_SET(pe->OptionalHeader.Magic, OPTHDR32_MAGIC, OPTHDR64_MAGIC); + (pe->FileHeader.Machine == TARGET_MACHINE_TYPE || + (allow_compatibility && pe->FileHeader.Machine == TARGET_MACHINE_TYPE_COMPATIBILITY)) && + pe->FileHeader.NumberOfSections > 0 && + pe->FileHeader.NumberOfSections <= MAX_SECTIONS && + IN_SET(pe->OptionalHeader.Magic, OPTHDR32_MAGIC, OPTHDR64_MAGIC) && + pe->FileHeader.SizeOfOptionalHeader < SIZE_MAX - (dos->ExeHeader + offsetof(PeFileHeader, OptionalHeader)); } static size_t section_table_offset(const DosFileHeader *dos, const PeFileHeader *pe) { assert(dos); assert(pe); + return dos->ExeHeader + offsetof(PeFileHeader, OptionalHeader) + pe->FileHeader.SizeOfOptionalHeader; } -static void locate_sections( +static bool pe_section_name_equal(const char *a, const char *b) { + + if (a == b) + return true; + if (!a != !b) + return false; + + /* Compares up to 8 characters of a and b i.e. the name size limit in the PE section header */ + + for (size_t i = 0; i < sizeof_field(PeSectionHeader, Name); i++) { + if (a[i] != b[i]) + return false; + + if (a[i] == 0) /* Name is shorter than 8 */ + return true; + } + + return true; +} + +static void pe_locate_sections( const PeSectionHeader section_table[], - size_t n_table, + size_t n_section_table, const char * const sections[], - size_t *offsets, - size_t *sizes, - bool in_memory) { + size_t validate_base, + PeSectionVector *ret_sections) { - assert(section_table); + assert(section_table || n_section_table == 0); assert(sections); - assert(offsets); - assert(sizes); + assert(ret_sections); - for (size_t i = 0; i < n_table; i++) { - const PeSectionHeader *sect = section_table + i; + /* Searches for the sections listed in 'sections[]' within the section table. Validates the resulted + * data. If 'validate_base' is non-zero also takes base offset when loaded into memory into account for + * qchecking for overflows. */ - for (size_t j = 0; sections[j]; j++) { - if (memcmp(sect->Name, sections[j], strlen8(sections[j])) != 0) + for (size_t i = 0; sections[i]; i++) + FOREACH_ARRAY(j, section_table, n_section_table) { + + if (!pe_section_name_equal((const char*) j->Name, sections[i])) continue; - offsets[j] = in_memory ? sect->VirtualAddress : sect->PointerToRawData; - sizes[j] = sect->VirtualSize; + /* Overflow check: ignore sections that are impossibly large, relative to the file + * address for the section. */ + size_t size_max = SIZE_MAX - j->PointerToRawData; + if ((size_t) j->VirtualSize > size_max) + continue; + + /* Overflow check: ignore sections that are impossibly large, given the virtual + * address for the section */ + size_max = SIZE_MAX - j->VirtualAddress; + if (j->VirtualSize > size_max) + continue; + + /* 2nd overflow check: ignore sections that are impossibly large also taking the + * loaded base into account. */ + if (validate_base != 0) { + if (validate_base > size_max) + continue; + size_max -= validate_base; + + if (j->VirtualAddress > size_max) + continue; + } + + /* At this time, the sizes and offsets have been validated. Store them away */ + ret_sections[i] = (PeSectionVector) { + .size = j->VirtualSize, + .file_offset = j->PointerToRawData, + .memory_offset = j->VirtualAddress, + }; + + /* First matching section wins, ignore the rest */ + break; } - } } static uint32_t get_compatibility_entry_address(const DosFileHeader *dos, const PeFileHeader *pe) { - size_t addr = 0, size = 0; static const char *sections[] = { ".compat", NULL }; + PeSectionVector vector = {}; /* The kernel may provide alternative PE entry points for different PE architectures. This allows * booting a 64-bit kernel on 32-bit EFI that is otherwise running on a 64-bit CPU. The locations of any * such compat entry points are located in a special PE section. */ - locate_sections((const PeSectionHeader *) ((const uint8_t *) dos + section_table_offset(dos, pe)), + pe_locate_sections( + (const PeSectionHeader *) ((const uint8_t *) dos + section_table_offset(dos, pe)), pe->FileHeader.NumberOfSections, sections, - &addr, - &size, - /*in_memory=*/true); + PTR_TO_SIZE(dos), + &vector); - if (size == 0) + if (vector.size == 0) /* not found */ return 0; typedef struct { @@ -191,6 +256,8 @@ static uint32_t get_compatibility_entry_address(const DosFileHeader *dos, const uint32_t entry_point; } _packed_ LinuxPeCompat1; + size_t addr = vector.memory_offset, size = vector.size; + while (size >= sizeof(LinuxPeCompat1) && addr % alignof(LinuxPeCompat1) == 0) { LinuxPeCompat1 *compat = (LinuxPeCompat1 *) ((uint8_t *) dos + addr); @@ -218,7 +285,7 @@ EFI_STATUS pe_kernel_info(const void *base, uint32_t *ret_compat_address) { return EFI_LOAD_ERROR; const PeFileHeader *pe = (const PeFileHeader *) ((const uint8_t *) base + dos->ExeHeader); - if (!verify_pe(pe, /* allow_compatibility= */ true)) + if (!verify_pe(dos, pe, /* allow_compatibility= */ true)) return EFI_LOAD_ERROR; /* Support for LINUX_INITRD_MEDIA_GUID was added in kernel stub 1.0. */ @@ -239,31 +306,34 @@ EFI_STATUS pe_kernel_info(const void *base, uint32_t *ret_compat_address) { return EFI_SUCCESS; } -EFI_STATUS pe_memory_locate_sections(const void *base, const char * const sections[], size_t *addrs, size_t *sizes) { +EFI_STATUS pe_memory_locate_sections( + const void *base, + const char* const sections[], + PeSectionVector *ret_sections) { + const DosFileHeader *dos; const PeFileHeader *pe; size_t offset; assert(base); assert(sections); - assert(addrs); - assert(sizes); + assert(ret_sections); dos = (const DosFileHeader *) base; if (!verify_dos(dos)) return EFI_LOAD_ERROR; - pe = (const PeFileHeader *) ((uint8_t *) base + dos->ExeHeader); - if (!verify_pe(pe, /* allow_compatibility= */ false)) + pe = (const PeFileHeader *) ((const uint8_t *) base + dos->ExeHeader); + if (!verify_pe(dos, pe, /* allow_compatibility= */ false)) return EFI_LOAD_ERROR; offset = section_table_offset(dos, pe); - locate_sections((PeSectionHeader *) ((uint8_t *) base + offset), + pe_locate_sections( + (const PeSectionHeader *) ((const uint8_t *) base + offset), pe->FileHeader.NumberOfSections, sections, - addrs, - sizes, - /*in_memory=*/true); + PTR_TO_SIZE(base), + ret_sections); return EFI_SUCCESS; } @@ -272,8 +342,7 @@ EFI_STATUS pe_file_locate_sections( EFI_FILE *dir, const char16_t *path, const char * const sections[], - size_t *offsets, - size_t *sizes) { + PeSectionVector *ret_sections) { _cleanup_free_ PeSectionHeader *section_table = NULL; _cleanup_(file_closep) EFI_FILE *handle = NULL; DosFileHeader dos; @@ -284,8 +353,7 @@ EFI_STATUS pe_file_locate_sections( assert(dir); assert(path); assert(sections); - assert(offsets); - assert(sizes); + assert(ret_sections); err = dir->Open(dir, &handle, (char16_t *) path, EFI_FILE_MODE_READ, 0ULL); if (err != EFI_SUCCESS) @@ -306,10 +374,16 @@ EFI_STATUS pe_file_locate_sections( err = handle->Read(handle, &len, &pe); if (err != EFI_SUCCESS) return err; - if (len != sizeof(pe) || !verify_pe(&pe, /* allow_compatibility= */ false)) + if (len != sizeof(pe) || !verify_pe(&dos, &pe, /* allow_compatibility= */ false)) return EFI_LOAD_ERROR; - section_table_len = pe.FileHeader.NumberOfSections * sizeof(PeSectionHeader); + DISABLE_WARNING_TYPE_LIMITS; + if ((size_t) pe.FileHeader.NumberOfSections > SIZE_MAX / sizeof(PeSectionHeader)) + return EFI_OUT_OF_RESOURCES; + REENABLE_WARNING; + section_table_len = (size_t) pe.FileHeader.NumberOfSections * sizeof(PeSectionHeader); + if (section_table_len > SECTION_TABLE_BYTES_MAX) + return EFI_OUT_OF_RESOURCES; section_table = xmalloc(section_table_len); if (!section_table) return EFI_OUT_OF_RESOURCES; @@ -325,8 +399,12 @@ EFI_STATUS pe_file_locate_sections( if (len != section_table_len) return EFI_LOAD_ERROR; - locate_sections(section_table, pe.FileHeader.NumberOfSections, - sections, offsets, sizes, /*in_memory=*/false); + pe_locate_sections( + section_table, + pe.FileHeader.NumberOfSections, + sections, + /* validate_base= */ 0, /* don't validate base */ + ret_sections); return EFI_SUCCESS; } diff --git a/src/boot/efi/pe.h b/src/boot/efi/pe.h index 7e2258fceb..bc6d74beeb 100644 --- a/src/boot/efi/pe.h +++ b/src/boot/efi/pe.h @@ -3,17 +3,27 @@ #include "efi.h" +/* This is a subset of the full PE section header structure, with validated values, and without + * the noise. */ +typedef struct PeSectionVector { + size_t size; + size_t memory_offset; /* Offset in memory, relative to base address */ + uint64_t file_offset; /* Offset on disk, relative to beginning of file */ +} PeSectionVector; + +static inline bool PE_SECTION_VECTOR_IS_SET(const PeSectionVector *v) { + return v && v->size != 0; +} + EFI_STATUS pe_memory_locate_sections( const void *base, const char * const sections[], - size_t *addrs, - size_t *sizes); + PeSectionVector *ret_sections); EFI_STATUS pe_file_locate_sections( EFI_FILE *dir, const char16_t *path, const char * const sections[], - size_t *offsets, - size_t *sizes); + PeSectionVector *ret_sections); EFI_STATUS pe_kernel_info(const void *base, uint32_t *ret_compat_address); diff --git a/src/boot/efi/stub.c b/src/boot/efi/stub.c index f5d2b1d4c5..db4f473410 100644 --- a/src/boot/efi/stub.c +++ b/src/boot/efi/stub.c @@ -3,7 +3,9 @@ #include "cpio.h" #include "device-path-util.h" #include "devicetree.h" +#include "export-vars.h" #include "graphics.h" +#include "iovec-util-fundamental.h" #include "linux.h" #include "measure.h" #include "memory-util-fundamental.h" @@ -21,11 +23,54 @@ #include "version.h" #include "vmm.h" +/* The list of initrds we combine into one, in the order we want to merge them */ +enum { + /* The first two are part of the PE binary */ + INITRD_UCODE, + INITRD_BASE, + + /* The rest are dynamically generated, and hence in dynamic memory */ + _INITRD_DYNAMIC_FIRST, + INITRD_CREDENTIAL = _INITRD_DYNAMIC_FIRST, + INITRD_GLOBAL_CREDENTIAL, + INITRD_SYSEXT, + INITRD_CONFEXT, + INITRD_PCRSIG, + INITRD_PCRPKEY, + _INITRD_MAX, +}; + /* magic string to find in the binary image */ DECLARE_NOALLOC_SECTION(".sdmagic", "#### LoaderInfo: systemd-stub " GIT_VERSION " ####"); DECLARE_SBAT(SBAT_STUB_SECTION_TEXT); +static char16_t* pe_section_to_str16( + EFI_LOADED_IMAGE_PROTOCOL *loaded_image, + const PeSectionVector *section) { + + assert(loaded_image); + assert(section); + + if (!PE_SECTION_VECTOR_IS_SET(section)) + return NULL; + + return xstrn8_to_16((const char *) loaded_image->ImageBase + section->memory_offset, section->size); +} + +static char *pe_section_to_str8( + EFI_LOADED_IMAGE_PROTOCOL *loaded_image, + const PeSectionVector *section) { + + assert(loaded_image); + assert(section); + + if (!PE_SECTION_VECTOR_IS_SET(section)) + return NULL; + + return xstrndup8((const char *)loaded_image->ImageBase + section->memory_offset, section->size); +} + static void combine_measured_flag(int *value, int measured) { assert(value); @@ -47,21 +92,19 @@ static void combine_measured_flag(int *value, int measured) { /* Combine initrds by concatenation in memory */ static EFI_STATUS combine_initrds( - const void * const initrds[], const size_t initrd_sizes[], size_t n_initrds, - Pages *ret_initr_pages, size_t *ret_initrd_size) { + struct iovec initrds[], size_t n_initrds, + Pages *ret_initrd_pages, size_t *ret_initrd_size) { size_t n = 0; - assert(ret_initr_pages); + assert(initrds || n_initrds == 0); + assert(ret_initrd_pages); assert(ret_initrd_size); - for (size_t i = 0; i < n_initrds; i++) { - if (!initrds[i]) - continue; + FOREACH_ARRAY(i, initrds, n_initrds) { + /* some initrds (the ones from UKI sections) need padding, pad all to be safe */ - /* some initrds (the ones from UKI sections) need padding, - * pad all to be safe */ - size_t initrd_size = ALIGN4(initrd_sizes[i]); + size_t initrd_size = ALIGN4(i->iov_len); if (n > SIZE_MAX - initrd_size) return EFI_OUT_OF_RESOURCES; @@ -74,31 +117,29 @@ static EFI_STATUS combine_initrds( EFI_SIZE_TO_PAGES(n), UINT32_MAX /* Below 4G boundary. */); uint8_t *p = PHYSICAL_ADDRESS_TO_POINTER(pages.addr); - for (size_t i = 0; i < n_initrds; i++) { - if (!initrds[i]) - continue; - + FOREACH_ARRAY(i, initrds, n_initrds) { size_t pad; - p = mempcpy(p, initrds[i], initrd_sizes[i]); + p = mempcpy(p, i->iov_base, i->iov_len); - pad = ALIGN4(initrd_sizes[i]) - initrd_sizes[i]; - if (pad > 0) { - memzero(p, pad); - p += pad; - } + pad = ALIGN4(i->iov_len) - i->iov_len; + if (pad == 0) + continue; + + memzero(p, pad); + p += pad; } assert(PHYSICAL_ADDRESS_TO_POINTER(pages.addr + n) == p); - *ret_initr_pages = pages; + *ret_initrd_pages = pages; *ret_initrd_size = n; pages.n_pages = 0; return EFI_SUCCESS; } -static void export_variables(EFI_LOADED_IMAGE_PROTOCOL *loaded_image) { +static void export_stub_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 */ @@ -113,41 +154,6 @@ static void export_variables(EFI_LOADED_IMAGE_PROTOCOL *loaded_image) { 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); @@ -268,96 +274,89 @@ static EFI_STATUS load_addons_from_dir( } static void cmdline_append_and_measure_addons( - char16_t *cmdline_global, - char16_t *cmdline_uki, + char16_t *cmdline_addon, char16_t **cmdline_append, - bool *ret_parameters_measured) { - - _cleanup_free_ char16_t *tmp = NULL, *merged = NULL; - bool m = false; + int *parameters_measured) { assert(cmdline_append); - assert(ret_parameters_measured); + assert(parameters_measured); - if (isempty(cmdline_global) && isempty(cmdline_uki)) + if (isempty(cmdline_addon)) 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)) + _cleanup_free_ char16_t *copy = xstrdup16(cmdline_addon); + mangle_stub_cmdline(copy); + if (isempty(copy)) return; - (void) tpm_log_load_options(merged, &m); - *ret_parameters_measured = m; + bool m = false; + (void) tpm_log_load_options(copy, &m); + combine_measured_flag(parameters_measured, m); - tmp = TAKE_PTR(*cmdline_append); - *cmdline_append = xasprintf("%ls%ls%ls", strempty(tmp), isempty(tmp) ? u"" : u" ", merged); + _cleanup_free_ char16_t *tmp = TAKE_PTR(*cmdline_append); + if (isempty(tmp)) + *cmdline_append = TAKE_PTR(copy); + else + *cmdline_append = xasprintf("%ls %ls", tmp, copy); } -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, - int *ret_parameters_measured) { +typedef struct DevicetreeAddon { + char16_t *filename; + struct iovec blob; +} DevicetreeAddon; + +static void devicetree_addon_done(DevicetreeAddon *a) { + assert(a); + + a->filename = mfree(a->filename); + iovec_done(&a->blob); +} + +static void devicetree_addon_free_many(DevicetreeAddon *a, size_t n) { + assert(a || n == 0); + + FOREACH_ARRAY(i, a, n) + devicetree_addon_done(i); + + free(a); +} + +static void install_addon_devicetrees( + struct devicetree_state *dt_state, + DevicetreeAddon *addons, + size_t n_addons, + int *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); + assert(addons || n_addons == 0); + assert(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) + FOREACH_ARRAY(a, addons, n_addons) { + err = devicetree_install_from_memory(dt_state, a->blob.iov_base, a->blob.iov_len); + 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); - - combine_measured_flag(¶meters_measured, m); + continue; } + + bool m = false; + err = tpm_log_tagged_event( + TPM2_PCR_KERNEL_CONFIG, + POINTER_TO_PHYSICAL_ADDRESS(a->blob.iov_base), + a->blob.iov_len, + DEVICETREE_ADDON_EVENT_TAG_ID, + a->filename, + &m); + if (err != EFI_SUCCESS) + return (void) log_error_status( + err, + "Unable to extend PCR %i with DTB addon '%ls': %m", + TPM2_PCR_KERNEL_CONFIG, + a->filename); + + combine_measured_flag(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( @@ -365,34 +364,22 @@ static EFI_STATUS load_addons( 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) { + char16_t **cmdline, /* Both input+output, extended with new addons we find */ + DevicetreeAddon **devicetree_addons, /* Ditto */ + size_t *n_devicetree_addons) { - _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; + size_t n_items = 0, n_allocated = 0; 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 @@ -413,7 +400,7 @@ static EFI_STATUS load_addons( 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] = {}; + PeSectionVector sections[ELEMENTSOF(unified_sections)] = {}; _cleanup_free_ EFI_DEVICE_PATH *addon_path = NULL; _cleanup_(unload_imagep) EFI_HANDLE addon = NULL; EFI_LOADED_IMAGE_PROTOCOL *loaded_addon = NULL; @@ -441,9 +428,10 @@ static EFI_STATUS load_addons( 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); + err = pe_memory_locate_sections(loaded_addon->ImageBase, unified_sections, sections); if (err != EFI_SUCCESS || - (szs[UNIFIED_SECTION_CMDLINE] == 0 && szs[UNIFIED_SECTION_DTB] == 0)) { + (!PE_SECTION_VECTOR_IS_SET(sections + UNIFIED_SECTION_CMDLINE) && + !PE_SECTION_VECTOR_IS_SET(sections + UNIFIED_SECTION_DTB))) { if (err == EFI_SUCCESS) err = EFI_NOT_FOUND; log_error_status(err, @@ -453,141 +441,80 @@ static EFI_STATUS load_addons( } /* 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]); + if (PE_SECTION_VECTOR_IS_SET(sections + UNIFIED_SECTION_LINUX)) { + log_error("%ls is a UKI, not an addon, ignoring.", 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 && + if (uname && PE_SECTION_VECTOR_IS_SET(sections + UNIFIED_SECTION_UNAME) && !strneq8(uname, - (char *)loaded_addon->ImageBase + addrs[UNIFIED_SECTION_UNAME], - szs[UNIFIED_SECTION_UNAME])) { + (const char *)loaded_addon->ImageBase + sections[UNIFIED_SECTION_UNAME].memory_offset, + sections[UNIFIED_SECTION_UNAME].size)) { 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 (cmdline && PE_SECTION_VECTOR_IS_SET(sections + UNIFIED_SECTION_CMDLINE)) { + _cleanup_free_ char16_t *tmp = TAKE_PTR(*cmdline), + *extra16 = pe_section_to_str16(loaded_addon, sections + 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]; + if (devicetree_addons && PE_SECTION_VECTOR_IS_SET(sections + UNIFIED_SECTION_DTB)) { + *devicetree_addons = xrealloc(*devicetree_addons, + *n_devicetree_addons * sizeof(size_t), + (*n_devicetree_addons + 1) * sizeof(size_t)); - 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; + *devicetree_addons[(*n_devicetree_addons)++] = (DevicetreeAddon) { + .blob = { + .iov_base = xmemdup((const uint8_t*) loaded_addon->ImageBase + sections[UNIFIED_SECTION_DTB].memory_offset, sections[UNIFIED_SECTION_DTB].size), + .iov_len = sections[UNIFIED_SECTION_DTB].size, + }, + .filename = xstrdup16(items[i]), + }; } } - 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, *confext_initrd = NULL, *pcrsig_initrd = NULL, *pcrpkey_initrd = NULL; - size_t credential_initrd_size = 0, global_credential_initrd_size = 0, sysext_initrd_size = 0, confext_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, ucode_size, dt_size, n_dts_addons_global = 0, n_dts_addons_uki = 0; - EFI_PHYSICAL_ADDRESS linux_base, initrd_base, ucode_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, sysext_measured = -1, confext_measured = -1; - _cleanup_free_ char *uname = NULL; - bool m; - uint64_t loader_features = 0; +static void refresh_random_seed(EFI_LOADED_IMAGE_PROTOCOL *loaded_image) { EFI_STATUS err; - err = BS->HandleProtocol(image, MAKE_GUID_PTR(EFI_LOADED_IMAGE_PROTOCOL), (void **) &loaded_image); + assert(loaded_image); + + /* Handle case, where bootloader doesn't support DeviceHandle. */ + if (!loaded_image->DeviceHandle) + return; + + uint64_t loader_features = 0; + err = efivar_get_uint64_le(MAKE_GUID_PTR(LOADER), u"LoaderFeatures", &loader_features); if (err != EFI_SUCCESS) - return log_error_status(err, "Error getting a LoadedImageProtocol handle: %m"); + return; - 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; + /* Don't measure again, if sd-boot already initialized the random seed */ + if (!FLAGS_SET(loader_features, EFI_LOADER_FEATURE_RANDOM_SEED)) + return; - 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); - } + _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. */ + return; - 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"); - } + (void) process_random_seed(esp_dir); +} - 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); +static void measure_sections( + EFI_LOADED_IMAGE_PROTOCOL *loaded_image, + const PeSectionVector sections[static _UNIFIED_SECTION_MAX], + int *sections_measured) { - if (szs[UNIFIED_SECTION_UNAME] > 0) - uname = xstrndup8((char *)loaded_image->ImageBase + addrs[UNIFIED_SECTION_UNAME], - szs[UNIFIED_SECTION_UNAME]); - - /* 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"); - } + assert(loaded_image); + assert(sections); + assert(sections_measured); /* 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 @@ -597,79 +524,103 @@ static EFI_STATUS run(EFI_HANDLE image) { if (!unified_section_measure(section)) /* shall not measure? */ continue; - if (szs[section] == 0) /* not found */ + if (!PE_SECTION_VECTOR_IS_SET(sections + section)) /* not found */ continue; /* First measure the name of the section */ - m = false; + bool m = false; (void) tpm_log_ipl_event_ascii( TPM2_PCR_KERNEL_BOOT, POINTER_TO_PHYSICAL_ADDRESS(unified_sections[section]), strsize8(unified_sections[section]), /* including NUL byte */ unified_sections[section], &m); - combine_measured_flag(§ions_measured, m); + combine_measured_flag(sections_measured, m); /* Then measure the data of the section */ m = false; (void) tpm_log_ipl_event_ascii( TPM2_PCR_KERNEL_BOOT, - POINTER_TO_PHYSICAL_ADDRESS(loaded_image->ImageBase) + addrs[section], - szs[section], + POINTER_TO_PHYSICAL_ADDRESS(loaded_image->ImageBase) + sections[section].memory_offset, + sections[section].size, unified_sections[section], &m); + combine_measured_flag(sections_measured, m); + } +} - combine_measured_flag(§ions_measured, m); +static void cmdline_append_and_measure_smbios(char16_t **cmdline, int *parameters_measured) { + assert(cmdline); + assert(parameters_measured); + + /* 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()) + return; + + const char *extra = smbios_find_oem_string("io.systemd.stub.kernel-cmdline-extra"); + if (!extra) + return; + + _cleanup_free_ char16_t *extra16 = xstr8_to_16(extra); + mangle_stub_cmdline(extra16); + if (isempty(extra16)) + return; + + /* 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. */ + bool m = false; + (void) tpm_log_load_options(extra16, &m); + combine_measured_flag(parameters_measured, m); + + _cleanup_free_ char16_t *tmp = TAKE_PTR(*cmdline); + if (isempty(tmp)) + *cmdline = TAKE_PTR(extra16); + else + *cmdline = xasprintf("%ls %ls", tmp, extra16); +} + +static void initrds_free(struct iovec (*initrds)[_INITRD_MAX]) { + assert(initrds); + + /* Free the dynamic initrds, but leave the non-dynamic ones around */ + + for (size_t i = _INITRD_DYNAMIC_FIRST; i < _INITRD_MAX; i++) + iovec_done((*initrds) + i); +} + +static bool initrds_need_combine(struct iovec initrds[static _INITRD_MAX]) { + assert(initrds); + + /* Returns true if we have any initrds set that aren't the base initrd. In that case we need to + * merge, otherwise we can pass the embedded initrd as is */ + + for (size_t i = 0; i < _INITRD_MAX; i++) { + if (i == INITRD_BASE) + continue; + + if (iovec_is_set(initrds + i)) + return true; } - /* 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); + return false; +} - /* Show splash screen as early as possible */ - graphics_splash((const uint8_t*) loaded_image->ImageBase + addrs[UNIFIED_SECTION_SPLASH], szs[UNIFIED_SECTION_SPLASH]); +static void generate_sidecar_initrds( + EFI_LOADED_IMAGE_PROTOCOL *loaded_image, + struct iovec initrds[static _INITRD_MAX], + int *parameters_measured, + int *sysext_measured, + int *confext_measured) { - 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); - combine_measured_flag(¶meters_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); - } + bool m; - /* 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); - combine_measured_flag(¶meters_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); - combine_measured_flag(¶meters_measured, m); - } - } - - export_variables(loaded_image); + assert(loaded_image); + assert(initrds); + assert(parameters_measured); + assert(sysext_measured); + assert(confext_measured); if (pack_cpio(loaded_image, /* dropin_dir= */ NULL, @@ -680,10 +631,9 @@ static EFI_STATUS run(EFI_HANDLE image) { /* access_mode= */ 0400, /* tpm_pcr= */ TPM2_PCR_KERNEL_CONFIG, u"Credentials initrd", - &credential_initrd, - &credential_initrd_size, + initrds + INITRD_CREDENTIAL, &m) == EFI_SUCCESS) - combine_measured_flag(¶meters_measured, m); + combine_measured_flag(parameters_measured, m); if (pack_cpio(loaded_image, u"\\loader\\credentials", @@ -694,10 +644,9 @@ static EFI_STATUS run(EFI_HANDLE image) { /* access_mode= */ 0400, /* tpm_pcr= */ TPM2_PCR_KERNEL_CONFIG, u"Global credentials initrd", - &global_credential_initrd, - &global_credential_initrd_size, + initrds + INITRD_GLOBAL_CREDENTIAL, &m) == EFI_SUCCESS) - combine_measured_flag(¶meters_measured, m); + combine_measured_flag(parameters_measured, m); if (pack_cpio(loaded_image, /* dropin_dir= */ NULL, @@ -708,10 +657,9 @@ static EFI_STATUS run(EFI_HANDLE image) { /* access_mode= */ 0444, /* tpm_pcr= */ TPM2_PCR_SYSEXTS, u"System extension initrd", - &sysext_initrd, - &sysext_initrd_size, + initrds + INITRD_CONFEXT, &m) == EFI_SUCCESS) - combine_measured_flag(&sysext_measured, m); + combine_measured_flag(sysext_measured, m); if (pack_cpio(loaded_image, /* dropin_dir= */ NULL, @@ -722,139 +670,281 @@ static EFI_STATUS run(EFI_HANDLE image) { /* access_mode= */ 0444, /* tpm_pcr= */ TPM2_PCR_KERNEL_CONFIG, u"Configuration extension initrd", - &confext_initrd, - &confext_initrd_size, + initrds + INITRD_SYSEXT, &m) == EFI_SUCCESS) - combine_measured_flag(&confext_measured, m); + combine_measured_flag(confext_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; +static void generate_embedded_initrds( + EFI_LOADED_IMAGE_PROTOCOL *loaded_image, + PeSectionVector sections[_UNIFIED_SECTION_MAX], + struct iovec initrds[static _INITRD_MAX]) { - /* 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"); - } - - int dtb_measured; - dtb_install_addons(&dt_state, - dt_bases_addons_global, - dt_sizes_addons_global, - dt_filenames_addons_global, - n_dts_addons_global, - &dtb_measured); - combine_measured_flag(¶meters_measured, dtb_measured); - - dtb_install_addons(&dt_state, - dt_bases_addons_uki, - dt_sizes_addons_uki, - dt_filenames_addons_uki, - n_dts_addons_uki, - &dtb_measured); - combine_measured_flag(¶meters_measured, dtb_measured); - - if (parameters_measured > 0) - (void) efivar_set_uint_string(MAKE_GUID_PTR(LOADER), u"StubPcrKernelParameters", TPM2_PCR_KERNEL_CONFIG, 0); - if (sysext_measured > 0) - (void) efivar_set_uint_string(MAKE_GUID_PTR(LOADER), u"StubPcrInitRDSysExts", TPM2_PCR_SYSEXTS, 0); - if (confext_measured > 0) - (void) efivar_set_uint_string(MAKE_GUID_PTR(LOADER), u"StubPcrInitRDConfExts", TPM2_PCR_KERNEL_CONFIG, 0); + assert(loaded_image); + assert(initrds); /* 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) + if (PE_SECTION_VECTOR_IS_SET(sections + UNIFIED_SECTION_PCRSIG)) (void) pack_cpio_literal( - (uint8_t*) loaded_image->ImageBase + addrs[UNIFIED_SECTION_PCRSIG], - szs[UNIFIED_SECTION_PCRSIG], + (const uint8_t*) loaded_image->ImageBase + sections[UNIFIED_SECTION_PCRSIG].memory_offset, + sections[UNIFIED_SECTION_PCRSIG].size, ".extra", u"tpm2-pcr-signature.json", /* dir_mode= */ 0555, /* access_mode= */ 0444, /* tpm_pcr= */ UINT32_MAX, /* tpm_description= */ NULL, - &pcrsig_initrd, - &pcrsig_initrd_size, + initrds + INITRD_PCRSIG, /* 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) + if (PE_SECTION_VECTOR_IS_SET(sections + UNIFIED_SECTION_PCRPKEY)) (void) pack_cpio_literal( - (uint8_t*) loaded_image->ImageBase + addrs[UNIFIED_SECTION_PCRPKEY], - szs[UNIFIED_SECTION_PCRPKEY], + (const uint8_t*) loaded_image->ImageBase + sections[UNIFIED_SECTION_PCRPKEY].memory_offset, + sections[UNIFIED_SECTION_PCRPKEY].size, ".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, + initrds + INITRD_PCRPKEY, /* ret_measured= */ NULL); +} - linux_size = szs[UNIFIED_SECTION_LINUX]; - linux_base = POINTER_TO_PHYSICAL_ADDRESS(loaded_image->ImageBase) + addrs[UNIFIED_SECTION_LINUX]; +static void lookup_embedded_initrds( + EFI_LOADED_IMAGE_PROTOCOL *loaded_image, + PeSectionVector sections[_UNIFIED_SECTION_MAX], + struct iovec initrds[static _INITRD_MAX]) { - initrd_size = szs[UNIFIED_SECTION_INITRD]; - initrd_base = initrd_size != 0 ? POINTER_TO_PHYSICAL_ADDRESS(loaded_image->ImageBase) + addrs[UNIFIED_SECTION_INITRD] : 0; + assert(loaded_image); + assert(sections); + assert(initrds); - ucode_size = szs[UNIFIED_SECTION_UCODE]; - ucode_base = ucode_size != 0 ? POINTER_TO_PHYSICAL_ADDRESS(loaded_image->ImageBase) + addrs[UNIFIED_SECTION_UCODE] : 0; + if (PE_SECTION_VECTOR_IS_SET(sections + UNIFIED_SECTION_INITRD)) + initrds[INITRD_BASE] = IOVEC_MAKE( + (const uint8_t*) loaded_image->ImageBase + sections[UNIFIED_SECTION_INITRD].memory_offset, + sections[UNIFIED_SECTION_INITRD].size); + if (PE_SECTION_VECTOR_IS_SET(sections + UNIFIED_SECTION_UCODE)) + initrds[INITRD_UCODE] = IOVEC_MAKE( + (const uint8_t*) loaded_image->ImageBase + sections[UNIFIED_SECTION_UCODE].memory_offset, + sections[UNIFIED_SECTION_UCODE].size); +} + +static void export_pcr_variables( + int sections_measured, + int parameters_measured, + int sysext_measured, + int confext_measured) { + + /* After we are done with measuring, 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); + if (parameters_measured > 0) + (void) efivar_set_uint_string(MAKE_GUID_PTR(LOADER), u"StubPcrKernelParameters", TPM2_PCR_KERNEL_CONFIG, 0); + if (sysext_measured > 0) + (void) efivar_set_uint_string(MAKE_GUID_PTR(LOADER), u"StubPcrInitRDSysExts", TPM2_PCR_SYSEXTS, 0); + if (confext_measured > 0) + (void) efivar_set_uint_string(MAKE_GUID_PTR(LOADER), u"StubPcrInitRDConfExts", TPM2_PCR_KERNEL_CONFIG, 0); +} + +static void install_embedded_devicetree( + EFI_LOADED_IMAGE_PROTOCOL *loaded_image, + const PeSectionVector sections[static _UNIFIED_SECTION_MAX], + struct devicetree_state *dt_state) { + + EFI_STATUS err; + + assert(loaded_image); + assert(sections); + assert(dt_state); + + if (!PE_SECTION_VECTOR_IS_SET(sections + UNIFIED_SECTION_DTB)) + return; + + err = devicetree_install_from_memory( + dt_state, + (const uint8_t*) loaded_image->ImageBase + sections[UNIFIED_SECTION_DTB].memory_offset, + sections[UNIFIED_SECTION_DTB].size); + if (err != EFI_SUCCESS) + log_error_status(err, "Error loading embedded devicetree, igoring: %m"); +} + +static void load_all_addons( + EFI_HANDLE image, + EFI_LOADED_IMAGE_PROTOCOL *loaded_image, + const char *uname, + char16_t **cmdline_addons, + DevicetreeAddon **dt_addons, + size_t *n_dt_addons) { + + EFI_STATUS err; + + assert(loaded_image); + assert(cmdline_addons); + assert(dt_addons); + assert(n_dt_addons); + + err = load_addons( + image, + loaded_image, + u"\\loader\\addons", + uname, + cmdline_addons, + dt_addons, + n_dt_addons); + 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) + return; + + err = load_addons( + image, + loaded_image, + dropin_dir, + uname, + cmdline_addons, + dt_addons, + n_dt_addons); + if (err != EFI_SUCCESS) + log_error_status(err, "Error loading UKI-specific addons, ignoring: %m"); +} + +static void display_splash( + EFI_LOADED_IMAGE_PROTOCOL *loaded_image, + const PeSectionVector sections[static _UNIFIED_SECTION_MAX]) { + + assert(loaded_image); + assert(sections); + + if (!PE_SECTION_VECTOR_IS_SET(sections + UNIFIED_SECTION_SPLASH)) + return; + + graphics_splash((const uint8_t*) loaded_image->ImageBase + sections[UNIFIED_SECTION_SPLASH].memory_offset, sections[UNIFIED_SECTION_SPLASH].size); +} + +static void determine_cmdline( + EFI_HANDLE image, + EFI_LOADED_IMAGE_PROTOCOL *loaded_image, + const PeSectionVector sections[static _UNIFIED_SECTION_MAX], + char16_t **ret_cmdline, + int *parameters_measured) { + + assert(loaded_image); + assert(sections); + + if (use_load_options(image, loaded_image, /* have_cmdline= */ PE_SECTION_VECTOR_IS_SET(sections + UNIFIED_SECTION_CMDLINE), ret_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. */ + bool m = false; + (void) tpm_log_load_options(*ret_cmdline, &m); + combine_measured_flag(parameters_measured, m); + } else { + *ret_cmdline = pe_section_to_str16(loaded_image, sections + UNIFIED_SECTION_CMDLINE); + if (*ret_cmdline) + mangle_stub_cmdline(*ret_cmdline); + } +} + +static EFI_STATUS run(EFI_HANDLE image) { + int sections_measured = -1, parameters_measured = -1, sysext_measured = -1, confext_measured = -1; + _cleanup_(devicetree_cleanup) struct devicetree_state dt_state = {}; + _cleanup_free_ char16_t *cmdline = NULL, *cmdline_addons = NULL; + _cleanup_(initrds_free) struct iovec initrds[_INITRD_MAX] = {}; + PeSectionVector sections[ELEMENTSOF(unified_sections)] = {}; + EFI_LOADED_IMAGE_PROTOCOL *loaded_image; + _cleanup_free_ char *uname = NULL; + DevicetreeAddon *dt_addons = NULL; + size_t n_dt_addons = 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"); + + err = pe_memory_locate_sections(loaded_image->ImageBase, unified_sections, sections); + if (err != EFI_SUCCESS) + return log_error_status(err, "Unable to locate embedded PE sections: %m"); + if (!PE_SECTION_VECTOR_IS_SET(sections + UNIFIED_SECTION_LINUX)) + return log_error_status(EFI_NOT_FOUND, "Image lacks .linux section."); + + measure_sections(loaded_image, sections, §ions_measured); + + /* Show splash screen as early as possible, but after measuring it */ + display_splash(loaded_image, sections); + + refresh_random_seed(loaded_image); + + uname = pe_section_to_str8(loaded_image, sections + UNIFIED_SECTION_UNAME); + + determine_cmdline(image, loaded_image, sections, &cmdline, ¶meters_measured); + + /* 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. */ + CLEANUP_ARRAY(dt_addons, n_dt_addons, devicetree_addon_free_many); + load_all_addons(image, loaded_image, uname, &cmdline_addons, &dt_addons, &n_dt_addons); + + /* 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, &cmdline, ¶meters_measured); + cmdline_append_and_measure_smbios(&cmdline, ¶meters_measured); + + export_common_variables(loaded_image); + export_stub_variables(loaded_image); + + /* First load the base device tree, then fix it up using addons - global first, then per-UKI. */ + install_embedded_devicetree(loaded_image, sections, &dt_state); + install_addon_devicetrees(&dt_state, dt_addons, n_dt_addons, ¶meters_measured); + + /* Generate & find all initrds */ + generate_sidecar_initrds(loaded_image, initrds, ¶meters_measured, &sysext_measured, &confext_measured); + generate_embedded_initrds(loaded_image, sections, initrds); + lookup_embedded_initrds(loaded_image, sections, initrds); + + /* Export variables indicating what we measured */ + export_pcr_variables(sections_measured, parameters_measured, sysext_measured, confext_measured); + + /* Combine the initrds into one */ _cleanup_pages_ Pages initrd_pages = {}; - if (ucode_base || credential_initrd || global_credential_initrd || sysext_initrd || confext_initrd || pcrsig_initrd || pcrpkey_initrd) { - /* If we have generated initrds dynamically or there is a microcode initrd, combine them with the built-in initrd. */ - err = combine_initrds( - (const void*const[]) { - /* Microcode must always be first as kernel only scans uncompressed cpios - * and later initrds might be compressed. */ - PHYSICAL_ADDRESS_TO_POINTER(ucode_base), - PHYSICAL_ADDRESS_TO_POINTER(initrd_base), - credential_initrd, - global_credential_initrd, - sysext_initrd, - confext_initrd, - pcrsig_initrd, - pcrpkey_initrd, - }, - (const size_t[]) { - ucode_size, - initrd_size, - credential_initrd_size, - global_credential_initrd_size, - sysext_initrd_size, - confext_initrd_size, - pcrsig_initrd_size, - pcrpkey_initrd_size, - }, - 8, - &initrd_pages, &initrd_size); + struct iovec final_initrd; + if (initrds_need_combine(initrds)) { + /* If we have generated initrds dynamically or there is a microcode initrd, combine them with + * the built-in initrd. */ + err = combine_initrds(initrds, _INITRD_MAX, &initrd_pages, &final_initrd.iov_len); if (err != EFI_SUCCESS) return err; - initrd_base = initrd_pages.addr; + final_initrd.iov_base = PHYSICAL_ADDRESS_TO_POINTER(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); - confext_initrd = mfree(confext_initrd); - pcrsig_initrd = mfree(pcrsig_initrd); - pcrpkey_initrd = mfree(pcrpkey_initrd); - } + /* Given these might be large let's free them explicitly before we pass control to Linux */ + initrds_free(&initrds); + } else + final_initrd = initrds[INITRD_BASE]; - err = linux_exec(image, cmdline, - PHYSICAL_ADDRESS_TO_POINTER(linux_base), linux_size, - PHYSICAL_ADDRESS_TO_POINTER(initrd_base), initrd_size); + struct iovec kernel = IOVEC_MAKE( + (const uint8_t*) loaded_image->ImageBase + sections[UNIFIED_SECTION_LINUX].memory_offset, + sections[UNIFIED_SECTION_LINUX].size); + + err = linux_exec(image, cmdline, &kernel, &final_initrd); graphics_mode(false); return err; } -DEFINE_EFI_MAIN_FUNCTION(run, "systemd-stub", /*wait_for_debugger=*/false); +DEFINE_EFI_MAIN_FUNCTION(run, "systemd-stub", /* wait_for_debugger= */ false); diff --git a/src/boot/efi/util.c b/src/boot/efi/util.c index b5c8c6306e..eb29eb2d5b 100644 --- a/src/boot/efi/util.c +++ b/src/boot/efi/util.c @@ -330,7 +330,14 @@ EFI_STATUS chunked_read(EFI_FILE *file, size_t *size, void *buf) { return EFI_SUCCESS; } -EFI_STATUS file_read(EFI_FILE *dir, const char16_t *name, size_t off, size_t size, char **ret, size_t *ret_size) { +EFI_STATUS file_read( + EFI_FILE *dir, + const char16_t *name, + uint64_t off, + size_t size, + char **ret, + size_t *ret_size) { + _cleanup_(file_closep) EFI_FILE *handle = NULL; _cleanup_free_ char *buf = NULL; EFI_STATUS err; @@ -350,6 +357,9 @@ EFI_STATUS file_read(EFI_FILE *dir, const char16_t *name, size_t off, size_t siz if (err != EFI_SUCCESS) return err; + if (info->FileSize > SIZE_MAX) + return EFI_BAD_BUFFER_SIZE; + size = info->FileSize; } diff --git a/src/boot/efi/util.h b/src/boot/efi/util.h index ceac07ca39..dc624f45ae 100644 --- a/src/boot/efi/util.h +++ b/src/boot/efi/util.h @@ -102,7 +102,7 @@ char16_t *xstr8_to_path(const char *stra); char16_t *mangle_stub_cmdline(char16_t *cmdline); EFI_STATUS chunked_read(EFI_FILE *file, size_t *size, void *buf); -EFI_STATUS file_read(EFI_FILE *dir, const char16_t *name, size_t off, size_t size, char **content, size_t *content_size); +EFI_STATUS file_read(EFI_FILE *dir, const char16_t *name, uint64_t off, size_t size, char **content, size_t *content_size); static inline void file_closep(EFI_FILE **handle) { if (!*handle) diff --git a/src/fundamental/iovec-util-fundamental.h b/src/fundamental/iovec-util-fundamental.h new file mode 100644 index 0000000000..68d5bf4ee0 --- /dev/null +++ b/src/fundamental/iovec-util-fundamental.h @@ -0,0 +1,37 @@ +/* SPDX-License-Identifier: LGPL-2.1-or-later */ +#pragma once + +#if SD_BOOT +/* struct iovec is a POSIX userspace construct. Let's introduce it also in EFI mode, it's just so useful */ +struct iovec { + void *iov_base; + size_t iov_len; +}; + +static inline void free(void *p); +#endif + +/* This accepts both const and non-const pointers */ +#define IOVEC_MAKE(base, len) \ + (struct iovec) { \ + .iov_base = (void*) (base), \ + .iov_len = (len), \ + } + +static inline void iovec_done(struct iovec *iovec) { + /* A _cleanup_() helper that frees the iov_base in the iovec */ + assert(iovec); + + iovec->iov_base = mfree(iovec->iov_base); + iovec->iov_len = 0; +} + +static inline bool iovec_is_set(const struct iovec *iovec) { + /* Checks if the iovec points to a non-empty chunk of memory */ + return iovec && iovec->iov_len > 0 && iovec->iov_base; +} + +static inline bool iovec_is_valid(const struct iovec *iovec) { + /* Checks if the iovec is either NULL, empty or points to a valid bit of memory */ + return !iovec || (iovec->iov_base || iovec->iov_len == 0); +} diff --git a/src/fundamental/macro-fundamental.h b/src/fundamental/macro-fundamental.h index bbfdcdb218..913c8b253c 100644 --- a/src/fundamental/macro-fundamental.h +++ b/src/fundamental/macro-fundamental.h @@ -546,3 +546,21 @@ static inline uint64_t ALIGN_OFFSET_U64(uint64_t l, uint64_t ali) { #else #define DECLARE_SBAT(text) #endif + +#define sizeof_field(struct_type, member) sizeof(((struct_type *) 0)->member) +#define endoffsetof_field(struct_type, member) (offsetof(struct_type, member) + sizeof_field(struct_type, member)) + +#define _FOREACH_ARRAY(i, array, num, m, end) \ + for (typeof(array[0]) *i = (array), *end = ({ \ + typeof(num) m = (num); \ + (i && m > 0) ? i + m : NULL; \ + }); end && i < end; i++) + +#define FOREACH_ARRAY(i, array, num) \ + _FOREACH_ARRAY(i, array, num, UNIQ_T(m, UNIQ), UNIQ_T(end, UNIQ)) + +#define FOREACH_ELEMENT(i, array) \ + FOREACH_ARRAY(i, array, ELEMENTSOF(array)) + +#define PTR_TO_SIZE(p) ((size_t) ((uintptr_t) (p))) +#define SIZE_TO_PTR(u) ((void *) ((uintptr_t) (u))) diff --git a/src/fundamental/meson.build b/src/fundamental/meson.build index f5f57ac3cb..3e9866ef70 100644 --- a/src/fundamental/meson.build +++ b/src/fundamental/meson.build @@ -5,6 +5,7 @@ fundamental_include = include_directories('.') fundamental_sources = files( 'bootspec-fundamental.c', 'efivars-fundamental.c', + 'iovec-util-fundamental.h', 'sha256-fundamental.c', 'string-util-fundamental.c', 'uki.c',