Current support for initrd in EFI booting has two flaws: 1. Installs a NULL initrd via LoadFile2 protocol if `efi_binary_run_dp` does not provide an initrd. In this case, a LoadFile2 should not be installed at all. 2. Initrd is not properly uninstalled if booting fails at some point between initrd installation and the call to `do_bootefi_exec`. This affects both booting via `efi_bootbin` and via `efi_bootmgr`.
This commit splits the process of setting an initrd into two steps: First, `efi_initrd_set_from_mem` creates a memory-backed device path for the initrd. (This step is not necessary when using `efi_bootmgr`, and for now is only used when booting FIT images, but the `bootefi` command could now be expanded to receive an initrd argument.) Second, `efi_initrd_install` creates an initrd handle and installs the LoadFile2 protocol exposing the initrd. This is needed both when booting with `efi_bootbin` and with `efi_bootmgr`, so it is called in `do_bootefi_exec`, which is the first common point between the two bootflows. Also, as `do_bootefi_exec` is the only place where `efi_initrd_install` is called, the cleanup function `efi_initrd_uninstall` can be called before exiting `do_bootefi_exec` instead of using the event `efi_guid_event_group_return_to_efibootmgr`. (This also makes more sense as now initrd is also used by `efi_bootbin`.) Fixes: 36835a9105c ("efi_loader: binary_run: register an initrd") Reported-by: Weizhao Ouyang <o451686...@gmail.com> Reported-by: Heinrich Schuchardt <xypron.g...@gmx.de> Signed-off-by: Adriano Cordova <adriano.cord...@canonical.com> --- include/efi_loader.h | 8 ++- lib/efi_loader/efi_bootbin.c | 14 +++--- lib/efi_loader/efi_bootmgr.c | 7 --- lib/efi_loader/efi_helper.c | 11 +++++ lib/efi_loader/efi_load_initrd.c | 84 +++++++++++++++++--------------- 5 files changed, 68 insertions(+), 56 deletions(-) diff --git a/include/efi_loader.h b/include/efi_loader.h index 144b749278a..0a3ca799287 100644 --- a/include/efi_loader.h +++ b/include/efi_loader.h @@ -597,6 +597,12 @@ efi_status_t efi_env_set_load_options(efi_handle_t handle, const char *env_var, void *efi_get_configuration_table(const efi_guid_t *guid); /* Install device tree */ efi_status_t efi_install_fdt(void *fdt); +/* Install initrd LoadFile2 protocol */ +efi_status_t efi_initrd_install(void); +/* Uninstall initrd LoadFile2 protocol */ +efi_status_t efi_initrd_uninstall(void); +/* Set an initrd */ +efi_status_t efi_initrd_set_from_mem(void *initrd, size_t initd_sz); /* Execute loaded UEFI image */ efi_status_t do_bootefi_exec(efi_handle_t handle, void *load_options); /* Run loaded UEFI image with given fdt */ @@ -667,8 +673,6 @@ efi_status_t efi_http_register(const efi_handle_t handle, struct efi_service_binding_protocol *http_service_binding); /* Called by bootefi to make the watchdog available */ efi_status_t efi_watchdog_register(void); -efi_status_t efi_initrd_register(struct efi_device_path *dp_initrd); -efi_status_t efi_initrd_deregister(void); /* Called by bootefi to make SMBIOS tables available */ /** * efi_acpi_register() - write out ACPI tables diff --git a/lib/efi_loader/efi_bootbin.c b/lib/efi_loader/efi_bootbin.c index d0f7da309ce..637f2c0d195 100644 --- a/lib/efi_loader/efi_bootbin.c +++ b/lib/efi_loader/efi_bootbin.c @@ -220,7 +220,6 @@ static efi_status_t efi_binary_run_dp(void *image, size_t size, void *fdt, struct efi_device_path *dp_img) { efi_status_t ret; - struct efi_device_path *dp_initrd; /* Initialize EFI drivers */ ret = efi_init_obj_list(); @@ -234,13 +233,12 @@ static efi_status_t efi_binary_run_dp(void *image, size_t size, void *fdt, if (ret != EFI_SUCCESS) return ret; - dp_initrd = efi_dp_from_mem(EFI_LOADER_DATA, (uintptr_t)initrd, initd_sz); - if (!dp_initrd) - return EFI_OUT_OF_RESOURCES; - - ret = efi_initrd_register(dp_initrd); - if (ret != EFI_SUCCESS) - return ret; + /* Set initrd if provided */ + if (IS_ENABLED(CONFIG_EFI_LOAD_FILE2_INITRD)) { + ret = efi_initrd_set_from_mem(initrd, initd_sz); + if (ret != EFI_SUCCESS) + return ret; + } return efi_run_image(image, size, dp_dev, dp_img); } diff --git a/lib/efi_loader/efi_bootmgr.c b/lib/efi_loader/efi_bootmgr.c index c0df5cb9acd..19c7e16a25b 100644 --- a/lib/efi_loader/efi_bootmgr.c +++ b/lib/efi_loader/efi_bootmgr.c @@ -683,13 +683,6 @@ static efi_status_t try_load_entry(u16 n, efi_handle_t *handle, if (ret != EFI_SUCCESS) goto error; - /* try to register load file2 for initrd's */ - if (IS_ENABLED(CONFIG_EFI_LOAD_FILE2_INITRD)) { - ret = efi_initrd_register(NULL); - if (ret != EFI_SUCCESS) - goto error; - } - log_info("Booting: Label: %ls Device path: %pD\n", lo.label, lo.file_path); /* Ignore the optional data in auto-generated boot options */ diff --git a/lib/efi_loader/efi_helper.c b/lib/efi_loader/efi_helper.c index 3936139ca41..b4c207ea33f 100644 --- a/lib/efi_loader/efi_helper.c +++ b/lib/efi_loader/efi_helper.c @@ -658,6 +658,13 @@ efi_status_t do_bootefi_exec(efi_handle_t handle, void *load_options) goto out; } + /* Create initrd handle and install LoadFile2 protocol */ + if (IS_ENABLED(CONFIG_EFI_LOAD_FILE2_INITRD)) { + ret = efi_initrd_install(); + if (ret != EFI_SUCCESS) + goto out; + } + /* Call our payload! */ ret = EFI_CALL(efi_start_image(handle, &exit_data_size, &exit_data)); if (ret != EFI_SUCCESS) { @@ -683,6 +690,10 @@ out: } } + /* Destroy initrd handle with LoadFile2 protocol */ + if (IS_ENABLED(CONFIG_EFI_LOAD_FILE2_INITRD)) + ret = efi_initrd_uninstall(); + /* Control is returned to U-Boot, disable EFI watchdog */ efi_set_watchdog(0); diff --git a/lib/efi_loader/efi_load_initrd.c b/lib/efi_loader/efi_load_initrd.c index b5d58943a80..7210bf16d09 100644 --- a/lib/efi_loader/efi_load_initrd.c +++ b/lib/efi_loader/efi_load_initrd.c @@ -74,7 +74,40 @@ static efi_status_t get_initrd_fp(struct efi_device_path **initrd_fp) } /** - * efi_initrd_from_mem() - load initial RAM disk from memory + * efi_initrd_set_from_mem() - set initial RAM disk from memory + * + * Set efi_initrd_dp to a memory mapped device path pointing + * to @initrd. If @initrd is NULL then efi_initrd_dp gets cleared. + * + * + * @initrd: address of initrd or NULL if none is provided + * @initrd_sz: size of initrd + * Return: status code + */ +efi_status_t efi_initrd_set_from_mem(void *initrd, size_t initd_sz) +{ + struct efi_device_path *old_initrd_dp; + + old_initrd_dp = efi_initrd_dp; + + if (!initrd) { + efi_initrd_dp = NULL; + goto out; + } + + efi_initrd_dp = efi_dp_from_mem(EFI_LOADER_DATA, (uintptr_t)initrd, initd_sz); + if (!efi_initrd_dp) { + efi_initrd_dp = old_initrd_dp; + return EFI_OUT_OF_RESOURCES; + } + +out: + efi_free_pool(old_initrd_dp); + return EFI_SUCCESS; +} + +/** + * efi_initrd_get_from_mem() - get initial RAM disk from memory * * This function copies the initrd from the memory mapped device * path pointed to by efi_initrd_dp @@ -84,7 +117,7 @@ static efi_status_t get_initrd_fp(struct efi_device_path **initrd_fp) * * Return: status code */ -static efi_status_t efi_initrd_from_mem(efi_uintn_t *buffer_size, void *buffer) +static efi_status_t efi_initrd_get_from_mem(efi_uintn_t *buffer_size, void *buffer) { efi_status_t ret = EFI_NOT_FOUND; efi_uintn_t bs; @@ -155,7 +188,7 @@ efi_load_file2_initrd(struct efi_load_file_protocol *this, } if (efi_initrd_dp) - return EFI_EXIT(efi_initrd_from_mem(buffer_size, buffer)); + return EFI_EXIT(efi_initrd_get_from_mem(buffer_size, buffer)); ret = get_initrd_fp(&initrd_fp); if (ret != EFI_SUCCESS) @@ -224,14 +257,14 @@ out: } /** - * efi_initrd_deregister() - delete the handle for loading initial RAM disk + * efi_initrd_uninstall() - delete the handle for loading initial RAM disk * * This will delete the handle containing the Linux specific vendor device - * path and EFI_LOAD_FILE2_PROTOCOL for loading an initrd + * path and the installed EFI_LOAD_FILE2_PROTOCOL for loading an initrd * * Return: status code */ -efi_status_t efi_initrd_deregister(void) +efi_status_t efi_initrd_uninstall(void) { efi_status_t ret; @@ -255,42 +288,22 @@ efi_status_t efi_initrd_deregister(void) } /** - * efi_initrd_return_notify() - return to efibootmgr callback - * - * @event: the event for which this notification function is registered - * @context: event context - */ -static void EFIAPI efi_initrd_return_notify(struct efi_event *event, - void *context) -{ - efi_status_t ret; - - EFI_ENTRY("%p, %p", event, context); - ret = efi_initrd_deregister(); - EFI_EXIT(ret); -} - -/** - * efi_initrd_register() - create handle for loading initial RAM disk + * efi_initrd_install() - create handle for loading initial RAM disk * * This function creates a new handle and installs a Linux specific vendor * device path and an EFI_LOAD_FILE2_PROTOCOL. Linux uses the device path * to identify the handle and then calls the LoadFile service of the - * EFI_LOAD_FILE2_PROTOCOL to read the initial RAM disk. If dp_initrd is - * not provided, the initrd will be taken from the BootCurrent variable - * - * @dp_initrd: optional device path containing an initrd + * EFI_LOAD_FILE2_PROTOCOL to read the initial RAM disk. If efi_initrd_dp + * is not set the initrd will be taken from the BootCurrent variable. If + * an initrd is not found the handle is not created. * * Return: status code */ -efi_status_t efi_initrd_register(struct efi_device_path *dp_initrd) +efi_status_t efi_initrd_install(void) { efi_status_t ret; - struct efi_event *event; - if (dp_initrd) { - efi_initrd_dp = dp_initrd; - } else { + if (!efi_initrd_dp) { /* * Allow the user to continue if Boot#### file path is not set for * an initrd @@ -314,12 +327,5 @@ efi_status_t efi_initrd_register(struct efi_device_path *dp_initrd) return ret; } - ret = efi_create_event(EVT_NOTIFY_SIGNAL, TPL_CALLBACK, - efi_initrd_return_notify, NULL, - &efi_guid_event_group_return_to_efibootmgr, - &event); - if (ret != EFI_SUCCESS) - log_err("Creating event failed\n"); - return ret; } -- 2.48.1