EFI urgent fixes
Subsystems (3)
Folks, this is v2 of this pull request. Ingo's comments have been addressed and new patches have been spun. Also now rebased against v4.8-rc5.
Please pull the following urgent fixes, which prevent an infinite loop on Xen, a boot failure on Qualcomm platforms and a data type bug in the fdt code.
The following changes since commit c6935931c1894ff857616ff8549b61236a19148f:
Linux 4.8-rc5 (2016-09-04 14:31:46 -0700)
are available in the git repository at:
git://git.kernel.org/pub/scm/linux/kernel/git/mfleming/efi.git tags/efi-urgent
for you to fetch changes up to d64934019f6cc39202e2f78063709f61ca5cb364:
x86/efi: Use efi_exit_boot_services() (2016-09-05 12:40:16 +0100)
----------------------------------------------------------------
* Make for_each_efi_memory_desc_in_map() safe on Xen and prevent an infinte loop - Jan Beulich
* Fix boot error on arm64 Qualcomm platforms by refactoring and improving the ExitBootServices() hack we already for x86 and moving it to the libstub - Jeffrey Hugo
* Use correct return data type for of_get_flat_dt_subnode_by_name() so that we correctly handle errors - Andrzej Hajda
----------------------------------------------------------------
Andrzej Hajda (1):
efi: Fix handling error value in fdt_find_uefi_params
Jan Beulich (1):
efi: Make for_each_efi_memory_desc_in_map() cope with running on Xen
Jeffrey Hugo (4):
efi/libstub: Allocate headspace in efi_get_memory_map() efi/libstub: Introduce ExitBootServices helper efi/libstub: Use efi_exit_boot_services() in FDT x86/efi: Use efi_exit_boot_services()
arch/x86/boot/compressed/eboot.c | 134 ++++++++++---------- drivers/firmware/efi/efi.c | 7 +- drivers/firmware/efi/libstub/efi-stub-helper.c | 169 ++++++++++++++++++++----- drivers/firmware/efi/libstub/fdt.c | 54 +++++--- drivers/firmware/efi/libstub/random.c | 12 +- include/linux/efi.h | 27 +++- 6 files changed, 283 insertions(+), 120 deletions(-)
PATCH 1/6 efi: Make for_each_efi_memory_desc_in_map() cope with running on Xen
From: Jan Beulich <JBeulich-IBi9RG/[email protected]>
While commit 55f1ea15216 ("efi: Fix for_each_efi_memory_desc_in_map() for empty memmaps") made an attempt to deal with empty memory maps, it didn't address the case where the map field never gets set, as is apparently the case when running under Xen.
--- include/linux/efi.h | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-)
include/linux/efi.h +1 -1
@@ -946,7 +946,7 @@ extern int efi_memattr_apply_permissions(struct mm_struct *mm,/* Iterate through an efi_memory_map */ #define for_each_efi_memory_desc_in_map (m, md) \ for ((md) = (m)->map; \ - ((void *)(md) + (m)->desc_size) <= (m)->map_end; \ + (md) && ((void *)(md) + (m)->desc_size) <= (m)->map_end; \ (md) = (void *)(md) + (m)->desc_size)/**
Signature · 1 line
2.9.3
PATCH 2/6 efi: Fix handling error value in fdt_find_uefi_params
In reply to Matt FlemingFrom: Andrzej Hajda <[email protected]>
of_get_flat_dt_subnode_by_name can return negative value in case of error. Assigning the result to unsigned variable and checking if the variable is lesser than zero is incorrect and always false.
The patch fixes it by using signed variable to check the result.
The problem has been detected using semantic patch scripts/coccinelle/tests/unsigned_lesser_than_zero.cocci
--- drivers/firmware/efi/efi.c | 7 +++++-- 1 file changed, 5 insertions(+), 2 deletions(-)
drivers/firmware/efi/efi.c +5 -2
@@ -657,9 +657,12 @@ static int __init fdt_find_uefi_params(unsigned long node, const char *uname, }if (subnode) { - node =of_get_flat_dt_subnode_by_name (node, subnode); -if (node <0 ) +int err =of_get_flat_dt_subnode_by_name (node, subnode); + +if (err <0 )return 0 ; + + node = err; }return __find_uefi_params (node, info, dt_params[i].params);
Signature · 1 line
2.9.3
PATCH 3/6 efi/libstub: Allocate headspace in efi_get_memory_map()
In reply to Matt FlemingFrom: Jeffrey Hugo <[email protected]>
efi_get_memory_map() allocates a buffer to store the memory map that it retrieves. This buffer may need to be reused by the client after ExitBootServices() is called, at which point allocations are not longer permitted. To support this usecase, provide the allocated buffer size back to the client, and allocate some additional headroom to account for any reasonable growth in the map that is likely to happen between the call to efi_get_memory_map() and the client reusing the buffer.
--- arch/x86/boot/compressed/eboot.c | 20 ++++-- drivers/firmware/efi/libstub/efi-stub-helper.c | 96 ++++++++++++++++++-------- drivers/firmware/efi/libstub/fdt.c | 17 +++-- drivers/firmware/efi/libstub/random.c | 12 +++- include/linux/efi.h | 15 ++-- 5 files changed, 111 insertions(+), 49 deletions(-)
arch/x86/boot/compressed/eboot.c +13 -7
@@ -1008,7 +1008,7 @@ static efi_status_t exit_boot(struct boot_params *boot_params, void *handle, bool is64) {struct efi_info *efi = &boot_params->efi_info; -unsigned long map_sz, key, desc_size; +unsigned long map_sz, key, desc_size, buff_size;efi_memory_desc_t *mem_map;struct setup_data *e820ext;const char *signature;
@@ -1019,14 +1019,20 @@ static efi_status_t exit_boot(struct boot_params *boot_params,bool called_exit = false;u8 nr_entries;int i; - - nr_desc =0 ; - e820ext =NULL ; - e820ext_size =0 ; +struct efi_boot_memmap map; + + nr_desc =0 ; + e820ext =NULL ; + e820ext_size =0 ; + map.map = &mem_map; + map.map_size = &map_sz; + map.desc_size = &desc_size; + map.desc_ver = &desc_version; + map.key_ptr = &key; + map.buff_size = &buff_size; get_map: - status =efi_get_memory_map (sys_table, &mem_map, &map_sz, &desc_size, - &desc_version, &key); + status =efi_get_memory_map (sys_table, &map);if (status !=EFI_SUCCESS )return status;
drivers/firmware/efi/libstub/efi-stub-helper.c +66 -30
163 lines not on this page · raw message
drivers/firmware/efi/libstub/fdt.c +12 -5
@@ -175,13 +175,21 @@ efi_status_t allocate_new_fdt_and_exit_boot(efi_system_table_t *sys_table, unsigned long fdt_addr, unsigned long fdt_size) { -unsigned long map_size, desc_size; +unsigned long map_size, desc_size, buff_size;u32 desc_ver;unsigned long mmap_key;efi_memory_desc_t *memory_map, *runtime_map;unsigned long new_fdt_size;efi_status_t status;int runtime_entry_count =0 ; +struct efi_boot_memmap map; + + map.map = &runtime_map; + map.map_size = &map_size; + map.desc_size = &desc_size; + map.desc_ver = &desc_ver; + map.key_ptr = &mmap_key; + map.buff_size = &buff_size;/* * Get a copy of the current memory map that we will use to prepare
@@ -189,8 +197,7 @@ efi_status_t allocate_new_fdt_and_exit_boot(efi_system_table_t *sys_table,* subsequent allocations adding entries, since they could not affect * the number of EFI_MEMORY_RUNTIME regions. */ - status =efi_get_memory_map (sys_table, &runtime_map, &map_size, - &desc_size, &desc_ver, &mmap_key); + status =efi_get_memory_map (sys_table, &map);if (status !=EFI_SUCCESS ) {pr_efi_err (sys_table,"Unable to retrieve UEFI memory map.\n" );return status;
@@ -199,6 +206,7 @@ efi_status_t allocate_new_fdt_and_exit_boot(efi_system_table_t *sys_table,pr_efi (sys_table,"Exiting boot services and installing virtual address map...\n" ); + map.map = &memory_map;/* * Estimate size of new FDT, and allocate memory for it. We * will allocate a bigger buffer if this ends up being too
@@ -218,8 +226,7 @@ efi_status_t allocate_new_fdt_and_exit_boot(efi_system_table_t *sys_table,* we can get the memory map key needed for * exit_boot_services(). */ - status =efi_get_memory_map (sys_table, &memory_map, &map_size, - &desc_size, &desc_ver, &mmap_key); + status =efi_get_memory_map (sys_table, &map);if (status !=EFI_SUCCESS ) goto fail_free_new_fdt;
drivers/firmware/efi/libstub/random.c +10 -2
@@ -73,12 +73,20 @@ efi_status_t efi_random_alloc(efi_system_table_t *sys_table_arg, unsigned long random_seed) {unsigned long map_size, desc_size, total_slots =0 , target_slot; +unsigned long buff_size;efi_status_t status;efi_memory_desc_t *memory_map;int map_offset; +struct efi_boot_memmap map; - status =efi_get_memory_map (sys_table_arg, &memory_map, &map_size, - &desc_size,NULL ,NULL ); + map.map = &memory_map; + map.map_size = &map_size; + map.desc_size = &desc_size; + map.desc_ver =NULL ; + map.key_ptr =NULL ; + map.buff_size = &buff_size; + + status =efi_get_memory_map (sys_table_arg, &map);if (status !=EFI_SUCCESS )return status;
include/linux/efi.h +10 -5
@@ -118,6 +118,15 @@ typedef struct {u32 imagesize; } efi_capsule_header_t; +struct efi_boot_memmap { +efi_memory_desc_t **map; +unsigned long *map_size; +unsigned long *desc_size; +u32 *desc_ver; +unsigned long *key_ptr; +unsigned long *buff_size; +}; +/* * EFI capsule flags */
@@ -1371,11 +1380,7 @@ char *efi_convert_cmdline(efi_system_table_t *sys_table_arg, efi_loaded_image_t *image, int *cmd_line_len);efi_status_t efi_get_memory_map (efi_system_table_t *sys_table_arg, -efi_memory_desc_t **map, -unsigned long *map_size, -unsigned long *desc_size, -u32 *desc_ver, -unsigned long *key_ptr); +struct efi_boot_memmap *map); efi_status_t efi_low_alloc(efi_system_table_t *sys_table_arg, unsigned long size, unsigned long align,
Signature · 1 line
2.9.3
PATCH 4/6 efi/libstub: Introduce ExitBootServices helper
In reply to Matt FlemingFrom: Jeffrey Hugo <[email protected]>
The spec allows ExitBootServices to fail with EFI_INVALID_PARAMETER if a race condition has occurred where the EFI has updated the memory map after the stub grabbed a reference to the map. The spec defines a retry proceedure with specific requirements to handle this scenario.
This scenario was previously observed on x86 - commit d3768d885c6c ("x86,
efi: retry ExitBootServices() on failure") but the current fix is not spec compliant and the scenario is now observed on the Qualcomm Technologies QDF2432 via the FDT stub which does not handle the error and thus causes boot failures. The user will notice the boot failure as the kernel is not executed and the system may drop back to a UEFI shell, but will be unresponsive to input and the system will require a power cycle to recover.
Add a helper to the stub library that correctly adheres to the spec in the case of EFI_INVALID_PARAMETER from ExitBootServices and can be universally used across all stub implementations.
--- drivers/firmware/efi/libstub/efi-stub-helper.c | 73 ++++++++++++++++++++++++++ include/linux/efi.h | 10 ++++ 2 files changed, 83 insertions(+)
drivers/firmware/efi/libstub/efi-stub-helper.c +73 -0
@@ -740,3 +740,76 @@ char *efi_convert_cmdline(efi_system_table_t *sys_table_arg, *cmd_line_len = options_bytes;return (char *)cmdline_addr; } + +/* +* Handle calling ExitBootServices according to the requirements set out by the +* spec. Obtains the current memory map, and returns that info after calling +* ExitBootServices. The client must specify a function to perform any +* processing of the memory map data prior to ExitBootServices. A client +* specific structure may be passed to the function via priv. The client +* function may be called multiple times. +*/ +efi_status_t efi_exit_boot_services (efi_system_table_t *sys_table_arg, +void *handle, +struct efi_boot_memmap *map, +void *priv, +efi_exit_boot_map_processing priv_func) +{ +efi_status_t status; + + status =efi_get_memory_map (sys_table_arg, map); + +if (status !=EFI_SUCCESS ) + goto fail; + + status =priv_func (sys_table_arg, map, priv); +if (status !=EFI_SUCCESS ) + goto free_map; + + status =efi_call_early (exit_boot_services, handle, *map->key_ptr); + +if (status ==EFI_INVALID_PARAMETER ) { +/* +* The memory map changed between efi_get_memory_map() and +* exit_boot_services(). Per the UEFI Spec v2.6, Section 6.4: +* EFI_BOOT_SERVICES.ExitBootServices we need to get the +* updated map, and try again. The spec implies one retry +* should be sufficent, which is confirmed against the EDK2 +* implementation. Per the spec, we can only invoke +* get_memory_map() and exit_boot_services() - we cannot alloc +* so efi_get_memory_map() cannot be used, and we must reuse +* the buffer. For all practical purposes, the headroom in the +* buffer should account for any changes in the map so the call +* to get_memory_map() is expected to succeed here. +*/ + *map->map_size = *map->buff_size; + status =efi_call_early (get_memory_map, + map->map_size, + *map->map, + map->key_ptr, + map->desc_size, + map->desc_ver); + +/* exit_boot_services() was called, thus cannot free */ +if (status !=EFI_SUCCESS ) + goto fail; + + status =priv_func (sys_table_arg, map, priv); +/* exit_boot_services() was called, thus cannot free */ +if (status !=EFI_SUCCESS ) + goto fail; + + status =efi_call_early (exit_boot_services, handle, *map->key_ptr); + } + +/* exit_boot_services() was called, thus cannot free */ +if (status !=EFI_SUCCESS ) + goto fail; + +return EFI_SUCCESS ; + +free_map: +efi_call_early (free_pool, *map->map); +fail: +return status; +}
include/linux/efi.h +10 -0
@@ -1462,4 +1462,14 @@ extern void efi_call_virt_check_flags(unsigned long flags, const char *call);arch_efi_call_virt_teardown (); \ }) +typedef efi_status_t (*efi_exit_boot_map_processing )( +efi_system_table_t *sys_table_arg, +struct efi_boot_memmap *map, +void *priv); + +efi_status_t efi_exit_boot_services (efi_system_table_t *sys_table, +void *handle, +struct efi_boot_memmap *map, +void *priv, +efi_exit_boot_map_processing priv_func);#endif /* _LINUX_EFI_H */
Signature · 1 line
2.9.3
PATCH 5/6 efi/libstub: Use efi_exit_boot_services() in FDT
In reply to Matt FlemingFrom: Jeffrey Hugo <[email protected]>
The FDT code directly calls ExitBootServices. This is inadvisable as the UEFI spec details a complex set of errors, race conditions, and API interactions that the caller of ExitBootServices must get correct. The FDT code does not handle EFI_INVALID_PARAMETER as required by the spec, which causes intermittent boot failures on the Qualcomm Technologies QDF2432. Call the efi_exit_boot_services() helper intead, which handles the EFI_INVALID_PARAMETER scenario properly.
--- drivers/firmware/efi/libstub/fdt.c | 37 +++++++++++++++++++++++++++---------- 1 file changed, 27 insertions(+), 10 deletions(-)
drivers/firmware/efi/libstub/fdt.c +27 -10
@@ -152,6 +152,27 @@ fdt_set_fail:#define EFI_FDT_ALIGN EFI_PAGE_SIZE#endif +struct exit_boot_struct { +efi_memory_desc_t *runtime_map; +int *runtime_entry_count; +}; + +static efi_status_t exit_boot_func (efi_system_table_t *sys_table_arg, +struct efi_boot_memmap *map, +void *priv) +{ +struct exit_boot_struct *p = priv; +/* +* Update the memory map with virtual addresses. The function will also +* populate @runtime_map with copies of just the EFI_MEMORY_RUNTIME +* entries so that we can pass it straight to SetVirtualAddressMap() +*/ +efi_get_virtmap (*map->map, *map->map_size, *map->desc_size, + p->runtime_map, p->runtime_entry_count); + +return EFI_SUCCESS ; +} +/* * Allocate memory for a new FDT, then add EFI, commandline, and * initrd related fields to the FDT. This routine increases the
@@ -183,6 +204,7 @@ efi_status_t allocate_new_fdt_and_exit_boot(efi_system_table_t *sys_table,efi_status_t status;int runtime_entry_count =0 ;struct efi_boot_memmap map; +struct exit_boot_struct priv; map.map = &runtime_map; map.map_size = &map_size;
@@ -257,16 +279,11 @@ efi_status_t allocate_new_fdt_and_exit_boot(efi_system_table_t *sys_table, } } -/* -* Update the memory map with virtual addresses. The function will also -* populate @runtime_map with copies of just the EFI_MEMORY_RUNTIME -* entries so that we can pass it straight into SetVirtualAddressMap() -*/ -efi_get_virtmap (memory_map, map_size, desc_size, runtime_map, - &runtime_entry_count); - -/* Now we are ready to exit_boot_services.*/ - status = sys_table->boottime->exit_boot_services (handle, mmap_key); + sys_table->boottime->free_pool (memory_map); + priv.runtime_map = runtime_map; + priv.runtime_entry_count = &runtime_entry_count; + status =efi_exit_boot_services (sys_table, handle, &map, &priv, + exit_boot_func);if (status ==EFI_SUCCESS ) {efi_set_virtual_address_map_t *svam;
Signature · 1 line
2.9.3
PATCH 6/6 x86/efi: Use efi_exit_boot_services()
In reply to Matt FlemingFrom: Jeffrey Hugo <[email protected]>
The eboot code directly calls ExitBootServices. This is inadvisable as the UEFI spec details a complex set of errors, race conditions, and API interactions that the caller of ExitBootServices must get correct. The eboot code attempts allocations after calling ExitBootSerives which is not permitted per the spec. Call the efi_exit_boot_services() helper intead, which handles the allocation scenario properly.
--- arch/x86/boot/compressed/eboot.c | 136 +++++++++++++++++++-------------------- 1 file changed, 67 insertions(+), 69 deletions(-)
arch/x86/boot/compressed/eboot.c +67 -69
@@ -1004,85 +1004,87 @@ static efi_status_t alloc_e820ext(u32 nr_desc, struct setup_data **e820ext,return status; } +struct exit_boot_struct { +struct boot_params *boot_params; +struct efi_info *efi; +struct setup_data *e820ext; +__u32 e820ext_size; +bool is64; +}; + +static efi_status_t exit_boot_func (efi_system_table_t *sys_table_arg, +struct efi_boot_memmap *map, +void *priv) +{ +static bool first = true; +const char *signature; +__u32 nr_desc; +efi_status_t status; +struct exit_boot_struct *p = priv; + +if (first) { + nr_desc = *map->buff_size / *map->desc_size; +if (nr_desc >ARRAY_SIZE (p->boot_params->e820_map)) { +u32 nr_e820ext = nr_desc - +ARRAY_SIZE (p->boot_params->e820_map); + + status =alloc_e820ext (nr_e820ext, &p->e820ext, + &p->e820ext_size); +if (status !=EFI_SUCCESS ) +return status; + } + first = false; + } + + signature = p->is64 ?EFI64_LOADER_SIGNATURE :EFI32_LOADER_SIGNATURE ; +memcpy (&p->efi->efi_loader_signature, signature,sizeof (__u32)); + + p->efi->efi_systab = (unsigned long )sys_table_arg; + p->efi->efi_memdesc_size = *map->desc_size; + p->efi->efi_memdesc_version = *map->desc_ver; + p->efi->efi_memmap = (unsigned long )*map->map; + p->efi->efi_memmap_size = *map->map_size; + +#ifdef CONFIG_X86_64 + p->efi->efi_systab_hi = (unsigned long )sys_table_arg >>32 ; + p->efi->efi_memmap_hi = (unsigned long )*map->map >>32 ; +#endif + +return EFI_SUCCESS ; +} +static efi_status_t exit_boot (struct boot_params *boot_params,void *handle,bool is64) { -struct efi_info *efi = &boot_params->efi_info;unsigned long map_sz, key, desc_size, buff_size;efi_memory_desc_t *mem_map;struct setup_data *e820ext; -const char *signature;__u32 e820ext_size; -__u32 nr_desc, prev_nr_desc;efi_status_t status;__u32 desc_version; -bool called_exit = false; -u8 nr_entries; -int i;struct efi_boot_memmap map; +struct exit_boot_struct priv; + + map.map = &mem_map; + map.map_size = &map_sz; + map.desc_size = &desc_size; + map.desc_ver = &desc_version; + map.key_ptr = &key; + map.buff_size = &buff_size; + priv.boot_params = boot_params; + priv.efi = &boot_params->efi_info; + priv.e820ext =NULL ; + priv.e820ext_size =0 ; + priv.is64 = is64; - nr_desc =0 ; - e820ext =NULL ; - e820ext_size =0 ; - map.map = &mem_map; - map.map_size = &map_sz; - map.desc_size = &desc_size; - map.desc_ver = &desc_version; - map.key_ptr = &key; - map.buff_size = &buff_size; - -get_map: - status =efi_get_memory_map (sys_table, &map); - +/* Might as well exit boot services now */ + status =efi_exit_boot_services (sys_table, handle, &map, &priv, + exit_boot_func);if (status !=EFI_SUCCESS )return status; - prev_nr_desc = nr_desc; - nr_desc = map_sz / desc_size; -if (nr_desc > prev_nr_desc && - nr_desc >ARRAY_SIZE (boot_params->e820_map)) { -u32 nr_e820ext = nr_desc -ARRAY_SIZE (boot_params->e820_map); - - status =alloc_e820ext (nr_e820ext, &e820ext, &e820ext_size); -if (status !=EFI_SUCCESS ) - goto free_mem_map; - -efi_call_early (free_pool, mem_map); - goto get_map;/* Allocated memory, get map again */ - } - - signature = is64 ?EFI64_LOADER_SIGNATURE :EFI32_LOADER_SIGNATURE ; -memcpy (&efi->efi_loader_signature, signature,sizeof (__u32)); - - efi->efi_systab = (unsigned long )sys_table; - efi->efi_memdesc_size = desc_size; - efi->efi_memdesc_version = desc_version; - efi->efi_memmap = (unsigned long )mem_map; - efi->efi_memmap_size = map_sz; - -#ifdef CONFIG_X86_64 - efi->efi_systab_hi = (unsigned long )sys_table >>32 ; - efi->efi_memmap_hi = (unsigned long )mem_map >>32 ; -#endif - -/* Might as well exit boot services now */ - status =efi_call_early (exit_boot_services, handle, key); -if (status !=EFI_SUCCESS ) { -/* -* ExitBootServices() will fail if any of the event -* handlers change the memory map. In which case, we -* must be prepared to retry, but only once so that -* we're guaranteed to exit on repeated failures instead -* of spinning forever. -*/ -if (called_exit) - goto free_mem_map; - - called_exit = true; -efi_call_early (free_pool, mem_map); - goto get_map; - } - + e820ext = priv.e820ext; + e820ext_size = priv.e820ext_size;/* Historic? */ boot_params->alt_mem_k =32 *1024 ;
@@ -1091,10 +1093,6 @@ get_map:return status;return EFI_SUCCESS ; - -free_mem_map: -efi_call_early (free_pool, mem_map); -return status; }/*
Signature · 1 line
2.9.3