#include "pe.h"
#include "proto/device-path.h"
#include "proto/loaded-image.h"
+#include "proto/memory-attribute.h"
#include "secure-boot.h"
#include "shim.h"
#include "util.h"
return log_error_status(err, "Error starting kernel image with shim: %m");
}
+static EFI_STATUS kernel_set_nx(EFI_PHYSICAL_ADDRESS addr, uint64_t length) {
+ EFI_MEMORY_ATTRIBUTE_PROTOCOL *memory_proto;
+ EFI_STATUS err;
+
+ err = BS->LocateProtocol(MAKE_GUID_PTR(EFI_MEMORY_ATTRIBUTE_PROTOCOL), NULL, (void **) &memory_proto);
+ if (err != EFI_SUCCESS) {
+ log_debug("No EFI_MEMORY_ATTRIBUTE_PROTOCOL found, skipping NX_COMPAT support.");
+ return EFI_SUCCESS; /* ignore if firmware lacks support */
+ }
+
+ err = memory_proto->SetMemoryAttributes(memory_proto, addr, length, EFI_MEMORY_RO);
+ if (err != EFI_SUCCESS)
+ return log_error_status(err, "Cannot make kernel image read-only: %m");
+
+ err = memory_proto->ClearMemoryAttributes(memory_proto, addr, length, EFI_MEMORY_XP);
+ if (err != EFI_SUCCESS)
+ return log_error_status(err, "Cannot make kernel image executable: %m");
+
+ log_debug("Changed kernel image to read-only for NX_COMPAT support.");
+
+ return EFI_SUCCESS;
+}
+
+static EFI_STATUS kernel_clear_nx(EFI_PHYSICAL_ADDRESS addr, uint64_t length) {
+ EFI_MEMORY_ATTRIBUTE_PROTOCOL *memory_proto;
+ EFI_STATUS err;
+
+ err = BS->LocateProtocol(MAKE_GUID_PTR(EFI_MEMORY_ATTRIBUTE_PROTOCOL), NULL, (void **) &memory_proto);
+ if (err != EFI_SUCCESS) {
+ log_debug("No EFI_MEMORY_ATTRIBUTE_PROTOCOL found, skipping NX_COMPAT support.");
+ return EFI_SUCCESS; /* ignore if firmware lacks support */
+ }
+
+ err = memory_proto->SetMemoryAttributes(memory_proto, addr, length, EFI_MEMORY_XP);
+ if (err != EFI_SUCCESS)
+ return log_error_status(err, "Cannot make kernel image non-executable: %m");
+
+ err = memory_proto->ClearMemoryAttributes(memory_proto, addr, length, EFI_MEMORY_RO);
+ if (err != EFI_SUCCESS)
+ return log_error_status(err, "Cannot make kernel image writable: %m");
+
+ return EFI_SUCCESS;
+}
+
EFI_STATUS linux_exec(
EFI_HANDLE parent_image,
const char16_t *cmdline,
if (err != EFI_SUCCESS)
return err;
+ /* As per MSFT requirement, memory pages need to be marked W^X.
+ * Firmwares will start enforcing this at some point in the near-ish future.
+ * The kernel needs to mark this as supported explicitly, otherwise it will crash.
+ * https://microsoft.github.io/mu/WhatAndWhy/enhancedmemoryprotection/
+ * https://www.kraxel.org/blog/2023/12/uefi-nx-linux-boot/ */
+ _cleanup_free_ EFI_PHYSICAL_ADDRESS *nx_sections_addrs = NULL;
+ _cleanup_free_ uint64_t *nx_sections_lengths = NULL;
+ size_t nx_sections = 0;
+ bool nx_compat = pe_kernel_check_nx_compat(kernel->iov_base);
+
const PeSectionHeader *headers;
size_t n_headers;
h->SizeOfRawData);
memzero(loaded_kernel + h->VirtualAddress + h->SizeOfRawData,
h->VirtualSize - h->SizeOfRawData);
+
+ /* Not a code section? Nothing to do, leave as-is. */
+ if (nx_compat && ((h->Characteristics & PE_CODE) || (h->Characteristics & PE_EXECUTE))) {
+ nx_sections_addrs = xrealloc(nx_sections_addrs, nx_sections * sizeof(EFI_PHYSICAL_ADDRESS), (nx_sections + 1) * sizeof(EFI_PHYSICAL_ADDRESS));
+ nx_sections_lengths = xrealloc(nx_sections_lengths, nx_sections * sizeof(uint64_t), (nx_sections + 1) * sizeof(uint64_t));
+ nx_sections_addrs[nx_sections] = POINTER_TO_PHYSICAL_ADDRESS(loaded_kernel + h->VirtualAddress - image_base);
+ nx_sections_lengths[nx_sections] = h->VirtualSize;
+
+ err = kernel_set_nx(nx_sections_addrs[nx_sections], nx_sections_lengths[nx_sections]);
+ if (err != EFI_SUCCESS)
+ return err;
+
+ ++nx_sections;
+ }
}
_cleanup_free_ KERNEL_FILE_PATH *kernel_file_path = xnew(KERNEL_FILE_PATH, 1);
err = compat_entry(parent_image, ST);
}
+ /* On failure we'll free the buffers. EDK2 requires the memory buffers to be writable and
+ * non-executable, as in some configurations it will overwrite them with a fixed pattern, so if the
+ * attributes are not restored FreePages() will crash. */
+ for (size_t i = 0; i < nx_sections; i++)
+ (void) kernel_clear_nx(nx_sections_addrs[i], nx_sections_lengths[i]);
+
return log_error_status(err, "Error starting kernel image: %m");
}
--- /dev/null
+/* SPDX-License-Identifier: LGPL-2.1-or-later */
+#pragma once
+
+#include "efi.h"
+
+#define EFI_MEMORY_ATTRIBUTE_PROTOCOL_GUID \
+ GUID_DEF(0xf4560cf6, 0x40ec, 0x4b4a, 0xa1, 0x92, 0xbf, 0x1d, 0x57, 0xd0, 0xb1, 0x89)
+
+#define EFI_MEMORY_RP 0x0000000000002000
+#define EFI_MEMORY_XP 0x0000000000004000
+#define EFI_MEMORY_RO 0x0000000000020000
+
+struct _EFI_MEMORY_ATTRIBUTE_PROTOCOL;
+
+typedef struct _EFI_MEMORY_ATTRIBUTE_PROTOCOL {
+ EFI_STATUS (EFIAPI *GetMemoryAttributes)(
+ struct _EFI_MEMORY_ATTRIBUTE_PROTOCOL *This,
+ EFI_PHYSICAL_ADDRESS BaseAddress,
+ uint64_t Length,
+ uint64_t *Attributes);
+ EFI_STATUS (EFIAPI *SetMemoryAttributes)(
+ struct _EFI_MEMORY_ATTRIBUTE_PROTOCOL *This,
+ EFI_PHYSICAL_ADDRESS BaseAddress,
+ uint64_t Length,
+ uint64_t Attributes);
+ EFI_STATUS (EFIAPI *ClearMemoryAttributes)(
+ struct _EFI_MEMORY_ATTRIBUTE_PROTOCOL *This,
+ EFI_PHYSICAL_ADDRESS BaseAddress,
+ uint64_t Length,
+ uint64_t Attributes);
+} EFI_MEMORY_ATTRIBUTE_PROTOCOL;