]> git.dujemihanovic.xyz Git - u-boot.git/commitdiff
efi_loader: Implement FileLoad2 for initramfs loading
authorIlias Apalodimas <ilias.apalodimas@linaro.org>
Fri, 21 Feb 2020 07:55:45 +0000 (09:55 +0200)
committerHeinrich Schuchardt <xypron.glpk@gmx.de>
Fri, 28 Feb 2020 18:37:14 +0000 (19:37 +0100)
Following kernel's proposal for an arch-agnostic initrd loading
mechanism [1] let's implement the U-boot counterpart.
This new approach has a number of advantages compared to what we did up
to now. The file is loaded into memory only when requested limiting the
area of TOCTOU attacks. Users will be allowed to place the initramfs
file on any u-boot accessible partition instead of just the ESP one.
Finally this is an attempt of a generic interface across architectures
in the linux kernel so it makes sense to support that.

The file location is intentionally only supported as a config option
argument(CONFIG_EFI_INITRD_FILESPEC), in an effort to enhance security.
Although U-boot is not responsible for verifying the integrity of the
initramfs, we can enhance the offered security by only accepting a
built-in option, which will be naturally verified by UEFI Secure Boot.
This can easily change in the future if needed and configure that via ENV
or UEFI variable.

[1] https://lore.kernel.org/linux-efi/20200207202637.GA3464906@rani.riverdale.lan/T/#m4a25eb33112fab7a22faa0fd65d4d663209af32f

Signed-off-by: Ilias Apalodimas <ilias.apalodimas@linaro.org>
Signed-off-by: Heinrich Schuchardt <xypron.glpk@gmx.de>
cmd/efidebug.c
include/efi_api.h
include/efi_load_initrd.h [new file with mode: 0644]
include/efi_loader.h
lib/efi_loader/Kconfig
lib/efi_loader/Makefile
lib/efi_loader/efi_load_initrd.c [new file with mode: 0644]
lib/efi_loader/efi_setup.c

index 510e258b12abc13c4c74f6dca6e61a2404df2642..21dfd44fcc94f9b2a67af660aadb9e1890b21bb6 100644 (file)
@@ -243,6 +243,10 @@ static const struct {
                "HII Config Routing",
                EFI_HII_CONFIG_ROUTING_PROTOCOL_GUID,
        },
