efi: libstub: ensure allocated memory to be executable

There are UEFI versions that restrict execution of memory regions,
preventing the kernel from booting. Parts that needs to be executable
are:

* Area used for trampoline placement.
* All memory regions that the kernel may be relocated before
  and during extraction.

Use DXE services to ensure aforementioned address ranges
to be executable. Only modify attributes that does not
have appropriate attributes.

Signed-off-by: Baskov Evgeniy <baskov@ispras.ru>
Link: https://lore.kernel.org/r/20220303142120.1975-3-baskov@ispras.ru
Signed-off-by: Ard Biesheuvel <ardb@kernel.org>
This commit is contained in:
Baskov Evgeniy 2022-03-03 17:21:20 +03:00 committed by Ard Biesheuvel
parent 3ba75c1316
commit 82e0d6d76a
2 changed files with 118 additions and 4 deletions

View File

@ -91,6 +91,18 @@ config EFI_SOFT_RESERVE
If unsure, say Y.
config EFI_DXE_MEM_ATTRIBUTES
bool "Adjust memory attributes in EFISTUB"
depends on EFI && EFI_STUB && X86
default y
help
UEFI specification does not guarantee all memory to be
accessible for both write and execute as the kernel expects
it to be.
Use DXE services to check and alter memory protection
attributes during boot via EFISTUB to ensure that memory
ranges used by the kernel are writable and executable.
config EFI_PARAMS_FROM_FDT
bool
help

View File

@ -212,9 +212,110 @@ static void retrieve_apple_device_properties(struct boot_params *boot_params)
}
}
static void
adjust_memory_range_protection(unsigned long start, unsigned long size)
{
efi_status_t status;
efi_gcd_memory_space_desc_t desc;
unsigned long end, next;
unsigned long rounded_start, rounded_end;
unsigned long unprotect_start, unprotect_size;
int has_system_memory = 0;
if (efi_dxe_table == NULL)
return;
rounded_start = rounddown(start, EFI_PAGE_SIZE);
rounded_end = roundup(start + size, EFI_PAGE_SIZE);
/*
* Don't modify memory region attributes, they are
* already suitable, to lower the possibility to
* encounter firmware bugs.
*/
for (end = start + size; start < end; start = next) {
status = efi_dxe_call(get_memory_space_descriptor, start, &desc);
if (status != EFI_SUCCESS)
return;
next = desc.base_address + desc.length;
/*
* Only system memory is suitable for trampoline/kernel image placement,
* so only this type of memory needs its attributes to be modified.
*/
if (desc.gcd_memory_type != EfiGcdMemoryTypeSystemMemory ||
(desc.attributes & (EFI_MEMORY_RO | EFI_MEMORY_XP)) == 0)
continue;
unprotect_start = max(rounded_start, (unsigned long)desc.base_address);
unprotect_size = min(rounded_end, next) - unprotect_start;
status = efi_dxe_call(set_memory_space_attributes,
unprotect_start, unprotect_size,
EFI_MEMORY_WB);
if (status != EFI_SUCCESS) {
efi_warn("Unable to unprotect memory range [%08lx,%08lx]: %d\n",
unprotect_start,
unprotect_start + unprotect_size,
(int)status);
}
}
}
/*
* Trampoline takes 2 pages and can be loaded in first megabyte of memory
* with its end placed between 128k and 640k where BIOS might start.
* (see arch/x86/boot/compressed/pgtable_64.c)
*
* We cannot find exact trampoline placement since memory map
* can be modified by UEFI, and it can alter the computed address.
*/
#define TRAMPOLINE_PLACEMENT_BASE ((128 - 8)*1024)
#define TRAMPOLINE_PLACEMENT_SIZE (640*1024 - (128 - 8)*1024)
void startup_32(struct boot_params *boot_params);
static void
setup_memory_protection(unsigned long image_base, unsigned long image_size)
{
/*
* Allow execution of possible trampoline used
* for switching between 4- and 5-level page tables
* and relocated kernel image.
*/
adjust_memory_range_protection(TRAMPOLINE_PLACEMENT_BASE,
TRAMPOLINE_PLACEMENT_SIZE);
#ifdef CONFIG_64BIT
if (image_base != (unsigned long)startup_32)
adjust_memory_range_protection(image_base, image_size);
#else
/*
* Clear protection flags on a whole range of possible
* addresses used for KASLR. We don't need to do that
* on x86_64, since KASLR/extraction is performed after
* dedicated identity page tables are built and we only
* need to remove possible protection on relocated image
* itself disregarding further relocations.
*/
adjust_memory_range_protection(LOAD_PHYSICAL_ADDR,
KERNEL_IMAGE_SIZE - LOAD_PHYSICAL_ADDR);
#endif
}
static const efi_char16_t apple[] = L"Apple";
static void setup_quirks(struct boot_params *boot_params)
static void setup_quirks(struct boot_params *boot_params,
unsigned long image_base,
unsigned long image_size)
{
efi_char16_t *fw_vendor = (efi_char16_t *)(unsigned long)
efi_table_attr(efi_system_table, fw_vendor);
@ -223,6 +324,9 @@ static void setup_quirks(struct boot_params *boot_params)
if (IS_ENABLED(CONFIG_APPLE_PROPERTIES))
retrieve_apple_device_properties(boot_params);
}
if (IS_ENABLED(CONFIG_EFI_DXE_MEM_ATTRIBUTES))
setup_memory_protection(image_base, image_size);
}
/*
@ -342,8 +446,6 @@ static void __noreturn efi_exit(efi_handle_t handle, efi_status_t status)
asm("hlt");
}
void startup_32(struct boot_params *boot_params);
void __noreturn efi_stub_entry(efi_handle_t handle,
efi_system_table_t *sys_table_arg,
struct boot_params *boot_params);
@ -798,7 +900,7 @@ unsigned long efi_main(efi_handle_t handle,
setup_efi_pci(boot_params);
setup_quirks(boot_params);
setup_quirks(boot_params, bzimage_addr, buffer_end - buffer_start);
status = exit_boot(boot_params, handle);
if (status != EFI_SUCCESS) {