提交 220dd769 编写于 作者: K Kairui Song 提交者: Ingo Molnar

x86, efi: Never relocate kernel below lowest acceptable address

Currently, kernel fails to boot on some HyperV VMs when using EFI.
And it's a potential issue on all x86 platforms.

It's caused by broken kernel relocation on EFI systems, when below three
conditions are met:

1. Kernel image is not loaded to the default address (LOAD_PHYSICAL_ADDR)
   by the loader.
2. There isn't enough room to contain the kernel, starting from the
   default load address (eg. something else occupied part the region).
3. In the memmap provided by EFI firmware, there is a memory region
   starts below LOAD_PHYSICAL_ADDR, and suitable for containing the
   kernel.

EFI stub will perform a kernel relocation when condition 1 is met. But
due to condition 2, EFI stub can't relocate kernel to the preferred
address, so it fallback to ask EFI firmware to alloc lowest usable memory
region, got the low region mentioned in condition 3, and relocated
kernel there.

It's incorrect to relocate the kernel below LOAD_PHYSICAL_ADDR. This
is the lowest acceptable kernel relocation address.

The first thing goes wrong is in arch/x86/boot/compressed/head_64.S.
Kernel decompression will force use LOAD_PHYSICAL_ADDR as the output
address if kernel is located below it. Then the relocation before
decompression, which move kernel to the end of the decompression buffer,
will overwrite other memory region, as there is no enough memory there.

To fix it, just don't let EFI stub relocate the kernel to any address
lower than lowest acceptable address.

