Skip to content

Commit

Permalink
x86/efi: Prevent mixed mode boot corruption with CONFIG_VMAP_STACK=y
Browse files Browse the repository at this point in the history
Booting an EFI mixed mode kernel has been crashing since commit:

  e37e43a ("x86/mm/64: Enable vmapped stacks (CONFIG_HAVE_ARCH_VMAP_STACK=y)")

The user-visible effect in my test setup was the kernel being unable
to find the root file system ramdisk. This was likely caused by silent
memory or page table corruption.

Enabling CONFIG_DEBUG_VIRTUAL=y immediately flagged the thunking code as
abusing virt_to_phys() because it was passing addresses that were not
part of the kernel direct mapping.

Use the slow version instead, which correctly handles all memory
regions by performing a page table walk.

Suggested-by: Andy Lutomirski <[email protected]>
Signed-off-by: Matt Fleming <[email protected]>
Cc: Andy Lutomirski <[email protected]>
Cc: Ard Biesheuvel <[email protected]>
Cc: Borislav Petkov <[email protected]>
Cc: Brian Gerst <[email protected]>
Cc: Denys Vlasenko <[email protected]>
Cc: H. Peter Anvin <[email protected]>
Cc: Josh Poimboeuf <[email protected]>
Cc: Linus Torvalds <[email protected]>
Cc: Peter Zijlstra <[email protected]>
Cc: Thomas Gleixner <[email protected]>
Cc: [email protected]
Link: http://lkml.kernel.org/r/[email protected]
Signed-off-by: Ingo Molnar <[email protected]>
  • Loading branch information
mfleming authored and Ingo Molnar committed Nov 13, 2016
1 parent 02e5690 commit f6697df
Showing 1 changed file with 57 additions and 23 deletions.
80 changes: 57 additions & 23 deletions arch/x86/platform/efi/efi_64.c
Original file line number Diff line number Diff line change
Expand Up @@ -31,6 +31,7 @@
#include <linux/io.h>
#include <linux/reboot.h>
#include <linux/slab.h>
#include <linux/ucs2_string.h>

#include <asm/setup.h>
#include <asm/page.h>
Expand Down Expand Up @@ -211,6 +212,35 @@ void efi_sync_low_kernel_mappings(void)
memcpy(pud_efi, pud_k, sizeof(pud_t) * num_entries);
}

/*
* Wrapper for slow_virt_to_phys() that handles NULL addresses.
*/
static inline phys_addr_t
virt_to_phys_or_null_size(void *va, unsigned long size)
{
bool bad_size;

if (!va)
return 0;

if (virt_addr_valid(va))
return virt_to_phys(va);

/*
* A fully aligned variable on the stack is guaranteed not to
* cross a page bounary. Try to catch strings on the stack by
* checking that 'size' is a power of two.
*/
bad_size = size > PAGE_SIZE || !is_power_of_2(size);

WARN_ON(!IS_ALIGNED((unsigned long)va, size) || bad_size);

return slow_virt_to_phys(va);
}

#define virt_to_phys_or_null(addr) \
virt_to_phys_or_null_size((addr), sizeof(*(addr)))

