* 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"
#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,
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
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;
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");
}
#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;
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 {
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))
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;
}