Skip to content

Commit b23b63c

Browse files
Ard Biesheuvelgregkh
Ard Biesheuvel
authored andcommitted
arm64/efi: Fix boot crash by not padding between EFI_MEMORY_RUNTIME regions
commit 0ce3cc0 upstream. The new Properties Table feature introduced in UEFIv2.5 may split memory regions that cover PE/COFF memory images into separate code and data regions. Since these regions only differ in the type (runtime code vs runtime data) and the permission bits, but not in the memory type attributes (UC/WC/WT/WB), the spec does not require them to be aligned to 64 KB. Since the relative offset of PE/COFF .text and .data segments cannot be changed on the fly, this means that we can no longer pad out those regions to be mappable using 64 KB pages. Unfortunately, there is no annotation in the UEFI memory map that identifies data regions that were split off from a code region, so we must apply this logic to all adjacent runtime regions whose attributes only differ in the permission bits. So instead of rounding each memory region to 64 KB alignment at both ends, only round down regions that are not directly preceded by another runtime region with the same type attributes. Since the UEFI spec does not mandate that the memory map be sorted, this means we also need to sort it first. Note that this change will result in all EFI_MEMORY_RUNTIME regions whose start addresses are not aligned to the OS page size to be mapped with executable permissions (i.e., on kernels compiled with 64 KB pages). However, since these mappings are only active during the time that UEFI Runtime Services are being invoked, the window for abuse is rather small. Tested-by: Mark Salter <msalter@redhat.com> Tested-by: Mark Rutland <mark.rutland@arm.com> [UEFI 2.4 only] Signed-off-by: Ard Biesheuvel <ard.biesheuvel@linaro.org> Signed-off-by: Matt Fleming <matt.fleming@intel.com> Reviewed-by: Mark Salter <msalter@redhat.com> Reviewed-by: Mark Rutland <mark.rutland@arm.com> Cc: Catalin Marinas <catalin.marinas@arm.com> Cc: Leif Lindholm <leif.lindholm@linaro.org> Cc: Linus Torvalds <torvalds@linux-foundation.org> Cc: Mike Galbraith <efault@gmx.de> Cc: Peter Zijlstra <peterz@infradead.org> Cc: Thomas Gleixner <tglx@linutronix.de> Cc: Will Deacon <will.deacon@arm.com> Cc: linux-kernel@vger.kernel.org Link: http://lkml.kernel.org/r/1443218539-7610-3-git-send-email-matt@codeblueprint.co.uk Signed-off-by: Ingo Molnar <mingo@kernel.org> Signed-off-by: Greg Kroah-Hartman <gregkh@linuxfoundation.org>
1 parent eed13ce commit b23b63c

File tree

2 files changed

+75
-16
lines changed

2 files changed

+75
-16
lines changed

arch/arm64/kernel/efi.c

+2-1
Original file line numberDiff line numberDiff line change
@@ -257,7 +257,8 @@ static bool __init efi_virtmap_init(void)
257257
*/
258258
if (!is_normal_ram(md))
259259
prot = __pgprot(PROT_DEVICE_nGnRE);
260-
else if (md->type == EFI_RUNTIME_SERVICES_CODE)
260+
else if (md->type == EFI_RUNTIME_SERVICES_CODE ||
261+
!PAGE_ALIGNED(md->phys_addr))
261262
prot = PAGE_KERNEL_EXEC;
262263
else
263264
prot = PAGE_KERNEL;

drivers/firmware/efi/libstub/arm-stub.c

+73-15
Original file line numberDiff line numberDiff line change
@@ -13,6 +13,7 @@
1313
*/
1414

1515
#include <linux/efi.h>
16+
#include <linux/sort.h>
1617
#include <asm/efi.h>
1718

