mbox series

[0/4] linux: implement LoadFile2 initrd loading

Message ID 20201023120825.30466-1-ard.biesheuvel@arm.com
Headers show
Series linux: implement LoadFile2 initrd loading | expand

Message

Ard Biesheuvel Oct. 23, 2020, 12:08 p.m. UTC
This implements the LoadFile2 initrd loading protocol, which is
essentially a callback face into the bootloader to load the initrd
data into a caller provided buffer. This means the bootloader no
longer has to contain any policy regarding where to load the initrd
(which differs between architectures and kernel versions) and no
longer has to manipulate arch specific data structures such as DT
or struct bootparams to inform the OS where the initrd resides in
memory.

Sample output from booting a recent Linux/arm64 kernel:

  grub> insmod part_msdos
  grub> linux (hd0,msdos1)/Image
  grub> initrd (hd0,msdos1)/initrd.img
  grub> boot
  EFI stub: Booting Linux Kernel...
  EFI stub: EFI_RNG_PROTOCOL unavailable, KASLR will be disabled
  EFI stub: Generating empty DTB
  EFI stub: Loaded initrd from LINUX_EFI_INITRD_MEDIA_GUID device path
  EFI stub: Exiting boot services and installing virtual address map...
  [    0.000000] Booting Linux on physical CPU 0x0000000000 [0x411fd070]

Cc: grub-devel@gnu.org
Cc: daniel.kiper@oracle.com
Cc: leif@nuviainc.com

Ard Biesheuvel (4):
  loader/linux: permit NULL argument for argv[] in grub_initrd_load()
  efi: add definition of LoadFile2 protocol
  efi: implemented LoadFile2 initr loading protocol for Linux
  linux: ignore FDT unless we need to modify it

 grub-core/commands/efi/lsefi.c |   1 +
 grub-core/loader/arm64/linux.c | 139 ++++++++++++++++++--
 grub-core/loader/efi/fdt.c     |   7 +-
 grub-core/loader/linux.c       |   2 +-
 include/grub/efi/api.h         |  15 +++
 5 files changed, 149 insertions(+), 15 deletions(-)

Comments

Leif Lindholm Oct. 23, 2020, 12:32 p.m. UTC | #1
initrd typo in subject

minor style comments below

