Re: [PATCH V3] x86/efi: Free allocated memory if remap fails
From: Ard Biesheuvel
Date: Fri Jun 22 2018 - 02:32:23 EST
On 20 June 2018 at 05:03, Sai Praneeth Prakhya
<sai.praneeth.prakhya@xxxxxxxxx> wrote:
> From: Sai Praneeth <sai.praneeth.prakhya@xxxxxxxxx>
>
> efi_memmap_alloc(), as the name suggests, allocates memory for a new efi
> memory map. It's referenced from couple of places, namely,
> efi_arch_mem_reserve() and efi_free_boot_services(). These callers,
> after allocating memory, remap it for further use. As usual, a routine
> check is performed to confirm successful remap. If the remap fails,
> ideally, the allocated memory should be freed but presently we just
> return without freeing it up. Hence, fix this bug by introducing
> efi_memmap_free() which frees memory allocated by efi_memmap_alloc().
>
> As efi_memmap_alloc() allocates memory depending on whether mm_init()
> has already been invoked or not, introduce a new argument called "late"
> that lets us know which type of allocation was performed by
> efi_memmap_alloc(). Later, this is used by efi_memmap_free() to invoke
> the appropriate method to free the allocated memory. The other main
> purpose "late" argument serves is to make sure that efi_memmap_alloc()
> and efi_memmap_free() are always binded properly i.e. there could be a
> scenario in which efi_memmap_alloc() is used before slab_is_available()
> and efi_memmap_free() could be used after slab_is_available(). Without
> "late", this could break because allocation would have been done using
> memblock_alloc() while freeing will be done using __free_pages().
>
> Since these API's could easily be misused make it explicit, so that the
> caller has to pass "late" argument to efi_memmap_alloc() and later use
> the same for efi_memmap_free().
>
> Also, efi_fake_memmap() references efi_memmap_alloc() but it frees
> memory correctly using memblock_free(), but replace it with
> efi_memmap_free() to maintain consistency, as in, allocate memory with
> efi_memmap_alloc() and free memory with efi_memmap_free().
>
> It's a fact that memremap() and early_memremap() might never fail and
> this code might never get a chance to run but to maintain good kernel
> programming semantics, we might need this patch.
>
> Signed-off-by: Sai Praneeth Prakhya <sai.praneeth.prakhya@xxxxxxxxx>
Thanks Sai. I have queued this for -next
> Cc: Lee Chun-Yi <jlee@xxxxxxxx>
> Cc: Borislav Petkov <bp@xxxxxxxxx>
> Cc: Tony Luck <tony.luck@xxxxxxxxx>
> Cc: Dave Hansen <dave.hansen@xxxxxxxxx>
> Cc: Bhupesh Sharma <bhsharma@xxxxxxxxxx>
> Cc: Ricardo Neri <ricardo.neri@xxxxxxxxx>
> Cc: Peter Zijlstra <peterz@xxxxxxxxxxxxx>
> Cc: Ravi Shankar <ravi.v.shankar@xxxxxxxxx>
> Cc: Matt Fleming <matt@xxxxxxxxxxxxxxxxxxx>
> Cc: Ard Biesheuvel <ard.biesheuvel@xxxxxxxxxx>
> ---
>
> Changes from V2 to V3:
> ----------------------
> 1. Add a new argument "late" to efi_memmap_alloc(), so that
> efi_memmap_alloc() could communicate the type of allocation performed.
> 2. Re-introduce efi_memmap_free() (from V1) but with an extra argument
> "late", to know the type of allocation performed by efi_memmap_alloc().
>
> Changes from V1 to V2:
> ----------------------
> 1. Fix the bug of freeing memory map that was just installed by correctly
> calling free_pages().
> 2. Call memblock_free() and __free_pages() directly from the appropriate
> places instead of efi_memmap_free().
>
> Note: Patch based on Linus's mainline tree V4.18-rc1
>
> arch/x86/platform/efi/quirks.c | 16 ++++++++++++----
> drivers/firmware/efi/fake_mem.c | 5 +++--
> drivers/firmware/efi/memmap.c | 38 ++++++++++++++++++++++++++++++++++++--
> include/linux/efi.h | 3 ++-
> 4 files changed, 53 insertions(+), 9 deletions(-)
>
> diff --git a/arch/x86/platform/efi/quirks.c b/arch/x86/platform/efi/quirks.c
> index 36c1f8b9f7e0..ef5698a3af7a 100644
> --- a/arch/x86/platform/efi/quirks.c
> +++ b/arch/x86/platform/efi/quirks.c
> @@ -248,6 +248,7 @@ void __init efi_arch_mem_reserve(phys_addr_t addr, u64 size)
> efi_memory_desc_t md;
> int num_entries;
> void *new;
> + bool late;
>
> if (efi_mem_desc_lookup(addr, &md)) {
> pr_err("Failed to lookup EFI memory descriptor for %pa\n", &addr);
> @@ -276,7 +277,7 @@ void __init efi_arch_mem_reserve(phys_addr_t addr, u64 size)
>
> new_size = efi.memmap.desc_size * num_entries;
>
> - new_phys = efi_memmap_alloc(num_entries);
> + new_phys = efi_memmap_alloc(num_entries, &late);
> if (!new_phys) {
> pr_err("Could not allocate boot services memmap\n");
> return;
> @@ -285,6 +286,7 @@ void __init efi_arch_mem_reserve(phys_addr_t addr, u64 size)
> new = early_memremap(new_phys, new_size);
> if (!new) {
> pr_err("Failed to map new boot services memmap\n");
> + efi_memmap_free(new_phys, num_entries, late);
> return;
> }
>
> @@ -375,6 +377,7 @@ void __init efi_free_boot_services(void)
> efi_memory_desc_t *md;
> int num_entries = 0;
> void *new, *new_md;
> + bool late;
>
> for_each_efi_memory_desc(md) {
> unsigned long long start = md->phys_addr;
> @@ -420,7 +423,7 @@ void __init efi_free_boot_services(void)
> return;
>
> new_size = efi.memmap.desc_size * num_entries;
> - new_phys = efi_memmap_alloc(num_entries);
> + new_phys = efi_memmap_alloc(num_entries, &late);
> if (!new_phys) {
> pr_err("Failed to allocate new EFI memmap\n");
> return;
> @@ -429,7 +432,7 @@ void __init efi_free_boot_services(void)
> new = memremap(new_phys, new_size, MEMREMAP_WB);
> if (!new) {
> pr_err("Failed to map new EFI memmap\n");
> - return;
> + goto free_mem;
> }
>
> /*
> @@ -452,8 +455,13 @@ void __init efi_free_boot_services(void)
>
> if (efi_memmap_install(new_phys, num_entries)) {
> pr_err("Could not install new EFI memmap\n");
> - return;
> + goto free_mem;
> }
> +
> + return;
> +
> +free_mem:
> + efi_memmap_free(new_phys, num_entries, late);
> }
>
> /*
> diff --git a/drivers/firmware/efi/fake_mem.c b/drivers/firmware/efi/fake_mem.c
> index 6c7d60c239b5..52cb2b7fc2f7 100644
> --- a/drivers/firmware/efi/fake_mem.c
> +++ b/drivers/firmware/efi/fake_mem.c
> @@ -57,6 +57,7 @@ void __init efi_fake_memmap(void)
> phys_addr_t new_memmap_phy;
> void *new_memmap;
> int i;
> + bool late;
>
> if (!nr_fake_mem)
> return;
> @@ -71,7 +72,7 @@ void __init efi_fake_memmap(void)
> }
>
> /* allocate memory for new EFI memmap */
> - new_memmap_phy = efi_memmap_alloc(new_nr_map);
> + new_memmap_phy = efi_memmap_alloc(new_nr_map, &late);
> if (!new_memmap_phy)
> return;
>
> @@ -79,7 +80,7 @@ void __init efi_fake_memmap(void)
> new_memmap = early_memremap(new_memmap_phy,
> efi.memmap.desc_size * new_nr_map);
> if (!new_memmap) {
> - memblock_free(new_memmap_phy, efi.memmap.desc_size * new_nr_map);
> + efi_memmap_free(new_memmap_phy, new_nr_map, late);
> return;
> }
>
> diff --git a/drivers/firmware/efi/memmap.c b/drivers/firmware/efi/memmap.c
> index 5fc70520e04c..678e85704054 100644
> --- a/drivers/firmware/efi/memmap.c
> +++ b/drivers/firmware/efi/memmap.c
> @@ -32,6 +32,7 @@ static phys_addr_t __init __efi_memmap_alloc_late(unsigned long size)
> /**
> * efi_memmap_alloc - Allocate memory for the EFI memory map
> * @num_entries: Number of entries in the allocated map.
> + * @late: Type of allocation performed (late or early?).
> *
> * Depending on whether mm_init() has already been invoked or not,
> * either memblock or "normal" page allocation is used.
> @@ -39,17 +40,50 @@ static phys_addr_t __init __efi_memmap_alloc_late(unsigned long size)
> * Returns the physical address of the allocated memory map on
> * success, zero on failure.
> */
> -phys_addr_t __init efi_memmap_alloc(unsigned int num_entries)
> +phys_addr_t __init efi_memmap_alloc(unsigned int num_entries, bool *late)
> {
> unsigned long size = num_entries * efi.memmap.desc_size;
>
> - if (slab_is_available())
> + if (!late)
> + return 0;
> +
> + *late = slab_is_available();
> +
> + if (*late)
> return __efi_memmap_alloc_late(size);
>
> return __efi_memmap_alloc_early(size);
> }
>
> /**
> + * efi_memmap_free - Free memory allocated by efi_memmap_alloc()
> + * @mem: Physical address allocated by efi_memmap_alloc().
> + * @num_entries: Number of entries in the allocated map.
> + * @late: What type of allocation did efi_memmap_alloc() perform?
> + *
> + * Use this function _only_ in conjunction with efi_memmap_alloc().
> + * efi_memmap_alloc() allocates memory depending on whether mm_init()
> + * has already been invoked or not. It uses either memblock or "normal"
> + * page allocation. Since the allocation is done in two different ways,
> + * similarly, we free it in two different ways.
> + *
> + */
> +void __init efi_memmap_free(phys_addr_t mem, unsigned int num_entries, bool late)
> +{
> + unsigned long size = num_entries * efi.memmap.desc_size;
> + unsigned int order = get_order(size);
> + phys_addr_t end = mem + size - 1;
> +
> + if (late) {
> + __free_pages(pfn_to_page(PHYS_PFN(mem)), order);
> + return;
> + }
> +
> + if (memblock_free(mem, size))
> + pr_err("Failed to free mem from %pa to %pa\n", &mem, &end);
> +}
> +
> +/**
> * __efi_memmap_init - Common code for mapping the EFI memory map
> * @data: EFI memory map data
> * @late: Use early or late mapping function?
> diff --git a/include/linux/efi.h b/include/linux/efi.h
> index 56add823f190..f7f98d9a76f2 100644
> --- a/include/linux/efi.h
> +++ b/include/linux/efi.h
> @@ -1007,7 +1007,7 @@ static inline efi_status_t efi_query_variable_store(u32 attributes,
> #endif
> extern void __iomem *efi_lookup_mapped_addr(u64 phys_addr);
>
> -extern phys_addr_t __init efi_memmap_alloc(unsigned int num_entries);
> +extern phys_addr_t __init efi_memmap_alloc(unsigned int num_entries, bool *late);
> extern int __init efi_memmap_init_early(struct efi_memory_map_data *data);
> extern int __init efi_memmap_init_late(phys_addr_t addr, unsigned long size);
> extern void __init efi_memmap_unmap(void);
> @@ -1016,6 +1016,7 @@ extern int __init efi_memmap_split_count(efi_memory_desc_t *md,
> struct range *range);
> extern void __init efi_memmap_insert(struct efi_memory_map *old_memmap,
> void *buf, struct efi_mem_range *mem);
> +void __init efi_memmap_free(phys_addr_t mem, unsigned int num_entries, bool late);
>
> extern int efi_config_init(efi_config_table_type_t *arch_tables);
> #ifdef CONFIG_EFI_ESRT
> --
> 2.7.4
>