1819
#include "efistub.h"
@@ -305,6 +306,44 @@ unsigned long efi_entry(void *handle, efi_system_table_t *sys_table,
305306
*/
306307
#define EFI_RT_VIRTUAL_BASE 0x40000000
307308

309+
static int cmp_mem_desc(const void *l, const void *r)
310+
{
311+
const efi_memory_desc_t *left = l, *right = r;
312+
313+
return (left->phys_addr > right->phys_addr) ? 1 : -1;
314+
}
315+
316+
/*
317+
* Returns whether region @left ends exactly where region @right starts,
318+
* or false if either argument is NULL.
319+
*/
320+
static bool regions_are_adjacent(efi_memory_desc_t *left,
321+
efi_memory_desc_t *right)
322+
{
323+
u64 left_end;
324+
325+
if (left == NULL || right == NULL)
326+
return false;
327+
328+
left_end = left->phys_addr + left->num_pages * EFI_PAGE_SIZE;
329+
330+
return left_end == right->phys_addr;
331+
}
332+
333+
/*
334+
* Returns whether region @left and region @right have compatible memory type
335+
* mapping attributes, and are both EFI_MEMORY_RUNTIME regions.
336+
*/
337+
static bool regions_have_compatible_memory_type_attrs(efi_memory_desc_t *left,
338+
efi_memory_desc_t *right)
339+
{
340+
static const u64 mem_type_mask = EFI_MEMORY_WB | EFI_MEMORY_WT |
341+
EFI_MEMORY_WC | EFI_MEMORY_UC |
342+
EFI_MEMORY_RUNTIME;
343+
344+
return ((left->attribute ^ right->attribute) & mem_type_mask) == 0;
345+
}
346+
308347
/*
309348
* efi_get_virtmap() - create a virtual mapping for the EFI memory map
310349
*
@@ -317,33 +356,52 @@ void efi_get_virtmap(efi_memory_desc_t *memory_map, unsigned long map_size,
317356
int *count)
318357
{
319358
u64 efi_virt_base = EFI_RT_VIRTUAL_BASE;
320-
efi_memory_desc_t *out = runtime_map;
359+
efi_memory_desc_t *in, *prev = NULL, *out = runtime_map;
321360
int l;
322361

323-
for (l = 0; l < map_size; l += desc_size) {
324-
efi_memory_desc_t *in = (void *)memory_map + l;
362+
/*
363+
* To work around potential issues with the Properties Table feature
364+
* introduced in UEFI 2.5, which may split PE/COFF executable images
365+
* in memory into several RuntimeServicesCode and RuntimeServicesData
366+
* regions, we need to preserve the relative offsets between adjacent
367+
* EFI_MEMORY_RUNTIME regions with the same memory type attributes.
368+
* The easiest way to find adjacent regions is to sort the memory map
369+
* before traversing it.
370+
*/
371+
sort(memory_map, map_size / desc_size, desc_size, cmp_mem_desc, NULL);
372+
373+
for (l = 0; l < map_size; l += desc_size, prev = in) {
325374
u64 paddr, size;
326375

376+
in = (void *)memory_map + l;
327377
if (!(in->attribute & EFI_MEMORY_RUNTIME))
328378
continue;
329379

380+
paddr = in->phys_addr;
381+
size = in->num_pages * EFI_PAGE_SIZE;
382+
330383
/*
331384
* Make the mapping compatible with 64k pages: this allows
332385
* a 4k page size kernel to kexec a 64k page size kernel and
333386
* vice versa.
334387
*/
335-
paddr = round_down(in->phys_addr, SZ_64K);
336-
size = round_up(in->num_pages * EFI_PAGE_SIZE +
337-
in->phys_addr - paddr, SZ_64K);
338-
339-
/*
340-
* Avoid wasting memory on PTEs by choosing a virtual base that
341-
* is compatible with section mappings if this region has the
342-
* appropriate size and physical alignment. (Sections are 2 MB
343-
* on 4k granule kernels)
344-
*/
345-
if (IS_ALIGNED(in->phys_addr, SZ_2M) && size >= SZ_2M)
346-
efi_virt_base = round_up(efi_virt_base, SZ_2M);
388+
if (!regions_are_adjacent(prev, in) ||
389+
!regions_have_compatible_memory_type_attrs(prev, in)) {
390+
391+
paddr = round_down(in->phys_addr, SZ_64K);
392+
size += in->phys_addr - paddr;
393+
394+
/*
395+
* Avoid wasting memory on PTEs by choosing a virtual
396+
* base that is compatible with section mappings if this
397+
* region has the appropriate size and physical
398+
* alignment. (Sections are 2 MB on 4k granule kernels)
399+
*/
400+
if (IS_ALIGNED(in->phys_addr, SZ_2M) && size >= SZ_2M)
401+
efi_virt_base = round_up(efi_virt_base, SZ_2M);
402+
else
403+
efi_virt_base = round_up(efi_virt_base, SZ_64K);
404+
}
347405

348406
in->virt_addr = efi_virt_base + in->phys_addr - paddr;
349407
efi_virt_base += size;

0 commit comments

Comments
 (0)