efi/libstub/x86: Work around page freeing issue in mixed mode
authorArd Biesheuvel <ardb@kernel.org>
Tue, 24 Dec 2019 15:10:20 +0000 (16:10 +0100)
committerIngo Molnar <mingo@kernel.org>
Wed, 25 Dec 2019 09:49:23 +0000 (10:49 +0100)
Mixed mode translates calls from the 64-bit kernel into the 32-bit
firmware by wrapping them in a call to a thunking routine that
pushes a 32-bit word onto the stack for each argument passed to the
function, regardless of the argument type. This works surprisingly
well for most services and protocols, with the exception of ones that
take explicit 64-bit arguments.

efi_free() invokes the FreePages() EFI boot service, which takes
a efi_physical_addr_t as its address argument, and this is one of
those 64-bit types. This means that the 32-bit firmware will
interpret the (addr, size) pair as a single 64-bit quantity, and
since it is guaranteed to have the high word set (as size > 0),
it will always fail due to the fact that EFI memory allocations are
always < 4 GB on 32-bit firmware.

So let's fix this by giving the thunking code a little hand, and
pass two values for the address, and a third one for the size.

Signed-off-by: Ard Biesheuvel <ardb@kernel.org>
Cc: Arvind Sankar <nivedita@alum.mit.edu>
Cc: Borislav Petkov <bp@alien8.de>
Cc: James Morse <james.morse@arm.com>
Cc: Matt Fleming <matt@codeblueprint.co.uk>
Cc: Thomas Gleixner <tglx@linutronix.de>
Cc: linux-efi@vger.kernel.org
Link: https://lkml.kernel.org/r/20191224151025.32482-21-ardb@kernel.org
Signed-off-by: Ingo Molnar <mingo@kernel.org>
arch/x86/boot/compressed/eboot.c
drivers/firmware/efi/libstub/efi-stub-helper.c

index f81dd66626cee071d347cd1155c91eb3b98897b6..ec92c4decc86817d0e2c6b778cfa95b35bd56618 100644 (file)
@@ -901,3 +901,19 @@ fail:
        for (;;)
                asm("hlt");
 }
+
+#ifdef CONFIG_EFI_MIXED
+void efi_free_native(unsigned long size, unsigned long addr);
+
+void efi_free(unsigned long size, unsigned long addr)
+{
+       if (!size)
+               return;
+
+       if (efi_is_native())
+               efi_free_native(size, addr);
+       else
+               efi64_thunk(efi_system_table()->boottime->mixed_mode.free_pages,
+                           addr, 0, DIV_ROUND_UP(size, EFI_PAGE_SIZE));
+}
+#endif
index d4215571f05a288c7baa851957faa49e73512f4d..b715ac6a0c94b4be60f13ec76de701acdba4c1f9 100644 (file)
@@ -346,6 +346,9 @@ fail:
 }
 
 void efi_free(unsigned long size, unsigned long addr)
+       __weak __alias(efi_free_native);
+
+void efi_free_native(unsigned long size, unsigned long addr)
 {
        unsigned long nr_pages;
 
@@ -353,7 +356,7 @@ void efi_free(unsigned long size, unsigned long addr)
                return;
 
        nr_pages = round_up(size, EFI_ALLOC_ALIGN) / EFI_PAGE_SIZE;
-       efi_call_early(free_pages, addr, nr_pages);
+       efi_system_table()->boottime->free_pages(addr, nr_pages);
 }
 
 static efi_status_t efi_file_size(void *__fh, efi_char16_t *filename_16,