Hi Heinrich,

Calling efi_initrd_register to both save the initrd device path and install
the initrd LF2 protocol complicates error handling. efi_bootmgr also calls
efi_initrd_register, but many functions are called between that and
do_bootefi_exec. If any fail, we should uninstall the protocol. The same
issue exists in efi_bootbin.

Delaying efi_initrd_register would require passing the initrd down the call
chain, e.g. to efi_run_image in efi_bootbin and passing it from
try_load_entry in efi_bootmgr. I think this should be avoided.

Instead, we can install the protocol in do_bootefi_exec—a shared point of
efi_bootmgr and efi_bootbin—and set the initrd device paths early, avoiding
the need to pass them. efi_initrd_dp could even be set once in
try_load_entry using BootCurrent, instead of retrieving the initrd each time
the LF2 protocol is used.

Best,
Adriano

On Thu, 1 May 2025 at 03:15, Heinrich Schuchardt <xypron.g...@gmx.de> wrote:

> On 5/1/25 03:15, Adriano Cordova wrote:
> > 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;
>
> In efi_initrd_uninstall() we free the device-path and set efi_initrd_dp
> = NULL.
> It seems you are handling the case of efi_initrd_install() failing here.
>
> To me this looks a bit convoluted. Calling efi_initrd_register() as in
> your v3 series would allow handling errors when they occur.
>
> The static variable efi_initrd_dp should receive some documentation in
> the code explaining how it is used. This could be in a separate patch.
>
> I will merge your v3 series as it fixes the case of no initrd.
>
> We can handle the uninstallation of the LoadFile2 protocol in a patch on
> top of that.
>
> Best regards
>
> Heinrich
>
> > +
> > +     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;
> >   }
>
>

Reply via email to