On Fri, Oct 23, 2020 at 14:08:24 +0200, Ard Biesheuvel wrote:
> Recent Linux kernels will invoke the LoadFile2 protocol installed on
> a well-known vendor media path to load the initrd if it is exposed by
> the firmware. Using this method is preferred for two reasons:
> - the Linux kernel is in charge of allocating the memory, and so it can
>   implement any placement policy it wants (given that these tend to
>   change between kernel versions),
> - it is no longer necessary to modify the device tree provided by the
>   firmware.
> 
> So let's install this protocol when handling the 'initrd' command if
> such a recent kernel was detected (based on the PE/COFF image version),
> and defer loading the initrd contents until the point where the kernel
> invokes the LoadFile2 protocol.
> 
> Signed-off-by: Ard Biesheuvel <ard.biesheuvel@arm.com>
> ---
>  grub-core/loader/arm64/linux.c | 117 +++++++++++++++++++-
>  1 file changed, 116 insertions(+), 1 deletion(-)
> 
> diff --git a/grub-core/loader/arm64/linux.c b/grub-core/loader/arm64/linux.c
> index ef3e9f9444ca..285422c7bd43 100644
> --- a/grub-core/loader/arm64/linux.c
> +++ b/grub-core/loader/arm64/linux.c
> @@ -48,9 +48,16 @@ static grub_uint32_t cmdline_size;
>  static grub_addr_t initrd_start;
>  static grub_addr_t initrd_end;
>  
> +static struct grub_linux_initrd_context initrd_ctx = { 0, 0, 0 };
> +static grub_efi_handle_t initrd_lf2_handle;
> +static int initrd_use_loadfile2;
> +
>  grub_err_t
>  grub_arch_efi_linux_check_image (struct linux_arch_kernel_header * lh)
>  {
> +  struct grub_pe32_coff_header *coff_header;
> +  struct grub_pe32_optional_header *optional_header;
> +
>    if (lh->magic != GRUB_LINUX_ARMXX_MAGIC_SIGNATURE)
>      return grub_error(GRUB_ERR_BAD_OS, "invalid magic number");
>  
> @@ -61,6 +68,21 @@ grub_arch_efi_linux_check_image (struct linux_arch_kernel_header * lh)
>    grub_dprintf ("linux", "UEFI stub kernel:\n");
>    grub_dprintf ("linux", "PE/COFF header @ %08x\n", lh->hdr_offset);
>  
> +  coff_header = (struct grub_pe32_coff_header *)((unsigned long)lh + lh->hdr_offset);
> +  optional_header = (struct grub_pe32_optional_header *)(coff_header + 1);
> +
> +  /*
> +   * Linux kernels built for any architecture are guaranteed to support the
> +   * LoadFile2 based initrd loading protocol if the image version is >= 1.
> +   */
> +  if (optional_header->major_image_version >= 1)
> +    initrd_use_loadfile2 = 1;
> +   else

funky indentation

> +    initrd_use_loadfile2 = 0;
> +
> +  grub_dprintf ("linux", "LoadFile2 initrd loading %sabled\n",
> +		initrd_use_loadfile2 ? "en" : "dis");
> +
>    return GRUB_ERR_NONE;
>  }
>  
> @@ -230,13 +252,88 @@ allocate_initrd_mem (int initrd_pages)
>  				       GRUB_EFI_LOADER_DATA);
>  }
>  
> +struct initrd_media_device_path {
> +  grub_efi_vendor_media_device_path_t	vendor;
> +  grub_efi_device_path_t		end;
> +} GRUB_PACKED;
> +
> +#define LINUX_EFI_INITRD_MEDIA_GUID  \
> +  { 0x5568e427, 0x68fc, 0x4f3d, \
> +    { 0xac, 0x74, 0xca, 0x55, 0x52, 0x31, 0xcc, 0x68 } \
> +  }
> +
> +static struct initrd_media_device_path initrd_lf2_device_path = {
> +  {
> +    {
> +      GRUB_EFI_MEDIA_DEVICE_PATH_TYPE,
> +      GRUB_EFI_VENDOR_MEDIA_DEVICE_PATH_SUBTYPE,
> +      sizeof(grub_efi_vendor_media_device_path_t),
> +    },
> +    LINUX_EFI_INITRD_MEDIA_GUID
> +  }, {
> +    GRUB_EFI_END_DEVICE_PATH_TYPE,
> +    GRUB_EFI_END_ENTIRE_DEVICE_PATH_SUBTYPE,
> +    sizeof(grub_efi_device_path_t)
> +  }
> +};
> +
> +static grub_efi_status_t
> +grub_efi_initrd_load_file2(grub_efi_load_file2_t *this,

Static functions don't need global prefixes (i.e., initrd_load_file2
would be sufficient).

