[RFC PATCH 3/3] efi/zboot: x86: Clear NX restrictions on populated code regions

From: Ard Biesheuvel
Date: Sun Apr 16 2023 - 08:08:25 EST


Future EFI firmware will require the PE/COFF NX_COMPAT header flag to be
set in order to retain access to all system facilities while features
such as UEFI secure boot or TCG measured boot are enabled.

The consequence of setting this flag is that the EFI firmware image
loader may configure the page allocator to set the NX attribute on all
allocations requested by the image. This means we should clear this
attribute on all regions we allocate and expect to be able to execute
from.

In the x86 EFI zboot case, the only code we execute under EFI's 1:1
mapping that was not loaded by the image loader itself is the trampoline
that effectuates the switch between 4 and 5 level paging, and the part
of the loaded kernel image that runs before switching to its own page
tables. So let's use the EFI memory attributes protocol to clear the NX
attribute on these regions.

Whether or not setting the read-only attribute first is required is
unclear at this point. Given that the kernel startup code uses two
different executable sections before switching to its own page tables
(normal text and inittext, with a writable data section in between),
this would require some minor reorganization of the kernel memory map.

Signed-off-by: Ard Biesheuvel <ardb@xxxxxxxxxx>
---
arch/x86/kernel/head_64.S | 4 +++
drivers/firmware/efi/libstub/x86-zboot.c | 27 ++++++++++++++++++++
2 files changed, 31 insertions(+)

diff --git a/arch/x86/kernel/head_64.S b/arch/x86/kernel/head_64.S
index 4ae067852fb28663..38897ac51f13bb55 100644
--- a/arch/x86/kernel/head_64.S
+++ b/arch/x86/kernel/head_64.S
@@ -74,6 +74,10 @@ SYM_CODE_START_NOALIGN(startup_64)
*/
.org startup_64 + 0x10 - 3, BYTES_NOP1
nopl (_end - startup_64)(%rax)
+
+ /* put the size of the initial executable mapping at offset 0x20 */
+ .org startup_64 + 0x20 - 3, BYTES_NOP1
+ nopl (_einittext - startup_64)(%rax)
#endif
leaq _text(%rip), %rdi

diff --git a/drivers/firmware/efi/libstub/x86-zboot.c b/drivers/firmware/efi/libstub/x86-zboot.c
index 16e8b315892dedda..70668104804fb050 100644
--- a/drivers/firmware/efi/libstub/x86-zboot.c
+++ b/drivers/firmware/efi/libstub/x86-zboot.c
@@ -60,10 +60,33 @@ efi_status_t efi_handle_cmdline(efi_loaded_image_t *image, char **cmdline_ptr)
return status;
}

+static void efi_remap_exec(unsigned long base, unsigned long size)
+{
+ static efi_memory_attribute_protocol_t *memattr = (void *)ULONG_MAX;
+ efi_guid_t guid = EFI_MEMORY_ATTRIBUTE_PROTOCOL_GUID;
+ efi_status_t status;
+
+ if (memattr == (void *)ULONG_MAX) {
+ memattr = NULL;
+ status = efi_bs_call(locate_protocol, &guid, NULL,
+ (void **)&memattr);
+ if (status != EFI_SUCCESS)
+ return;
+ } else if (!memattr) {
+ return;
+ }
+
+ status = memattr->clear_memory_attributes(memattr, base, size,
+ EFI_MEMORY_XP);
+ if (status != EFI_SUCCESS)
+ efi_warn("Failed to clear NX attribute on code region\n");
+}
+
void efi_cache_sync_image(unsigned long image_base, unsigned long alloc_size)
{
const u32 payload_size = *(u32 *)(_gzdata_end - 4);
const u32 image_size = *(u32 *)(image_base + 0x10);
+ const u32 code_size = *(u32 *)(image_base + 0x20);
const s32 *reloc = (s32 *)(image_base + payload_size);
u64 va_offset = __START_KERNEL - image_base;
u64 range, delta;
@@ -107,6 +130,8 @@ void efi_cache_sync_image(unsigned long image_base, unsigned long alloc_size)
*(u64 *)((s64)*reloc - va_offset) += delta;

efi_free(alloc_size - image_size, image_base + image_size);
+
+ efi_remap_exec(image_base, PAGE_ALIGN(code_size));
}

static void __naked tmpl_toggle(void *cr3, void *gdt)
@@ -197,6 +222,8 @@ static efi_status_t efi_setup_5level_paging(void)
*/
*(u32 *)&la57_code[tmpl_size - 6] += (u64)la57_code;

+ efi_remap_exec((unsigned long)la57_code, PAGE_SIZE);
+
return EFI_SUCCESS;
}

--
2.39.2