efi: libstub: Implement devicepath support for initrd commandline loader
authorArd Biesheuvel <ardb@kernel.org>
Mon, 26 Sep 2022 19:32:16 +0000 (21:32 +0200)
committerArd Biesheuvel <ardb@kernel.org>
Fri, 18 Nov 2022 08:14:08 +0000 (09:14 +0100)
Currently, the initrd= command line option to the EFI stub only supports
loading files that reside on the same volume as the loaded image, which
is not workable for loaders like GRUB that don't even implement the
volume abstraction (EFI_SIMPLE_FILE_SYSTEM_PROTOCOL), and load the
kernel from an anonymous buffer in memory. For this reason, another
method was devised that relies on the LoadFile2 protocol.

However, the command line loader is rather useful when using the UEFI
shell or other generic loaders that have no awareness of Linux specific
protocols so let's make it a bit more flexible, by permitting textual
device paths to be provided to initrd= as well, provided that they refer
to a file hosted on a EFI_SIMPLE_FILE_SYSTEM_PROTOCOL volume. E.g.,

  initrd=PciRoot(0x0)/Pci(0x3,0x0)/HD(1,MBR,0xBE1AFDFA,0x3F,0xFBFC1)/rootfs.cpio.gz

Signed-off-by: Ard Biesheuvel <ardb@kernel.org>
drivers/firmware/efi/libstub/efistub.h
drivers/firmware/efi/libstub/file.c
include/linux/efi.h

index e85916ed5311139380f53f99789eb8c429332515..1fde9cbe68997eb6a1997f0b2df397ec7277d2a6 100644 (file)
@@ -179,6 +179,21 @@ union efi_device_path_to_text_protocol {
 
 typedef union efi_device_path_to_text_protocol efi_device_path_to_text_protocol_t;
 
+union efi_device_path_from_text_protocol {
+       struct {
+               efi_device_path_protocol_t *
+                       (__efiapi *convert_text_to_device_node)(const efi_char16_t *);
+               efi_device_path_protocol_t *
+                       (__efiapi *convert_text_to_device_path)(const efi_char16_t *);
+       };
+       struct {
+               u32 convert_text_to_device_node;
+               u32 convert_text_to_device_path;
+       } mixed_mode;
+};
+
+typedef union efi_device_path_from_text_protocol efi_device_path_from_text_protocol_t;
+
 typedef void *efi_event_t;
 /* Note that notifications won't work in mixed mode */
 typedef void (__efiapi *efi_event_notify_t)(efi_event_t, void *);
index 246ccc5b015d25a6cbca1faffe77fa179e3bb16c..20d3530ca9c57d2f37aee4e62c38e31d86108478 100644 (file)
@@ -43,6 +43,13 @@ static efi_status_t efi_open_file(efi_file_protocol_t *volume,
        efi_file_protocol_t *fh;
        unsigned long info_sz;
        efi_status_t status;
+       efi_char16_t *c;
+
+       /* Replace UNIX dir separators with EFI standard ones */
+       for (c = fi->filename; *c != L'\0'; c++) {
+               if (*c == L'/')
+                       *c = L'\\';
+       }
 
        status = volume->open(volume, &fh, fi->filename, EFI_FILE_MODE_READ, 0);
        if (status != EFI_SUCCESS) {
@@ -111,16 +118,61 @@ static int find_file_option(const efi_char16_t *cmdline, int cmdline_len,
 
                if (c == L'\0' || c == L'\n' || c == L' ')
                        break;
-               else if (c == L'/')
-                       /* Replace UNIX dir separators with EFI standard ones */
-                       *result++ = L'\\';
-               else
-                       *result++ = c;
+               *result++ = c;
        }
        *result = L'\0';
        return i;
 }
 
+static efi_status_t efi_open_device_path(efi_file_protocol_t **volume,
+                                        struct finfo *fi)
+{
+       efi_guid_t text_to_dp_guid = EFI_DEVICE_PATH_FROM_TEXT_PROTOCOL_GUID;
+       static efi_device_path_from_text_protocol_t *text_to_dp = NULL;
+       efi_guid_t fs_proto = EFI_FILE_SYSTEM_GUID;
+       efi_device_path_protocol_t *initrd_dp;
+       efi_simple_file_system_protocol_t *io;
+       struct efi_file_path_dev_path *fpath;
+       efi_handle_t handle;
+       efi_status_t status;
+
+       /* See if the text to device path protocol exists */
+       if (!text_to_dp &&
+           efi_bs_call(locate_protocol, &text_to_dp_guid, NULL,
+                       (void **)&text_to_dp) != EFI_SUCCESS)
+               return EFI_UNSUPPORTED;
+
+
+       /* Convert the filename wide string into a device path */
+       initrd_dp = text_to_dp->convert_text_to_device_path(fi->filename);
+
+       /* Check whether the device path in question implements simple FS */
+       if ((efi_bs_call(locate_device_path, &fs_proto, &initrd_dp, &handle) ?:
+            efi_bs_call(handle_protocol, handle, &fs_proto, (void **)&io))
+           != EFI_SUCCESS)
+               return EFI_NOT_FOUND;
+
+       /* Check whether the remaining device path is a file device path */
+       if (initrd_dp->type != EFI_DEV_MEDIA ||
+           initrd_dp->sub_type != EFI_DEV_MEDIA_FILE) {
+               efi_warn("Unexpected device path node type: (%x, %x)\n",
+                        initrd_dp->type, initrd_dp->sub_type);
+               return EFI_LOAD_ERROR;
+       }
+
+       /* Copy the remaining file path into the fi structure */
+       fpath = (struct efi_file_path_dev_path *)initrd_dp;
+       memcpy(fi->filename, fpath->filename,
+              min(sizeof(fi->filename),
+                  fpath->header.length - sizeof(fpath->header)));
+
+       status = io->open_volume(io, volume);
+       if (status != EFI_SUCCESS)
+               efi_err("Failed to open volume\n");
+
+       return status;
+}
+
 /*
  * Check the cmdline for a LILO-style file= arguments.
  *
@@ -170,11 +222,13 @@ efi_status_t handle_cmdline_files(efi_loaded_image_t *image,
                cmdline += offset;
                cmdline_len -= offset;
 
-               if (!volume) {
+               status = efi_open_device_path(&volume, &fi);
+               if (status == EFI_UNSUPPORTED || status == EFI_NOT_FOUND)
+                       /* try the volume that holds the kernel itself */
                        status = efi_open_volume(image, &volume);
-                       if (status != EFI_SUCCESS)
-                               return status;
-               }
+
+               if (status != EFI_SUCCESS)
+                       goto err_free_alloc;
 
                status = efi_open_file(volume, &fi, &file, &size);
                if (status != EFI_SUCCESS)
@@ -231,14 +285,12 @@ efi_status_t handle_cmdline_files(efi_loaded_image_t *image,
                        size -= chunksize;
                }
                file->close(file);
+               volume->close(volume);
        } while (offset > 0);
 
        *load_addr = alloc_addr;
        *load_size = alloc_size;
 