> +                           grub_efi_device_path_t *device_path,
> +                           grub_efi_boolean_t boot_policy,
> +                           grub_efi_uintn_t *buffer_size,
> +                           void *buffer);
> +
> +static grub_efi_load_file2_t initrd_lf2 = {
> +  grub_efi_initrd_load_file2
> +};
> +
> +static grub_efi_status_t
> +grub_efi_initrd_load_file2(grub_efi_load_file2_t *this,
> +			   grub_efi_device_path_t *device_path,
> +			   grub_efi_boolean_t boot_policy,
> +			   grub_efi_uintn_t *buffer_size,
> +			   void *buffer)
> +{
> +  grub_efi_status_t status = GRUB_EFI_SUCCESS;
> +  grub_efi_uintn_t initrd_size;
> +
> +  if (!this || this != &initrd_lf2 || !buffer_size)
> +    return GRUB_EFI_INVALID_PARAMETER;
> +
> +  if (device_path->type != GRUB_EFI_END_DEVICE_PATH_TYPE ||
> +      device_path->subtype != GRUB_EFI_END_ENTIRE_DEVICE_PATH_SUBTYPE)
> +    return GRUB_EFI_NOT_FOUND;
> +
> +  if (boot_policy)
> +    return GRUB_EFI_UNSUPPORTED;
> +
> +  initrd_size = grub_get_initrd_size (&initrd_ctx);
> +  if (!buffer || *buffer_size < initrd_size)
> +    {
> +      *buffer_size = initrd_size;
> +      return GRUB_EFI_BUFFER_TOO_SMALL;
> +    }
> +
> +  grub_dprintf ("linux", "Loading initrd via LOAD_FILE2_PROTOCOL\n");
> +
> +  if (grub_initrd_load (&initrd_ctx, NULL, buffer))
> +    status = GRUB_EFI_LOAD_ERROR;
> +
> +  grub_initrd_close (&initrd_ctx);
> +  return status;
> +}
> +
>  static grub_err_t
>  grub_cmd_initrd (grub_command_t cmd __attribute__ ((unused)),
>  		 int argc, char *argv[])
>  {
> -  struct grub_linux_initrd_context initrd_ctx = { 0, 0, 0 };
>    int initrd_size, initrd_pages;
>    void *initrd_mem = NULL;
> +  grub_efi_guid_t load_file2_guid = GRUB_EFI_LOAD_FILE2_PROTOCOL_GUID;
> +  grub_efi_guid_t device_path_guid = GRUB_EFI_DEVICE_PATH_GUID;
> +  grub_efi_boot_services_t *b;
> +  grub_efi_status_t status;
>  
>    if (argc == 0)
>      {
> @@ -254,6 +351,24 @@ grub_cmd_initrd (grub_command_t cmd __attribute__ ((unused)),
>    if (grub_initrd_init (argc, argv, &initrd_ctx))
>      goto fail;
>  
> +  if (initrd_use_loadfile2 && !initrd_lf2_handle)
> +    {
> +      b = grub_efi_system_table->boot_services;
> +      status = b->install_multiple_protocol_interfaces (&initrd_lf2_handle,
> +							&load_file2_guid,
> +							&initrd_lf2,
> +							&device_path_guid,
> +							&initrd_lf2_device_path,
> +							NULL);
> +      if (status == GRUB_EFI_OUT_OF_RESOURCES)
> +        {

indented by spaces

> +	  grub_error (GRUB_ERR_OUT_OF_MEMORY, N_("out of memory"));
> +	  return grub_errno;
> +	}

indented with tab

/
    Leif

> +      grub_dprintf ("linux", "LoadFile2 initrd loading protocol installed\n");
> +      return GRUB_ERR_NONE;
> +    }
> +
>    initrd_size = grub_get_initrd_size (&initrd_ctx);
>    grub_dprintf ("linux", "Loading initrd\n");
>  
> -- 
> 2.17.1
>
Leif Lindholm Oct. 23, 2020, 12:50 p.m. UTC | #2
On Fri, Oct 23, 2020 at 14:08:21 +0200, Ard Biesheuvel wrote:
> This implements the LoadFile2 initrd loading protocol, which is
> essentially a callback face into the bootloader to load the initrd
> data into a caller provided buffer. This means the bootloader no
> longer has to contain any policy regarding where to load the initrd
> (which differs between architectures and kernel versions) and no
> longer has to manipulate arch specific data structures such as DT
> or struct bootparams to inform the OS where the initrd resides in
> memory.
> 
> Sample output from booting a recent Linux/arm64 kernel:
> 
>   grub> insmod part_msdos
>   grub> linux (hd0,msdos1)/Image
>   grub> initrd (hd0,msdos1)/initrd.img
>   grub> boot
>   EFI stub: Booting Linux Kernel...
>   EFI stub: EFI_RNG_PROTOCOL unavailable, KASLR will be disabled
>   EFI stub: Generating empty DTB
>   EFI stub: Loaded initrd from LINUX_EFI_INITRD_MEDIA_GUID device path
>   EFI stub: Exiting boot services and installing virtual address map...
>   [    0.000000] Booting Linux on physical CPU 0x0000000000 [0x411fd070]

I don't review enough grub code to be certain I've caught all aspects
of style adherence, so with that in mind, for 1-2/4:
Reviewed-by: Leif Lindholm <leif@nuviainc.com>

For 3-4/4, I did have some minor comments, but this is a really great
feature and I would like to see it merged.

/
    Leif

> Cc: grub-devel@gnu.org
> Cc: daniel.kiper@oracle.com
> Cc: leif@nuviainc.com
> 
> Ard Biesheuvel (4):
>   loader/linux: permit NULL argument for argv[] in grub_initrd_load()
>   efi: add definition of LoadFile2 protocol
>   efi: implemented LoadFile2 initr loading protocol for Linux
>   linux: ignore FDT unless we need to modify it
> 
>  grub-core/commands/efi/lsefi.c |   1 +
>  grub-core/loader/arm64/linux.c | 139 ++++++++++++++++++--
>  grub-core/loader/efi/fdt.c     |   7 +-
>  grub-core/loader/linux.c       |   2 +-
>  include/grub/efi/api.h         |  15 +++
>  5 files changed, 149 insertions(+), 15 deletions(-)
> 
> -- 
> 2.17.1
>
Ard Biesheuvel Oct. 23, 2020, 5:02 p.m. UTC | #3
On Fri, 23 Oct 2020 at 14:32, Leif Lindholm <leif@nuviainc.com> wrote:
>

> initrd typo in subject

>

> minor style comments below

>

> On Fri, Oct 23, 2020 at 14:08:24 +0200, Ard Biesheuvel wrote:

> > Recent Linux kernels will invoke the LoadFile2 protocol installed on

> > a well-known vendor media path to load the initrd if it is exposed by

> > the firmware. Using this method is preferred for two reasons:

> > - the Linux kernel is in charge of allocating the memory, and so it can

> >   implement any placement policy it wants (given that these tend to

> >   change between kernel versions),

> > - it is no longer necessary to modify the device tree provided by the

> >   firmware.

> >

> > So let's install this protocol when handling the 'initrd' command if

> > such a recent kernel was detected (based on the PE/COFF image version),

> > and defer loading the initrd contents until the point where the kernel

> > invokes the LoadFile2 protocol.

> >

> > Signed-off-by: Ard Biesheuvel <ard.biesheuvel@arm.com>

> > ---

> >  grub-core/loader/arm64/linux.c | 117 +++++++++++++++++++-

> >  1 file changed, 116 insertions(+), 1 deletion(-)

> >

> > diff --git a/grub-core/loader/arm64/linux.c b/grub-core/loader/arm64/linux.c

> > index ef3e9f9444ca..285422c7bd43 100644

> > --- a/grub-core/loader/arm64/linux.c

> > +++ b/grub-core/loader/arm64/linux.c

> > @@ -48,9 +48,16 @@ static grub_uint32_t cmdline_size;

> >  static grub_addr_t initrd_start;

> >  static grub_addr_t initrd_end;

> >

> > +static struct grub_linux_initrd_context initrd_ctx = { 0, 0, 0 };

> > +static grub_efi_handle_t initrd_lf2_handle;

> > +static int initrd_use_loadfile2;

> > +

> >  grub_err_t

> >  grub_arch_efi_linux_check_image (struct linux_arch_kernel_header * lh)

> >  {

> > +  struct grub_pe32_coff_header *coff_header;

> > +  struct grub_pe32_optional_header *optional_header;

> > +

> >    if (lh->magic != GRUB_LINUX_ARMXX_MAGIC_SIGNATURE)

> >      return grub_error(GRUB_ERR_BAD_OS, "invalid magic number");

> >

> > @@ -61,6 +68,21 @@ grub_arch_efi_linux_check_image (struct linux_arch_kernel_header * lh)

> >    grub_dprintf ("linux", "UEFI stub kernel:\n");

> >    grub_dprintf ("linux", "PE/COFF header @ %08x\n", lh->hdr_offset);

> >

> > +  coff_header = (struct grub_pe32_coff_header *)((unsigned long)lh + lh->hdr_offset);

> > +  optional_header = (struct grub_pe32_optional_header *)(coff_header + 1);

> > +

> > +  /*

> > +   * Linux kernels built for any architecture are guaranteed to support the

> > +   * LoadFile2 based initrd loading protocol if the image version is >= 1.

> > +   */

> > +  if (optional_header->major_image_version >= 1)

> > +    initrd_use_loadfile2 = 1;

> > +   else

>

> funky indentation

>

> > +    initrd_use_loadfile2 = 0;

> > +

> > +  grub_dprintf ("linux", "LoadFile2 initrd loading %sabled\n",

> > +             initrd_use_loadfile2 ? "en" : "dis");

> > +

> >    return GRUB_ERR_NONE;

> >  }

> >

> > @@ -230,13 +252,88 @@ allocate_initrd_mem (int initrd_pages)

> >                                      GRUB_EFI_LOADER_DATA);

> >  }

> >

> > +struct initrd_media_device_path {

> > +  grub_efi_vendor_media_device_path_t        vendor;

> > +  grub_efi_device_path_t             end;

> > +} GRUB_PACKED;

> > +

> > +#define LINUX_EFI_INITRD_MEDIA_GUID  \

> > +  { 0x5568e427, 0x68fc, 0x4f3d, \

> > +    { 0xac, 0x74, 0xca, 0x55, 0x52, 0x31, 0xcc, 0x68 } \

> > +  }

> > +

> > +static struct initrd_media_device_path initrd_lf2_device_path = {

> > +  {

> > +    {

> > +      GRUB_EFI_MEDIA_DEVICE_PATH_TYPE,

> > +      GRUB_EFI_VENDOR_MEDIA_DEVICE_PATH_SUBTYPE,

> > +      sizeof(grub_efi_vendor_media_device_path_t),

> > +    },

> > +    LINUX_EFI_INITRD_MEDIA_GUID

> > +  }, {

> > +    GRUB_EFI_END_DEVICE_PATH_TYPE,

> > +    GRUB_EFI_END_ENTIRE_DEVICE_PATH_SUBTYPE,

> > +    sizeof(grub_efi_device_path_t)

> > +  }

> > +};

> > +

> > +static grub_efi_status_t

> > +grub_efi_initrd_load_file2(grub_efi_load_file2_t *this,

>

> Static functions don't need global prefixes (i.e., initrd_load_file2

> would be sufficient).

>

> > +                           grub_efi_device_path_t *device_path,

> > +                           grub_efi_boolean_t boot_policy,

> > +                           grub_efi_uintn_t *buffer_size,

> > +                           void *buffer);

