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.
This commit is contained in:
Valentin David
2025-06-11 13:04:53 +02:00
parent 114ad16c4d
commit cab9c7b5a4
5 changed files with 202 additions and 101 deletions
+88 -86
View File
@@ -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);
if (err != EFI_SUCCESS)
return log_error_status(err, "Error loading kernel image: %m");
EFI_LOADED_IMAGE_PROTOCOL *loaded_image;
EFI_LOADED_IMAGE_PROTOCOL* parent_loaded_image;
err = BS->HandleProtocol(
kernel_image, MAKE_GUID_PTR(EFI_LOADED_IMAGE_PROTOCOL), (void **) &loaded_image);
parent, MAKE_GUID_PTR(EFI_LOADED_IMAGE_PROTOCOL), (void **) &parent_loaded_image);
if (err != EFI_SUCCESS)
return log_error_status(err, "Error getting kernel loaded image protocol: %m");
return log_error_status(err, "Cannot get parent loaded image: %m");
err = pe_kernel_check_no_relocation(kernel->iov_base);
if (err != EFI_SUCCESS)
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");
log_wait();
err = BS->StartImage(kernel_image, NULL, NULL);
EFI_HANDLE kernel_image = NULL;
/* Try calling the kernel compat entry point if one exists. */
if (err == EFI_UNSUPPORTED && compat_address > 0) {
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();
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");
}