From: Valentin David Date: Tue, 6 May 2025 12:34:14 +0000 (+0200) Subject: stub: call inner kernel directly X-Git-Tag: v258-rc1~302^2~1 X-Git-Url: http://git.ipfire.org/gitweb.cgi?a=commitdiff_plain;h=cab9c7b5a42effa8a45611fc6b8556138c869b5f;p=thirdparty%2Fsystemd.git stub: call inner kernel directly Since shim 16, it is not possible anymore to override the security arch protocol to avoid signature check and measurements. Also on Dell servers, EFI_SECURITY2_ARCH_PROTOCOL is not implemented, so unexpected measurements on PCR 4 were still happening. This just loads and run the kernel pe. We verify that there is no relocation needed. Also for simplification, we assume and verify that the base address is expected to be 0. --- diff --git a/src/boot/boot.c b/src/boot/boot.c index 925cc0c41d2..d1a3850f4af 100644 --- a/src/boot/boot.c +++ b/src/boot/boot.c @@ -2688,7 +2688,8 @@ static EFI_STATUS call_image_start( if (err == EFI_UNSUPPORTED && entry->type == LOADER_LINUX) { uint32_t compat_address; - err = pe_kernel_info(loaded_image->ImageBase, &compat_address, /* ret_size_in_memory= */ NULL); + err = pe_kernel_info(loaded_image->ImageBase, /* ret_entry_point= */ NULL, &compat_address, + /* ret_image_base= */ NULL, /* ret_size_in_memory= */ NULL); if (err != EFI_SUCCESS) { if (err != EFI_UNSUPPORTED) return log_error_status(err, "Error finding kernel compat entry address: %m"); diff --git a/src/boot/device-path-util.c b/src/boot/device-path-util.c index 2764373d94a..d8c8e5780d4 100644 --- a/src/boot/device-path-util.c +++ b/src/boot/device-path-util.c @@ -5,6 +5,12 @@ #include "string-util-fundamental.h" #include "util.h" +static const EFI_DEVICE_PATH *device_path_find_end_node(const EFI_DEVICE_PATH *dp) { + while (!device_path_is_end(dp)) + dp = device_path_next_node(dp); + return dp; +} + EFI_STATUS make_file_device_path(EFI_HANDLE device, const char16_t *file, EFI_DEVICE_PATH **ret_dp) { EFI_STATUS err; EFI_DEVICE_PATH *dp; @@ -16,9 +22,7 @@ EFI_STATUS make_file_device_path(EFI_HANDLE device, const char16_t *file, EFI_DE if (err != EFI_SUCCESS) return err; - EFI_DEVICE_PATH *end_node = dp; - while (!device_path_is_end(end_node)) - end_node = device_path_next_node(end_node); + const EFI_DEVICE_PATH *end_node = device_path_find_end_node(dp); size_t file_size = strsize16(file); size_t dp_size = (uint8_t *) end_node - (uint8_t *) dp; @@ -158,7 +162,9 @@ EFI_DEVICE_PATH *device_path_replace_node( * node. If new_node is provided, it is appended at the end of the new path. */ assert(path); - assert(node); + + if (!node) + node = device_path_find_end_node(path); size_t len = (uint8_t *) node - (uint8_t *) path; EFI_DEVICE_PATH *ret = xmalloc(len + (new_node ? new_node->Length : 0) + sizeof(EFI_DEVICE_PATH)); diff --git a/src/boot/linux.c b/src/boot/linux.c index 00bdf8c1ce4..b2c5200f1b3 100644 --- a/src/boot/linux.c +++ b/src/boot/linux.c @@ -8,6 +8,7 @@ * This method works for Linux 5.8 and newer on ARM/Aarch64, x86/x68_64 and RISC-V. */ +#include "device-path-util.h" #include "efi-log.h" #include "initrd.h" #include "linux.h" @@ -15,82 +16,12 @@ #include "proto/device-path.h" #include "proto/loaded-image.h" #include "secure-boot.h" +#include "shim.h" #include "util.h" #define STUB_PAYLOAD_GUID \ { 0x55c5d1f8, 0x04cd, 0x46b5, { 0x8a, 0x20, 0xe5, 0x6c, 0xbb, 0x30, 0x52, 0xd0 } } -typedef struct { - const void *addr; - size_t len; - const EFI_DEVICE_PATH *device_path; -} ValidationContext; - -static bool validate_payload( - const void *ctx, const EFI_DEVICE_PATH *device_path, const void *file_buffer, size_t file_size) { - - const ValidationContext *payload = ASSERT_PTR(ctx); - - if (device_path != payload->device_path) - return false; - - /* Security arch (1) protocol does not provide a file buffer. Instead we are supposed to fetch the payload - * ourselves, which is not needed as we already have everything in memory and the device paths match. */ - if (file_buffer && (file_buffer != payload->addr || file_size != payload->len)) - return false; - - return true; -} - -static EFI_STATUS load_image(EFI_HANDLE parent, const void *source, size_t len, EFI_HANDLE *ret_image) { - assert(parent); - assert(source); - assert(ret_image); - - /* We could pass a NULL device path, but it's nicer to provide something and it allows us to identify - * the loaded image from within the security hooks. */ - struct { - VENDOR_DEVICE_PATH payload; - EFI_DEVICE_PATH end; - } _packed_ payload_device_path = { - .payload = { - .Header = { - .Type = MEDIA_DEVICE_PATH, - .SubType = MEDIA_VENDOR_DP, - .Length = sizeof(payload_device_path.payload), - }, - .Guid = STUB_PAYLOAD_GUID, - }, - .end = { - .Type = END_DEVICE_PATH_TYPE, - .SubType = END_ENTIRE_DEVICE_PATH_SUBTYPE, - .Length = sizeof(payload_device_path.end), - }, - }; - - /* We want to support unsigned kernel images as payload, which is safe to do under secure boot - * because it is embedded in this stub loader (and since it is already running it must be trusted). */ - install_security_override( - validate_payload, - &(ValidationContext) { - .addr = source, - .len = len, - .device_path = &payload_device_path.payload.Header, - }); - - EFI_STATUS ret = BS->LoadImage( - /*BootPolicy=*/false, - parent, - &payload_device_path.payload.Header, - (void *) source, - len, - ret_image); - - uninstall_security_override(); - - return ret; -} - EFI_STATUS linux_exec( EFI_HANDLE parent, const char16_t *cmdline, @@ -98,14 +29,15 @@ EFI_STATUS linux_exec( const struct iovec *initrd) { size_t kernel_size_in_memory = 0; - uint32_t compat_address; + uint32_t compat_entry_point, entry_point; + uint64_t image_base; EFI_STATUS err; assert(parent); assert(iovec_is_set(kernel)); assert(iovec_is_valid(initrd)); - err = pe_kernel_info(kernel->iov_base, &compat_address, &kernel_size_in_memory); + err = pe_kernel_info(kernel->iov_base, &entry_point, &compat_entry_point, &image_base, &kernel_size_in_memory); #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 @@ -120,16 +52,69 @@ EFI_STATUS linux_exec( 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, kernel->iov_base, kernel->iov_len, &kernel_image); + EFI_LOADED_IMAGE_PROTOCOL* parent_loaded_image; + err = BS->HandleProtocol( + parent, MAKE_GUID_PTR(EFI_LOADED_IMAGE_PROTOCOL), (void **) &parent_loaded_image); if (err != EFI_SUCCESS) - return log_error_status(err, "Error loading kernel image: %m"); + return log_error_status(err, "Cannot get parent loaded image: %m"); - EFI_LOADED_IMAGE_PROTOCOL *loaded_image; - err = BS->HandleProtocol( - kernel_image, MAKE_GUID_PTR(EFI_LOADED_IMAGE_PROTOCOL), (void **) &loaded_image); + err = pe_kernel_check_no_relocation(kernel->iov_base); if (err != EFI_SUCCESS) - return log_error_status(err, "Error getting kernel loaded image protocol: %m"); + return err; + + const PeSectionHeader *headers; + size_t n_headers; + + /* Do we need to validate anyting here? the len? */ + err = pe_section_table_from_base(kernel->iov_base, &headers, &n_headers); + if (err != EFI_SUCCESS) + return log_error_status(err, "Cannot read sections: %m"); + + /* Do we need to ensure under 4gb address on x86? */ + _cleanup_pages_ Pages loaded_kernel_pages = xmalloc_pages( + AllocateAnyPages, EfiLoaderCode, EFI_SIZE_TO_PAGES(kernel_size_in_memory), 0); + + uint8_t* loaded_kernel = PHYSICAL_ADDRESS_TO_POINTER(loaded_kernel_pages.addr); + FOREACH_ARRAY(h, headers, n_headers) { + if (h->PointerToRelocations != 0) + return log_error_status(EFI_LOAD_ERROR, "Inner kernel image contains sections with relocations, which we do not support."); + if (h->SizeOfRawData == 0) + continue; + + if ((h->VirtualAddress < image_base) + || (h->VirtualAddress - image_base + h->SizeOfRawData > kernel_size_in_memory)) + return log_error_status(EFI_LOAD_ERROR, "Section would write outside of memory"); + memcpy(loaded_kernel + h->VirtualAddress - image_base, + (const uint8_t*)kernel->iov_base + h->PointerToRawData, + h->SizeOfRawData); + memzero(loaded_kernel + h->VirtualAddress + h->SizeOfRawData, + h->VirtualSize - h->SizeOfRawData); + } + + _cleanup_free_ EFI_LOADED_IMAGE_PROTOCOL* loaded_image = xnew(EFI_LOADED_IMAGE_PROTOCOL, 1); + + VENDOR_DEVICE_PATH device_node = { + .Header = { + .Type = MEDIA_DEVICE_PATH, + .SubType = MEDIA_VENDOR_DP, + .Length = sizeof(device_node), + }, + .Guid = STUB_PAYLOAD_GUID, + }; + + _cleanup_free_ EFI_DEVICE_PATH* file_path = device_path_replace_node(parent_loaded_image->FilePath, NULL, &device_node.Header); + + *loaded_image = (EFI_LOADED_IMAGE_PROTOCOL) { + .Revision = 0x1000, + .ParentHandle = parent, + .SystemTable = ST, + .DeviceHandle = parent_loaded_image->DeviceHandle, + .FilePath = file_path, + .ImageBase = loaded_kernel, + .ImageSize = kernel_size_in_memory, + .ImageCodeType = /*EFI_LOADER_CODE*/1, + .ImageDataType = /*EFI_LOADER_DATA*/2, + }; if (cmdline) { loaded_image->LoadOptions = (void *) cmdline; @@ -141,15 +126,32 @@ EFI_STATUS linux_exec( if (err != EFI_SUCCESS) return log_error_status(err, "Error registering initrd: %m"); + EFI_HANDLE kernel_image = NULL; + + err = BS->InstallMultipleProtocolInterfaces( + &kernel_image, MAKE_GUID_PTR(EFI_LOADED_IMAGE_PROTOCOL), loaded_image, + NULL); + if (err != EFI_SUCCESS) + return log_error_status(err, "Cannot install loaded image protocol: %m"); + log_wait(); - err = BS->StartImage(kernel_image, NULL, NULL); - /* Try calling the kernel compat entry point if one exists. */ - if (err == EFI_UNSUPPORTED && compat_address > 0) { + if (entry_point > 0) { + EFI_IMAGE_ENTRY_POINT entry = + (EFI_IMAGE_ENTRY_POINT) ((const uint8_t *) loaded_image->ImageBase + entry_point); + err = entry(kernel_image, ST); + } else if (compat_entry_point > 0) { + /* Try calling the kernel compat entry point if one exists. */ EFI_IMAGE_ENTRY_POINT compat_entry = - (EFI_IMAGE_ENTRY_POINT) ((uint8_t *) loaded_image->ImageBase + compat_address); + (EFI_IMAGE_ENTRY_POINT) ((const uint8_t *) loaded_image->ImageBase + compat_entry_point); err = compat_entry(kernel_image, ST); } + EFI_STATUS uninstall_err = BS->UninstallMultipleProtocolInterfaces( + kernel_image, MAKE_GUID_PTR(EFI_LOADED_IMAGE_PROTOCOL), loaded_image, + NULL); + if (uninstall_err != EFI_SUCCESS) + return log_error_status(uninstall_err, "Cannot uninstall loaded image protocol: %m"); + return log_error_status(err, "Error starting kernel image: %m"); } diff --git a/src/boot/pe.c b/src/boot/pe.c index 9ce978a48b7..beab0eb670a 100644 --- a/src/boot/pe.c +++ b/src/boot/pe.c @@ -70,6 +70,13 @@ typedef struct CoffFileHeader { #define OPTHDR32_MAGIC 0x10B /* PE32 OptionalHeader */ #define OPTHDR64_MAGIC 0x20B /* PE32+ OptionalHeader */ +typedef struct PeImageDataDirectory { + uint32_t VirtualAddress; + uint32_t Size; +} _packed_ PeImageDataDirectory; + +#define IMAGE_NUMBEROF_DIRECTORY_ENTRIES 16 + typedef struct PeOptionalHeader { uint16_t Magic; uint8_t LinkerMajor; @@ -100,7 +107,28 @@ typedef struct PeOptionalHeader { uint32_t CheckSum; uint16_t Subsystem; uint16_t DllCharacteristics; - /* fields with different sizes for 32/64 omitted */ + union { + struct { + uint64_t SizeOfStackReserve64; + uint64_t SizeOfStackCommit64; + uint64_t SizeOfHeapReserve64; + uint64_t SizeOfHeapCommit64; + uint32_t LoaderFlags64; + uint32_t NumberOfRvaAndSizes64; + + PeImageDataDirectory DataDirectory64[IMAGE_NUMBEROF_DIRECTORY_ENTRIES]; + }; + struct { + uint32_t SizeOfStackReserve32; + uint32_t SizeOfStackCommit32; + uint32_t SizeOfHeapReserve32; + uint32_t SizeOfHeapCommit32; + uint32_t LoaderFlags32; + uint32_t NumberOfRvaAndSizes32; + + PeImageDataDirectory DataDirectory32[IMAGE_NUMBEROF_DIRECTORY_ENTRIES]; + }; + }; } _packed_ PeOptionalHeader; typedef struct PeFileHeader { @@ -430,9 +458,8 @@ static uint32_t get_compatibility_entry_address(const DosFileHeader *dos, const return 0; } -EFI_STATUS pe_kernel_info(const void *base, uint32_t *ret_compat_address, size_t *ret_size_in_memory) { +EFI_STATUS pe_kernel_info(const void *base, uint32_t *ret_entry_point, uint32_t *ret_compat_entry_point, uint64_t *ret_image_base, size_t *ret_size_in_memory) { assert(base); - assert(ret_compat_address); const DosFileHeader *dos = (const DosFileHeader *) base; if (!verify_dos(dos)) @@ -442,26 +469,89 @@ EFI_STATUS pe_kernel_info(const void *base, uint32_t *ret_compat_address, size_t if (!verify_pe(dos, pe, /* allow_compatibility= */ true)) return EFI_LOAD_ERROR; + uint64_t image_base; + switch (pe->OptionalHeader.Magic) { + case OPTHDR32_MAGIC: + image_base = pe->OptionalHeader.ImageBase32; + break; + case OPTHDR64_MAGIC: + image_base = pe->OptionalHeader.ImageBase64; + break; + default: + assert_not_reached(); + } + /* When allocating we need to also consider the virtual/uninitialized data sections, so parse it out * of the SizeOfImage field in the PE header and return it */ - if (ret_size_in_memory) - *ret_size_in_memory = pe->OptionalHeader.SizeOfImage; + size_t size_in_memory = pe->OptionalHeader.SizeOfImage; /* Support for LINUX_INITRD_MEDIA_GUID was added in kernel stub 1.0. */ if (pe->OptionalHeader.MajorImageVersion < 1) return EFI_UNSUPPORTED; if (pe->FileHeader.Machine == TARGET_MACHINE_TYPE) { - *ret_compat_address = 0; + if (ret_entry_point) + *ret_entry_point = pe->OptionalHeader.AddressOfEntryPoint; + if (ret_compat_entry_point) + *ret_compat_entry_point = 0; + if (ret_image_base) + *ret_image_base = image_base; + if (ret_size_in_memory) + *ret_size_in_memory = size_in_memory; return EFI_SUCCESS; } - uint32_t compat_address = get_compatibility_entry_address(dos, pe); - if (compat_address == 0) + uint32_t compat_entry_point = get_compatibility_entry_address(dos, pe); + if (compat_entry_point == 0) /* Image type not supported and no compat entry found. */ return EFI_UNSUPPORTED; - *ret_compat_address = compat_address; + if (ret_entry_point) + *ret_entry_point = 0; + if (ret_compat_entry_point) + *ret_compat_entry_point = compat_entry_point; + if (ret_image_base) + *ret_image_base = image_base; + if (ret_size_in_memory) + *ret_size_in_memory = size_in_memory; + + return EFI_SUCCESS; +} + +/* https://learn.microsoft.com/en-us/windows/win32/debug/pe-format#optional-header-data-directories-image-only */ +#define BASE_RELOCATION_TABLE_DATA_DIRECTORY_ENTRY 5 + +/* We do not expect PE inner kernels to have any relocations. However that might be wrong for some + * architectures, or it might change in the future. If the case of relocation arise, we should transform this + * function in a function applying the relocations. However for now, since it would not be exercised and + * would bitrot, we leave it as a check that relocations are never expected. + */ +EFI_STATUS pe_kernel_check_no_relocation(const void *base) { + assert(base); + + const DosFileHeader *dos = base; + if (!verify_dos(dos)) + return EFI_LOAD_ERROR; + + const PeFileHeader *pe = (const PeFileHeader *) ((const uint8_t *) base + dos->ExeHeader); + if (!verify_pe(dos, pe, /* allow_compatibility= */ true)) + return EFI_LOAD_ERROR; + + const PeImageDataDirectory *data_directory; + switch (pe->OptionalHeader.Magic) { + case OPTHDR32_MAGIC: + data_directory = pe->OptionalHeader.DataDirectory32; + break; + case OPTHDR64_MAGIC: + data_directory = pe->OptionalHeader.DataDirectory64; + break; + default: + assert_not_reached(); + } + + if (data_directory[BASE_RELOCATION_TABLE_DATA_DIRECTORY_ENTRY].Size != 0) + return log_error_status(EFI_LOAD_ERROR, "Inner kernel image contains base relocations, which we do not support."); + return EFI_SUCCESS; } diff --git a/src/boot/pe.h b/src/boot/pe.h index a2847de8827..dc4088c948c 100644 --- a/src/boot/pe.h +++ b/src/boot/pe.h @@ -53,4 +53,6 @@ EFI_STATUS pe_memory_locate_sections( const char *const section_names[], PeSectionVector sections[]); -EFI_STATUS pe_kernel_info(const void *base, uint32_t *ret_compat_address, size_t *ret_size_in_memory); +EFI_STATUS pe_kernel_info(const void *base, uint32_t *ret_entry_point, uint32_t *ret_compat_entry_point, uint64_t *ret_image_base, size_t *ret_size_in_memory); + +EFI_STATUS pe_kernel_check_no_relocation(const void *base);