> > +

> > +static grub_efi_load_file2_t initrd_lf2 = {

> > +  grub_efi_initrd_load_file2

> > +};

> > +

> > +static grub_efi_status_t

> > +grub_efi_initrd_load_file2(grub_efi_load_file2_t *this,

> > +                        grub_efi_device_path_t *device_path,

> > +                        grub_efi_boolean_t boot_policy,

> > +                        grub_efi_uintn_t *buffer_size,

> > +                        void *buffer)

> > +{

> > +  grub_efi_status_t status = GRUB_EFI_SUCCESS;

> > +  grub_efi_uintn_t initrd_size;

> > +

> > +  if (!this || this != &initrd_lf2 || !buffer_size)

> > +    return GRUB_EFI_INVALID_PARAMETER;

> > +

> > +  if (device_path->type != GRUB_EFI_END_DEVICE_PATH_TYPE ||

> > +      device_path->subtype != GRUB_EFI_END_ENTIRE_DEVICE_PATH_SUBTYPE)

> > +    return GRUB_EFI_NOT_FOUND;

> > +

> > +  if (boot_policy)

> > +    return GRUB_EFI_UNSUPPORTED;

> > +

> > +  initrd_size = grub_get_initrd_size (&initrd_ctx);

> > +  if (!buffer || *buffer_size < initrd_size)

> > +    {

> > +      *buffer_size = initrd_size;

> > +      return GRUB_EFI_BUFFER_TOO_SMALL;

> > +    }

> > +

> > +  grub_dprintf ("linux", "Loading initrd via LOAD_FILE2_PROTOCOL\n");

> > +

> > +  if (grub_initrd_load (&initrd_ctx, NULL, buffer))

> > +    status = GRUB_EFI_LOAD_ERROR;

> > +

> > +  grub_initrd_close (&initrd_ctx);

> > +  return status;

> > +}