-       if (volume)
-               volume->close(volume);
-
        if (*load_size == 0)
                return EFI_NOT_READY;
        return EFI_SUCCESS;
@@ -248,6 +300,8 @@ err_close_file:
 
 err_close_volume:
        volume->close(volume);
+
+err_free_alloc:
        efi_free(alloc_size, alloc_addr);
        return status;
 }
index 2d4d5a7fd7817dbed061ddeb1913ffa5c01d3dcb..ee034f0c5cc250b1522eda9865ebc3968c3a4b80 100644 (file)
@@ -371,6 +371,7 @@ void efi_native_runtime_setup(void);
 #define LOADED_IMAGE_DEVICE_PATH_PROTOCOL_GUID EFI_GUID(0xbc62157e, 0x3e33, 0x4fec,  0x99, 0x20, 0x2d, 0x3b, 0x36, 0xd7, 0x50, 0xdf)
 #define EFI_DEVICE_PATH_PROTOCOL_GUID          EFI_GUID(0x09576e91, 0x6d3f, 0x11d2,  0x8e, 0x39, 0x00, 0xa0, 0xc9, 0x69, 0x72, 0x3b)
 #define EFI_DEVICE_PATH_TO_TEXT_PROTOCOL_GUID  EFI_GUID(0x8b843e20, 0x8132, 0x4852,  0x90, 0xcc, 0x55, 0x1a, 0x4e, 0x4a, 0x7f, 0x1c)
+#define EFI_DEVICE_PATH_FROM_TEXT_PROTOCOL_GUID        EFI_GUID(0x05c99a21, 0xc70f, 0x4ad2,  0x8a, 0x5f, 0x35, 0xdf, 0x33, 0x43, 0xf5, 0x1e)
 #define EFI_GRAPHICS_OUTPUT_PROTOCOL_GUID      EFI_GUID(0x9042a9de, 0x23dc, 0x4a38,  0x96, 0xfb, 0x7a, 0xde, 0xd0, 0x80, 0x51, 0x6a)
 #define EFI_UGA_PROTOCOL_GUID                  EFI_GUID(0x982c298b, 0xf4fa, 0x41cb,  0xb8, 0x38, 0x77, 0xaa, 0x68, 0x8f, 0xb8, 0x39)
 #define EFI_PCI_IO_PROTOCOL_GUID               EFI_GUID(0x4cf5b200, 0x68b8, 0x4ca5,  0x9e, 0xec, 0xb2, 0x3e, 0x3f, 0x50, 0x02, 0x9a)
@@ -1011,6 +1012,11 @@ struct efi_mem_mapped_dev_path {
        u64                             ending_addr;
 } __packed;
 
+struct efi_file_path_dev_path {
+       struct efi_generic_dev_path     header;
+       efi_char16_t                    filename[];
+} __packed;
+
 struct efi_dev_path {
        union {
                struct efi_generic_dev_path     header;