+       {
+               "Load File2",
+               EFI_LOAD_FILE2_PROTOCOL_GUID,
+       },
        {
                "Simple Network",
                EFI_SIMPLE_NETWORK_PROTOCOL_GUID,
index b7b68cb7a1f0670977a750c673e0a04e889f47be..3d1a6beeeaca4c0a2a6f429de52a4515f35c51a2 100644 (file)
@@ -331,6 +331,14 @@ struct efi_runtime_services {
        EFI_GUID(0xeb9d2d31, 0x2d88, 0x11d3,  \
                 0x9a, 0x16, 0x00, 0x90, 0x27, 0x3f, 0xc1, 0x4d)
 
+#define EFI_LOAD_FILE_PROTOCOL_GUID \
+       EFI_GUID(0x56ec3091, 0x954c, 0x11d2, \
+                0x8e, 0x3f, 0x00, 0xa0, 0xc9, 0x69, 0x72, 0x3b)
+
+#define EFI_LOAD_FILE2_PROTOCOL_GUID \
+       EFI_GUID(0x4006c0c1, 0xfcb3, 0x403e, \
+                0x99, 0x6d, 0x4a, 0x6c, 0x87, 0x24, 0xe0, 0x6d)
+
 struct efi_configuration_table {
        efi_guid_t guid;
        void *table;
@@ -486,6 +494,7 @@ struct efi_device_path_nvme {
 #define DEVICE_PATH_TYPE_MEDIA_DEVICE          0x04
 #  define DEVICE_PATH_SUB_TYPE_HARD_DRIVE_PATH 0x01
 #  define DEVICE_PATH_SUB_TYPE_CDROM_PATH      0x02
+#  define DEVICE_PATH_SUB_TYPE_VENDOR_PATH     0x03
 #  define DEVICE_PATH_SUB_TYPE_FILE_PATH       0x04
 
 struct efi_device_path_hard_drive_path {
@@ -1619,6 +1628,14 @@ struct efi_unicode_collation_protocol {
        char *supported_languages;
 };
 
+struct efi_load_file_protocol {
+       efi_status_t (EFIAPI *load_file)(struct efi_load_file_protocol *this,
+                                        struct efi_device_path *file_path,
+                                        bool boot_policy,
+                                        efi_uintn_t *buffer_size,
+                                        void *buffer);
+};
+
 /* Boot manager load options */
 #define LOAD_OPTION_ACTIVE             0x00000001
 #define LOAD_OPTION_FORCE_RECONNECT    0x00000002
diff --git a/include/efi_load_initrd.h b/include/efi_load_initrd.h
new file mode 100644 (file)
index 0000000..478ae80
--- /dev/null
@@ -0,0 +1,25 @@
+/* SPDX-License-Identifier: GPL-2.0+ */
+/*
+ * Copyright (c) 2020, Linaro Limited
+ */
+
+#if !defined _EFI_LOAD_INITRD_H_
+#define _EFI_LOAD_INITRD_H_
+
+#include <efi.h>
+#include <efi_api.h>
+
+/*
+ * Vendor GUID used by Linux to identify the handle with the
+ * EFI_LOAD_FILE2_PROTOCOL and load an initial ramdisk.
+ */
+#define EFI_INITRD_MEDIA_GUID \
+       EFI_GUID(0x5568e427, 0x68fc, 0x4f3d, \
+                0xac, 0x74, 0xca, 0x55, 0x52, 0x31, 0xcc, 0x68)
+
+struct efi_initrd_dp {
+       struct efi_device_path_vendor vendor;
+       struct efi_device_path end;
+} __packed;
+
+#endif
index d4c59b54c48b4e53a456aed05b06ad0cf056bd55..8e343798339110469eabcfdf6deaf931d4131006 100644 (file)
@@ -378,6 +378,7 @@ efi_status_t efi_gop_register(void);
 efi_status_t efi_net_register(void);
 /* Called by bootefi to make the watchdog available */
 efi_status_t efi_watchdog_register(void);
+efi_status_t efi_initrd_register(void);
 /* Called by bootefi to make SMBIOS tables available */
 /**
  * efi_acpi_register() - write out ACPI tables
index 76f43eca95f10a9b1dc5aba1a6a5dd5af072daab..9890144d4161117a4388c132acb0d515a2bcab6d 100644 (file)
@@ -130,4 +130,19 @@ config EFI_RNG_PROTOCOL
          Provide a EFI_RNG_PROTOCOL implementation using the hardware random
          number generator of the platform.
 
+config EFI_LOAD_FILE2_INITRD
+       bool "EFI_FILE_LOAD2_PROTOCOL for Linux initial ramdisk"
+       default n
+       help
+         Expose a EFI_FILE_LOAD2_PROTOCOL that the Linux UEFI stub can
+         use to load the initial ramdisk. Once this is enabled using
+         initrd=<ramdisk> will stop working.
+
+config EFI_INITRD_FILESPEC
+       string "initramfs path"
+       default "host 0:1 initrd"
+       depends on EFI_LOAD_FILE2_INITRD
+       help
+         Full path of the initramfs file, e.g. mmc 0:2 initramfs.cpio.gz.
+
 endif
index 04dc8648512b0f3591c9687e1cdf62680530ca2e..9b3b704473367ac78d5f4d1dc10c3e59b0520e16 100644 (file)
@@ -43,3 +43,4 @@ obj-$(CONFIG_NET) += efi_net.o
 obj-$(CONFIG_GENERATE_ACPI_TABLE) += efi_acpi.o
 obj-$(CONFIG_GENERATE_SMBIOS_TABLE) += efi_smbios.o
 obj-$(CONFIG_EFI_RNG_PROTOCOL) += efi_rng.o
+obj-$(CONFIG_EFI_LOAD_FILE2_INITRD) += efi_load_initrd.o
diff --git a/lib/efi_loader/efi_load_initrd.c b/lib/efi_loader/efi_load_initrd.c
new file mode 100644 (file)
index 0000000..574a83d
--- /dev/null
@@ -0,0 +1,198 @@
+// SPDX-License-Identifier: GPL-2.0+
+/*
+ * Copyright (c) 2020, Linaro Limited
+ */
+
+#include <common.h>
+#include <env.h>
+#include <malloc.h>
+#include <mapmem.h>
+#include <dm.h>
+#include <fs.h>
+#include <efi_loader.h>
+#include <efi_load_initrd.h>
+
+static const efi_guid_t efi_guid_load_file2_protocol =
+               EFI_LOAD_FILE2_PROTOCOL_GUID;
+
+static efi_status_t EFIAPI
+efi_load_file2_initrd(struct efi_load_file_protocol *this,
+                     struct efi_device_path *file_path, bool boot_policy,
+                     efi_uintn_t *buffer_size, void *buffer);
+
+static const struct efi_load_file_protocol efi_lf2_protocol = {
+       .load_file = efi_load_file2_initrd,
+};
+
+/*
+ * Device path defined by Linux to identify the handle providing the
+ * EFI_LOAD_FILE2_PROTOCOL used for loading the initial ramdisk.
+ */
+static const struct efi_initrd_dp dp = {
+       .vendor = {
+               {
+                  DEVICE_PATH_TYPE_MEDIA_DEVICE,
+                  DEVICE_PATH_SUB_TYPE_VENDOR_PATH,
+                  sizeof(dp.vendor),
+               },
+               EFI_INITRD_MEDIA_GUID,
+       },
+       .end = {
+               DEVICE_PATH_TYPE_END,
+               DEVICE_PATH_SUB_TYPE_END,
+               sizeof(dp.end),
+       }
+};
+
+/**
+ * get_file_size() - retrieve the size of initramfs, set efi status on error
+ *
+ * @dev:                       device to read from. i.e "mmc"
+ * @part:                      device partition. i.e "0:1"
+ * @file:                      name fo file
+ * @status:                    EFI exit code in case of failure
+ *
+ * Return:                     size of file
+ */
+static loff_t get_file_size(const char *dev, const char *part, const char *file,
+                           efi_status_t *status)
+{
+       loff_t sz = 0;
+       int ret;
+
+       ret = fs_set_blk_dev(dev, part, FS_TYPE_ANY);
+       if (ret) {
+               *status = EFI_NO_MEDIA;
+               goto out;
+       }
+
+       ret = fs_size(file, &sz);
+       if (ret) {
+               sz = 0;
+               *status = EFI_NOT_FOUND;
+               goto out;
+       }
+
+out:
+       return sz;
+}
+
+/**
+ * load_file2() - get information about random number generation
+ *
+ * This function implement the LoadFile2() service in order to load an initram
+ * disk requested by the Linux kernel stub.
+ * See the UEFI spec for details.
+ *
+ * @this:                      loadfile2 protocol instance
+ * @file_path:                 relative path of the file. "" in this case
+ * @boot_policy:               must be false for Loadfile2
+ * @buffer_size:               size of allocated buffer
+ * @buffer:                    buffer to load the file
+ *
+ * Return:                     status code
+ */
+static efi_status_t EFIAPI
+efi_load_file2_initrd(struct efi_load_file_protocol *this,
+                     struct efi_device_path *file_path, bool boot_policy,
+                     efi_uintn_t *buffer_size, void *buffer)
+{
+       const char *filespec = CONFIG_EFI_INITRD_FILESPEC;
+       efi_status_t status = EFI_NOT_FOUND;
+       loff_t file_sz = 0, read_sz = 0;
+       char *dev, *part, *file;
+       char *s;
+       int ret;
+
+       EFI_ENTRY("%p, %p, %d, %p, %p", this, file_path, boot_policy,
+                 buffer_size, buffer);
+
+       s = strdup(filespec);
+       if (!s)
+               goto out;
+
+       if (!this || this != &efi_lf2_protocol ||
+           !buffer_size) {
+               status = EFI_INVALID_PARAMETER;
+               goto out;
+       }
+
+       if (file_path->type != dp.end.type ||
+           file_path->sub_type != dp.end.sub_type) {
+               status = EFI_INVALID_PARAMETER;
+               goto out;
+       }
+
+       if (boot_policy) {
+               status = EFI_UNSUPPORTED;
+               goto out;
+       }
+
+       /* expect something like 'mmc 0:1 initrd.cpio.gz' */
+       dev = strsep(&s, " ");
+       if (!dev)
+               goto out;
+       part = strsep(&s, " ");
+       if (!part)
+               goto out;
+       file = strsep(&s, " ");
+       if (!file)
+               goto out;
+
+       file_sz = get_file_size(dev, part, file, &status);
+       if (!file_sz)
+               goto out;
+
+       if (!buffer || *buffer_size < file_sz) {
+               status = EFI_BUFFER_TOO_SMALL;
+               *buffer_size = file_sz;
+       } else {
+               ret = fs_set_blk_dev(dev, part, FS_TYPE_ANY);
+               if (ret) {
+                       status = EFI_NO_MEDIA;
+                       goto out;
+               }
+
+               ret = fs_read(file, map_to_sysmem(buffer), 0, *buffer_size,
+                             &read_sz);
+               if (ret || read_sz != file_sz)
+                       goto out;
+               *buffer_size = read_sz;
+
+               status = EFI_SUCCESS;
+       }
+
+out:
+       free(s);
+       return EFI_EXIT(status);
+}
+
+/**
+ * efi_initrd_register() - Register a handle and loadfile2 protocol
+ *
+ * This function creates a new handle and installs a linux specific GUID
+ * to handle initram disk loading during boot.
+ * See the UEFI spec for details.
+ *
+ * Return:                     status code
+ */
+efi_status_t efi_initrd_register(void)
+{
+       efi_handle_t efi_initrd_handle = NULL;
+       efi_status_t ret;
+
+       /*
+        * Set up the handle with the EFI_LOAD_FILE2_PROTOCOL which Linux may
+        * use to load the initial ramdisk.
+        */
+       ret = EFI_CALL(efi_install_multiple_protocol_interfaces
+                      (&efi_initrd_handle,
+                       /* initramfs */
+                       &efi_guid_device_path, &dp,
+                       /* LOAD_FILE2 */
+                       &efi_guid_load_file2_protocol,
+                       (void *)&efi_lf2_protocol,
+                       NULL));
+
+       return ret;
+}
index 2060307b053f8edd10e3983b3ff648397d94a1fa..b458093dfbd53adfe51db536020bfacd8a65b2ea 100644 (file)
@@ -155,6 +155,11 @@ efi_status_t efi_init_obj_list(void)
        if (ret != EFI_SUCCESS)
                goto out;
 #endif
+#ifdef CONFIG_EFI_LOAD_FILE2_INITRD
+       ret = efi_initrd_register();
+       if (ret != EFI_SUCCESS)
+               goto out;
+#endif
 #ifdef CONFIG_NET
        ret = efi_net_register();
        if (ret != EFI_SUCCESS)