> > +

> >  static grub_err_t

> >  grub_cmd_initrd (grub_command_t cmd __attribute__ ((unused)),

> >                int argc, char *argv[])

> >  {

> > -  struct grub_linux_initrd_context initrd_ctx = { 0, 0, 0 };

> >    int initrd_size, initrd_pages;

> >    void *initrd_mem = NULL;

> > +  grub_efi_guid_t load_file2_guid = GRUB_EFI_LOAD_FILE2_PROTOCOL_GUID;

> > +  grub_efi_guid_t device_path_guid = GRUB_EFI_DEVICE_PATH_GUID;

> > +  grub_efi_boot_services_t *b;

> > +  grub_efi_status_t status;

> >

> >    if (argc == 0)

> >      {

> > @@ -254,6 +351,24 @@ grub_cmd_initrd (grub_command_t cmd __attribute__ ((unused)),

> >    if (grub_initrd_init (argc, argv, &initrd_ctx))

> >      goto fail;

> >

> > +  if (initrd_use_loadfile2 && !initrd_lf2_handle)

> > +    {

> > +      b = grub_efi_system_table->boot_services;

> > +      status = b->install_multiple_protocol_interfaces (&initrd_lf2_handle,

> > +                                                     &load_file2_guid,

> > +                                                     &initrd_lf2,

> > +                                                     &device_path_guid,

> > +                                                     &initrd_lf2_device_path,

> > +                                                     NULL);

> > +      if (status == GRUB_EFI_OUT_OF_RESOURCES)

> > +        {

>

> indented by spaces

>

> > +       grub_error (GRUB_ERR_OUT_OF_MEMORY, N_("out of memory"));

> > +       return grub_errno;

> > +     }

>

> indented with tab

>


Thanks. I'll fix that up for the next revision. There's actually a bug
in this patch which I need to fix too: the code that checks the
PE/COFF header for the image version doesn't actually load the entire
header :-(