From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 From: Peter Jones Date: Wed, 3 Jul 2024 17:54:41 -0600 Subject: [PATCH] nx: set attrs in our kernel loaders For NX, our kernel loaders need to set write and execute page permissions on allocated pages and the stack. This patch adds those calls. Signed-off-by: Peter Jones [rharwood: fix stack_attrs undefined, fix aarch64 callsites] Signed-off-by: Robbie Harwood --- grub-core/kern/efi/mm.c | 79 ++++++++++++++++ grub-core/loader/arm64/xen_boot.c | 5 +- grub-core/loader/efi/chainloader.c | 11 +++ grub-core/loader/efi/linux.c | 188 ++++++++++++++++++++++++++++++++++--- grub-core/loader/i386/efi/linux.c | 26 ++++- grub-core/loader/i386/linux.c | 5 + include/grub/efi/efi.h | 6 +- include/grub/efi/linux.h | 16 +++- include/grub/efi/pe32.h | 2 + 9 files changed, 319 insertions(+), 19 deletions(-) diff --git a/grub-core/kern/efi/mm.c b/grub-core/kern/efi/mm.c index 560b859b6f4..464fe1c3c01 100644 --- a/grub-core/kern/efi/mm.c +++ b/grub-core/kern/efi/mm.c @@ -603,6 +603,83 @@ print_memory_map (grub_efi_memory_descriptor_t *memory_map, } #endif +grub_addr_t grub_stack_addr = (grub_addr_t)-1ll; +grub_size_t grub_stack_size = 0; + +static void +grub_nx_init (void) +{ + grub_uint64_t attrs, stack_attrs; + grub_err_t err; + grub_addr_t stack_current, stack_end; + const grub_uint64_t page_size = 4096; + const grub_uint64_t page_mask = ~(page_size - 1); + + /* + * These are to confirm that the flags are working as expected when + * debugging. + */ + attrs = 0; + stack_current = (grub_addr_t)grub_nx_init & page_mask; + err = grub_get_mem_attrs (stack_current, page_size, &attrs); + if (err) + { + grub_dprintf ("nx", + "grub_get_mem_attrs(0x%"PRIxGRUB_UINT64_T", ...) -> 0x%x\n", + stack_current, err); + grub_error_pop (); + } + else + grub_dprintf ("nx", "page attrs for grub_nx_init (%p) are %c%c%c\n", + grub_dl_load_core, + (attrs & GRUB_MEM_ATTR_R) ? 'r' : '-', + (attrs & GRUB_MEM_ATTR_R) ? 'w' : '-', + (attrs & GRUB_MEM_ATTR_R) ? 'x' : '-'); + + stack_current = (grub_addr_t)&stack_current & page_mask; + err = grub_get_mem_attrs (stack_current, page_size, &stack_attrs); + if (err) + { + grub_dprintf ("nx", + "grub_get_mem_attrs(0x%"PRIxGRUB_UINT64_T", ...) -> 0x%x\n", + stack_current, err); + grub_error_pop (); + } + else + { + attrs = stack_attrs; + grub_dprintf ("nx", "page attrs for stack (%p) are %c%c%c\n", + &attrs, + (attrs & GRUB_MEM_ATTR_R) ? 'r' : '-', + (attrs & GRUB_MEM_ATTR_R) ? 'w' : '-', + (attrs & GRUB_MEM_ATTR_R) ? 'x' : '-'); + } + for (stack_end = stack_current + page_size ; + !(attrs & GRUB_MEM_ATTR_R); + stack_end += page_size) + { + err = grub_get_mem_attrs (stack_current, page_size, &attrs); + if (err) + { + grub_dprintf ("nx", + "grub_get_mem_attrs(0x%"PRIxGRUB_UINT64_T", ...) -> 0x%x\n", + stack_current, err); + grub_error_pop (); + break; + } + } + if (stack_end > stack_current) + { + grub_stack_addr = stack_current; + grub_stack_size = stack_end - stack_current; + grub_dprintf ("nx", + "detected stack from 0x%"PRIxGRUB_ADDR" to 0x%"PRIxGRUB_ADDR"\n", + grub_stack_addr, grub_stack_addr + grub_stack_size - 1); + } +} + + + static grub_err_t grub_efi_mm_add_regions (grub_size_t required_bytes, unsigned int flags) { @@ -615,6 +692,8 @@ grub_efi_mm_add_regions (grub_size_t required_bytes, unsigned int flags) grub_err_t err; int mm_status; + grub_nx_init (); + /* Prepare a memory region to store two memory maps. */ memory_map = grub_efi_allocate_any_pages (2 * BYTES_TO_PAGES (MEMORY_MAP_SIZE)); if (! memory_map) diff --git a/grub-core/loader/arm64/xen_boot.c b/grub-core/loader/arm64/xen_boot.c index 9838a0f878b..5d7c8603db9 100644 --- a/grub-core/loader/arm64/xen_boot.c +++ b/grub-core/loader/arm64/xen_boot.c @@ -252,7 +252,10 @@ xen_boot (void) return err; return grub_arch_efi_linux_boot_image (xen_hypervisor->start, - xen_hypervisor->cmdline); + xen_hypervisor->size, + xen_hypervisor->cmdline, + 0); + } static void diff --git a/grub-core/loader/efi/chainloader.c b/grub-core/loader/efi/chainloader.c index badff5e50b5..5cd6f6a5993 100644 --- a/grub-core/loader/efi/chainloader.c +++ b/grub-core/loader/efi/chainloader.c @@ -1078,6 +1078,17 @@ grub_cmd_chainloader (grub_command_t cmd __attribute__ ((unused)), goto fail; } + /* + * The OS kernel is going to set its own permissions when it takes over + * paging a few million instructions from now, and load_image() will set up + * anything that's needed based on the section headers, so there's no point + * in doing anything but clearing the protection bits here. + */ + grub_dprintf("nx", "setting attributes for %p (%lu bytes) to %llx\n", + (void *)(grub_addr_t)address, fsize, 0llu); + grub_update_mem_attrs (address, fsize, + GRUB_MEM_ATTR_R|GRUB_MEM_ATTR_W|GRUB_MEM_ATTR_X, 0); + #if defined (__i386__) || defined (__x86_64__) if (fsize >= (grub_ssize_t) sizeof (struct grub_macho_fat_header)) { diff --git a/grub-core/loader/efi/linux.c b/grub-core/loader/efi/linux.c index 3ada50ed969..fe48001442a 100644 --- a/grub-core/loader/efi/linux.c +++ b/grub-core/loader/efi/linux.c @@ -96,16 +96,127 @@ static grub_efi_load_file2_t initrd_lf2 = { #pragma GCC diagnostic push #pragma GCC diagnostic ignored "-Wcast-align" +#pragma GCC diagnostic ignored "-Wint-to-pointer-cast" + +grub_err_t +grub_efi_check_nx_image_support (grub_addr_t k_add, + grub_size_t k_size, + int *nx_supported) +{ + struct grub_dos_header *doshdr; + grub_size_t sz = sizeof (*doshdr); + + struct grub_pe32_header_32 *pe32; + struct grub_pe32_header_64 *pe64; + + int image_is_compatible = 0; + int is_64_bit; + + if (k_size < sz) + return grub_error (GRUB_ERR_BAD_OS, N_("kernel is too small")); + + doshdr = (void *)k_add; + + if ((doshdr->magic & 0xffff) != GRUB_DOS_MAGIC) + return grub_error (GRUB_ERR_BAD_OS, N_("kernel DOS magic is invalid")); + + sz = doshdr->lfanew + sizeof (*pe32); + if (k_size < sz) + return grub_error (GRUB_ERR_BAD_OS, N_("kernel is too small")); + + pe32 = (struct grub_pe32_header_32 *)(k_add + doshdr->lfanew); + pe64 = (struct grub_pe32_header_64 *)pe32; + + if (grub_memcmp (pe32->signature, GRUB_PE32_SIGNATURE, + GRUB_PE32_SIGNATURE_SIZE) != 0) + return grub_error (GRUB_ERR_BAD_OS, N_("kernel PE magic is invalid")); + + switch (pe32->coff_header.machine) + { + case GRUB_PE32_MACHINE_ARMTHUMB_MIXED: + case GRUB_PE32_MACHINE_I386: + case GRUB_PE32_MACHINE_RISCV32: + is_64_bit = 0; + break; + case GRUB_PE32_MACHINE_ARM64: + case GRUB_PE32_MACHINE_IA64: + case GRUB_PE32_MACHINE_RISCV64: + case GRUB_PE32_MACHINE_X86_64: + is_64_bit = 1; + break; + default: + return grub_error (GRUB_ERR_BAD_OS, N_("PE machine type 0x%04hx unknown"), + pe32->coff_header.machine); + } + + if (is_64_bit) + { + sz = doshdr->lfanew + sizeof (*pe64); + if (k_size < sz) + return grub_error (GRUB_ERR_BAD_OS, N_("kernel is too small")); + + if (pe64->optional_header.dll_characteristics & GRUB_PE32_NX_COMPAT) + image_is_compatible = 1; + } + else + { + if (pe32->optional_header.dll_characteristics & GRUB_PE32_NX_COMPAT) + image_is_compatible = 1; + } + + *nx_supported = image_is_compatible; + return GRUB_ERR_NONE; +} + +grub_err_t +grub_efi_check_nx_required (int *nx_required) +{ + grub_efi_status_t status; + grub_guid_t guid = GRUB_EFI_SHIM_LOCK_GUID; + grub_size_t mok_policy_sz = 0; + char *mok_policy = NULL; + grub_uint32_t mok_policy_attrs = 0; + + status = grub_efi_get_variable_with_attributes ("MokPolicy", &guid, + &mok_policy_sz, + (void **)&mok_policy, + &mok_policy_attrs); + if (status == GRUB_EFI_NOT_FOUND || + mok_policy_sz == 0 || + mok_policy == NULL) + { + *nx_required = 0; + return GRUB_ERR_NONE; + } + + *nx_required = 0; + if (mok_policy_sz < 1 || + mok_policy_attrs != (GRUB_EFI_VARIABLE_BOOTSERVICE_ACCESS | + GRUB_EFI_VARIABLE_RUNTIME_ACCESS) || + (mok_policy[mok_policy_sz-1] & GRUB_MOK_POLICY_NX_REQUIRED)) + *nx_required = 1; + + return GRUB_ERR_NONE; +} typedef void (*handover_func) (void *, grub_efi_system_table_t *, void *); grub_err_t -grub_efi_linux_boot (void *kernel_address, grub_off_t ho_offset, - void *kernel_params) +grub_efi_linux_boot (grub_addr_t k_address, grub_size_t k_size, + grub_off_t h_offset, void *k_params, + int nx_supported) { grub_efi_loaded_image_t *loaded_image = NULL; handover_func hf; int offset = 0; + grub_uint64_t stack_set_attrs = GRUB_MEM_ATTR_R | + GRUB_MEM_ATTR_W | + GRUB_MEM_ATTR_X; + grub_uint64_t stack_clear_attrs = 0; + grub_uint64_t kernel_set_attrs = stack_set_attrs; + grub_uint64_t kernel_clear_attrs = stack_clear_attrs; + grub_uint64_t attrs; + int nx_required = 0; #ifdef __x86_64__ offset = 512; @@ -118,14 +229,59 @@ grub_efi_linux_boot (void *kernel_address, grub_off_t ho_offset, */ loaded_image = grub_efi_get_loaded_image (grub_efi_image_handle); if (loaded_image) - loaded_image->image_base = kernel_address; + loaded_image->image_base = (void *)k_address; else grub_dprintf ("linux", "Loaded Image base address could not be set\n"); grub_dprintf ("linux", "kernel_address: %p handover_offset: %p params: %p\n", - kernel_address, (void *)(grub_efi_uintn_t)ho_offset, kernel_params); - hf = (handover_func)((char *)kernel_address + ho_offset + offset); - hf (grub_efi_image_handle, grub_efi_system_table, kernel_params); + (void *)k_address, (void *)h_offset, k_params); + + + if (nx_required && !nx_supported) + return grub_error (GRUB_ERR_BAD_OS, N_("kernel does not support NX loading required by policy")); + + if (nx_supported) + { + kernel_set_attrs &= ~GRUB_MEM_ATTR_W; + kernel_clear_attrs |= GRUB_MEM_ATTR_W; + stack_set_attrs &= ~GRUB_MEM_ATTR_X; + stack_clear_attrs |= GRUB_MEM_ATTR_X; + } + + grub_dprintf ("nx", "Setting attributes for 0x%"PRIxGRUB_ADDR"-0x%"PRIxGRUB_ADDR" to r%cx\n", + k_address, k_address + k_size - 1, + (kernel_set_attrs & GRUB_MEM_ATTR_W) ? 'w' : '-'); + grub_update_mem_attrs (k_address, k_size, + kernel_set_attrs, kernel_clear_attrs); + + grub_get_mem_attrs (k_address, 4096, &attrs); + grub_dprintf ("nx", "permissions for 0x%"PRIxGRUB_ADDR" are %s%s%s\n", + (grub_addr_t)k_address, + (attrs & GRUB_MEM_ATTR_R) ? "r" : "-", + (attrs & GRUB_MEM_ATTR_W) ? "w" : "-", + (attrs & GRUB_MEM_ATTR_X) ? "x" : "-"); + if (grub_stack_addr != (grub_addr_t)-1ll) + { + grub_dprintf ("nx", "Setting attributes for stack at 0x%"PRIxGRUB_ADDR"-0x%"PRIxGRUB_ADDR" to rw%c\n", + grub_stack_addr, grub_stack_addr + grub_stack_size - 1, + (stack_set_attrs & GRUB_MEM_ATTR_X) ? 'x' : '-'); + grub_update_mem_attrs (grub_stack_addr, grub_stack_size, + stack_set_attrs, stack_clear_attrs); + + grub_get_mem_attrs (grub_stack_addr, 4096, &attrs); + grub_dprintf ("nx", "permissions for 0x%"PRIxGRUB_ADDR" are %s%s%s\n", + grub_stack_addr, + (attrs & GRUB_MEM_ATTR_R) ? "r" : "-", + (attrs & GRUB_MEM_ATTR_W) ? "w" : "-", + (attrs & GRUB_MEM_ATTR_X) ? "x" : "-"); + } + +#if defined(__i386__) || defined(__x86_64__) + asm volatile ("cli"); +#endif + + hf = (handover_func)((char *)k_address + h_offset + offset); + hf (grub_efi_image_handle, grub_efi_system_table, k_params); return GRUB_ERR_BUG; } @@ -287,13 +443,15 @@ free_params (void) } grub_err_t -grub_arch_efi_linux_boot_image (grub_addr_t addr, char *args) +grub_arch_efi_linux_boot_image (grub_addr_t addr, grub_size_t size, char *args, + int nx_supported) { grub_err_t retval; grub_dprintf ("linux", "linux command line: '%s'\n", args); - retval = grub_efi_linux_boot ((char *)addr, handover_offset, (void *)addr); + retval = grub_efi_linux_boot (addr, size, handover_offset, + (void *)addr, nx_supported); /* Never reached... */ free_params(); @@ -308,7 +466,10 @@ grub_linux_boot (void) return grub_errno; #endif - return grub_arch_efi_linux_boot_image ((grub_addr_t) kernel_addr, linux_args); + return grub_arch_efi_linux_boot_image((grub_addr_t)kernel_addr, + (grub_size_t)kernel_size, + linux_args, + 0); } static grub_err_t @@ -560,10 +721,9 @@ grub_cmd_linux (grub_command_t cmd __attribute__ ((unused)), struct linux_arch_kernel_header lh; grub_off_t filelen; grub_off_t filereadlen; - grub_uint32_t align; - grub_uint32_t code_size; void *kernel = NULL; grub_err_t err; + int nx_supported = 1; grub_dl_ref (my_mod); @@ -634,6 +794,8 @@ fallback: #if !defined(__i386__) && !defined(__x86_64__) + grub_uint32_t align; + grub_uint32_t code_size; if (parse_pe_header (kernel, &kernel_size, &handover_offset, &align, &code_size) != GRUB_ERR_NONE) goto fail; grub_dprintf ("linux", "kernel mem size : %lld\n", (long long) kernel_size); @@ -641,6 +803,10 @@ fallback: grub_dprintf ("linux", "kernel alignment : 0x%x\n", align); grub_dprintf ("linux", "kernel size : 0x%x\n", code_size); + err = grub_efi_check_nx_image_support((grub_addr_t)kernel, filelen, &nx_supported); + if (err != GRUB_ERR_NONE) + goto fail; + grub_loader_unset(); kernel_alloc_pages = GRUB_EFI_BYTES_TO_PAGES (kernel_size + align - 1); diff --git a/grub-core/loader/i386/efi/linux.c b/grub-core/loader/i386/efi/linux.c index f97b123a51e..abbf6b24f50 100644 --- a/grub-core/loader/i386/efi/linux.c +++ b/grub-core/loader/i386/efi/linux.c @@ -44,7 +44,7 @@ struct grub_linuxefi_context { grub_uint32_t handover_offset; struct linux_kernel_params *params; char *cmdline; - + int nx_supported; void *initrd_mem; }; @@ -134,13 +134,19 @@ kernel_alloc(kernel_alloc_purpose_t purpose, pages = BYTES_TO_PAGES(size); grub_dprintf ("linux", "Trying to allocate %lu pages from %p\n", (unsigned long)pages, (void *)(unsigned long)max); + size = pages * GRUB_EFI_PAGE_SIZE; prev_max = max; addr = grub_efi_allocate_pages_real (max, pages, max_addresses[i].alloc_type, memtype); if (addr) - grub_dprintf ("linux", "Allocated at %p\n", addr); + { + grub_dprintf ("linux", "Allocated at %p\n", addr); + grub_update_mem_attrs ((grub_addr_t)addr, size, + GRUB_MEM_ATTR_R|GRUB_MEM_ATTR_W, + GRUB_MEM_ATTR_X); + } } while (grub_error_pop ()) @@ -161,9 +167,11 @@ grub_linuxefi_boot (void *data) asm volatile ("cli"); - return grub_efi_linux_boot ((char *)context->kernel_mem, + return grub_efi_linux_boot ((grub_addr_t)context->kernel_mem, + context->kernel_size, context->handover_offset, - context->params); + context->params, + context->nx_supported); } static grub_err_t @@ -331,7 +339,9 @@ grub_cmd_linux (grub_command_t cmd __attribute__ ((unused)), grub_uint32_t handover_offset; struct linux_kernel_params *params = 0; char *cmdline = 0; + int nx_supported = 1; struct grub_linuxefi_context *context = 0; + grub_err_t err; grub_dl_ref (my_mod); @@ -361,6 +371,13 @@ grub_cmd_linux (grub_command_t cmd __attribute__ ((unused)), goto fail; } + err = grub_efi_check_nx_image_support ((grub_addr_t)kernel, filelen, + &nx_supported); + if (err != GRUB_ERR_NONE) + return err; + grub_dprintf ("linux", "nx is%s supported by this kernel\n", + nx_supported ? "" : " not"); + lh = (struct linux_i386_kernel_header *)kernel; grub_dprintf ("linux", "original lh is at %p\n", kernel); @@ -530,6 +547,7 @@ grub_cmd_linux (grub_command_t cmd __attribute__ ((unused)), context->handover_offset = handover_offset; context->params = params; context->cmdline = cmdline; + context->nx_supported = nx_supported; grub_loader_set_ex (grub_linuxefi_boot, grub_linuxefi_unload, context, 0); diff --git a/grub-core/loader/i386/linux.c b/grub-core/loader/i386/linux.c index 5a257552234..90121e9bc5a 100644 --- a/grub-core/loader/i386/linux.c +++ b/grub-core/loader/i386/linux.c @@ -817,6 +817,11 @@ grub_cmd_linux (grub_command_t cmd __attribute__ ((unused)), kernel_offset += len; } + grub_dprintf("efi", "setting attributes for %p (%zu bytes) to +rw-x\n", + &linux_params, sizeof (lh) + len); + grub_update_mem_attrs ((grub_addr_t)&linux_params, sizeof (lh) + len, + GRUB_MEM_ATTR_R|GRUB_MEM_ATTR_W, GRUB_MEM_ATTR_X); + linux_params.code32_start = prot_mode_target + lh.code32_start - GRUB_LINUX_BZIMAGE_ADDR; linux_params.kernel_alignment = (1 << align); linux_params.ps_mouse = linux_params.padding11 = 0; diff --git a/include/grub/efi/efi.h b/include/grub/efi/efi.h index c40684821e1..2c0e7f24bda 100644 --- a/include/grub/efi/efi.h +++ b/include/grub/efi/efi.h @@ -190,6 +190,9 @@ extern void (*EXPORT_VAR(grub_efi_net_config)) (grub_efi_handle_t hnd, void * EXPORT_FUNC (grub_efi_find_configuration_table) (const grub_guid_t *target_guid); +extern grub_addr_t EXPORT_VAR(grub_stack_addr); +extern grub_size_t EXPORT_VAR(grub_stack_size); + #if defined(__arm__) || defined(__aarch64__) || defined(__riscv) || defined(__loongarch__) void *EXPORT_FUNC(grub_efi_get_firmware_fdt)(void); grub_err_t EXPORT_FUNC(grub_efi_get_ram_base)(grub_addr_t *); @@ -197,7 +200,8 @@ grub_err_t EXPORT_FUNC(grub_efi_get_ram_base)(grub_addr_t *); #include grub_err_t grub_arch_efi_linux_load_image_header(grub_file_t file, struct linux_arch_kernel_header *lh); -grub_err_t grub_arch_efi_linux_boot_image(grub_addr_t addr, char *args); +grub_err_t grub_arch_efi_linux_boot_image(grub_addr_t addr, grub_size_t size, + char *args, int nx_enabled); grub_addr_t grub_efi_section_addr (const char *section); diff --git a/include/grub/efi/linux.h b/include/grub/efi/linux.h index c806a7757f3..5b4e626c37c 100644 --- a/include/grub/efi/linux.h +++ b/include/grub/efi/linux.h @@ -22,8 +22,20 @@ #include #include +#define GRUB_MOK_POLICY_NX_REQUIRED 0x1 + grub_err_t -EXPORT_FUNC(grub_efi_linux_boot) (void *kernel_address, grub_off_t offset, - void *kernel_param); +EXPORT_FUNC(grub_efi_linux_boot) (grub_addr_t kernel_address, + grub_size_t kernel_size, + grub_off_t handover_offset, + void *kernel_param, int nx_enabled); + +grub_err_t +EXPORT_FUNC(grub_efi_check_nx_image_support) (grub_addr_t kernel_addr, + grub_size_t kernel_size, + int *nx_supported); + +grub_err_t +EXPORT_FUNC(grub_efi_check_nx_required) (int *nx_required); #endif /* ! GRUB_EFI_LINUX_HEADER */ diff --git a/include/grub/efi/pe32.h b/include/grub/efi/pe32.h index 13fdd0e7d98..a4d62373cc9 100644 --- a/include/grub/efi/pe32.h +++ b/include/grub/efi/pe32.h @@ -194,6 +194,8 @@ struct grub_pe32_optional_header struct grub_pe32_data_directory reserved_entry; }; +#define GRUB_PE32_NX_COMPAT 0x0100 + struct grub_pe64_optional_header { grub_uint16_t magic;