int __init efi_setup_page_tables(unsigned long pa_memmap, unsigned num_pages)
{
unsigned long pfn, text;
Expand Down Expand Up @@ -494,8 +524,8 @@ static efi_status_t efi_thunk_get_time(efi_time_t *tm, efi_time_cap_t *tc)

spin_lock(&rtc_lock);

phys_tm = virt_to_phys(tm);
phys_tc = virt_to_phys(tc);
phys_tm = virt_to_phys_or_null(tm);
phys_tc = virt_to_phys_or_null(tc);

status = efi_thunk(get_time, phys_tm, phys_tc);

Expand All @@ -511,7 +541,7 @@ static efi_status_t efi_thunk_set_time(efi_time_t *tm)

spin_lock(&rtc_lock);

phys_tm = virt_to_phys(tm);
phys_tm = virt_to_phys_or_null(tm);

status = efi_thunk(set_time, phys_tm);

Expand All @@ -529,9 +559,9 @@ efi_thunk_get_wakeup_time(efi_bool_t *enabled, efi_bool_t *pending,

spin_lock(&rtc_lock);

phys_enabled = virt_to_phys(enabled);
phys_pending = virt_to_phys(pending);
phys_tm = virt_to_phys(tm);
phys_enabled = virt_to_phys_or_null(enabled);
phys_pending = virt_to_phys_or_null(pending);
phys_tm = virt_to_phys_or_null(tm);

status = efi_thunk(get_wakeup_time, phys_enabled,
phys_pending, phys_tm);
Expand All @@ -549,7 +579,7 @@ efi_thunk_set_wakeup_time(efi_bool_t enabled, efi_time_t *tm)

spin_lock(&rtc_lock);

phys_tm = virt_to_phys(tm);
phys_tm = virt_to_phys_or_null(tm);

status = efi_thunk(set_wakeup_time, enabled, phys_tm);

Expand All @@ -558,6 +588,10 @@ efi_thunk_set_wakeup_time(efi_bool_t enabled, efi_time_t *tm)
return status;
}

static unsigned long efi_name_size(efi_char16_t *name)
{
return ucs2_strsize(name, EFI_VAR_NAME_LEN) + 1;
}

static efi_status_t
efi_thunk_get_variable(efi_char16_t *name, efi_guid_t *vendor,
Expand All @@ -567,11 +601,11 @@ efi_thunk_get_variable(efi_char16_t *name, efi_guid_t *vendor,
u32 phys_name, phys_vendor, phys_attr;
u32 phys_data_size, phys_data;

phys_data_size = virt_to_phys(data_size);
phys_vendor = virt_to_phys(vendor);
phys_name = virt_to_phys(name);
phys_attr = virt_to_phys(attr);
phys_data = virt_to_phys(data);
phys_data_size = virt_to_phys_or_null(data_size);
phys_vendor = virt_to_phys_or_null(vendor);
phys_name = virt_to_phys_or_null_size(name, efi_name_size(name));
phys_attr = virt_to_phys_or_null(attr);
phys_data = virt_to_phys_or_null_size(data, *data_size);

status = efi_thunk(get_variable, phys_name, phys_vendor,
phys_attr, phys_data_size, phys_data);
Expand All @@ -586,9 +620,9 @@ efi_thunk_set_variable(efi_char16_t *name, efi_guid_t *vendor,
u32 phys_name, phys_vendor, phys_data;
efi_status_t status;

phys_name = virt_to_phys(name);
phys_vendor = virt_to_phys(vendor);
phys_data = virt_to_phys(data);
phys_name = virt_to_phys_or_null_size(name, efi_name_size(name));
phys_vendor = virt_to_phys_or_null(vendor);
phys_data = virt_to_phys_or_null_size(data, data_size);

/* If data_size is > sizeof(u32) we've got problems */
status = efi_thunk(set_variable, phys_name, phys_vendor,
Expand All @@ -605,9 +639,9 @@ efi_thunk_get_next_variable(unsigned long *name_size,
efi_status_t status;
u32 phys_name_size, phys_name, phys_vendor;

phys_name_size = virt_to_phys(name_size);
phys_vendor = virt_to_phys(vendor);
phys_name = virt_to_phys(name);
phys_name_size = virt_to_phys_or_null(name_size);
phys_vendor = virt_to_phys_or_null(vendor);
phys_name = virt_to_phys_or_null_size(name, *name_size);

status = efi_thunk(get_next_variable, phys_name_size,
phys_name, phys_vendor);
Expand All @@ -621,7 +655,7 @@ efi_thunk_get_next_high_mono_count(u32 *count)
efi_status_t status;
u32 phys_count;

phys_count = virt_to_phys(count);
phys_count = virt_to_phys_or_null(count);
status = efi_thunk(get_next_high_mono_count, phys_count);

return status;
Expand All @@ -633,7 +667,7 @@ efi_thunk_reset_system(int reset_type, efi_status_t status,
{
u32 phys_data;

phys_data = virt_to_phys(data);
phys_data = virt_to_phys_or_null_size(data, data_size);

efi_thunk(reset_system, reset_type, status, data_size, phys_data);
}
Expand Down Expand Up @@ -661,9 +695,9 @@ efi_thunk_query_variable_info(u32 attr, u64 *storage_space,
if (efi.runtime_version < EFI_2_00_SYSTEM_TABLE_REVISION)
return EFI_UNSUPPORTED;

phys_storage = virt_to_phys(storage_space);
phys_remaining = virt_to_phys(remaining_space);
phys_max = virt_to_phys(max_variable_size);
phys_storage = virt_to_phys_or_null(storage_space);
phys_remaining = virt_to_phys_or_null(remaining_space);
phys_max = virt_to_phys_or_null(max_variable_size);

status = efi_thunk(query_variable_info, attr, phys_storage,
phys_remaining, phys_max);
Expand Down

0 comments on commit f6697df

Please sign in to comment.