[ ardb: introduce efi_low_alloc_above() to reduce the scope of the change ]
Signed-off-by: NKairui Song <kasong@redhat.com>
Signed-off-by: NArd Biesheuvel <ard.biesheuvel@linaro.org>
Acked-by: NJarkko Sakkinen <jarkko.sakkinen@linux.intel.com>
Cc: Linus Torvalds <torvalds@linux-foundation.org>
Cc: Peter Zijlstra <peterz@infradead.org>
Cc: Thomas Gleixner <tglx@linutronix.de>
Cc: linux-efi@vger.kernel.org
Link: https://lkml.kernel.org/r/20191029173755.27149-6-ardb@kernel.orgSigned-off-by: NIngo Molnar <mingo@kernel.org>
上级 41cd96fa
...@@ -13,6 +13,7 @@ ...@@ -13,6 +13,7 @@
#include <asm/e820/types.h> #include <asm/e820/types.h>
#include <asm/setup.h> #include <asm/setup.h>
#include <asm/desc.h> #include <asm/desc.h>
#include <asm/boot.h>
#include "../string.h" #include "../string.h"
#include "eboot.h" #include "eboot.h"
...@@ -813,7 +814,8 @@ efi_main(struct efi_config *c, struct boot_params *boot_params) ...@@ -813,7 +814,8 @@ efi_main(struct efi_config *c, struct boot_params *boot_params)
status = efi_relocate_kernel(sys_table, &bzimage_addr, status = efi_relocate_kernel(sys_table, &bzimage_addr,
hdr->init_size, hdr->init_size, hdr->init_size, hdr->init_size,
hdr->pref_address, hdr->pref_address,
hdr->kernel_alignment); hdr->kernel_alignment,
LOAD_PHYSICAL_ADDR);
if (status != EFI_SUCCESS) { if (status != EFI_SUCCESS) {
efi_printk(sys_table, "efi_relocate_kernel() failed!\n"); efi_printk(sys_table, "efi_relocate_kernel() failed!\n");
goto fail; goto fail;
......
...@@ -230,7 +230,7 @@ efi_status_t handle_kernel_image(efi_system_table_t *sys_table, ...@@ -230,7 +230,7 @@ efi_status_t handle_kernel_image(efi_system_table_t *sys_table,
*image_size = image->image_size; *image_size = image->image_size;
status = efi_relocate_kernel(sys_table, image_addr, *image_size, status = efi_relocate_kernel(sys_table, image_addr, *image_size,
*image_size, *image_size,
kernel_base + MAX_UNCOMP_KERNEL_SIZE, 0); kernel_base + MAX_UNCOMP_KERNEL_SIZE, 0, 0);
if (status != EFI_SUCCESS) { if (status != EFI_SUCCESS) {
pr_efi_err(sys_table, "Failed to relocate kernel.\n"); pr_efi_err(sys_table, "Failed to relocate kernel.\n");
efi_free(sys_table, *reserve_size, *reserve_addr); efi_free(sys_table, *reserve_size, *reserve_addr);
......
...@@ -260,11 +260,11 @@ efi_status_t efi_high_alloc(efi_system_table_t *sys_table_arg, ...@@ -260,11 +260,11 @@ efi_status_t efi_high_alloc(efi_system_table_t *sys_table_arg,
} }
/* /*
* Allocate at the lowest possible address. * Allocate at the lowest possible address that is not below 'min'.
*/ */
efi_status_t efi_low_alloc(efi_system_table_t *sys_table_arg, efi_status_t efi_low_alloc_above(efi_system_table_t *sys_table_arg,
unsigned long size, unsigned long align, unsigned long size, unsigned long align,
unsigned long *addr) unsigned long *addr, unsigned long min)
{ {
unsigned long map_size, desc_size, buff_size; unsigned long map_size, desc_size, buff_size;
efi_memory_desc_t *map; efi_memory_desc_t *map;
...@@ -311,13 +311,8 @@ efi_status_t efi_low_alloc(efi_system_table_t *sys_table_arg, ...@@ -311,13 +311,8 @@ efi_status_t efi_low_alloc(efi_system_table_t *sys_table_arg,
start = desc->phys_addr; start = desc->phys_addr;
end = start + desc->num_pages * EFI_PAGE_SIZE; end = start + desc->num_pages * EFI_PAGE_SIZE;
/* if (start < min)
* Don't allocate at 0x0. It will confuse code that start = min;
* checks pointers against NULL. Skip the first 8
* bytes so we start at a nice even number.
*/
if (start == 0x0)
start += 8;
start = round_up(start, align); start = round_up(start, align);
if ((start + size) > end) if ((start + size) > end)
...@@ -698,7 +693,8 @@ efi_status_t efi_relocate_kernel(efi_system_table_t *sys_table_arg, ...@@ -698,7 +693,8 @@ efi_status_t efi_relocate_kernel(efi_system_table_t *sys_table_arg,
unsigned long image_size, unsigned long image_size,
unsigned long alloc_size, unsigned long alloc_size,
unsigned long preferred_addr, unsigned long preferred_addr,
unsigned long alignment) unsigned long alignment,
unsigned long min_addr)
{ {
unsigned long cur_image_addr; unsigned long cur_image_addr;
unsigned long new_addr = 0; unsigned long new_addr = 0;
...@@ -731,8 +727,8 @@ efi_status_t efi_relocate_kernel(efi_system_table_t *sys_table_arg, ...@@ -731,8 +727,8 @@ efi_status_t efi_relocate_kernel(efi_system_table_t *sys_table_arg,
* possible. * possible.
*/ */
if (status != EFI_SUCCESS) { if (status != EFI_SUCCESS) {
status = efi_low_alloc(sys_table_arg, alloc_size, alignment, status = efi_low_alloc_above(sys_table_arg, alloc_size,
&new_addr); alignment, &new_addr, min_addr);
} }
if (status != EFI_SUCCESS) { if (status != EFI_SUCCESS) {
pr_efi_err(sys_table_arg, "Failed to allocate usable memory for kernel.\n"); pr_efi_err(sys_table_arg, "Failed to allocate usable memory for kernel.\n");
......
...@@ -1579,9 +1579,22 @@ char *efi_convert_cmdline(efi_system_table_t *sys_table_arg, ...@@ -1579,9 +1579,22 @@ char *efi_convert_cmdline(efi_system_table_t *sys_table_arg,
efi_status_t efi_get_memory_map(efi_system_table_t *sys_table_arg, efi_status_t efi_get_memory_map(efi_system_table_t *sys_table_arg,
struct efi_boot_memmap *map); struct efi_boot_memmap *map);
efi_status_t efi_low_alloc_above(efi_system_table_t *sys_table_arg,
unsigned long size, unsigned long align,
unsigned long *addr, unsigned long min);
static inline
efi_status_t efi_low_alloc(efi_system_table_t *sys_table_arg, efi_status_t efi_low_alloc(efi_system_table_t *sys_table_arg,
unsigned long size, unsigned long align, unsigned long size, unsigned long align,
unsigned long *addr); unsigned long *addr)
{
/*
* Don't allocate at 0x0. It will confuse code that
* checks pointers against NULL. Skip the first 8
* bytes so we start at a nice even number.
*/
return efi_low_alloc_above(sys_table_arg, size, align, addr, 0x8);
}
efi_status_t efi_high_alloc(efi_system_table_t *sys_table_arg, efi_status_t efi_high_alloc(efi_system_table_t *sys_table_arg,
unsigned long size, unsigned long align, unsigned long size, unsigned long align,
...@@ -1592,7 +1605,8 @@ efi_status_t efi_relocate_kernel(efi_system_table_t *sys_table_arg, ...@@ -1592,7 +1605,8 @@ efi_status_t efi_relocate_kernel(efi_system_table_t *sys_table_arg,
unsigned long image_size, unsigned long image_size,
unsigned long alloc_size, unsigned long alloc_size,
unsigned long preferred_addr, unsigned long preferred_addr,
unsigned long alignment); unsigned long alignment,
unsigned long min_addr);
efi_status_t handle_cmdline_files(efi_system_table_t *sys_table_arg, efi_status_t handle_cmdline_files(efi_system_table_t *sys_table_arg,
efi_loaded_image_t *image, efi_loaded_image_t *image,
......
Markdown is supported
0% .
You are about to add 0 people to the discussion. Proceed with caution.
先完成此消息的编辑!
想要评论请 注册