Linux-EFI Archive on lore.kernel.org
 help / color / Atom feed
* [PATCH 0/2] arch-agnostic initrd loading method for EFI systems
@ 2020-02-06 14:03 Ard Biesheuvel
  2020-02-06 14:03 ` [PATCH 1/2] efi/libstub: add support for loading the initrd from a device path Ard Biesheuvel
                   ` (3 more replies)
  0 siblings, 4 replies; 39+ messages in thread
From: Ard Biesheuvel @ 2020-02-06 14:03 UTC (permalink / raw)
  To: linux-efi
  Cc: linux-arm-kernel, Ard Biesheuvel, lersek, leif, pjones, mjg59,
	agraf, ilias.apalodimas, xypron.glpk, daniel.kiper

This series introduces an arch agnostic way of loading the initrd into
memory from the EFI stub. This addresses a number of shortcomings that
affect the current implementations that exist across architectures:

- The initrd=<file> command line option can only load files that reside
  on the same file system that the kernel itself was loaded from, which
  requires the bootloader or firmware to expose that file system via the
  appropriate EFI protocol, which is not always feasible. From the kernel
  side, this protocol is problematic since it is incompatible with mixed
  mode on x86 (this is due to the fact that some of its methods have
  prototypes that are difficult to marshall)

- The approach that is ordinarily taken by GRUB is to load the initrd into
  memory, and pass it to the kernel proper via the bootparams structure or
  via the device tree. This requires the boot loader to have an understanding
  of those structures, which are not always set in stone, and of the policies
  around where the initrd may be loaded into memory. In the ARM case, it
  requires GRUB to modify the hardware description provided by the firmware,
  given that the initrd base and offset in memory are passed via the same
  data structure. It also creates a time window where the initrd data sits
  in memory, and can potentially be corrupted before the kernel is booted.

Considering that we will soon have new users of these interfaces (EFI for
kvmtool on ARM, RISC-V in u-boot, etc), it makes sense to add a generic
interface now, before having another wave of bespoke arch specific code
coming in.

Another aspect to take into account is that support for UEFI secure boot
and measured boot is being taken into the upstream, and being able to
rely on the PE entry point for booting any architecture makes the GRUB
vs shim story much cleaner, as we should be able to rely on LoadImage
and StartImage on all architectures, while retaining the ability to
load initrds from anywhere.

Note that these patches depend on a fair amount of cleanup work that I
am targetting for v5.7. Branch can be found at:
https://git.kernel.org/pub/scm/linux/kernel/git/ardb/linux.git/log/?h=efistub-unification2

An implementation for ArmVirtQemu (OVMF for ARM aka AAVMF) can be found
at https://github.com/ardbiesheuvel/edk2/commits/linux-efi-generic.
The change is for ARM only, but the exact same code could be used on x86.

Cc: lersek@redhat.com
Cc: leif@nuviainc.com
Cc: pjones@redhat.com
Cc: mjg59@google.com
Cc: agraf@csgraf.de
Cc: ilias.apalodimas@linaro.org
Cc: xypron.glpk@gmx.de 
Cc: daniel.kiper@oracle.com

Ard Biesheuvel (2):
  efi/libstub: add support for loading the initrd from a device path
  efi/libstub: take noinitrd cmdline argument into account for devpath
    initrd

 drivers/firmware/efi/libstub/arm-stub.c       | 21 ++++--
 .../firmware/efi/libstub/efi-stub-helper.c    | 74 +++++++++++++++++++
 drivers/firmware/efi/libstub/efistub.h        | 13 ++++
 drivers/firmware/efi/libstub/x86-stub.c       | 51 ++++++++++---
 include/linux/efi.h                           |  1 +
 5 files changed, 146 insertions(+), 14 deletions(-)

-- 
2.17.1


^ permalink raw reply	[flat|nested] 39+ messages in thread

* [PATCH 1/2] efi/libstub: add support for loading the initrd from a device path
  2020-02-06 14:03 [PATCH 0/2] arch-agnostic initrd loading method for EFI systems Ard Biesheuvel
@ 2020-02-06 14:03 ` Ard Biesheuvel
  2020-02-06 18:26   ` Heinrich Schuchardt
                     ` (2 more replies)
  2020-02-06 14:03 ` [PATCH 2/2] efi/libstub: take noinitrd cmdline argument into account for devpath initrd Ard Biesheuvel
                   ` (2 subsequent siblings)
  3 siblings, 3 replies; 39+ messages in thread
From: Ard Biesheuvel @ 2020-02-06 14:03 UTC (permalink / raw)
  To: linux-efi
  Cc: linux-arm-kernel, Ard Biesheuvel, lersek, leif, pjones, mjg59,
	agraf, ilias.apalodimas, xypron.glpk, daniel.kiper

There are currently two ways to specify the initrd to be passed to the
Linux kernel when booting via the EFI stub:
- it can be passed as a initrd= command line option when doing a pure PE
  boot (as opposed to the EFI handover protocol that exists for x86)
- otherwise, the bootloader or firmware can load the initrd into memory,
  and pass the address and size via the bootparams struct (x86) or
  device tree (ARM)

In the first case, we are limited to loading from the same file system
that the kernel was loaded from, and it is also problematic in a trusted
boot context, given that we cannot easily protect the command line from
tampering without either adding complicated white/blacklisting of boot
arguments or locking down the command line altogether.

In the second case, we force the bootloader to duplicate knowledge about
the boot protocol which is already encoded in the stub, and which may be
subject to change over time, e.g., bootparams struct definitions, memory
allocation/alignment requirements for the placement of the initrd etc etc.
In the ARM case, it also requires the bootloader to modify the hardware
description provided by the firmware, as it is passed in the same file.
On systems where the initrd is measured after loading, it creates a time
window where the initrd contents might be manipulated in memory before
handing over to the kernel.

Address these concerns by adding support for loading the initrd into
memory by invoking the EFI LoadFile2 protocol installed on a vendor
GUIDed device path that specifically designates a Linux initrd.
This addresses the above concerns, by putting the EFI stub in charge of
placement in memory and of passing the base and size to the kernel proper
(via whatever means it desires) while still leaving it up to the firmware
or bootloader to obtain the file contents, potentially from other file
systems than the one the kernel itself was loaded from. On platforms that
implement measured boot, it permits the firmware to take the measurement
right before the kernel actually consumes the contents.

Signed-off-by: Ard Biesheuvel <ardb@kernel.org>
---
 drivers/firmware/efi/libstub/arm-stub.c        | 16 +++--
 drivers/firmware/efi/libstub/efi-stub-helper.c | 65 ++++++++++++++++++++
 drivers/firmware/efi/libstub/efistub.h         | 12 ++++
 drivers/firmware/efi/libstub/x86-stub.c        | 36 ++++++++++-
 include/linux/efi.h                            |  1 +
 5 files changed, 123 insertions(+), 7 deletions(-)

diff --git a/drivers/firmware/efi/libstub/arm-stub.c b/drivers/firmware/efi/libstub/arm-stub.c
index c7b091f50e55..1db943c1ba2b 100644
--- a/drivers/firmware/efi/libstub/arm-stub.c
+++ b/drivers/firmware/efi/libstub/arm-stub.c
@@ -157,6 +157,7 @@ unsigned long efi_entry(void *handle, efi_system_table_t *sys_table_arg,
 	enum efi_secureboot_mode secure_boot;
 	struct screen_info *si;
 	efi_properties_table_t *prop_tbl;
+	unsigned long max_addr;
 
 	sys_table = sys_table_arg;
 
@@ -255,11 +256,18 @@ unsigned long efi_entry(void *handle, efi_system_table_t *sys_table_arg,
 	if (!fdt_addr)
 		pr_efi("Generating empty DTB\n");
 
-	status = efi_load_initrd(image, ULONG_MAX,
-				 efi_get_max_initrd_addr(dram_base, *image_addr),
-				 &initrd_addr, &initrd_size);
+	max_addr = efi_get_max_initrd_addr(dram_base, *image_addr);
+	status = efi_load_initrd_devpath(&initrd_addr, &initrd_size, max_addr);
+	if (status == EFI_SUCCESS)
+		pr_efi("Loaded initrd from LINUX_EFI_INITRD_MEDIA_GUID device path\n");
+	else if (status == EFI_NOT_FOUND) {
+		status = efi_load_initrd(image, ULONG_MAX, max_addr,
+					 &initrd_addr, &initrd_size);
+		if (status == EFI_SUCCESS)
+			pr_efi("Loaded initrd from command line option\n");
+	}
 	if (status != EFI_SUCCESS)
-		pr_efi_err("Failed initrd from command line!\n");
+		pr_efi_err("Failed to load initrd!\n");
 
 	efi_random_get_seed();
 
diff --git a/drivers/firmware/efi/libstub/efi-stub-helper.c b/drivers/firmware/efi/libstub/efi-stub-helper.c
index 8e60a39df3c5..eaf45ea749b3 100644
--- a/drivers/firmware/efi/libstub/efi-stub-helper.c
+++ b/drivers/firmware/efi/libstub/efi-stub-helper.c
@@ -323,3 +323,68 @@ void efi_char16_printk(efi_char16_t *str)
 	efi_call_proto(efi_table_attr(efi_system_table(), con_out),
 		       output_string, str);
 }
+
+static const struct {
+	struct efi_vendor_dev_path	vendor;
+	struct efi_generic_dev_path	end;
+} __packed initrd_devpath = {
+	{
+		EFI_DEV_MEDIA,
+		EFI_DEV_MEDIA_VENDOR,
+		sizeof(struct efi_vendor_dev_path),
+		LINUX_EFI_INITRD_MEDIA_GUID
+	}, {
+		EFI_DEV_END_PATH,
+		EFI_DEV_END_ENTIRE,
+		sizeof(struct efi_generic_dev_path)
+	}
+};
+
+efi_status_t efi_load_initrd_devpath(unsigned long *load_addr,
+				     unsigned long *load_size,
+				     unsigned long max)
+{
+	efi_guid_t lf2_proto_guid = EFI_LOAD_FILE2_PROTOCOL_GUID;
+	efi_device_path_protocol_t *dp;
+	efi_load_file2_protocol_t *lf2;
+	unsigned long initrd_addr;
+	unsigned long initrd_size;
+	efi_handle_t handle;
+	efi_status_t status;
+
+	if (!load_addr || !load_size)
+		return EFI_INVALID_PARAMETER;
+
+	dp = (efi_device_path_protocol_t *)&initrd_devpath;
+	status = efi_bs_call(locate_device_path, &lf2_proto_guid, &dp, &handle);
+	if (status != EFI_SUCCESS)
+		return status;
+
+	status = efi_bs_call(handle_protocol, handle, &lf2_proto_guid,
+			     (void **)&lf2);
+	if (status != EFI_SUCCESS)
+		return status;
+
+	initrd_size = 0;
+	status = efi_call_proto(lf2, load_file,
+				(efi_device_path_protocol_t *)&initrd_devpath,
+				false, &initrd_size, NULL);
+	if (status != EFI_BUFFER_TOO_SMALL)
+		return EFI_LOAD_ERROR;
+
+	status = efi_allocate_pages(initrd_size, &initrd_addr, max);
+	if (status != EFI_SUCCESS)
+		return status;
+
+	status = efi_call_proto(lf2, load_file,
+				(efi_device_path_protocol_t *)&initrd_devpath,
+				false, &initrd_size, (void *)initrd_addr);
+	if (status != EFI_SUCCESS) {
+		efi_free(initrd_size, initrd_addr);
+		return status;
+	}
+
+	*load_addr = initrd_addr;
+	*load_size = initrd_size;
+	return EFI_SUCCESS;
+}
diff --git a/drivers/firmware/efi/libstub/efistub.h b/drivers/firmware/efi/libstub/efistub.h
index 99e93fd76ec5..fbf9f9442eed 100644
--- a/drivers/firmware/efi/libstub/efistub.h
+++ b/drivers/firmware/efi/libstub/efistub.h
@@ -566,6 +566,14 @@ union efi_load_file_protocol {
 	} mixed_mode;
 };
 
+struct efi_vendor_dev_path {
+	u8		type;
+	u8		sub_type;
+	u16		length;
+	efi_guid_t	vendorguid;
+	u8		vendordata[];
+} __packed;
+
 void efi_pci_disable_bridge_busmaster(void);
 
 typedef efi_status_t (*efi_exit_boot_map_processing)(
@@ -651,4 +659,8 @@ efi_status_t efi_load_initrd(efi_loaded_image_t *image,
 			     unsigned long *load_addr,
 			     unsigned long *load_size);
 
+efi_status_t efi_load_initrd_devpath(unsigned long *load_addr,
+				     unsigned long *load_size,
+				     unsigned long max);
+
 #endif
diff --git a/drivers/firmware/efi/libstub/x86-stub.c b/drivers/firmware/efi/libstub/x86-stub.c
index f3e2ff31b624..7f38f95676dd 100644
--- a/drivers/firmware/efi/libstub/x86-stub.c
+++ b/drivers/firmware/efi/libstub/x86-stub.c
@@ -419,9 +419,20 @@ efi_status_t __efiapi efi_pe_entry(efi_handle_t handle,
 	if (status != EFI_SUCCESS)
 		goto fail2;
 
-	status = efi_load_initrd(image, hdr->initrd_addr_max,
-				 above4g ? ULONG_MAX : hdr->initrd_addr_max,
-				 &ramdisk_addr, &ramdisk_size);
+	/*
+	 * The initrd loaded from the Linux initrd vendor device
+	 * path should take precedence, as we don't want the
+	 * [unverified] command line to override the initrd
+	 * supplied by the [potentially verified] firmware.
+	 */
+	status = efi_load_initrd_devpath(&ramdisk_addr, &ramdisk_size,
+					 above4g ? ULONG_MAX
+						 : hdr->initrd_addr_max);
+	if (status == EFI_NOT_FOUND)
+		status = efi_load_initrd(image, hdr->initrd_addr_max,
+					 above4g ? ULONG_MAX
+						 : hdr->initrd_addr_max,
+					 &ramdisk_addr, &ramdisk_size);
 	if (status != EFI_SUCCESS)
 		goto fail2;
 	hdr->ramdisk_image = ramdisk_addr & 0xffffffff;
@@ -732,6 +743,25 @@ struct boot_params *efi_main(efi_handle_t handle,
 			 ((u64)boot_params->ext_cmd_line_ptr << 32));
 	efi_parse_options((char *)cmdline_paddr);
 
+	if (!hdr->ramdisk_size && !boot_params->ext_ramdisk_size) {
+		unsigned long max = hdr->initrd_addr_max;
+		unsigned long addr, size;
+
+		if (hdr->xloadflags & XLF_CAN_BE_LOADED_ABOVE_4G)
+			max = ULONG_MAX;
+
+		status = efi_load_initrd_devpath(&addr, &size, max);
+		if (status == EFI_SUCCESS) {
+			hdr->ramdisk_image		= (u32)addr;
+			hdr->ramdisk_size 		= (u32)size;
+			boot_params->ext_ramdisk_image	= (u64)addr >> 32;
+			boot_params->ext_ramdisk_size 	= (u64)size >> 32;
+		} else if (status != EFI_NOT_FOUND) {
+			efi_printk("efi_load_initrd_devpath() failed!\n");
+			goto fail;
+		}
+	}
+
 	/*
 	 * If the boot loader gave us a value for secure_boot then we use that,
 	 * otherwise we ask the BIOS.
diff --git a/include/linux/efi.h b/include/linux/efi.h
index 9ccf313fe9de..75c83c322c40 100644
--- a/include/linux/efi.h
+++ b/include/linux/efi.h
@@ -353,6 +353,7 @@ void efi_native_runtime_setup(void);
 #define LINUX_EFI_TPM_EVENT_LOG_GUID		EFI_GUID(0xb7799cb0, 0xeca2, 0x4943,  0x96, 0x67, 0x1f, 0xae, 0x07, 0xb7, 0x47, 0xfa)
 #define LINUX_EFI_TPM_FINAL_LOG_GUID		EFI_GUID(0x1e2ed096, 0x30e2, 0x4254,  0xbd, 0x89, 0x86, 0x3b, 0xbe, 0xf8, 0x23, 0x25)
 #define LINUX_EFI_MEMRESERVE_TABLE_GUID		EFI_GUID(0x888eb0c6, 0x8ede, 0x4ff5,  0xa8, 0xf0, 0x9a, 0xee, 0x5c, 0xb9, 0x77, 0xc2)
+#define LINUX_EFI_INITRD_MEDIA_GUID		EFI_GUID(0x5568e427, 0x68fc, 0x4f3d,  0xac, 0x74, 0xca, 0x55, 0x52, 0x31, 0xcc, 0x68)
 
 /* OEM GUIDs */
 #define DELLEMC_EFI_RCI2_TABLE_GUID		EFI_GUID(0x2d9f28a2, 0xa886, 0x456a,  0x97, 0xa8, 0xf1, 0x1e, 0xf2, 0x4f, 0xf4, 0x55)
-- 
2.17.1


^ permalink raw reply	[flat|nested] 39+ messages in thread

* [PATCH 2/2] efi/libstub: take noinitrd cmdline argument into account for devpath initrd
  2020-02-06 14:03 [PATCH 0/2] arch-agnostic initrd loading method for EFI systems Ard Biesheuvel
  2020-02-06 14:03 ` [PATCH 1/2] efi/libstub: add support for loading the initrd from a device path Ard Biesheuvel
@ 2020-02-06 14:03 ` Ard Biesheuvel
  2020-02-06 18:33   ` Heinrich Schuchardt
  2020-02-12 16:01   ` Peter Jones
  2020-02-07  9:09 ` [PATCH 0/2] arch-agnostic initrd loading method for EFI systems Laszlo Ersek
  2020-02-07 18:45 ` Arvind Sankar
  3 siblings, 2 replies; 39+ messages in thread
From: Ard Biesheuvel @ 2020-02-06 14:03 UTC (permalink / raw)
  To: linux-efi
  Cc: linux-arm-kernel, Ard Biesheuvel, lersek, leif, pjones, mjg59,
	agraf, ilias.apalodimas, xypron.glpk, daniel.kiper

One of the advantages of using what basically amounts to a callback
interface into the bootloader for loading the initrd is that it provides
a natural place for the bootloader or firmware to measure the initrd
contents while they are being passed to the kernel.

Unfortunately, this is not a guarantee that the initrd will in fact be
loaded and its /init invoked by the kernel, since the command line may
contain the 'noinitrd' option, in which case the initrd is ignored, but
this will not be reflected in the PCR that covers the initrd measurement.

This could be addressed by measuring the command line as well, and
including that PCR in the attestation policy, but this locks down the
command line completely, which may be too restrictive.

So let's take the noinitrd argument into account in the stub, too. This
forces the PCR that covers the initrd to assume a different value when
noinitrd is passed, allowing an attestation policy to disregard the
command line if there is no need to take its measurement into account
for other reasons.

Signed-off-by: Ard Biesheuvel <ardb@kernel.org>
---
 drivers/firmware/efi/libstub/arm-stub.c        | 23 +++++-----
 drivers/firmware/efi/libstub/efi-stub-helper.c |  9 ++++
 drivers/firmware/efi/libstub/efistub.h         |  1 +
 drivers/firmware/efi/libstub/x86-stub.c        | 45 +++++++++++---------
 4 files changed, 47 insertions(+), 31 deletions(-)

diff --git a/drivers/firmware/efi/libstub/arm-stub.c b/drivers/firmware/efi/libstub/arm-stub.c
index 1db943c1ba2b..5e8f16cf016e 100644
--- a/drivers/firmware/efi/libstub/arm-stub.c
+++ b/drivers/firmware/efi/libstub/arm-stub.c
@@ -256,18 +256,21 @@ unsigned long efi_entry(void *handle, efi_system_table_t *sys_table_arg,
 	if (!fdt_addr)
 		pr_efi("Generating empty DTB\n");
 
-	max_addr = efi_get_max_initrd_addr(dram_base, *image_addr);
-	status = efi_load_initrd_devpath(&initrd_addr, &initrd_size, max_addr);
-	if (status == EFI_SUCCESS)
-		pr_efi("Loaded initrd from LINUX_EFI_INITRD_MEDIA_GUID device path\n");
-	else if (status == EFI_NOT_FOUND) {
-		status = efi_load_initrd(image, ULONG_MAX, max_addr,
-					 &initrd_addr, &initrd_size);
+	if (!noinitrd()) {
+		max_addr = efi_get_max_initrd_addr(dram_base, *image_addr);
+		status = efi_load_initrd_devpath(&initrd_addr, &initrd_size,
+						 max_addr);
 		if (status == EFI_SUCCESS)
-			pr_efi("Loaded initrd from command line option\n");
+			pr_efi("Loaded initrd from LINUX_EFI_INITRD_MEDIA_GUID device path\n");
+		else if (status == EFI_NOT_FOUND) {
+			status = efi_load_initrd(image, ULONG_MAX, max_addr,
+						 &initrd_addr, &initrd_size);
+			if (status == EFI_SUCCESS)
+				pr_efi("Loaded initrd from command line option\n");
+		}
+		if (status != EFI_SUCCESS)
+			pr_efi_err("Failed to load initrd!\n");
 	}
-	if (status != EFI_SUCCESS)
-		pr_efi_err("Failed to load initrd!\n");
 
 	efi_random_get_seed();
 
diff --git a/drivers/firmware/efi/libstub/efi-stub-helper.c b/drivers/firmware/efi/libstub/efi-stub-helper.c
index eaf45ea749b3..367575fb8424 100644
--- a/drivers/firmware/efi/libstub/efi-stub-helper.c
+++ b/drivers/firmware/efi/libstub/efi-stub-helper.c
@@ -11,6 +11,7 @@
 
 static bool __efistub_global efi_nochunk;
 static bool __efistub_global efi_nokaslr;
+static bool __efistub_global efi_noinitrd;
 static bool __efistub_global efi_quiet;
 static bool __efistub_global efi_novamap;
 static bool __efistub_global efi_nosoftreserve;
@@ -25,6 +26,10 @@ bool __pure nokaslr(void)
 {
 	return efi_nokaslr;
 }
+bool __pure noinitrd(void)
+{
+	return efi_noinitrd;
+}
 bool __pure is_quiet(void)
 {
 	return efi_quiet;
@@ -71,6 +76,10 @@ efi_status_t efi_parse_options(char const *cmdline)
 	if (str == cmdline || (str && str > cmdline && *(str - 1) == ' '))
 		efi_nokaslr = true;
 
+	str = strstr(cmdline, "noinitrd");
+	if (str == cmdline || (str && str > cmdline && *(str - 1) == ' '))
+		efi_noinitrd = true;
+
 	str = strstr(cmdline, "quiet");
 	if (str == cmdline || (str && str > cmdline && *(str - 1) == ' '))
 		efi_quiet = true;
diff --git a/drivers/firmware/efi/libstub/efistub.h b/drivers/firmware/efi/libstub/efistub.h
index fbf9f9442eed..29a5d0e9554a 100644
--- a/drivers/firmware/efi/libstub/efistub.h
+++ b/drivers/firmware/efi/libstub/efistub.h
@@ -44,6 +44,7 @@
 
 extern bool __pure nochunk(void);
 extern bool __pure nokaslr(void);
+extern bool __pure noinitrd(void);
 extern bool __pure is_quiet(void);
 extern bool __pure novamap(void);
 
diff --git a/drivers/firmware/efi/libstub/x86-stub.c b/drivers/firmware/efi/libstub/x86-stub.c
index 7f38f95676dd..9d86c0949b3c 100644
--- a/drivers/firmware/efi/libstub/x86-stub.c
+++ b/drivers/firmware/efi/libstub/x86-stub.c
@@ -419,26 +419,28 @@ efi_status_t __efiapi efi_pe_entry(efi_handle_t handle,
 	if (status != EFI_SUCCESS)
 		goto fail2;
 
-	/*
-	 * The initrd loaded from the Linux initrd vendor device
-	 * path should take precedence, as we don't want the
-	 * [unverified] command line to override the initrd
-	 * supplied by the [potentially verified] firmware.
-	 */
-	status = efi_load_initrd_devpath(&ramdisk_addr, &ramdisk_size,
-					 above4g ? ULONG_MAX
-						 : hdr->initrd_addr_max);
-	if (status == EFI_NOT_FOUND)
-		status = efi_load_initrd(image, hdr->initrd_addr_max,
-					 above4g ? ULONG_MAX
-						 : hdr->initrd_addr_max,
-					 &ramdisk_addr, &ramdisk_size);
-	if (status != EFI_SUCCESS)
-		goto fail2;
-	hdr->ramdisk_image = ramdisk_addr & 0xffffffff;
-	hdr->ramdisk_size  = ramdisk_size & 0xffffffff;
-	boot_params->ext_ramdisk_image = (u64)ramdisk_addr >> 32;
-	boot_params->ext_ramdisk_size  = (u64)ramdisk_size >> 32;
+	if (!noinitrd()) {
+		/*
+		 * The initrd loaded from the Linux initrd vendor device
+		 * path should take precedence, as we don't want the
+		 * [unverified] command line to override the initrd
+		 * supplied by the [potentially verified] firmware.
+		 */
+		status = efi_load_initrd_devpath(&ramdisk_addr, &ramdisk_size,
+						 above4g ? ULONG_MAX
+							 : hdr->initrd_addr_max);
+		if (status == EFI_NOT_FOUND)
+			status = efi_load_initrd(image, hdr->initrd_addr_max,
+						 above4g ? ULONG_MAX
+							 : hdr->initrd_addr_max,
+						 &ramdisk_addr, &ramdisk_size);
+		if (status != EFI_SUCCESS)
+			goto fail2;
+		hdr->ramdisk_image = ramdisk_addr & 0xffffffff;
+		hdr->ramdisk_size  = ramdisk_size & 0xffffffff;
+		boot_params->ext_ramdisk_image = (u64)ramdisk_addr >> 32;
+		boot_params->ext_ramdisk_size  = (u64)ramdisk_size >> 32;
+	}
 
 	efi_stub_entry(handle, sys_table, boot_params);
 	/* not reached */
@@ -743,7 +745,8 @@ struct boot_params *efi_main(efi_handle_t handle,
 			 ((u64)boot_params->ext_cmd_line_ptr << 32));
 	efi_parse_options((char *)cmdline_paddr);
 
-	if (!hdr->ramdisk_size && !boot_params->ext_ramdisk_size) {
+	if (!hdr->ramdisk_size && !boot_params->ext_ramdisk_size &&
+	    !noinitrd()) {
 		unsigned long max = hdr->initrd_addr_max;
 		unsigned long addr, size;
 
-- 
2.17.1


^ permalink raw reply	[flat|nested] 39+ messages in thread

* Re: [PATCH 1/2] efi/libstub: add support for loading the initrd from a device path
  2020-02-06 14:03 ` [PATCH 1/2] efi/libstub: add support for loading the initrd from a device path Ard Biesheuvel
@ 2020-02-06 18:26   ` Heinrich Schuchardt
  2020-02-06 18:46     ` Ilias Apalodimas
                       ` (2 more replies)
  2020-02-07  9:48   ` Laszlo Ersek
  2020-02-09  6:39   ` Lukas Wunner
  2 siblings, 3 replies; 39+ messages in thread
From: Heinrich Schuchardt @ 2020-02-06 18:26 UTC (permalink / raw)
  To: Ard Biesheuvel, linux-efi
  Cc: linux-arm-kernel, lersek, leif, pjones, mjg59, agraf,
	ilias.apalodimas, daniel.kiper

On 2/6/20 3:03 PM, Ard Biesheuvel wrote:
> There are currently two ways to specify the initrd to be passed to the
> Linux kernel when booting via the EFI stub:
> - it can be passed as a initrd= command line option when doing a pure PE
>    boot (as opposed to the EFI handover protocol that exists for x86)
> - otherwise, the bootloader or firmware can load the initrd into memory,
>    and pass the address and size via the bootparams struct (x86) or
>    device tree (ARM)
>
> In the first case, we are limited to loading from the same file system
> that the kernel was loaded from, and it is also problematic in a trusted

Hello Ard,

"same file system" is not a limitation of using a command line
parameter. Any device path can be passed as a string.

> boot context, given that we cannot easily protect the command line from
> tampering without either adding complicated white/blacklisting of boot
> arguments or locking down the command line altogether.

Not relying on the command line for finding the initrd image does not
secure the integrity and the validity of the initrd image.

A signature on the initrd image could solve the security problem you
describe. It would not require non-Linux software to be changed, and
would provide much better security.

>
> In the second case, we force the bootloader to duplicate knowledge about
> the boot protocol which is already encoded in the stub, and which may be
> subject to change over time, e.g., bootparams struct definitions, memory
> allocation/alignment requirements for the placement of the initrd etc etc.
> In the ARM case, it also requires the bootloader to modify the hardware
> description provided by the firmware, as it is passed in the same file.
> On systems where the initrd is measured after loading, it creates a time
> window where the initrd contents might be manipulated in memory before
> handing over to the kernel.
>
> Address these concerns by adding support for loading the initrd into
> memory by invoking the EFI LoadFile2 protocol installed on a vendor
> GUIDed device path that specifically designates a Linux initrd.
> This addresses the above concerns, by putting the EFI stub in charge of
> placement in memory and of passing the base and size to the kernel proper
> (via whatever means it desires) while still leaving it up to the firmware
> or bootloader to obtain the file contents, potentially from other file
> systems than the one the kernel itself was loaded from. On platforms that
> implement measured boot, it permits the firmware to take the measurement
> right before the kernel actually consumes the contents.

A firmware implementing the UEFI standard will not be aware of any
initrd image as such an object does not exist in the standard. It was a
wise decision that the UEFI standard is operating system agnostic
(accomodating BSD, Linux, Windows, etc.). So the firmware (EDK2, U-Boot,
etc.) seems to be out of scope for providing a Linux specific
EFI_LOAD_FILE2_PROTOCOL.

When booting via GRUB it will be GRUB knowing which initrd to load.

Please, indicate which software you expect to expose the initrd related
EFI_LOAD_FILE2_PROTOCOL.

Using an UEFI variable for passing the initrd device path would be a
leaner solution on the bootloader side than requiring an extra
EFI_LOAD_FILE2_PROTOCOL implementation.

>
> Signed-off-by: Ard Biesheuvel <ardb@kernel.org>
> ---
>   drivers/firmware/efi/libstub/arm-stub.c        | 16 +++--
>   drivers/firmware/efi/libstub/efi-stub-helper.c | 65 ++++++++++++++++++++
>   drivers/firmware/efi/libstub/efistub.h         | 12 ++++
>   drivers/firmware/efi/libstub/x86-stub.c        | 36 ++++++++++-
>   include/linux/efi.h                            |  1 +
>   5 files changed, 123 insertions(+), 7 deletions(-)
>
> diff --git a/drivers/firmware/efi/libstub/arm-stub.c b/drivers/firmware/efi/libstub/arm-stub.c
> index c7b091f50e55..1db943c1ba2b 100644
> --- a/drivers/firmware/efi/libstub/arm-stub.c
> +++ b/drivers/firmware/efi/libstub/arm-stub.c
> @@ -157,6 +157,7 @@ unsigned long efi_entry(void *handle, efi_system_table_t *sys_table_arg,
>   	enum efi_secureboot_mode secure_boot;
>   	struct screen_info *si;
>   	efi_properties_table_t *prop_tbl;
> +	unsigned long max_addr;
>
>   	sys_table = sys_table_arg;
>
> @@ -255,11 +256,18 @@ unsigned long efi_entry(void *handle, efi_system_table_t *sys_table_arg,
>   	if (!fdt_addr)
>   		pr_efi("Generating empty DTB\n");
>
> -	status = efi_load_initrd(image, ULONG_MAX,
> -				 efi_get_max_initrd_addr(dram_base, *image_addr),
> -				 &initrd_addr, &initrd_size);
> +	max_addr = efi_get_max_initrd_addr(dram_base, *image_addr);
> +	status = efi_load_initrd_devpath(&initrd_addr, &initrd_size, max_addr);
> +	if (status == EFI_SUCCESS)
> +		pr_efi("Loaded initrd from LINUX_EFI_INITRD_MEDIA_GUID device path\n");
> +	else if (status == EFI_NOT_FOUND) {
> +		status = efi_load_initrd(image, ULONG_MAX, max_addr,
> +					 &initrd_addr, &initrd_size);
> +		if (status == EFI_SUCCESS)
> +			pr_efi("Loaded initrd from command line option\n");
> +	}
>   	if (status != EFI_SUCCESS)
> -		pr_efi_err("Failed initrd from command line!\n");
> +		pr_efi_err("Failed to load initrd!\n");
>
>   	efi_random_get_seed();
>
> diff --git a/drivers/firmware/efi/libstub/efi-stub-helper.c b/drivers/firmware/efi/libstub/efi-stub-helper.c
> index 8e60a39df3c5..eaf45ea749b3 100644
> --- a/drivers/firmware/efi/libstub/efi-stub-helper.c
> +++ b/drivers/firmware/efi/libstub/efi-stub-helper.c
> @@ -323,3 +323,68 @@ void efi_char16_printk(efi_char16_t *str)
>   	efi_call_proto(efi_table_attr(efi_system_table(), con_out),
>   		       output_string, str);
>   }
> +
> +static const struct {
> +	struct efi_vendor_dev_path	vendor;
> +	struct efi_generic_dev_path	end;
> +} __packed initrd_devpath = {
> +	{
> +		EFI_DEV_MEDIA,
> +		EFI_DEV_MEDIA_VENDOR,
> +		sizeof(struct efi_vendor_dev_path),
> +		LINUX_EFI_INITRD_MEDIA_GUID
> +	}, {
> +		EFI_DEV_END_PATH,
> +		EFI_DEV_END_ENTIRE,
> +		sizeof(struct efi_generic_dev_path)
> +	}
> +};
> +
> +efi_status_t efi_load_initrd_devpath(unsigned long *load_addr,
> +				     unsigned long *load_size,
> +				     unsigned long max)
> +{
> +	efi_guid_t lf2_proto_guid = EFI_LOAD_FILE2_PROTOCOL_GUID;
> +	efi_device_path_protocol_t *dp;
> +	efi_load_file2_protocol_t *lf2;
> +	unsigned long initrd_addr;
> +	unsigned long initrd_size;
> +	efi_handle_t handle;
> +	efi_status_t status;
> +
> +	if (!load_addr || !load_size)
> +		return EFI_INVALID_PARAMETER;
> +
> +	dp = (efi_device_path_protocol_t *)&initrd_devpath;
> +	status = efi_bs_call(locate_device_path, &lf2_proto_guid, &dp, &handle);
> +	if (status != EFI_SUCCESS)
> +		return status;
> +
> +	status = efi_bs_call(handle_protocol, handle, &lf2_proto_guid,
> +			     (void **)&lf2);
> +	if (status != EFI_SUCCESS)
> +		return status;

You require here that there is a handle exposing the device path
protocol with the initrd specific device path. On the same handle the
EFI_LOAD_FILE2_PROTOCOL must be installed which will load the initrd
file when called with the same device path.

An alternative implementation would simple loop over all instances of
the EFI_LOAD_FILE2_PROTOCOL and try to load the initrd.

It would be worthwhile to describe the requirements on the
implementation of the EFI_LOAD_FILE2_PROTOCOL in a code comment and in
the documentation.

Unfortunately the documentation of UEFI has been duplicated into two files:

Documentation/arm/uefi.rst
Documentation/x86/x86_64/uefi.rst

Best regards

Heinrich

> +
> +	initrd_size = 0;
> +	status = efi_call_proto(lf2, load_file,
> +				(efi_device_path_protocol_t *)&initrd_devpath,
> +				false, &initrd_size, NULL);
> +	if (status != EFI_BUFFER_TOO_SMALL)
> +		return EFI_LOAD_ERROR;
> +
> +	status = efi_allocate_pages(initrd_size, &initrd_addr, max);
> +	if (status != EFI_SUCCESS)
> +		return status;
> +
> +	status = efi_call_proto(lf2, load_file,
> +				(efi_device_path_protocol_t *)&initrd_devpath,
> +				false, &initrd_size, (void *)initrd_addr);
> +	if (status != EFI_SUCCESS) {
> +		efi_free(initrd_size, initrd_addr);
> +		return status;
> +	}
> +
> +	*load_addr = initrd_addr;
> +	*load_size = initrd_size;
> +	return EFI_SUCCESS;
> +}
> diff --git a/drivers/firmware/efi/libstub/efistub.h b/drivers/firmware/efi/libstub/efistub.h
> index 99e93fd76ec5..fbf9f9442eed 100644
> --- a/drivers/firmware/efi/libstub/efistub.h
> +++ b/drivers/firmware/efi/libstub/efistub.h
> @@ -566,6 +566,14 @@ union efi_load_file_protocol {
>   	} mixed_mode;
>   };
>
> +struct efi_vendor_dev_path {
> +	u8		type;
> +	u8		sub_type;
> +	u16		length;
> +	efi_guid_t	vendorguid;
> +	u8		vendordata[];
> +} __packed;
> +
>   void efi_pci_disable_bridge_busmaster(void);
>
>   typedef efi_status_t (*efi_exit_boot_map_processing)(
> @@ -651,4 +659,8 @@ efi_status_t efi_load_initrd(efi_loaded_image_t *image,
>   			     unsigned long *load_addr,
>   			     unsigned long *load_size);
>
> +efi_status_t efi_load_initrd_devpath(unsigned long *load_addr,
> +				     unsigned long *load_size,
> +				     unsigned long max);
> +
>   #endif
> diff --git a/drivers/firmware/efi/libstub/x86-stub.c b/drivers/firmware/efi/libstub/x86-stub.c
> index f3e2ff31b624..7f38f95676dd 100644
> --- a/drivers/firmware/efi/libstub/x86-stub.c
> +++ b/drivers/firmware/efi/libstub/x86-stub.c
> @@ -419,9 +419,20 @@ efi_status_t __efiapi efi_pe_entry(efi_handle_t handle,
>   	if (status != EFI_SUCCESS)
>   		goto fail2;
>
> -	status = efi_load_initrd(image, hdr->initrd_addr_max,
> -				 above4g ? ULONG_MAX : hdr->initrd_addr_max,
> -				 &ramdisk_addr, &ramdisk_size);
> +	/*
> +	 * The initrd loaded from the Linux initrd vendor device
> +	 * path should take precedence, as we don't want the
> +	 * [unverified] command line to override the initrd
> +	 * supplied by the [potentially verified] firmware.
> +	 */
> +	status = efi_load_initrd_devpath(&ramdisk_addr, &ramdisk_size,
> +					 above4g ? ULONG_MAX
> +						 : hdr->initrd_addr_max);
> +	if (status == EFI_NOT_FOUND)
> +		status = efi_load_initrd(image, hdr->initrd_addr_max,
> +					 above4g ? ULONG_MAX
> +						 : hdr->initrd_addr_max,
> +					 &ramdisk_addr, &ramdisk_size);
>   	if (status != EFI_SUCCESS)
>   		goto fail2;
>   	hdr->ramdisk_image = ramdisk_addr & 0xffffffff;
> @@ -732,6 +743,25 @@ struct boot_params *efi_main(efi_handle_t handle,
>   			 ((u64)boot_params->ext_cmd_line_ptr << 32));
>   	efi_parse_options((char *)cmdline_paddr);
>
> +	if (!hdr->ramdisk_size && !boot_params->ext_ramdisk_size) {
> +		unsigned long max = hdr->initrd_addr_max;
> +		unsigned long addr, size;
> +
> +		if (hdr->xloadflags & XLF_CAN_BE_LOADED_ABOVE_4G)
> +			max = ULONG_MAX;
> +
> +		status = efi_load_initrd_devpath(&addr, &size, max);
> +		if (status == EFI_SUCCESS) {
> +			hdr->ramdisk_image		= (u32)addr;
> +			hdr->ramdisk_size 		= (u32)size;
> +			boot_params->ext_ramdisk_image	= (u64)addr >> 32;
> +			boot_params->ext_ramdisk_size 	= (u64)size >> 32;
> +		} else if (status != EFI_NOT_FOUND) {
> +			efi_printk("efi_load_initrd_devpath() failed!\n");
> +			goto fail;
> +		}
> +	}
> +
>   	/*
>   	 * If the boot loader gave us a value for secure_boot then we use that,
>   	 * otherwise we ask the BIOS.
> diff --git a/include/linux/efi.h b/include/linux/efi.h
> index 9ccf313fe9de..75c83c322c40 100644
> --- a/include/linux/efi.h
> +++ b/include/linux/efi.h
> @@ -353,6 +353,7 @@ void efi_native_runtime_setup(void);
>   #define LINUX_EFI_TPM_EVENT_LOG_GUID		EFI_GUID(0xb7799cb0, 0xeca2, 0x4943,  0x96, 0x67, 0x1f, 0xae, 0x07, 0xb7, 0x47, 0xfa)
>   #define LINUX_EFI_TPM_FINAL_LOG_GUID		EFI_GUID(0x1e2ed096, 0x30e2, 0x4254,  0xbd, 0x89, 0x86, 0x3b, 0xbe, 0xf8, 0x23, 0x25)
>   #define LINUX_EFI_MEMRESERVE_TABLE_GUID		EFI_GUID(0x888eb0c6, 0x8ede, 0x4ff5,  0xa8, 0xf0, 0x9a, 0xee, 0x5c, 0xb9, 0x77, 0xc2)
> +#define LINUX_EFI_INITRD_MEDIA_GUID		EFI_GUID(0x5568e427, 0x68fc, 0x4f3d,  0xac, 0x74, 0xca, 0x55, 0x52, 0x31, 0xcc, 0x68)
>
>   /* OEM GUIDs */
>   #define DELLEMC_EFI_RCI2_TABLE_GUID		EFI_GUID(0x2d9f28a2, 0xa886, 0x456a,  0x97, 0xa8, 0xf1, 0x1e, 0xf2, 0x4f, 0xf4, 0x55)
>


^ permalink raw reply	[flat|nested] 39+ messages in thread

* Re: [PATCH 2/2] efi/libstub: take noinitrd cmdline argument into account for devpath initrd
  2020-02-06 14:03 ` [PATCH 2/2] efi/libstub: take noinitrd cmdline argument into account for devpath initrd Ard Biesheuvel
@ 2020-02-06 18:33   ` Heinrich Schuchardt
  2020-02-06 23:44     ` Ard Biesheuvel
  2020-02-12 16:01   ` Peter Jones
  1 sibling, 1 reply; 39+ messages in thread
From: Heinrich Schuchardt @ 2020-02-06 18:33 UTC (permalink / raw)
  To: Ard Biesheuvel, linux-efi
  Cc: linux-arm-kernel, lersek, leif, pjones, mjg59, agraf,
	ilias.apalodimas, daniel.kiper

On 2/6/20 3:03 PM, Ard Biesheuvel wrote:
> One of the advantages of using what basically amounts to a callback
> interface into the bootloader for loading the initrd is that it provides
> a natural place for the bootloader or firmware to measure the initrd
> contents while they are being passed to the kernel.
>
> Unfortunately, this is not a guarantee that the initrd will in fact be
> loaded and its /init invoked by the kernel, since the command line may
> contain the 'noinitrd' option, in which case the initrd is ignored, but
> this will not be reflected in the PCR that covers the initrd measurement.

Does PCR here refer to the TPM Platform Configuration Register?

>
> This could be addressed by measuring the command line as well, and
> including that PCR in the attestation policy, but this locks down the
> command line completely, which may be too restrictive.
>
> So let's take the noinitrd argument into account in the stub, too. This
> forces the PCR that covers the initrd to assume a different value when
> noinitrd is passed, allowing an attestation policy to disregard the
> command line if there is no need to take its measurement into account
> for other reasons.
>
> Signed-off-by: Ard Biesheuvel <ardb@kernel.org>
> ---
>   drivers/firmware/efi/libstub/arm-stub.c        | 23 +++++-----
>   drivers/firmware/efi/libstub/efi-stub-helper.c |  9 ++++
>   drivers/firmware/efi/libstub/efistub.h         |  1 +
>   drivers/firmware/efi/libstub/x86-stub.c        | 45 +++++++++++---------
>   4 files changed, 47 insertions(+), 31 deletions(-)
>
> diff --git a/drivers/firmware/efi/libstub/arm-stub.c b/drivers/firmware/efi/libstub/arm-stub.c
> index 1db943c1ba2b..5e8f16cf016e 100644
> --- a/drivers/firmware/efi/libstub/arm-stub.c
> +++ b/drivers/firmware/efi/libstub/arm-stub.c
> @@ -256,18 +256,21 @@ unsigned long efi_entry(void *handle, efi_system_table_t *sys_table_arg,
>   	if (!fdt_addr)
>   		pr_efi("Generating empty DTB\n");
>
> -	max_addr = efi_get_max_initrd_addr(dram_base, *image_addr);
> -	status = efi_load_initrd_devpath(&initrd_addr, &initrd_size, max_addr);
> -	if (status == EFI_SUCCESS)
> -		pr_efi("Loaded initrd from LINUX_EFI_INITRD_MEDIA_GUID device path\n");
> -	else if (status == EFI_NOT_FOUND) {
> -		status = efi_load_initrd(image, ULONG_MAX, max_addr,
> -					 &initrd_addr, &initrd_size);
> +	if (!noinitrd()) {
> +		max_addr = efi_get_max_initrd_addr(dram_base, *image_addr);
> +		status = efi_load_initrd_devpath(&initrd_addr, &initrd_size,
> +						 max_addr);
>   		if (status == EFI_SUCCESS)
> -			pr_efi("Loaded initrd from command line option\n");
> +			pr_efi("Loaded initrd from LINUX_EFI_INITRD_MEDIA_GUID device path\n");
> +		else if (status == EFI_NOT_FOUND) {
> +			status = efi_load_initrd(image, ULONG_MAX, max_addr,
> +						 &initrd_addr, &initrd_size);
> +			if (status == EFI_SUCCESS)
> +				pr_efi("Loaded initrd from command line option\n");
> +		}
> +		if (status != EFI_SUCCESS)
> +			pr_efi_err("Failed to load initrd!\n");
>   	}
> -	if (status != EFI_SUCCESS)
> -		pr_efi_err("Failed to load initrd!\n");
>
>   	efi_random_get_seed();
>
> diff --git a/drivers/firmware/efi/libstub/efi-stub-helper.c b/drivers/firmware/efi/libstub/efi-stub-helper.c
> index eaf45ea749b3..367575fb8424 100644
> --- a/drivers/firmware/efi/libstub/efi-stub-helper.c
> +++ b/drivers/firmware/efi/libstub/efi-stub-helper.c
> @@ -11,6 +11,7 @@
>
>   static bool __efistub_global efi_nochunk;
>   static bool __efistub_global efi_nokaslr;
> +static bool __efistub_global efi_noinitrd;
>   static bool __efistub_global efi_quiet;
>   static bool __efistub_global efi_novamap;
>   static bool __efistub_global efi_nosoftreserve;
> @@ -25,6 +26,10 @@ bool __pure nokaslr(void)
>   {
>   	return efi_nokaslr;
>   }
> +bool __pure noinitrd(void)
> +{
> +	return efi_noinitrd;
> +}
>   bool __pure is_quiet(void)
>   {
>   	return efi_quiet;
> @@ -71,6 +76,10 @@ efi_status_t efi_parse_options(char const *cmdline)
>   	if (str == cmdline || (str && str > cmdline && *(str - 1) == ' '))
>   		efi_nokaslr = true;
>
> +	str = strstr(cmdline, "noinitrd");
> +	if (str == cmdline || (str && str > cmdline && *(str - 1) == ' '))
> +		efi_noinitrd = true;
> +
>   	str = strstr(cmdline, "quiet");
>   	if (str == cmdline || (str && str > cmdline && *(str - 1) == ' '))
>   		efi_quiet = true;
> diff --git a/drivers/firmware/efi/libstub/efistub.h b/drivers/firmware/efi/libstub/efistub.h
> index fbf9f9442eed..29a5d0e9554a 100644
> --- a/drivers/firmware/efi/libstub/efistub.h
> +++ b/drivers/firmware/efi/libstub/efistub.h
> @@ -44,6 +44,7 @@
>
>   extern bool __pure nochunk(void);
>   extern bool __pure nokaslr(void);
> +extern bool __pure noinitrd(void);
>   extern bool __pure is_quiet(void);
>   extern bool __pure novamap(void);
>
> diff --git a/drivers/firmware/efi/libstub/x86-stub.c b/drivers/firmware/efi/libstub/x86-stub.c
> index 7f38f95676dd..9d86c0949b3c 100644
> --- a/drivers/firmware/efi/libstub/x86-stub.c
> +++ b/drivers/firmware/efi/libstub/x86-stub.c
> @@ -419,26 +419,28 @@ efi_status_t __efiapi efi_pe_entry(efi_handle_t handle,
>   	if (status != EFI_SUCCESS)
>   		goto fail2;
>
> -	/*
> -	 * The initrd loaded from the Linux initrd vendor device
> -	 * path should take precedence, as we don't want the
> -	 * [unverified] command line to override the initrd
> -	 * supplied by the [potentially verified] firmware.
> -	 */
> -	status = efi_load_initrd_devpath(&ramdisk_addr, &ramdisk_size,
> -					 above4g ? ULONG_MAX
> -						 : hdr->initrd_addr_max);
> -	if (status == EFI_NOT_FOUND)
> -		status = efi_load_initrd(image, hdr->initrd_addr_max,
> -					 above4g ? ULONG_MAX
> -						 : hdr->initrd_addr_max,
> -					 &ramdisk_addr, &ramdisk_size);
> -	if (status != EFI_SUCCESS)
> -		goto fail2;
> -	hdr->ramdisk_image = ramdisk_addr & 0xffffffff;
> -	hdr->ramdisk_size  = ramdisk_size & 0xffffffff;
> -	boot_params->ext_ramdisk_image = (u64)ramdisk_addr >> 32;
> -	boot_params->ext_ramdisk_size  = (u64)ramdisk_size >> 32;
> +	if (!noinitrd()) {
> +		/*
> +		 * The initrd loaded from the Linux initrd vendor device
> +		 * path should take precedence, as we don't want the
> +		 * [unverified] command line to override the initrd
> +		 * supplied by the [potentially verified] firmware.
> +		 */
> +		status = efi_load_initrd_devpath(&ramdisk_addr, &ramdisk_size,
> +						 above4g ? ULONG_MAX
> +							 : hdr->initrd_addr_max);
> +		if (status == EFI_NOT_FOUND)
> +			status = efi_load_initrd(image, hdr->initrd_addr_max,
> +						 above4g ? ULONG_MAX
> +							 : hdr->initrd_addr_max,
> +						 &ramdisk_addr, &ramdisk_size);
> +		if (status != EFI_SUCCESS)
> +			goto fail2;
> +		hdr->ramdisk_image = ramdisk_addr & 0xffffffff;
> +		hdr->ramdisk_size  = ramdisk_size & 0xffffffff;
> +		boot_params->ext_ramdisk_image = (u64)ramdisk_addr >> 32;
> +		boot_params->ext_ramdisk_size  = (u64)ramdisk_size >> 32;
> +	}
>
>   	efi_stub_entry(handle, sys_table, boot_params);
>   	/* not reached */
> @@ -743,7 +745,8 @@ struct boot_params *efi_main(efi_handle_t handle,
>   			 ((u64)boot_params->ext_cmd_line_ptr << 32));
>   	efi_parse_options((char *)cmdline_paddr);
>
> -	if (!hdr->ramdisk_size && !boot_params->ext_ramdisk_size) {
> +	if (!hdr->ramdisk_size && !boot_params->ext_ramdisk_size &&
> +	    !noinitrd()) {
>   		unsigned long max = hdr->initrd_addr_max;
>   		unsigned long addr, size;
>
>


^ permalink raw reply	[flat|nested] 39+ messages in thread

* Re: [PATCH 1/2] efi/libstub: add support for loading the initrd from a device path
  2020-02-06 18:26   ` Heinrich Schuchardt
@ 2020-02-06 18:46     ` Ilias Apalodimas
  2020-02-06 19:15       ` Heinrich Schuchardt
  2020-02-06 22:35     ` Ard Biesheuvel
  2020-02-07 11:03     ` Laszlo Ersek
  2 siblings, 1 reply; 39+ messages in thread
From: Ilias Apalodimas @ 2020-02-06 18:46 UTC (permalink / raw)
  To: Heinrich Schuchardt
  Cc: Ard Biesheuvel, linux-efi, linux-arm-kernel, lersek, leif,
	pjones, mjg59, agraf, daniel.kiper

Hi Heinrich,

I actually like the approach.

On Thu, Feb 06, 2020 at 07:26:20PM +0100, Heinrich Schuchardt wrote:
[...] 
> > boot context, given that we cannot easily protect the command line from
> > tampering without either adding complicated white/blacklisting of boot
> > arguments or locking down the command line altogether.
> 
> Not relying on the command line for finding the initrd image does not
> secure the integrity and the validity of the initrd image.

This patch isn't supposed to protect you against a bogus initrd and I don't
think it should be the bootloaders job to verify that. The kernel already has
tools to do that. 

> 
> A signature on the initrd image could solve the security problem you
> describe. It would not require non-Linux software to be changed, and
> would provide much better security.
> 

The problem with parsing the kernel command line is that at the moment,
different options are supported by different architectures. A quick grep for 
CONFIG_CMDLINE_(FORCE/OVERWRITE/EXTEND/FROM_BOOTLOADER) will give you an idea.

What you can do in U-Boot currently is not have an environment in any of the
flashes and set the bootdelay to 0, in order to prevent the user from 
changing that command line.

> > 
> > In the second case, we force the bootloader to duplicate knowledge about
> > the boot protocol which is already encoded in the stub, and which may be
> > subject to change over time, e.g., bootparams struct definitions, memory
> > allocation/alignment requirements for the placement of the initrd etc etc.
> > In the ARM case, it also requires the bootloader to modify the hardware
> > description provided by the firmware, as it is passed in the same file.
> > On systems where the initrd is measured after loading, it creates a time
> > window where the initrd contents might be manipulated in memory before
> > handing over to the kernel.
> > 
> > Address these concerns by adding support for loading the initrd into
> > memory by invoking the EFI LoadFile2 protocol installed on a vendor
> > GUIDed device path that specifically designates a Linux initrd.
> > This addresses the above concerns, by putting the EFI stub in charge of
> > placement in memory and of passing the base and size to the kernel proper
> > (via whatever means it desires) while still leaving it up to the firmware
> > or bootloader to obtain the file contents, potentially from other file
> > systems than the one the kernel itself was loaded from. On platforms that
> > implement measured boot, it permits the firmware to take the measurement
> > right before the kernel actually consumes the contents.
> 
> A firmware implementing the UEFI standard will not be aware of any
> initrd image as such an object does not exist in the standard. It was a
> wise decision that the UEFI standard is operating system agnostic
> (accomodating BSD, Linux, Windows, etc.). So the firmware (EDK2, U-Boot,
> etc.) seems to be out of scope for providing a Linux specific
> EFI_LOAD_FILE2_PROTOCOL.
> 
> When booting via GRUB it will be GRUB knowing which initrd to load.

What about booting the kernel directly?

> 
> Please, indicate which software you expect to expose the initrd related
> EFI_LOAD_FILE2_PROTOCOL.

I have an implementation for this on U-Boot which works. The file and device are
hardcoded at the moment, but the rest of the functionality works fine. I'll
share it with you once I clean it up a bit. 

> 
> Using an UEFI variable for passing the initrd device path would be a
> leaner solution on the bootloader side than requiring an extra
> EFI_LOAD_FILE2_PROTOCOL implementation.
> 

Thanks
/Ilias

> > 
> > Signed-off-by: Ard Biesheuvel <ardb@kernel.org>
> > ---
> >   drivers/firmware/efi/libstub/arm-stub.c        | 16 +++--
> >   drivers/firmware/efi/libstub/efi-stub-helper.c | 65 ++++++++++++++++++++
> >   drivers/firmware/efi/libstub/efistub.h         | 12 ++++
> >   drivers/firmware/efi/libstub/x86-stub.c        | 36 ++++++++++-
> >   include/linux/efi.h                            |  1 +
> >   5 files changed, 123 insertions(+), 7 deletions(-)
> > 
> > diff --git a/drivers/firmware/efi/libstub/arm-stub.c b/drivers/firmware/efi/libstub/arm-stub.c
> > index c7b091f50e55..1db943c1ba2b 100644
> > --- a/drivers/firmware/efi/libstub/arm-stub.c
> > +++ b/drivers/firmware/efi/libstub/arm-stub.c
> > @@ -157,6 +157,7 @@ unsigned long efi_entry(void *handle, efi_system_table_t *sys_table_arg,
> >   	enum efi_secureboot_mode secure_boot;
> >   	struct screen_info *si;
> >   	efi_properties_table_t *prop_tbl;
> > +	unsigned long max_addr;
> > 
> >   	sys_table = sys_table_arg;
> > 
> > @@ -255,11 +256,18 @@ unsigned long efi_entry(void *handle, efi_system_table_t *sys_table_arg,
> >   	if (!fdt_addr)
> >   		pr_efi("Generating empty DTB\n");
> > 
> > -	status = efi_load_initrd(image, ULONG_MAX,
> > -				 efi_get_max_initrd_addr(dram_base, *image_addr),
> > -				 &initrd_addr, &initrd_size);
> > +	max_addr = efi_get_max_initrd_addr(dram_base, *image_addr);
> > +	status = efi_load_initrd_devpath(&initrd_addr, &initrd_size, max_addr);
> > +	if (status == EFI_SUCCESS)
> > +		pr_efi("Loaded initrd from LINUX_EFI_INITRD_MEDIA_GUID device path\n");
> > +	else if (status == EFI_NOT_FOUND) {
> > +		status = efi_load_initrd(image, ULONG_MAX, max_addr,
> > +					 &initrd_addr, &initrd_size);
> > +		if (status == EFI_SUCCESS)
> > +			pr_efi("Loaded initrd from command line option\n");
> > +	}
> >   	if (status != EFI_SUCCESS)
> > -		pr_efi_err("Failed initrd from command line!\n");
> > +		pr_efi_err("Failed to load initrd!\n");
> > 
> >   	efi_random_get_seed();
> > 
> > diff --git a/drivers/firmware/efi/libstub/efi-stub-helper.c b/drivers/firmware/efi/libstub/efi-stub-helper.c
> > index 8e60a39df3c5..eaf45ea749b3 100644
> > --- a/drivers/firmware/efi/libstub/efi-stub-helper.c
> > +++ b/drivers/firmware/efi/libstub/efi-stub-helper.c
> > @@ -323,3 +323,68 @@ void efi_char16_printk(efi_char16_t *str)
> >   	efi_call_proto(efi_table_attr(efi_system_table(), con_out),
> >   		       output_string, str);
> >   }
> > +
> > +static const struct {
> > +	struct efi_vendor_dev_path	vendor;
> > +	struct efi_generic_dev_path	end;
> > +} __packed initrd_devpath = {
> > +	{
> > +		EFI_DEV_MEDIA,
> > +		EFI_DEV_MEDIA_VENDOR,
> > +		sizeof(struct efi_vendor_dev_path),
> > +		LINUX_EFI_INITRD_MEDIA_GUID
> > +	}, {
> > +		EFI_DEV_END_PATH,
> > +		EFI_DEV_END_ENTIRE,
> > +		sizeof(struct efi_generic_dev_path)
> > +	}
> > +};
> > +
> > +efi_status_t efi_load_initrd_devpath(unsigned long *load_addr,
> > +				     unsigned long *load_size,
> > +				     unsigned long max)
> > +{
> > +	efi_guid_t lf2_proto_guid = EFI_LOAD_FILE2_PROTOCOL_GUID;
> > +	efi_device_path_protocol_t *dp;
> > +	efi_load_file2_protocol_t *lf2;
> > +	unsigned long initrd_addr;
> > +	unsigned long initrd_size;
> > +	efi_handle_t handle;
> > +	efi_status_t status;
> > +
> > +	if (!load_addr || !load_size)
> > +		return EFI_INVALID_PARAMETER;
> > +
> > +	dp = (efi_device_path_protocol_t *)&initrd_devpath;
> > +	status = efi_bs_call(locate_device_path, &lf2_proto_guid, &dp, &handle);
> > +	if (status != EFI_SUCCESS)
> > +		return status;
> > +
> > +	status = efi_bs_call(handle_protocol, handle, &lf2_proto_guid,
> > +			     (void **)&lf2);
> > +	if (status != EFI_SUCCESS)
> > +		return status;
> 
> You require here that there is a handle exposing the device path
> protocol with the initrd specific device path. On the same handle the
> EFI_LOAD_FILE2_PROTOCOL must be installed which will load the initrd
> file when called with the same device path.
> 
> An alternative implementation would simple loop over all instances of
> the EFI_LOAD_FILE2_PROTOCOL and try to load the initrd.
> 
> It would be worthwhile to describe the requirements on the
> implementation of the EFI_LOAD_FILE2_PROTOCOL in a code comment and in
> the documentation.
> 
> Unfortunately the documentation of UEFI has been duplicated into two files:
> 
> Documentation/arm/uefi.rst
> Documentation/x86/x86_64/uefi.rst
> 
> Best regards
> 
> Heinrich
> 
> > +
> > +	initrd_size = 0;
> > +	status = efi_call_proto(lf2, load_file,
> > +				(efi_device_path_protocol_t *)&initrd_devpath,
> > +				false, &initrd_size, NULL);
> > +	if (status != EFI_BUFFER_TOO_SMALL)
> > +		return EFI_LOAD_ERROR;
> > +
> > +	status = efi_allocate_pages(initrd_size, &initrd_addr, max);
> > +	if (status != EFI_SUCCESS)
> > +		return status;
> > +
> > +	status = efi_call_proto(lf2, load_file,
> > +				(efi_device_path_protocol_t *)&initrd_devpath,
> > +				false, &initrd_size, (void *)initrd_addr);
> > +	if (status != EFI_SUCCESS) {
> > +		efi_free(initrd_size, initrd_addr);
> > +		return status;
> > +	}
> > +
> > +	*load_addr = initrd_addr;
> > +	*load_size = initrd_size;
> > +	return EFI_SUCCESS;
> > +}
> > diff --git a/drivers/firmware/efi/libstub/efistub.h b/drivers/firmware/efi/libstub/efistub.h
> > index 99e93fd76ec5..fbf9f9442eed 100644
> > --- a/drivers/firmware/efi/libstub/efistub.h
> > +++ b/drivers/firmware/efi/libstub/efistub.h
> > @@ -566,6 +566,14 @@ union efi_load_file_protocol {
> >   	} mixed_mode;
> >   };
> > 
> > +struct efi_vendor_dev_path {
> > +	u8		type;
> > +	u8		sub_type;
> > +	u16		length;
> > +	efi_guid_t	vendorguid;
> > +	u8		vendordata[];
> > +} __packed;
> > +
> >   void efi_pci_disable_bridge_busmaster(void);
> > 
> >   typedef efi_status_t (*efi_exit_boot_map_processing)(
> > @@ -651,4 +659,8 @@ efi_status_t efi_load_initrd(efi_loaded_image_t *image,
> >   			     unsigned long *load_addr,
> >   			     unsigned long *load_size);
> > 
> > +efi_status_t efi_load_initrd_devpath(unsigned long *load_addr,
> > +				     unsigned long *load_size,
> > +				     unsigned long max);
> > +
> >   #endif
> > diff --git a/drivers/firmware/efi/libstub/x86-stub.c b/drivers/firmware/efi/libstub/x86-stub.c
> > index f3e2ff31b624..7f38f95676dd 100644
> > --- a/drivers/firmware/efi/libstub/x86-stub.c
> > +++ b/drivers/firmware/efi/libstub/x86-stub.c
> > @@ -419,9 +419,20 @@ efi_status_t __efiapi efi_pe_entry(efi_handle_t handle,
> >   	if (status != EFI_SUCCESS)
> >   		goto fail2;
> > 
> > -	status = efi_load_initrd(image, hdr->initrd_addr_max,
> > -				 above4g ? ULONG_MAX : hdr->initrd_addr_max,
> > -				 &ramdisk_addr, &ramdisk_size);
> > +	/*
> > +	 * The initrd loaded from the Linux initrd vendor device
> > +	 * path should take precedence, as we don't want the
> > +	 * [unverified] command line to override the initrd
> > +	 * supplied by the [potentially verified] firmware.
> > +	 */
> > +	status = efi_load_initrd_devpath(&ramdisk_addr, &ramdisk_size,
> > +					 above4g ? ULONG_MAX
> > +						 : hdr->initrd_addr_max);
> > +	if (status == EFI_NOT_FOUND)
> > +		status = efi_load_initrd(image, hdr->initrd_addr_max,
> > +					 above4g ? ULONG_MAX
> > +						 : hdr->initrd_addr_max,
> > +					 &ramdisk_addr, &ramdisk_size);
> >   	if (status != EFI_SUCCESS)
> >   		goto fail2;
> >   	hdr->ramdisk_image = ramdisk_addr & 0xffffffff;
> > @@ -732,6 +743,25 @@ struct boot_params *efi_main(efi_handle_t handle,
> >   			 ((u64)boot_params->ext_cmd_line_ptr << 32));
> >   	efi_parse_options((char *)cmdline_paddr);
> > 
> > +	if (!hdr->ramdisk_size && !boot_params->ext_ramdisk_size) {
> > +		unsigned long max = hdr->initrd_addr_max;
> > +		unsigned long addr, size;
> > +
> > +		if (hdr->xloadflags & XLF_CAN_BE_LOADED_ABOVE_4G)
> > +			max = ULONG_MAX;
> > +
> > +		status = efi_load_initrd_devpath(&addr, &size, max);
> > +		if (status == EFI_SUCCESS) {
> > +			hdr->ramdisk_image		= (u32)addr;
> > +			hdr->ramdisk_size 		= (u32)size;
> > +			boot_params->ext_ramdisk_image	= (u64)addr >> 32;
> > +			boot_params->ext_ramdisk_size 	= (u64)size >> 32;
> > +		} else if (status != EFI_NOT_FOUND) {
> > +			efi_printk("efi_load_initrd_devpath() failed!\n");
> > +			goto fail;
> > +		}
> > +	}
> > +
> >   	/*
> >   	 * If the boot loader gave us a value for secure_boot then we use that,
> >   	 * otherwise we ask the BIOS.
> > diff --git a/include/linux/efi.h b/include/linux/efi.h
> > index 9ccf313fe9de..75c83c322c40 100644
> > --- a/include/linux/efi.h
> > +++ b/include/linux/efi.h
> > @@ -353,6 +353,7 @@ void efi_native_runtime_setup(void);
> >   #define LINUX_EFI_TPM_EVENT_LOG_GUID		EFI_GUID(0xb7799cb0, 0xeca2, 0x4943,  0x96, 0x67, 0x1f, 0xae, 0x07, 0xb7, 0x47, 0xfa)
> >   #define LINUX_EFI_TPM_FINAL_LOG_GUID		EFI_GUID(0x1e2ed096, 0x30e2, 0x4254,  0xbd, 0x89, 0x86, 0x3b, 0xbe, 0xf8, 0x23, 0x25)
> >   #define LINUX_EFI_MEMRESERVE_TABLE_GUID		EFI_GUID(0x888eb0c6, 0x8ede, 0x4ff5,  0xa8, 0xf0, 0x9a, 0xee, 0x5c, 0xb9, 0x77, 0xc2)
> > +#define LINUX_EFI_INITRD_MEDIA_GUID		EFI_GUID(0x5568e427, 0x68fc, 0x4f3d,  0xac, 0x74, 0xca, 0x55, 0x52, 0x31, 0xcc, 0x68)
> > 
> >   /* OEM GUIDs */
> >   #define DELLEMC_EFI_RCI2_TABLE_GUID		EFI_GUID(0x2d9f28a2, 0xa886, 0x456a,  0x97, 0xa8, 0xf1, 0x1e, 0xf2, 0x4f, 0xf4, 0x55)
> > 
> 

^ permalink raw reply	[flat|nested] 39+ messages in thread

* Re: [PATCH 1/2] efi/libstub: add support for loading the initrd from a device path
  2020-02-06 18:46     ` Ilias Apalodimas
@ 2020-02-06 19:15       ` Heinrich Schuchardt
  2020-02-06 20:09         ` Ilias Apalodimas
  0 siblings, 1 reply; 39+ messages in thread
From: Heinrich Schuchardt @ 2020-02-06 19:15 UTC (permalink / raw)
  To: Ilias Apalodimas
  Cc: Ard Biesheuvel, linux-efi, linux-arm-kernel, lersek, leif,
	pjones, mjg59, agraf, daniel.kiper

On 2/6/20 7:46 PM, Ilias Apalodimas wrote:
> Hi Heinrich,
>
> I actually like the approach.
>
> On Thu, Feb 06, 2020 at 07:26:20PM +0100, Heinrich Schuchardt wrote:
> [...]
>>> boot context, given that we cannot easily protect the command line from
>>> tampering without either adding complicated white/blacklisting of boot
>>> arguments or locking down the command line altogether.
>>
>> Not relying on the command line for finding the initrd image does not
>> secure the integrity and the validity of the initrd image.
>
> This patch isn't supposed to protect you against a bogus initrd and I don't
> think it should be the bootloaders job to verify that. The kernel already has
> tools to do that.

How do you expect a bootloader identify if the initrd is compatible with
the kernel?

>
>>
>> A signature on the initrd image could solve the security problem you
>> describe. It would not require non-Linux software to be changed, and
>> would provide much better security.
>>
>
> The problem with parsing the kernel command line is that at the moment,
> different options are supported by different architectures. A quick grep for
> CONFIG_CMDLINE_(FORCE/OVERWRITE/EXTEND/FROM_BOOTLOADER) will give you an idea.
>
> What you can do in U-Boot currently is not have an environment in any of the
> flashes and set the bootdelay to 0, in order to prevent the user from
> changing that command line.

If you don't have an environment or boot script how would
update-initramfs set the path of the initrd when it is updated?

Using a UEFI variable seems to be the natural choice.

>
>>>
>>> In the second case, we force the bootloader to duplicate knowledge about
>>> the boot protocol which is already encoded in the stub, and which may be
>>> subject to change over time, e.g., bootparams struct definitions, memory
>>> allocation/alignment requirements for the placement of the initrd etc etc.
>>> In the ARM case, it also requires the bootloader to modify the hardware
>>> description provided by the firmware, as it is passed in the same file.
>>> On systems where the initrd is measured after loading, it creates a time
>>> window where the initrd contents might be manipulated in memory before
>>> handing over to the kernel.
>>>
>>> Address these concerns by adding support for loading the initrd into
>>> memory by invoking the EFI LoadFile2 protocol installed on a vendor
>>> GUIDed device path that specifically designates a Linux initrd.
>>> This addresses the above concerns, by putting the EFI stub in charge of
>>> placement in memory and of passing the base and size to the kernel proper
>>> (via whatever means it desires) while still leaving it up to the firmware
>>> or bootloader to obtain the file contents, potentially from other file
>>> systems than the one the kernel itself was loaded from. On platforms that
>>> implement measured boot, it permits the firmware to take the measurement
>>> right before the kernel actually consumes the contents.
>>
>> A firmware implementing the UEFI standard will not be aware of any
>> initrd image as such an object does not exist in the standard. It was a
>> wise decision that the UEFI standard is operating system agnostic
>> (accomodating BSD, Linux, Windows, etc.). So the firmware (EDK2, U-Boot,
>> etc.) seems to be out of scope for providing a Linux specific
>> EFI_LOAD_FILE2_PROTOCOL.
>>
>> When booting via GRUB it will be GRUB knowing which initrd to load.
>
> What about booting the kernel directly?
>
>>
>> Please, indicate which software you expect to expose the initrd related
>> EFI_LOAD_FILE2_PROTOCOL.
>
> I have an implementation for this on U-Boot which works. The file and device are
> hardcoded at the moment, but the rest of the functionality works fine. I'll
> share it with you once I clean it up a bit.

Using a UEFI variable for passing the intird device path to Linux does
not require any change in U-Boot and is compatible with the UEFI
implementations of existing hardware like the laptop on which I am
writing this email.

Best regards

Heinrich

>
>>
>> Using an UEFI variable for passing the initrd device path would be a
>> leaner solution on the bootloader side than requiring an extra
>> EFI_LOAD_FILE2_PROTOCOL implementation.
>>
>
> Thanks
> /Ilias
>
>>>
>>> Signed-off-by: Ard Biesheuvel <ardb@kernel.org>
>>> ---
>>>    drivers/firmware/efi/libstub/arm-stub.c        | 16 +++--
>>>    drivers/firmware/efi/libstub/efi-stub-helper.c | 65 ++++++++++++++++++++
>>>    drivers/firmware/efi/libstub/efistub.h         | 12 ++++
>>>    drivers/firmware/efi/libstub/x86-stub.c        | 36 ++++++++++-
>>>    include/linux/efi.h                            |  1 +
>>>    5 files changed, 123 insertions(+), 7 deletions(-)
>>>
>>> diff --git a/drivers/firmware/efi/libstub/arm-stub.c b/drivers/firmware/efi/libstub/arm-stub.c
>>> index c7b091f50e55..1db943c1ba2b 100644
>>> --- a/drivers/firmware/efi/libstub/arm-stub.c
>>> +++ b/drivers/firmware/efi/libstub/arm-stub.c
>>> @@ -157,6 +157,7 @@ unsigned long efi_entry(void *handle, efi_system_table_t *sys_table_arg,
>>>    	enum efi_secureboot_mode secure_boot;
>>>    	struct screen_info *si;
>>>    	efi_properties_table_t *prop_tbl;
>>> +	unsigned long max_addr;
>>>
>>>    	sys_table = sys_table_arg;
>>>
>>> @@ -255,11 +256,18 @@ unsigned long efi_entry(void *handle, efi_system_table_t *sys_table_arg,
>>>    	if (!fdt_addr)
>>>    		pr_efi("Generating empty DTB\n");
>>>
>>> -	status = efi_load_initrd(image, ULONG_MAX,
>>> -				 efi_get_max_initrd_addr(dram_base, *image_addr),
>>> -				 &initrd_addr, &initrd_size);
>>> +	max_addr = efi_get_max_initrd_addr(dram_base, *image_addr);
>>> +	status = efi_load_initrd_devpath(&initrd_addr, &initrd_size, max_addr);
>>> +	if (status == EFI_SUCCESS)
>>> +		pr_efi("Loaded initrd from LINUX_EFI_INITRD_MEDIA_GUID device path\n");
>>> +	else if (status == EFI_NOT_FOUND) {
>>> +		status = efi_load_initrd(image, ULONG_MAX, max_addr,
>>> +					 &initrd_addr, &initrd_size);
>>> +		if (status == EFI_SUCCESS)
>>> +			pr_efi("Loaded initrd from command line option\n");
>>> +	}
>>>    	if (status != EFI_SUCCESS)
>>> -		pr_efi_err("Failed initrd from command line!\n");
>>> +		pr_efi_err("Failed to load initrd!\n");
>>>
>>>    	efi_random_get_seed();
>>>
>>> diff --git a/drivers/firmware/efi/libstub/efi-stub-helper.c b/drivers/firmware/efi/libstub/efi-stub-helper.c
>>> index 8e60a39df3c5..eaf45ea749b3 100644
>>> --- a/drivers/firmware/efi/libstub/efi-stub-helper.c
>>> +++ b/drivers/firmware/efi/libstub/efi-stub-helper.c
>>> @@ -323,3 +323,68 @@ void efi_char16_printk(efi_char16_t *str)
>>>    	efi_call_proto(efi_table_attr(efi_system_table(), con_out),
>>>    		       output_string, str);
>>>    }
>>> +
>>> +static const struct {
>>> +	struct efi_vendor_dev_path	vendor;
>>> +	struct efi_generic_dev_path	end;
>>> +} __packed initrd_devpath = {
>>> +	{
>>> +		EFI_DEV_MEDIA,
>>> +		EFI_DEV_MEDIA_VENDOR,
>>> +		sizeof(struct efi_vendor_dev_path),
>>> +		LINUX_EFI_INITRD_MEDIA_GUID
>>> +	}, {
>>> +		EFI_DEV_END_PATH,
>>> +		EFI_DEV_END_ENTIRE,
>>> +		sizeof(struct efi_generic_dev_path)
>>> +	}
>>> +};
>>> +
>>> +efi_status_t efi_load_initrd_devpath(unsigned long *load_addr,
>>> +				     unsigned long *load_size,
>>> +				     unsigned long max)
>>> +{
>>> +	efi_guid_t lf2_proto_guid = EFI_LOAD_FILE2_PROTOCOL_GUID;
>>> +	efi_device_path_protocol_t *dp;
>>> +	efi_load_file2_protocol_t *lf2;
>>> +	unsigned long initrd_addr;
>>> +	unsigned long initrd_size;
>>> +	efi_handle_t handle;
>>> +	efi_status_t status;
>>> +
>>> +	if (!load_addr || !load_size)
>>> +		return EFI_INVALID_PARAMETER;
>>> +
>>> +	dp = (efi_device_path_protocol_t *)&initrd_devpath;
>>> +	status = efi_bs_call(locate_device_path, &lf2_proto_guid, &dp, &handle);
>>> +	if (status != EFI_SUCCESS)
>>> +		return status;
>>> +
>>> +	status = efi_bs_call(handle_protocol, handle, &lf2_proto_guid,
>>> +			     (void **)&lf2);
>>> +	if (status != EFI_SUCCESS)
>>> +		return status;
>>
>> You require here that there is a handle exposing the device path
>> protocol with the initrd specific device path. On the same handle the
>> EFI_LOAD_FILE2_PROTOCOL must be installed which will load the initrd
>> file when called with the same device path.
>>
>> An alternative implementation would simple loop over all instances of
>> the EFI_LOAD_FILE2_PROTOCOL and try to load the initrd.
>>
>> It would be worthwhile to describe the requirements on the
>> implementation of the EFI_LOAD_FILE2_PROTOCOL in a code comment and in
>> the documentation.
>>
>> Unfortunately the documentation of UEFI has been duplicated into two files:
>>
>> Documentation/arm/uefi.rst
>> Documentation/x86/x86_64/uefi.rst
>>
>> Best regards
>>
>> Heinrich
>>
>>> +
>>> +	initrd_size = 0;
>>> +	status = efi_call_proto(lf2, load_file,
>>> +				(efi_device_path_protocol_t *)&initrd_devpath,
>>> +				false, &initrd_size, NULL);
>>> +	if (status != EFI_BUFFER_TOO_SMALL)
>>> +		return EFI_LOAD_ERROR;
>>> +
>>> +	status = efi_allocate_pages(initrd_size, &initrd_addr, max);
>>> +	if (status != EFI_SUCCESS)
>>> +		return status;
>>> +
>>> +	status = efi_call_proto(lf2, load_file,
>>> +				(efi_device_path_protocol_t *)&initrd_devpath,
>>> +				false, &initrd_size, (void *)initrd_addr);
>>> +	if (status != EFI_SUCCESS) {
>>> +		efi_free(initrd_size, initrd_addr);
>>> +		return status;
>>> +	}
>>> +
>>> +	*load_addr = initrd_addr;
>>> +	*load_size = initrd_size;
>>> +	return EFI_SUCCESS;
>>> +}
>>> diff --git a/drivers/firmware/efi/libstub/efistub.h b/drivers/firmware/efi/libstub/efistub.h
>>> index 99e93fd76ec5..fbf9f9442eed 100644
>>> --- a/drivers/firmware/efi/libstub/efistub.h
>>> +++ b/drivers/firmware/efi/libstub/efistub.h
>>> @@ -566,6 +566,14 @@ union efi_load_file_protocol {
>>>    	} mixed_mode;
>>>    };
>>>
>>> +struct efi_vendor_dev_path {
>>> +	u8		type;
>>> +	u8		sub_type;
>>> +	u16		length;
>>> +	efi_guid_t	vendorguid;
>>> +	u8		vendordata[];
>>> +} __packed;
>>> +
>>>    void efi_pci_disable_bridge_busmaster(void);
>>>
>>>    typedef efi_status_t (*efi_exit_boot_map_processing)(
>>> @@ -651,4 +659,8 @@ efi_status_t efi_load_initrd(efi_loaded_image_t *image,
>>>    			     unsigned long *load_addr,
>>>    			     unsigned long *load_size);
>>>
>>> +efi_status_t efi_load_initrd_devpath(unsigned long *load_addr,
>>> +				     unsigned long *load_size,
>>> +				     unsigned long max);
>>> +
>>>    #endif
>>> diff --git a/drivers/firmware/efi/libstub/x86-stub.c b/drivers/firmware/efi/libstub/x86-stub.c
>>> index f3e2ff31b624..7f38f95676dd 100644
>>> --- a/drivers/firmware/efi/libstub/x86-stub.c
>>> +++ b/drivers/firmware/efi/libstub/x86-stub.c
>>> @@ -419,9 +419,20 @@ efi_status_t __efiapi efi_pe_entry(efi_handle_t handle,
>>>    	if (status != EFI_SUCCESS)
>>>    		goto fail2;
>>>
>>> -	status = efi_load_initrd(image, hdr->initrd_addr_max,
>>> -				 above4g ? ULONG_MAX : hdr->initrd_addr_max,
>>> -				 &ramdisk_addr, &ramdisk_size);
>>> +	/*
>>> +	 * The initrd loaded from the Linux initrd vendor device
>>> +	 * path should take precedence, as we don't want the
>>> +	 * [unverified] command line to override the initrd
>>> +	 * supplied by the [potentially verified] firmware.
>>> +	 */
>>> +	status = efi_load_initrd_devpath(&ramdisk_addr, &ramdisk_size,
>>> +					 above4g ? ULONG_MAX
>>> +						 : hdr->initrd_addr_max);
>>> +	if (status == EFI_NOT_FOUND)
>>> +		status = efi_load_initrd(image, hdr->initrd_addr_max,
>>> +					 above4g ? ULONG_MAX
>>> +						 : hdr->initrd_addr_max,
>>> +					 &ramdisk_addr, &ramdisk_size);
>>>    	if (status != EFI_SUCCESS)
>>>    		goto fail2;
>>>    	hdr->ramdisk_image = ramdisk_addr & 0xffffffff;
>>> @@ -732,6 +743,25 @@ struct boot_params *efi_main(efi_handle_t handle,
>>>    			 ((u64)boot_params->ext_cmd_line_ptr << 32));
>>>    	efi_parse_options((char *)cmdline_paddr);
>>>
>>> +	if (!hdr->ramdisk_size && !boot_params->ext_ramdisk_size) {
>>> +		unsigned long max = hdr->initrd_addr_max;
>>> +		unsigned long addr, size;
>>> +
>>> +		if (hdr->xloadflags & XLF_CAN_BE_LOADED_ABOVE_4G)
>>> +			max = ULONG_MAX;
>>> +
>>> +		status = efi_load_initrd_devpath(&addr, &size, max);
>>> +		if (status == EFI_SUCCESS) {
>>> +			hdr->ramdisk_image		= (u32)addr;
>>> +			hdr->ramdisk_size 		= (u32)size;
>>> +			boot_params->ext_ramdisk_image	= (u64)addr >> 32;
>>> +			boot_params->ext_ramdisk_size 	= (u64)size >> 32;
>>> +		} else if (status != EFI_NOT_FOUND) {
>>> +			efi_printk("efi_load_initrd_devpath() failed!\n");
>>> +			goto fail;
>>> +		}
>>> +	}
>>> +
>>>    	/*
>>>    	 * If the boot loader gave us a value for secure_boot then we use that,
>>>    	 * otherwise we ask the BIOS.
>>> diff --git a/include/linux/efi.h b/include/linux/efi.h
>>> index 9ccf313fe9de..75c83c322c40 100644
>>> --- a/include/linux/efi.h
>>> +++ b/include/linux/efi.h
>>> @@ -353,6 +353,7 @@ void efi_native_runtime_setup(void);
>>>    #define LINUX_EFI_TPM_EVENT_LOG_GUID		EFI_GUID(0xb7799cb0, 0xeca2, 0x4943,  0x96, 0x67, 0x1f, 0xae, 0x07, 0xb7, 0x47, 0xfa)
>>>    #define LINUX_EFI_TPM_FINAL_LOG_GUID		EFI_GUID(0x1e2ed096, 0x30e2, 0x4254,  0xbd, 0x89, 0x86, 0x3b, 0xbe, 0xf8, 0x23, 0x25)
>>>    #define LINUX_EFI_MEMRESERVE_TABLE_GUID		EFI_GUID(0x888eb0c6, 0x8ede, 0x4ff5,  0xa8, 0xf0, 0x9a, 0xee, 0x5c, 0xb9, 0x77, 0xc2)
>>> +#define LINUX_EFI_INITRD_MEDIA_GUID		EFI_GUID(0x5568e427, 0x68fc, 0x4f3d,  0xac, 0x74, 0xca, 0x55, 0x52, 0x31, 0xcc, 0x68)
>>>
>>>    /* OEM GUIDs */
>>>    #define DELLEMC_EFI_RCI2_TABLE_GUID		EFI_GUID(0x2d9f28a2, 0xa886, 0x456a,  0x97, 0xa8, 0xf1, 0x1e, 0xf2, 0x4f, 0xf4, 0x55)
>>>
>>


^ permalink raw reply	[flat|nested] 39+ messages in thread

* Re: [PATCH 1/2] efi/libstub: add support for loading the initrd from a device path
  2020-02-06 19:15       ` Heinrich Schuchardt
@ 2020-02-06 20:09         ` Ilias Apalodimas
  2020-02-06 22:49           ` Heinrich Schuchardt
  0 siblings, 1 reply; 39+ messages in thread
From: Ilias Apalodimas @ 2020-02-06 20:09 UTC (permalink / raw)
  To: Heinrich Schuchardt
  Cc: Ard Biesheuvel, linux-efi, linux-arm-kernel, lersek, leif,
	pjones, mjg59, agraf, daniel.kiper

On Thu, Feb 06, 2020 at 08:15:11PM +0100, Heinrich Schuchardt wrote:
> On 2/6/20 7:46 PM, Ilias Apalodimas wrote:
> > Hi Heinrich,
> > 
> > I actually like the approach.
> > 
> > On Thu, Feb 06, 2020 at 07:26:20PM +0100, Heinrich Schuchardt wrote:
> > [...]
> > > > boot context, given that we cannot easily protect the command line from
> > > > tampering without either adding complicated white/blacklisting of boot
> > > > arguments or locking down the command line altogether.
> > > 
> > > Not relying on the command line for finding the initrd image does not
> > > secure the integrity and the validity of the initrd image.
> > 
> > This patch isn't supposed to protect you against a bogus initrd and I don't
> > think it should be the bootloaders job to verify that. The kernel already has
> > tools to do that.
> 
> How do you expect a bootloader identify if the initrd is compatible with
> the kernel?

Uh? Were exactly did i say that i expect that to happen?

> 
> > 
> > > 
> > > A signature on the initrd image could solve the security problem you
> > > describe. It would not require non-Linux software to be changed, and
> > > would provide much better security.
> > > 
> > 
> > The problem with parsing the kernel command line is that at the moment,
> > different options are supported by different architectures. A quick grep for
> > CONFIG_CMDLINE_(FORCE/OVERWRITE/EXTEND/FROM_BOOTLOADER) will give you an idea.
> > 
> > What you can do in U-Boot currently is not have an environment in any of the
> > flashes and set the bootdelay to 0, in order to prevent the user from
> > changing that command line.
> 
> If you don't have an environment or boot script how would
> update-initramfs set the path of the initrd when it is updated?

The path isn't hardcoded in any code here is it?
This specifies a way for the linux stub to load the actual file. It's pretty a
callback to the firmware. Were the firmware will find and how it will load it 
eventually is implementation specific. 

> 
> Using a UEFI variable seems to be the natural choice.
> 

You might as well use that to specify were you should load the file from.
The Loadfile2 (with the specified guid)  implementation of the firmware will 
take care of that.

> > 
> > > > 
> > > > In the second case, we force the bootloader to duplicate knowledge about
> > > > the boot protocol which is already encoded in the stub, and which may be
> > > > subject to change over time, e.g., bootparams struct definitions, memory
> > > > allocation/alignment requirements for the placement of the initrd etc etc.
> > > > In the ARM case, it also requires the bootloader to modify the hardware
> > > > description provided by the firmware, as it is passed in the same file.
> > > > On systems where the initrd is measured after loading, it creates a time
> > > > window where the initrd contents might be manipulated in memory before
> > > > handing over to the kernel.
> > > > 
> > > > Address these concerns by adding support for loading the initrd into
> > > > memory by invoking the EFI LoadFile2 protocol installed on a vendor
> > > > GUIDed device path that specifically designates a Linux initrd.
> > > > This addresses the above concerns, by putting the EFI stub in charge of
> > > > placement in memory and of passing the base and size to the kernel proper
> > > > (via whatever means it desires) while still leaving it up to the firmware
> > > > or bootloader to obtain the file contents, potentially from other file
> > > > systems than the one the kernel itself was loaded from. On platforms that
> > > > implement measured boot, it permits the firmware to take the measurement
> > > > right before the kernel actually consumes the contents.
> > > 
> > > A firmware implementing the UEFI standard will not be aware of any
> > > initrd image as such an object does not exist in the standard. It was a
> > > wise decision that the UEFI standard is operating system agnostic
> > > (accomodating BSD, Linux, Windows, etc.). So the firmware (EDK2, U-Boot,
> > > etc.) seems to be out of scope for providing a Linux specific
> > > EFI_LOAD_FILE2_PROTOCOL.
> > > 
> > > When booting via GRUB it will be GRUB knowing which initrd to load.
> > 
> > What about booting the kernel directly?
> > 
> > > 
> > > Please, indicate which software you expect to expose the initrd related
> > > EFI_LOAD_FILE2_PROTOCOL.
> > 
> > I have an implementation for this on U-Boot which works. The file and device are
> > hardcoded at the moment, but the rest of the functionality works fine. I'll
> > share it with you once I clean it up a bit.
> 
> Using a UEFI variable for passing the intird device path to Linux does
> not require any change in U-Boot and is compatible with the UEFI
> implementations of existing hardware like the laptop on which I am
> writing this email.

This still has the same issues we have now, uefi variable, kernel command line
or whatever, it won't be common across architectures.

Thanks
/Ilias
> 
> Best regards
> 
> Heinrich
> 
> > 
> > > 
> > > Using an UEFI variable for passing the initrd device path would be a
> > > leaner solution on the bootloader side than requiring an extra
> > > EFI_LOAD_FILE2_PROTOCOL implementation.
> > > 
> > 
> > Thanks
> > /Ilias
> > 
> > > > 
> > > > Signed-off-by: Ard Biesheuvel <ardb@kernel.org>
> > > > ---
> > > >    drivers/firmware/efi/libstub/arm-stub.c        | 16 +++--
> > > >    drivers/firmware/efi/libstub/efi-stub-helper.c | 65 ++++++++++++++++++++
> > > >    drivers/firmware/efi/libstub/efistub.h         | 12 ++++
> > > >    drivers/firmware/efi/libstub/x86-stub.c        | 36 ++++++++++-
> > > >    include/linux/efi.h                            |  1 +
> > > >    5 files changed, 123 insertions(+), 7 deletions(-)
> > > > 
> > > > diff --git a/drivers/firmware/efi/libstub/arm-stub.c b/drivers/firmware/efi/libstub/arm-stub.c
> > > > index c7b091f50e55..1db943c1ba2b 100644
> > > > --- a/drivers/firmware/efi/libstub/arm-stub.c
> > > > +++ b/drivers/firmware/efi/libstub/arm-stub.c
> > > > @@ -157,6 +157,7 @@ unsigned long efi_entry(void *handle, efi_system_table_t *sys_table_arg,
> > > >    	enum efi_secureboot_mode secure_boot;
> > > >    	struct screen_info *si;
> > > >    	efi_properties_table_t *prop_tbl;
> > > > +	unsigned long max_addr;
> > > > 
> > > >    	sys_table = sys_table_arg;
> > > > 
> > > > @@ -255,11 +256,18 @@ unsigned long efi_entry(void *handle, efi_system_table_t *sys_table_arg,
> > > >    	if (!fdt_addr)
> > > >    		pr_efi("Generating empty DTB\n");
> > > > 
> > > > -	status = efi_load_initrd(image, ULONG_MAX,
> > > > -				 efi_get_max_initrd_addr(dram_base, *image_addr),
> > > > -				 &initrd_addr, &initrd_size);
> > > > +	max_addr = efi_get_max_initrd_addr(dram_base, *image_addr);
> > > > +	status = efi_load_initrd_devpath(&initrd_addr, &initrd_size, max_addr);
> > > > +	if (status == EFI_SUCCESS)
> > > > +		pr_efi("Loaded initrd from LINUX_EFI_INITRD_MEDIA_GUID device path\n");
> > > > +	else if (status == EFI_NOT_FOUND) {
> > > > +		status = efi_load_initrd(image, ULONG_MAX, max_addr,
> > > > +					 &initrd_addr, &initrd_size);
> > > > +		if (status == EFI_SUCCESS)
> > > > +			pr_efi("Loaded initrd from command line option\n");
> > > > +	}
> > > >    	if (status != EFI_SUCCESS)
> > > > -		pr_efi_err("Failed initrd from command line!\n");
> > > > +		pr_efi_err("Failed to load initrd!\n");
> > > > 
> > > >    	efi_random_get_seed();
> > > > 
> > > > diff --git a/drivers/firmware/efi/libstub/efi-stub-helper.c b/drivers/firmware/efi/libstub/efi-stub-helper.c
> > > > index 8e60a39df3c5..eaf45ea749b3 100644
> > > > --- a/drivers/firmware/efi/libstub/efi-stub-helper.c
> > > > +++ b/drivers/firmware/efi/libstub/efi-stub-helper.c
> > > > @@ -323,3 +323,68 @@ void efi_char16_printk(efi_char16_t *str)
> > > >    	efi_call_proto(efi_table_attr(efi_system_table(), con_out),
> > > >    		       output_string, str);
> > > >    }
> > > > +
> > > > +static const struct {
> > > > +	struct efi_vendor_dev_path	vendor;
> > > > +	struct efi_generic_dev_path	end;
> > > > +} __packed initrd_devpath = {
> > > > +	{
> > > > +		EFI_DEV_MEDIA,
> > > > +		EFI_DEV_MEDIA_VENDOR,
> > > > +		sizeof(struct efi_vendor_dev_path),
> > > > +		LINUX_EFI_INITRD_MEDIA_GUID
> > > > +	}, {
> > > > +		EFI_DEV_END_PATH,
> > > > +		EFI_DEV_END_ENTIRE,
> > > > +		sizeof(struct efi_generic_dev_path)
> > > > +	}
> > > > +};
> > > > +
> > > > +efi_status_t efi_load_initrd_devpath(unsigned long *load_addr,
> > > > +				     unsigned long *load_size,
> > > > +				     unsigned long max)
> > > > +{
> > > > +	efi_guid_t lf2_proto_guid = EFI_LOAD_FILE2_PROTOCOL_GUID;
> > > > +	efi_device_path_protocol_t *dp;
> > > > +	efi_load_file2_protocol_t *lf2;
> > > > +	unsigned long initrd_addr;
> > > > +	unsigned long initrd_size;
> > > > +	efi_handle_t handle;
> > > > +	efi_status_t status;
> > > > +
> > > > +	if (!load_addr || !load_size)
> > > > +		return EFI_INVALID_PARAMETER;
> > > > +
> > > > +	dp = (efi_device_path_protocol_t *)&initrd_devpath;
> > > > +	status = efi_bs_call(locate_device_path, &lf2_proto_guid, &dp, &handle);
> > > > +	if (status != EFI_SUCCESS)
> > > > +		return status;
> > > > +
> > > > +	status = efi_bs_call(handle_protocol, handle, &lf2_proto_guid,
> > > > +			     (void **)&lf2);
> > > > +	if (status != EFI_SUCCESS)
> > > > +		return status;
> > > 
> > > You require here that there is a handle exposing the device path
> > > protocol with the initrd specific device path. On the same handle the
> > > EFI_LOAD_FILE2_PROTOCOL must be installed which will load the initrd
> > > file when called with the same device path.
> > > 
> > > An alternative implementation would simple loop over all instances of
> > > the EFI_LOAD_FILE2_PROTOCOL and try to load the initrd.
> > > 
> > > It would be worthwhile to describe the requirements on the
> > > implementation of the EFI_LOAD_FILE2_PROTOCOL in a code comment and in
> > > the documentation.
> > > 
> > > Unfortunately the documentation of UEFI has been duplicated into two files:
> > > 
> > > Documentation/arm/uefi.rst
> > > Documentation/x86/x86_64/uefi.rst
> > > 
> > > Best regards
> > > 
> > > Heinrich
> > > 
> > > > +
> > > > +	initrd_size = 0;
> > > > +	status = efi_call_proto(lf2, load_file,
> > > > +				(efi_device_path_protocol_t *)&initrd_devpath,
> > > > +				false, &initrd_size, NULL);
> > > > +	if (status != EFI_BUFFER_TOO_SMALL)
> > > > +		return EFI_LOAD_ERROR;
> > > > +
> > > > +	status = efi_allocate_pages(initrd_size, &initrd_addr, max);
> > > > +	if (status != EFI_SUCCESS)
> > > > +		return status;
> > > > +
> > > > +	status = efi_call_proto(lf2, load_file,
> > > > +				(efi_device_path_protocol_t *)&initrd_devpath,
> > > > +				false, &initrd_size, (void *)initrd_addr);
> > > > +	if (status != EFI_SUCCESS) {
> > > > +		efi_free(initrd_size, initrd_addr);
> > > > +		return status;
> > > > +	}
> > > > +
> > > > +	*load_addr = initrd_addr;
> > > > +	*load_size = initrd_size;
> > > > +	return EFI_SUCCESS;
> > > > +}
> > > > diff --git a/drivers/firmware/efi/libstub/efistub.h b/drivers/firmware/efi/libstub/efistub.h
> > > > index 99e93fd76ec5..fbf9f9442eed 100644
> > > > --- a/drivers/firmware/efi/libstub/efistub.h
> > > > +++ b/drivers/firmware/efi/libstub/efistub.h
> > > > @@ -566,6 +566,14 @@ union efi_load_file_protocol {
> > > >    	} mixed_mode;
> > > >    };
> > > > 
> > > > +struct efi_vendor_dev_path {
> > > > +	u8		type;
> > > > +	u8		sub_type;
> > > > +	u16		length;
> > > > +	efi_guid_t	vendorguid;
> > > > +	u8		vendordata[];
> > > > +} __packed;
> > > > +
> > > >    void efi_pci_disable_bridge_busmaster(void);
> > > > 
> > > >    typedef efi_status_t (*efi_exit_boot_map_processing)(
> > > > @@ -651,4 +659,8 @@ efi_status_t efi_load_initrd(efi_loaded_image_t *image,
> > > >    			     unsigned long *load_addr,
> > > >    			     unsigned long *load_size);
> > > > 
> > > > +efi_status_t efi_load_initrd_devpath(unsigned long *load_addr,
> > > > +				     unsigned long *load_size,
> > > > +				     unsigned long max);
> > > > +
> > > >    #endif
> > > > diff --git a/drivers/firmware/efi/libstub/x86-stub.c b/drivers/firmware/efi/libstub/x86-stub.c
> > > > index f3e2ff31b624..7f38f95676dd 100644
> > > > --- a/drivers/firmware/efi/libstub/x86-stub.c
> > > > +++ b/drivers/firmware/efi/libstub/x86-stub.c
> > > > @@ -419,9 +419,20 @@ efi_status_t __efiapi efi_pe_entry(efi_handle_t handle,
> > > >    	if (status != EFI_SUCCESS)
> > > >    		goto fail2;
> > > > 
> > > > -	status = efi_load_initrd(image, hdr->initrd_addr_max,
> > > > -				 above4g ? ULONG_MAX : hdr->initrd_addr_max,
> > > > -				 &ramdisk_addr, &ramdisk_size);
> > > > +	/*
> > > > +	 * The initrd loaded from the Linux initrd vendor device
> > > > +	 * path should take precedence, as we don't want the
> > > > +	 * [unverified] command line to override the initrd
> > > > +	 * supplied by the [potentially verified] firmware.
> > > > +	 */
> > > > +	status = efi_load_initrd_devpath(&ramdisk_addr, &ramdisk_size,
> > > > +					 above4g ? ULONG_MAX
> > > > +						 : hdr->initrd_addr_max);
> > > > +	if (status == EFI_NOT_FOUND)
> > > > +		status = efi_load_initrd(image, hdr->initrd_addr_max,
> > > > +					 above4g ? ULONG_MAX
> > > > +						 : hdr->initrd_addr_max,
> > > > +					 &ramdisk_addr, &ramdisk_size);
> > > >    	if (status != EFI_SUCCESS)
> > > >    		goto fail2;
> > > >    	hdr->ramdisk_image = ramdisk_addr & 0xffffffff;
> > > > @@ -732,6 +743,25 @@ struct boot_params *efi_main(efi_handle_t handle,
> > > >    			 ((u64)boot_params->ext_cmd_line_ptr << 32));
> > > >    	efi_parse_options((char *)cmdline_paddr);
> > > > 
> > > > +	if (!hdr->ramdisk_size && !boot_params->ext_ramdisk_size) {
> > > > +		unsigned long max = hdr->initrd_addr_max;
> > > > +		unsigned long addr, size;
> > > > +
> > > > +		if (hdr->xloadflags & XLF_CAN_BE_LOADED_ABOVE_4G)
> > > > +			max = ULONG_MAX;
> > > > +
> > > > +		status = efi_load_initrd_devpath(&addr, &size, max);
> > > > +		if (status == EFI_SUCCESS) {
> > > > +			hdr->ramdisk_image		= (u32)addr;
> > > > +			hdr->ramdisk_size 		= (u32)size;
> > > > +			boot_params->ext_ramdisk_image	= (u64)addr >> 32;
> > > > +			boot_params->ext_ramdisk_size 	= (u64)size >> 32;
> > > > +		} else if (status != EFI_NOT_FOUND) {
> > > > +			efi_printk("efi_load_initrd_devpath() failed!\n");
> > > > +			goto fail;
> > > > +		}
> > > > +	}
> > > > +
> > > >    	/*
> > > >    	 * If the boot loader gave us a value for secure_boot then we use that,
> > > >    	 * otherwise we ask the BIOS.
> > > > diff --git a/include/linux/efi.h b/include/linux/efi.h
> > > > index 9ccf313fe9de..75c83c322c40 100644
> > > > --- a/include/linux/efi.h
> > > > +++ b/include/linux/efi.h
> > > > @@ -353,6 +353,7 @@ void efi_native_runtime_setup(void);
> > > >    #define LINUX_EFI_TPM_EVENT_LOG_GUID		EFI_GUID(0xb7799cb0, 0xeca2, 0x4943,  0x96, 0x67, 0x1f, 0xae, 0x07, 0xb7, 0x47, 0xfa)
> > > >    #define LINUX_EFI_TPM_FINAL_LOG_GUID		EFI_GUID(0x1e2ed096, 0x30e2, 0x4254,  0xbd, 0x89, 0x86, 0x3b, 0xbe, 0xf8, 0x23, 0x25)
> > > >    #define LINUX_EFI_MEMRESERVE_TABLE_GUID		EFI_GUID(0x888eb0c6, 0x8ede, 0x4ff5,  0xa8, 0xf0, 0x9a, 0xee, 0x5c, 0xb9, 0x77, 0xc2)
> > > > +#define LINUX_EFI_INITRD_MEDIA_GUID		EFI_GUID(0x5568e427, 0x68fc, 0x4f3d,  0xac, 0x74, 0xca, 0x55, 0x52, 0x31, 0xcc, 0x68)
> > > > 
> > > >    /* OEM GUIDs */
> > > >    #define DELLEMC_EFI_RCI2_TABLE_GUID		EFI_GUID(0x2d9f28a2, 0xa886, 0x456a,  0x97, 0xa8, 0xf1, 0x1e, 0xf2, 0x4f, 0xf4, 0x55)
> > > > 
> > > 
> 

^ permalink raw reply	[flat|nested] 39+ messages in thread

* Re: [PATCH 1/2] efi/libstub: add support for loading the initrd from a device path
  2020-02-06 18:26   ` Heinrich Schuchardt
  2020-02-06 18:46     ` Ilias Apalodimas
@ 2020-02-06 22:35     ` Ard Biesheuvel
  2020-02-07  0:01       ` Heinrich Schuchardt
  2020-02-07 11:09       ` Laszlo Ersek
  2020-02-07 11:03     ` Laszlo Ersek
  2 siblings, 2 replies; 39+ messages in thread
From: Ard Biesheuvel @ 2020-02-06 22:35 UTC (permalink / raw)
  To: Heinrich Schuchardt
  Cc: Ard Biesheuvel, linux-efi, linux-arm-kernel, Laszlo Ersek,
	Leif Lindholm, Peter Jones, Matthew Garrett, Alexander Graf,
	Ilias Apalodimas, Daniel Kiper

On Thu, 6 Feb 2020 at 18:26, Heinrich Schuchardt <xypron.glpk@gmx.de> wrote:
>
> On 2/6/20 3:03 PM, Ard Biesheuvel wrote:
> > There are currently two ways to specify the initrd to be passed to the
> > Linux kernel when booting via the EFI stub:
> > - it can be passed as a initrd= command line option when doing a pure PE
> >    boot (as opposed to the EFI handover protocol that exists for x86)
> > - otherwise, the bootloader or firmware can load the initrd into memory,
> >    and pass the address and size via the bootparams struct (x86) or
> >    device tree (ARM)
> >
> > In the first case, we are limited to loading from the same file system
> > that the kernel was loaded from, and it is also problematic in a trusted
>
> Hello Ard,
>
> "same file system" is not a limitation of using a command line
> parameter. Any device path can be passed as a string.
>

What do you mean? The current implementation opens the volume via the
loaded_image_info struct, and finds the file based on its filename,
not on a device path. So how can it access files on other file
systems?


> > boot context, given that we cannot easily protect the command line from
> > tampering without either adding complicated white/blacklisting of boot
> > arguments or locking down the command line altogether.
>
> Not relying on the command line for finding the initrd image does not
> secure the integrity and the validity of the initrd image.
>

It does. It ensures that [signed] bootloader code is in charge of
providing the initrd at the point during the boot where the kernel is
ready to consume this data.

> A signature on the initrd image could solve the security problem you
> describe. It would not require non-Linux software to be changed, and
> would provide much better security.
>

A signed initrd would be useful, too, but it doesn't fix the whole problem.

Linux does not support signed initrds today, and this feature permits
a firmware implementation to do something very similar, i.e., it
permits the bootloader to perform the verification as it is passed to
the kernel.


> >
> > In the second case, we force the bootloader to duplicate knowledge about
> > the boot protocol which is already encoded in the stub, and which may be
> > subject to change over time, e.g., bootparams struct definitions, memory
> > allocation/alignment requirements for the placement of the initrd etc etc.
> > In the ARM case, it also requires the bootloader to modify the hardware
> > description provided by the firmware, as it is passed in the same file.
> > On systems where the initrd is measured after loading, it creates a time
> > window where the initrd contents might be manipulated in memory before
> > handing over to the kernel.
> >
> > Address these concerns by adding support for loading the initrd into
> > memory by invoking the EFI LoadFile2 protocol installed on a vendor
> > GUIDed device path that specifically designates a Linux initrd.
> > This addresses the above concerns, by putting the EFI stub in charge of
> > placement in memory and of passing the base and size to the kernel proper
> > (via whatever means it desires) while still leaving it up to the firmware
> > or bootloader to obtain the file contents, potentially from other file
> > systems than the one the kernel itself was loaded from. On platforms that
> > implement measured boot, it permits the firmware to take the measurement
> > right before the kernel actually consumes the contents.
>
> A firmware implementing the UEFI standard will not be aware of any
> initrd image as such an object does not exist in the standard. It was a
> wise decision that the UEFI standard is operating system agnostic
> (accomodating BSD, Linux, Windows, etc.). So the firmware (EDK2, U-Boot,
> etc.) seems to be out of scope for providing a Linux specific
> EFI_LOAD_FILE2_PROTOCOL.
>

You know we are currently patching bootparams struct and DTs to
provide the initrd information, right? And we have code that knows
about low/high memory limits, alignment, etc, that is different per
architecture.

> When booting via GRUB it will be GRUB knowing which initrd to load.
>

Exactly, which is why GRUB will implement this protocol. That way, it
does not have to touch the DT at all, or create a bootparams struct
from setup data and inspect the various flags about placement,
alignment, preferred addresses, etc.

> Please, indicate which software you expect to expose the initrd related
> EFI_LOAD_FILE2_PROTOCOL.
>

The primary use case is GRUB and other intermediate loaders, since it
would remove any need for these components to know any such details.
My aim is to make the next architecture that gets added to GRUB for
EFI boot 100% generic.

> Using an UEFI variable for passing the initrd device path would be a
> leaner solution on the bootloader side than requiring an extra
> EFI_LOAD_FILE2_PROTOCOL implementation.
>

This would also require kernel changes, since we don't currently load
initrds from arbitrary device paths. The EFI_FILE_PROTOCOL is much
more complicated than needed, and it doesn't work well with mixed
mode. It also requires GRUB to expose the filesystem it loads the
initrd from via EFI protocols, which is currently unnecessary and
therefore not implemented.

Also, using an EFI variable defeats the purpose. The whole point of
this is making it more likely that the kernel loaded the initrd that
the bootloader or firmware intended it to load, and having a piece of
simple [signed] code that implements this is the easiest way to
achieve that.

For u-boot, it should be trivial to implement a simple LoadFile2
protocol wrapper around EFI_FILE_PROTOCOL that can be installed on a
handle that also carries EFI_FILE_PROTOCOL.

> >
> > Signed-off-by: Ard Biesheuvel <ardb@kernel.org>
> > ---
> >   drivers/firmware/efi/libstub/arm-stub.c        | 16 +++--
> >   drivers/firmware/efi/libstub/efi-stub-helper.c | 65 ++++++++++++++++++++
> >   drivers/firmware/efi/libstub/efistub.h         | 12 ++++
> >   drivers/firmware/efi/libstub/x86-stub.c        | 36 ++++++++++-
> >   include/linux/efi.h                            |  1 +
> >   5 files changed, 123 insertions(+), 7 deletions(-)
> >
> > diff --git a/drivers/firmware/efi/libstub/arm-stub.c b/drivers/firmware/efi/libstub/arm-stub.c
> > index c7b091f50e55..1db943c1ba2b 100644
> > --- a/drivers/firmware/efi/libstub/arm-stub.c
> > +++ b/drivers/firmware/efi/libstub/arm-stub.c
> > @@ -157,6 +157,7 @@ unsigned long efi_entry(void *handle, efi_system_table_t *sys_table_arg,
> >       enum efi_secureboot_mode secure_boot;
> >       struct screen_info *si;
> >       efi_properties_table_t *prop_tbl;
> > +     unsigned long max_addr;
> >
> >       sys_table = sys_table_arg;
> >
> > @@ -255,11 +256,18 @@ unsigned long efi_entry(void *handle, efi_system_table_t *sys_table_arg,
> >       if (!fdt_addr)
> >               pr_efi("Generating empty DTB\n");
> >
> > -     status = efi_load_initrd(image, ULONG_MAX,
> > -                              efi_get_max_initrd_addr(dram_base, *image_addr),
> > -                              &initrd_addr, &initrd_size);
> > +     max_addr = efi_get_max_initrd_addr(dram_base, *image_addr);
> > +     status = efi_load_initrd_devpath(&initrd_addr, &initrd_size, max_addr);
> > +     if (status == EFI_SUCCESS)
> > +             pr_efi("Loaded initrd from LINUX_EFI_INITRD_MEDIA_GUID device path\n");
> > +     else if (status == EFI_NOT_FOUND) {
> > +             status = efi_load_initrd(image, ULONG_MAX, max_addr,
> > +                                      &initrd_addr, &initrd_size);
> > +             if (status == EFI_SUCCESS)
> > +                     pr_efi("Loaded initrd from command line option\n");
> > +     }
> >       if (status != EFI_SUCCESS)
> > -             pr_efi_err("Failed initrd from command line!\n");
> > +             pr_efi_err("Failed to load initrd!\n");
> >
> >       efi_random_get_seed();
> >
> > diff --git a/drivers/firmware/efi/libstub/efi-stub-helper.c b/drivers/firmware/efi/libstub/efi-stub-helper.c
> > index 8e60a39df3c5..eaf45ea749b3 100644
> > --- a/drivers/firmware/efi/libstub/efi-stub-helper.c
> > +++ b/drivers/firmware/efi/libstub/efi-stub-helper.c
> > @@ -323,3 +323,68 @@ void efi_char16_printk(efi_char16_t *str)
> >       efi_call_proto(efi_table_attr(efi_system_table(), con_out),
> >                      output_string, str);
> >   }
> > +
> > +static const struct {
> > +     struct efi_vendor_dev_path      vendor;
> > +     struct efi_generic_dev_path     end;
> > +} __packed initrd_devpath = {
> > +     {
> > +             EFI_DEV_MEDIA,
> > +             EFI_DEV_MEDIA_VENDOR,
> > +             sizeof(struct efi_vendor_dev_path),
> > +             LINUX_EFI_INITRD_MEDIA_GUID
> > +     }, {
> > +             EFI_DEV_END_PATH,
> > +             EFI_DEV_END_ENTIRE,
> > +             sizeof(struct efi_generic_dev_path)
> > +     }
> > +};
> > +
> > +efi_status_t efi_load_initrd_devpath(unsigned long *load_addr,
> > +                                  unsigned long *load_size,
> > +                                  unsigned long max)
> > +{
> > +     efi_guid_t lf2_proto_guid = EFI_LOAD_FILE2_PROTOCOL_GUID;
> > +     efi_device_path_protocol_t *dp;
> > +     efi_load_file2_protocol_t *lf2;
> > +     unsigned long initrd_addr;
> > +     unsigned long initrd_size;
> > +     efi_handle_t handle;
> > +     efi_status_t status;
> > +
> > +     if (!load_addr || !load_size)
> > +             return EFI_INVALID_PARAMETER;
> > +
> > +     dp = (efi_device_path_protocol_t *)&initrd_devpath;
> > +     status = efi_bs_call(locate_device_path, &lf2_proto_guid, &dp, &handle);
> > +     if (status != EFI_SUCCESS)
> > +             return status;
> > +
> > +     status = efi_bs_call(handle_protocol, handle, &lf2_proto_guid,
> > +                          (void **)&lf2);
> > +     if (status != EFI_SUCCESS)
> > +             return status;
>
> You require here that there is a handle exposing the device path
> protocol with the initrd specific device path. On the same handle the
> EFI_LOAD_FILE2_PROTOCOL must be installed which will load the initrd
> file when called with the same device path.
>

Exactly.

> An alternative implementation would simple loop over all instances of
> the EFI_LOAD_FILE2_PROTOCOL and try to load the initrd.
>

How do I distinguish the initrd from other EFI_LOAD_FILE2_PROTOCOL
instances? For instance, PCI option ROMs are also exposed as
EFI_LOAD_FILE2_PROTOCOL.

> It would be worthwhile to describe the requirements on the
> implementation of the EFI_LOAD_FILE2_PROTOCOL in a code comment and in
> the documentation.
>
> Unfortunately the documentation of UEFI has been duplicated into two files:
>
> Documentation/arm/uefi.rst
> Documentation/x86/x86_64/uefi.rst
>

Yes, that is a good point. I will work on factoring out the generic
parts and share them.


Thanks for the review,
Ard.



>
> > +
> > +     initrd_size = 0;
> > +     status = efi_call_proto(lf2, load_file,
> > +                             (efi_device_path_protocol_t *)&initrd_devpath,
> > +                             false, &initrd_size, NULL);
> > +     if (status != EFI_BUFFER_TOO_SMALL)
> > +             return EFI_LOAD_ERROR;
> > +
> > +     status = efi_allocate_pages(initrd_size, &initrd_addr, max);
> > +     if (status != EFI_SUCCESS)
> > +             return status;
> > +
> > +     status = efi_call_proto(lf2, load_file,
> > +                             (efi_device_path_protocol_t *)&initrd_devpath,
> > +                             false, &initrd_size, (void *)initrd_addr);
> > +     if (status != EFI_SUCCESS) {
> > +             efi_free(initrd_size, initrd_addr);
> > +             return status;
> > +     }
> > +
> > +     *load_addr = initrd_addr;
> > +     *load_size = initrd_size;
> > +     return EFI_SUCCESS;
> > +}
> > diff --git a/drivers/firmware/efi/libstub/efistub.h b/drivers/firmware/efi/libstub/efistub.h
> > index 99e93fd76ec5..fbf9f9442eed 100644
> > --- a/drivers/firmware/efi/libstub/efistub.h
> > +++ b/drivers/firmware/efi/libstub/efistub.h
> > @@ -566,6 +566,14 @@ union efi_load_file_protocol {
> >       } mixed_mode;
> >   };
> >
> > +struct efi_vendor_dev_path {
> > +     u8              type;
> > +     u8              sub_type;
> > +     u16             length;
> > +     efi_guid_t      vendorguid;
> > +     u8              vendordata[];
> > +} __packed;
> > +
> >   void efi_pci_disable_bridge_busmaster(void);
> >
> >   typedef efi_status_t (*efi_exit_boot_map_processing)(
> > @@ -651,4 +659,8 @@ efi_status_t efi_load_initrd(efi_loaded_image_t *image,
> >                            unsigned long *load_addr,
> >                            unsigned long *load_size);
> >
> > +efi_status_t efi_load_initrd_devpath(unsigned long *load_addr,
> > +                                  unsigned long *load_size,
> > +                                  unsigned long max);
> > +
> >   #endif
> > diff --git a/drivers/firmware/efi/libstub/x86-stub.c b/drivers/firmware/efi/libstub/x86-stub.c
> > index f3e2ff31b624..7f38f95676dd 100644
> > --- a/drivers/firmware/efi/libstub/x86-stub.c
> > +++ b/drivers/firmware/efi/libstub/x86-stub.c
> > @@ -419,9 +419,20 @@ efi_status_t __efiapi efi_pe_entry(efi_handle_t handle,
> >       if (status != EFI_SUCCESS)
> >               goto fail2;
> >
> > -     status = efi_load_initrd(image, hdr->initrd_addr_max,
> > -                              above4g ? ULONG_MAX : hdr->initrd_addr_max,
> > -                              &ramdisk_addr, &ramdisk_size);
> > +     /*
> > +      * The initrd loaded from the Linux initrd vendor device
> > +      * path should take precedence, as we don't want the
> > +      * [unverified] command line to override the initrd
> > +      * supplied by the [potentially verified] firmware.
> > +      */
> > +     status = efi_load_initrd_devpath(&ramdisk_addr, &ramdisk_size,
> > +                                      above4g ? ULONG_MAX
> > +                                              : hdr->initrd_addr_max);
> > +     if (status == EFI_NOT_FOUND)
> > +             status = efi_load_initrd(image, hdr->initrd_addr_max,
> > +                                      above4g ? ULONG_MAX
> > +                                              : hdr->initrd_addr_max,
> > +                                      &ramdisk_addr, &ramdisk_size);
> >       if (status != EFI_SUCCESS)
> >               goto fail2;
> >       hdr->ramdisk_image = ramdisk_addr & 0xffffffff;
> > @@ -732,6 +743,25 @@ struct boot_params *efi_main(efi_handle_t handle,
> >                        ((u64)boot_params->ext_cmd_line_ptr << 32));
> >       efi_parse_options((char *)cmdline_paddr);
> >
> > +     if (!hdr->ramdisk_size && !boot_params->ext_ramdisk_size) {
> > +             unsigned long max = hdr->initrd_addr_max;
> > +             unsigned long addr, size;
> > +
> > +             if (hdr->xloadflags & XLF_CAN_BE_LOADED_ABOVE_4G)
> > +                     max = ULONG_MAX;
> > +
> > +             status = efi_load_initrd_devpath(&addr, &size, max);
> > +             if (status == EFI_SUCCESS) {
> > +                     hdr->ramdisk_image              = (u32)addr;
> > +                     hdr->ramdisk_size               = (u32)size;
> > +                     boot_params->ext_ramdisk_image  = (u64)addr >> 32;
> > +                     boot_params->ext_ramdisk_size   = (u64)size >> 32;
> > +             } else if (status != EFI_NOT_FOUND) {
> > +                     efi_printk("efi_load_initrd_devpath() failed!\n");
> > +                     goto fail;
> > +             }
> > +     }
> > +
> >       /*
> >        * If the boot loader gave us a value for secure_boot then we use that,
> >        * otherwise we ask the BIOS.
> > diff --git a/include/linux/efi.h b/include/linux/efi.h
> > index 9ccf313fe9de..75c83c322c40 100644
> > --- a/include/linux/efi.h
> > +++ b/include/linux/efi.h
> > @@ -353,6 +353,7 @@ void efi_native_runtime_setup(void);
> >   #define LINUX_EFI_TPM_EVENT_LOG_GUID                EFI_GUID(0xb7799cb0, 0xeca2, 0x4943,  0x96, 0x67, 0x1f, 0xae, 0x07, 0xb7, 0x47, 0xfa)
> >   #define LINUX_EFI_TPM_FINAL_LOG_GUID                EFI_GUID(0x1e2ed096, 0x30e2, 0x4254,  0xbd, 0x89, 0x86, 0x3b, 0xbe, 0xf8, 0x23, 0x25)
> >   #define LINUX_EFI_MEMRESERVE_TABLE_GUID             EFI_GUID(0x888eb0c6, 0x8ede, 0x4ff5,  0xa8, 0xf0, 0x9a, 0xee, 0x5c, 0xb9, 0x77, 0xc2)
> > +#define LINUX_EFI_INITRD_MEDIA_GUID          EFI_GUID(0x5568e427, 0x68fc, 0x4f3d,  0xac, 0x74, 0xca, 0x55, 0x52, 0x31, 0xcc, 0x68)
> >
> >   /* OEM GUIDs */
> >   #define DELLEMC_EFI_RCI2_TABLE_GUID         EFI_GUID(0x2d9f28a2, 0xa886, 0x456a,  0x97, 0xa8, 0xf1, 0x1e, 0xf2, 0x4f, 0xf4, 0x55)
> >
>

^ permalink raw reply	[flat|nested] 39+ messages in thread

* Re: [PATCH 1/2] efi/libstub: add support for loading the initrd from a device path
  2020-02-06 20:09         ` Ilias Apalodimas
@ 2020-02-06 22:49           ` Heinrich Schuchardt
  2020-02-07  7:35             ` Ilias Apalodimas
  0 siblings, 1 reply; 39+ messages in thread
From: Heinrich Schuchardt @ 2020-02-06 22:49 UTC (permalink / raw)
  To: Ilias Apalodimas
  Cc: Ard Biesheuvel, linux-efi, linux-arm-kernel, lersek, leif,
	pjones, mjg59, agraf, daniel.kiper

On 2/6/20 9:09 PM, Ilias Apalodimas wrote:
> On Thu, Feb 06, 2020 at 08:15:11PM +0100, Heinrich Schuchardt wrote:
>> On 2/6/20 7:46 PM, Ilias Apalodimas wrote:
>>> Hi Heinrich,
>>>
>>> I actually like the approach.
>>>
>>> On Thu, Feb 06, 2020 at 07:26:20PM +0100, Heinrich Schuchardt wrote:
>>> [...]
>>>>> boot context, given that we cannot easily protect the command line from
>>>>> tampering without either adding complicated white/blacklisting of boot
>>>>> arguments or locking down the command line altogether.
>>>>
>>>> Not relying on the command line for finding the initrd image does not
>>>> secure the integrity and the validity of the initrd image.
>>>
>>> This patch isn't supposed to protect you against a bogus initrd and I don't
>>> think it should be the bootloaders job to verify that. The kernel already has
>>> tools to do that.
>>
>> How do you expect a bootloader identify if the initrd is compatible with
>> the kernel?
>
> Uh? Were exactly did i say that i expect that to happen?
>
>>
>>>
>>>>
>>>> A signature on the initrd image could solve the security problem you
>>>> describe. It would not require non-Linux software to be changed, and
>>>> would provide much better security.
>>>>
>>>
>>> The problem with parsing the kernel command line is that at the moment,
>>> different options are supported by different architectures. A quick grep for
>>> CONFIG_CMDLINE_(FORCE/OVERWRITE/EXTEND/FROM_BOOTLOADER) will give you an idea.
>>>
>>> What you can do in U-Boot currently is not have an environment in any of the
>>> flashes and set the bootdelay to 0, in order to prevent the user from
>>> changing that command line.
>>
>> If you don't have an environment or boot script how would
>> update-initramfs set the path of the initrd when it is updated?
>
> The path isn't hardcoded in any code here is it?
> This specifies a way for the linux stub to load the actual file. It's pretty a
> callback to the firmware. Were the firmware will find and how it will load it
> eventually is implementation specific.

"Implementation specific" - This does not sound like anything you would
want to have in mainline Linux, U-Boot, or EDK2.

>
>>
>> Using a UEFI variable seems to be the natural choice.
>>
>
> You might as well use that to specify were you should load the file from.
> The Loadfile2 (with the specified guid)  implementation of the firmware will
> take care of that.
>

If we have a UEFI variable, the Linux kernel can use it to find the
handle with the file system and load initrd via the
EFI_SIMPLE_FILE_SYSTEM_PROTOCOL.

This way we stay within the existing UEFI specification and avoid
anything "implementation specific" in the firmware.

If you want extra security, Linux can use an authenticated variable.

>>>
>>>>>
>>>>> In the second case, we force the bootloader to duplicate knowledge about
>>>>> the boot protocol which is already encoded in the stub, and which may be
>>>>> subject to change over time, e.g., bootparams struct definitions, memory
>>>>> allocation/alignment requirements for the placement of the initrd etc etc.
>>>>> In the ARM case, it also requires the bootloader to modify the hardware
>>>>> description provided by the firmware, as it is passed in the same file.
>>>>> On systems where the initrd is measured after loading, it creates a time
>>>>> window where the initrd contents might be manipulated in memory before
>>>>> handing over to the kernel.
>>>>>
>>>>> Address these concerns by adding support for loading the initrd into
>>>>> memory by invoking the EFI LoadFile2 protocol installed on a vendor
>>>>> GUIDed device path that specifically designates a Linux initrd.
>>>>> This addresses the above concerns, by putting the EFI stub in charge of
>>>>> placement in memory and of passing the base and size to the kernel proper
>>>>> (via whatever means it desires) while still leaving it up to the firmware
>>>>> or bootloader to obtain the file contents, potentially from other file
>>>>> systems than the one the kernel itself was loaded from. On platforms that
>>>>> implement measured boot, it permits the firmware to take the measurement
>>>>> right before the kernel actually consumes the contents.
>>>>
>>>> A firmware implementing the UEFI standard will not be aware of any
>>>> initrd image as such an object does not exist in the standard. It was a
>>>> wise decision that the UEFI standard is operating system agnostic
>>>> (accomodating BSD, Linux, Windows, etc.). So the firmware (EDK2, U-Boot,
>>>> etc.) seems to be out of scope for providing a Linux specific
>>>> EFI_LOAD_FILE2_PROTOCOL.
>>>>
>>>> When booting via GRUB it will be GRUB knowing which initrd to load.
>>>
>>> What about booting the kernel directly?
>>>
>>>>
>>>> Please, indicate which software you expect to expose the initrd related
>>>> EFI_LOAD_FILE2_PROTOCOL.
>>>
>>> I have an implementation for this on U-Boot which works. The file and device are
>>> hardcoded at the moment, but the rest of the functionality works fine. I'll
>>> share it with you once I clean it up a bit.
>>
>> Using a UEFI variable for passing the intird device path to Linux does
>> not require any change in U-Boot and is compatible with the UEFI
>> implementations of existing hardware like the laptop on which I am
>> writing this email.
>
> This still has the same issues we have now, uefi variable, kernel command line
> or whatever, it won't be common across architectures.

This would be a bad design choice by Linux. I cannot see why a UEFI
variable should not be interpreted in a consistent way inside Linux to
load a file via the EFI_SIMPLE_FILE_PROTOCOL.

>
> Thanks
> /Ilias
>>
>> Best regards
>>
>> Heinrich
>>
>>>
>>>>
>>>> Using an UEFI variable for passing the initrd device path would be a
>>>> leaner solution on the bootloader side than requiring an extra
>>>> EFI_LOAD_FILE2_PROTOCOL implementation.
>>>>
>>>
>>> Thanks
>>> /Ilias
>>>
>>>>>
>>>>> Signed-off-by: Ard Biesheuvel <ardb@kernel.org>
>>>>> ---
>>>>>     drivers/firmware/efi/libstub/arm-stub.c        | 16 +++--
>>>>>     drivers/firmware/efi/libstub/efi-stub-helper.c | 65 ++++++++++++++++++++
>>>>>     drivers/firmware/efi/libstub/efistub.h         | 12 ++++
>>>>>     drivers/firmware/efi/libstub/x86-stub.c        | 36 ++++++++++-
>>>>>     include/linux/efi.h                            |  1 +
>>>>>     5 files changed, 123 insertions(+), 7 deletions(-)
>>>>>
>>>>> diff --git a/drivers/firmware/efi/libstub/arm-stub.c b/drivers/firmware/efi/libstub/arm-stub.c
>>>>> index c7b091f50e55..1db943c1ba2b 100644
>>>>> --- a/drivers/firmware/efi/libstub/arm-stub.c
>>>>> +++ b/drivers/firmware/efi/libstub/arm-stub.c
>>>>> @@ -157,6 +157,7 @@ unsigned long efi_entry(void *handle, efi_system_table_t *sys_table_arg,
>>>>>     	enum efi_secureboot_mode secure_boot;
>>>>>     	struct screen_info *si;
>>>>>     	efi_properties_table_t *prop_tbl;
>>>>> +	unsigned long max_addr;
>>>>>
>>>>>     	sys_table = sys_table_arg;
>>>>>
>>>>> @@ -255,11 +256,18 @@ unsigned long efi_entry(void *handle, efi_system_table_t *sys_table_arg,
>>>>>     	if (!fdt_addr)
>>>>>     		pr_efi("Generating empty DTB\n");
>>>>>
>>>>> -	status = efi_load_initrd(image, ULONG_MAX,
>>>>> -				 efi_get_max_initrd_addr(dram_base, *image_addr),
>>>>> -				 &initrd_addr, &initrd_size);
>>>>> +	max_addr = efi_get_max_initrd_addr(dram_base, *image_addr);
>>>>> +	status = efi_load_initrd_devpath(&initrd_addr, &initrd_size, max_addr);
>>>>> +	if (status == EFI_SUCCESS)
>>>>> +		pr_efi("Loaded initrd from LINUX_EFI_INITRD_MEDIA_GUID device path\n");
>>>>> +	else if (status == EFI_NOT_FOUND) {
>>>>> +		status = efi_load_initrd(image, ULONG_MAX, max_addr,
>>>>> +					 &initrd_addr, &initrd_size);

If I delete the initrd that otherwise would be loaded by the
EFI_LOAD_FILE2_PROTOCOL I end up with the old behavior. So where is the
security gain provided by this patch?

Best regards

Heinrich


>>>>> +		if (status == EFI_SUCCESS)
>>>>> +			pr_efi("Loaded initrd from command line option\n");
>>>>> +	}
>>>>>     	if (status != EFI_SUCCESS)
>>>>> -		pr_efi_err("Failed initrd from command line!\n");
>>>>> +		pr_efi_err("Failed to load initrd!\n");
>>>>>
>>>>>     	efi_random_get_seed();
>>>>>
>>>>> diff --git a/drivers/firmware/efi/libstub/efi-stub-helper.c b/drivers/firmware/efi/libstub/efi-stub-helper.c
>>>>> index 8e60a39df3c5..eaf45ea749b3 100644
>>>>> --- a/drivers/firmware/efi/libstub/efi-stub-helper.c
>>>>> +++ b/drivers/firmware/efi/libstub/efi-stub-helper.c
>>>>> @@ -323,3 +323,68 @@ void efi_char16_printk(efi_char16_t *str)
>>>>>     	efi_call_proto(efi_table_attr(efi_system_table(), con_out),
>>>>>     		       output_string, str);
>>>>>     }
>>>>> +
>>>>> +static const struct {
>>>>> +	struct efi_vendor_dev_path	vendor;
>>>>> +	struct efi_generic_dev_path	end;
>>>>> +} __packed initrd_devpath = {
>>>>> +	{
>>>>> +		EFI_DEV_MEDIA,
>>>>> +		EFI_DEV_MEDIA_VENDOR,
>>>>> +		sizeof(struct efi_vendor_dev_path),
>>>>> +		LINUX_EFI_INITRD_MEDIA_GUID
>>>>> +	}, {
>>>>> +		EFI_DEV_END_PATH,
>>>>> +		EFI_DEV_END_ENTIRE,
>>>>> +		sizeof(struct efi_generic_dev_path)
>>>>> +	}
>>>>> +};
>>>>> +
>>>>> +efi_status_t efi_load_initrd_devpath(unsigned long *load_addr,
>>>>> +				     unsigned long *load_size,
>>>>> +				     unsigned long max)
>>>>> +{
>>>>> +	efi_guid_t lf2_proto_guid = EFI_LOAD_FILE2_PROTOCOL_GUID;
>>>>> +	efi_device_path_protocol_t *dp;
>>>>> +	efi_load_file2_protocol_t *lf2;
>>>>> +	unsigned long initrd_addr;
>>>>> +	unsigned long initrd_size;
>>>>> +	efi_handle_t handle;
>>>>> +	efi_status_t status;
>>>>> +
>>>>> +	if (!load_addr || !load_size)
>>>>> +		return EFI_INVALID_PARAMETER;
>>>>> +
>>>>> +	dp = (efi_device_path_protocol_t *)&initrd_devpath;
>>>>> +	status = efi_bs_call(locate_device_path, &lf2_proto_guid, &dp, &handle);
>>>>> +	if (status != EFI_SUCCESS)
>>>>> +		return status;
>>>>> +
>>>>> +	status = efi_bs_call(handle_protocol, handle, &lf2_proto_guid,
>>>>> +			     (void **)&lf2);
>>>>> +	if (status != EFI_SUCCESS)
>>>>> +		return status;
>>>>
>>>> You require here that there is a handle exposing the device path
>>>> protocol with the initrd specific device path. On the same handle the
>>>> EFI_LOAD_FILE2_PROTOCOL must be installed which will load the initrd
>>>> file when called with the same device path.
>>>>
>>>> An alternative implementation would simple loop over all instances of
>>>> the EFI_LOAD_FILE2_PROTOCOL and try to load the initrd.
>>>>
>>>> It would be worthwhile to describe the requirements on the
>>>> implementation of the EFI_LOAD_FILE2_PROTOCOL in a code comment and in
>>>> the documentation.
>>>>
>>>> Unfortunately the documentation of UEFI has been duplicated into two files:
>>>>
>>>> Documentation/arm/uefi.rst
>>>> Documentation/x86/x86_64/uefi.rst
>>>>
>>>> Best regards
>>>>
>>>> Heinrich
>>>>
>>>>> +
>>>>> +	initrd_size = 0;
>>>>> +	status = efi_call_proto(lf2, load_file,
>>>>> +				(efi_device_path_protocol_t *)&initrd_devpath,
>>>>> +				false, &initrd_size, NULL);
>>>>> +	if (status != EFI_BUFFER_TOO_SMALL)
>>>>> +		return EFI_LOAD_ERROR;
>>>>> +
>>>>> +	status = efi_allocate_pages(initrd_size, &initrd_addr, max);
>>>>> +	if (status != EFI_SUCCESS)
>>>>> +		return status;
>>>>> +
>>>>> +	status = efi_call_proto(lf2, load_file,
>>>>> +				(efi_device_path_protocol_t *)&initrd_devpath,
>>>>> +				false, &initrd_size, (void *)initrd_addr);
>>>>> +	if (status != EFI_SUCCESS) {
>>>>> +		efi_free(initrd_size, initrd_addr);
>>>>> +		return status;
>>>>> +	}
>>>>> +
>>>>> +	*load_addr = initrd_addr;
>>>>> +	*load_size = initrd_size;
>>>>> +	return EFI_SUCCESS;
>>>>> +}
>>>>> diff --git a/drivers/firmware/efi/libstub/efistub.h b/drivers/firmware/efi/libstub/efistub.h
>>>>> index 99e93fd76ec5..fbf9f9442eed 100644
>>>>> --- a/drivers/firmware/efi/libstub/efistub.h
>>>>> +++ b/drivers/firmware/efi/libstub/efistub.h
>>>>> @@ -566,6 +566,14 @@ union efi_load_file_protocol {
>>>>>     	} mixed_mode;
>>>>>     };
>>>>>
>>>>> +struct efi_vendor_dev_path {
>>>>> +	u8		type;
>>>>> +	u8		sub_type;
>>>>> +	u16		length;
>>>>> +	efi_guid_t	vendorguid;
>>>>> +	u8		vendordata[];
>>>>> +} __packed;
>>>>> +
>>>>>     void efi_pci_disable_bridge_busmaster(void);
>>>>>
>>>>>     typedef efi_status_t (*efi_exit_boot_map_processing)(
>>>>> @@ -651,4 +659,8 @@ efi_status_t efi_load_initrd(efi_loaded_image_t *image,
>>>>>     			     unsigned long *load_addr,
>>>>>     			     unsigned long *load_size);
>>>>>
>>>>> +efi_status_t efi_load_initrd_devpath(unsigned long *load_addr,
>>>>> +				     unsigned long *load_size,
>>>>> +				     unsigned long max);
>>>>> +
>>>>>     #endif
>>>>> diff --git a/drivers/firmware/efi/libstub/x86-stub.c b/drivers/firmware/efi/libstub/x86-stub.c
>>>>> index f3e2ff31b624..7f38f95676dd 100644
>>>>> --- a/drivers/firmware/efi/libstub/x86-stub.c
>>>>> +++ b/drivers/firmware/efi/libstub/x86-stub.c
>>>>> @@ -419,9 +419,20 @@ efi_status_t __efiapi efi_pe_entry(efi_handle_t handle,
>>>>>     	if (status != EFI_SUCCESS)
>>>>>     		goto fail2;
>>>>>
>>>>> -	status = efi_load_initrd(image, hdr->initrd_addr_max,
>>>>> -				 above4g ? ULONG_MAX : hdr->initrd_addr_max,
>>>>> -				 &ramdisk_addr, &ramdisk_size);
>>>>> +	/*
>>>>> +	 * The initrd loaded from the Linux initrd vendor device
>>>>> +	 * path should take precedence, as we don't want the
>>>>> +	 * [unverified] command line to override the initrd
>>>>> +	 * supplied by the [potentially verified] firmware.
>>>>> +	 */
>>>>> +	status = efi_load_initrd_devpath(&ramdisk_addr, &ramdisk_size,
>>>>> +					 above4g ? ULONG_MAX
>>>>> +						 : hdr->initrd_addr_max);
>>>>> +	if (status == EFI_NOT_FOUND)
>>>>> +		status = efi_load_initrd(image, hdr->initrd_addr_max,
>>>>> +					 above4g ? ULONG_MAX
>>>>> +						 : hdr->initrd_addr_max,
>>>>> +					 &ramdisk_addr, &ramdisk_size);
>>>>>     	if (status != EFI_SUCCESS)
>>>>>     		goto fail2;
>>>>>     	hdr->ramdisk_image = ramdisk_addr & 0xffffffff;
>>>>> @@ -732,6 +743,25 @@ struct boot_params *efi_main(efi_handle_t handle,
>>>>>     			 ((u64)boot_params->ext_cmd_line_ptr << 32));
>>>>>     	efi_parse_options((char *)cmdline_paddr);
>>>>>
>>>>> +	if (!hdr->ramdisk_size && !boot_params->ext_ramdisk_size) {
>>>>> +		unsigned long max = hdr->initrd_addr_max;
>>>>> +		unsigned long addr, size;
>>>>> +
>>>>> +		if (hdr->xloadflags & XLF_CAN_BE_LOADED_ABOVE_4G)
>>>>> +			max = ULONG_MAX;
>>>>> +
>>>>> +		status = efi_load_initrd_devpath(&addr, &size, max);
>>>>> +		if (status == EFI_SUCCESS) {
>>>>> +			hdr->ramdisk_image		= (u32)addr;
>>>>> +			hdr->ramdisk_size 		= (u32)size;
>>>>> +			boot_params->ext_ramdisk_image	= (u64)addr >> 32;
>>>>> +			boot_params->ext_ramdisk_size 	= (u64)size >> 32;
>>>>> +		} else if (status != EFI_NOT_FOUND) {
>>>>> +			efi_printk("efi_load_initrd_devpath() failed!\n");
>>>>> +			goto fail;
>>>>> +		}
>>>>> +	}
>>>>> +
>>>>>     	/*
>>>>>     	 * If the boot loader gave us a value for secure_boot then we use that,
>>>>>     	 * otherwise we ask the BIOS.
>>>>> diff --git a/include/linux/efi.h b/include/linux/efi.h
>>>>> index 9ccf313fe9de..75c83c322c40 100644
>>>>> --- a/include/linux/efi.h
>>>>> +++ b/include/linux/efi.h
>>>>> @@ -353,6 +353,7 @@ void efi_native_runtime_setup(void);
>>>>>     #define LINUX_EFI_TPM_EVENT_LOG_GUID		EFI_GUID(0xb7799cb0, 0xeca2, 0x4943,  0x96, 0x67, 0x1f, 0xae, 0x07, 0xb7, 0x47, 0xfa)
>>>>>     #define LINUX_EFI_TPM_FINAL_LOG_GUID		EFI_GUID(0x1e2ed096, 0x30e2, 0x4254,  0xbd, 0x89, 0x86, 0x3b, 0xbe, 0xf8, 0x23, 0x25)
>>>>>     #define LINUX_EFI_MEMRESERVE_TABLE_GUID		EFI_GUID(0x888eb0c6, 0x8ede, 0x4ff5,  0xa8, 0xf0, 0x9a, 0xee, 0x5c, 0xb9, 0x77, 0xc2)
>>>>> +#define LINUX_EFI_INITRD_MEDIA_GUID		EFI_GUID(0x5568e427, 0x68fc, 0x4f3d,  0xac, 0x74, 0xca, 0x55, 0x52, 0x31, 0xcc, 0x68)
>>>>>
>>>>>     /* OEM GUIDs */
>>>>>     #define DELLEMC_EFI_RCI2_TABLE_GUID		EFI_GUID(0x2d9f28a2, 0xa886, 0x456a,  0x97, 0xa8, 0xf1, 0x1e, 0xf2, 0x4f, 0xf4, 0x55)
>>>>>
>>>>
>>


^ permalink raw reply	[flat|nested] 39+ messages in thread

* Re: [PATCH 2/2] efi/libstub: take noinitrd cmdline argument into account for devpath initrd
  2020-02-06 18:33   ` Heinrich Schuchardt
@ 2020-02-06 23:44     ` Ard Biesheuvel
  0 siblings, 0 replies; 39+ messages in thread
From: Ard Biesheuvel @ 2020-02-06 23:44 UTC (permalink / raw)
  To: Heinrich Schuchardt
  Cc: Ard Biesheuvel, linux-efi, linux-arm-kernel, Laszlo Ersek,
	Leif Lindholm, Peter Jones, Matthew Garrett, Alexander Graf,
	Ilias Apalodimas, Daniel Kiper

On Thu, 6 Feb 2020 at 18:33, Heinrich Schuchardt <xypron.glpk@gmx.de> wrote:
>
> On 2/6/20 3:03 PM, Ard Biesheuvel wrote:
> > One of the advantages of using what basically amounts to a callback
> > interface into the bootloader for loading the initrd is that it provides
> > a natural place for the bootloader or firmware to measure the initrd
> > contents while they are being passed to the kernel.
> >
> > Unfortunately, this is not a guarantee that the initrd will in fact be
> > loaded and its /init invoked by the kernel, since the command line may
> > contain the 'noinitrd' option, in which case the initrd is ignored, but
> > this will not be reflected in the PCR that covers the initrd measurement.
>
> Does PCR here refer to the TPM Platform Configuration Register?
>

Yes.

^ permalink raw reply	[flat|nested] 39+ messages in thread

* Re: [PATCH 1/2] efi/libstub: add support for loading the initrd from a device path
  2020-02-06 22:35     ` Ard Biesheuvel
@ 2020-02-07  0:01       ` Heinrich Schuchardt
  2020-02-07  0:21         ` Ard Biesheuvel
  2020-02-07 11:09       ` Laszlo Ersek
  1 sibling, 1 reply; 39+ messages in thread
From: Heinrich Schuchardt @ 2020-02-07  0:01 UTC (permalink / raw)
  To: Ard Biesheuvel
  Cc: Ard Biesheuvel, linux-efi, linux-arm-kernel, Laszlo Ersek,
	Leif Lindholm, Peter Jones, Matthew Garrett, Alexander Graf,
	Ilias Apalodimas, Daniel Kiper

On 2/6/20 11:35 PM, Ard Biesheuvel wrote:
> On Thu, 6 Feb 2020 at 18:26, Heinrich Schuchardt <xypron.glpk@gmx.de> wrote:
>>
>> On 2/6/20 3:03 PM, Ard Biesheuvel wrote:
>>> There are currently two ways to specify the initrd to be passed to the
>>> Linux kernel when booting via the EFI stub:
>>> - it can be passed as a initrd= command line option when doing a pure PE
>>>     boot (as opposed to the EFI handover protocol that exists for x86)
>>> - otherwise, the bootloader or firmware can load the initrd into memory,
>>>     and pass the address and size via the bootparams struct (x86) or
>>>     device tree (ARM)
>>>
>>> In the first case, we are limited to loading from the same file system
>>> that the kernel was loaded from, and it is also problematic in a trusted
>>
>> Hello Ard,
>>
>> "same file system" is not a limitation of using a command line
>> parameter. Any device path can be passed as a string.
>>
>
> What do you mean? The current implementation opens the volume via the
> loaded_image_info struct, and finds the file based on its filename,
> not on a device path. So how can it access files on other file
> systems?
>

I talked aobut the information can be transmitted in a parameter not
about the restrictions the current Linux command line implementation has.

If you would pass a complete device path as parameter, you could use the
UEFI API to find the device with block file system and use the
EFI_SIMPLE_FILE_SYSTEM_PROTOCOL to load the file.

>
>>> boot context, given that we cannot easily protect the command line from
>>> tampering without either adding complicated white/blacklisting of boot
>>> arguments or locking down the command line altogether.
>>
>> Not relying on the command line for finding the initrd image does not
>> secure the integrity and the validity of the initrd image.
>>
>
> It does. It ensures that [signed] bootloader code is in charge of
> providing the initrd at the point during the boot where the kernel is
> ready to consume this data.
>
>> A signature on the initrd image could solve the security problem you
>> describe. It would not require non-Linux software to be changed, and
>> would provide much better security.
>>
>
> A signed initrd would be useful, too, but it doesn't fix the whole problem.
>
> Linux does not support signed initrds today, and this feature permits
> a firmware implementation to do something very similar, i.e., it
> permits the bootloader to perform the verification as it is passed to
> the kernel.

One source of changed initrds is update-initramfs called in an operating
system update process.

How shall a bootloader verify the new initrd if we do not define what a
signed initrd looks like?

Would a bootloader be in a better position to verify an intird then
Linux which already has code for file verification used when loading
modules?

>
>
>>>
>>> In the second case, we force the bootloader to duplicate knowledge about
>>> the boot protocol which is already encoded in the stub, and which may be
>>> subject to change over time, e.g., bootparams struct definitions, memory
>>> allocation/alignment requirements for the placement of the initrd etc etc.
>>> In the ARM case, it also requires the bootloader to modify the hardware
>>> description provided by the firmware, as it is passed in the same file.
>>> On systems where the initrd is measured after loading, it creates a time
>>> window where the initrd contents might be manipulated in memory before
>>> handing over to the kernel.
>>>
>>> Address these concerns by adding support for loading the initrd into
>>> memory by invoking the EFI LoadFile2 protocol installed on a vendor
>>> GUIDed device path that specifically designates a Linux initrd.
>>> This addresses the above concerns, by putting the EFI stub in charge of
>>> placement in memory and of passing the base and size to the kernel proper
>>> (via whatever means it desires) while still leaving it up to the firmware
>>> or bootloader to obtain the file contents, potentially from other file
>>> systems than the one the kernel itself was loaded from. On platforms that
>>> implement measured boot, it permits the firmware to take the measurement
>>> right before the kernel actually consumes the contents.
>>
>> A firmware implementing the UEFI standard will not be aware of any
>> initrd image as such an object does not exist in the standard. It was a
>> wise decision that the UEFI standard is operating system agnostic
>> (accomodating BSD, Linux, Windows, etc.). So the firmware (EDK2, U-Boot,
>> etc.) seems to be out of scope for providing a Linux specific
>> EFI_LOAD_FILE2_PROTOCOL.
>>
>
> You know we are currently patching bootparams struct and DTs to
> provide the initrd information, right? And we have code that knows
> about low/high memory limits, alignment, etc, that is different per
> architecture.
>
>> When booting via GRUB it will be GRUB knowing which initrd to load.
>>
>
> Exactly, which is why GRUB will implement this protocol. That way, it
> does not have to touch the DT at all, or create a bootparams struct
> from setup data and inspect the various flags about placement,
> alignment, preferred addresses, etc.
>
>> Please, indicate which software you expect to expose the initrd related
>> EFI_LOAD_FILE2_PROTOCOL.
>>
>
> The primary use case is GRUB and other intermediate loaders, since it
> would remove any need for these components to know any such details.
> My aim is to make the next architecture that gets added to GRUB for
> EFI boot 100% generic.
>
>> Using an UEFI variable for passing the initrd device path would be a
>> leaner solution on the bootloader side than requiring an extra
>> EFI_LOAD_FILE2_PROTOCOL implementation.
>>
>
> This would also require kernel changes, since we don't currently load
> initrds from arbitrary device paths. The EFI_FILE_PROTOCOL is much
> more complicated than needed, and it doesn't work well with mixed
> mode. It also requires GRUB to expose the filesystem it loads the
> initrd from via EFI protocols, which is currently unnecessary and
> therefore not implemented.

This means you move the complexity of EFI_FILE_PROTOCOL from Linux to GRUB.

I would not have a problem if this would only touch GRUB. But if listen
to Ilias we are replacing one implementation in Linux by one in GRUB and
one in U-Boot and one in EDK2 and one in any other firmware.

>
> Also, using an EFI variable defeats the purpose. The whole point of
> this is making it more likely that the kernel loaded the initrd that
> the bootloader or firmware intended it to load, and having a piece of
> simple [signed] code that implements this is the easiest way to
> achieve that.

At least on my Debian system it is the operating system creating initrd
and defining which initrd matches which kernel. GRUB simply assumes that
files ending on the same version number match. Therefore I would say
Linux hopes that GRUB loads what Linux intended.

The chain of trust would not be broken if the kernel were responsible
for loading the initrd and for checking if it matches the kernel. Linux
already does this for the kernel modules in initrd.

>
> For u-boot, it should be trivial to implement a simple LoadFile2
> protocol wrapper around EFI_FILE_PROTOCOL that can be installed on a
> handle that also carries EFI_FILE_PROTOCOL.
>

A U-Boot implementation of the EFI_LOAD_FILE2_PROTOCOL would need a
device path variable to find the block device and to open the
EFI_SIMPLE_FILE_SYSTEM_PROTOCOL before accessing the file.

Linux would not be needing more lines and we would not repeat the same
code in GRUB, U-Boot, EDK2, etc.

As said Linux updates the initrd often. If that file is not signed by
Linux in a well defined way, do not expect any security at all.

If Linux does not tell which kernel matches which initrd, how should
U-Boot know? - How do you plan to transfer this piece of information?

>>>
>>> Signed-off-by: Ard Biesheuvel <ardb@kernel.org>
>>> ---
>>>    drivers/firmware/efi/libstub/arm-stub.c        | 16 +++--
>>>    drivers/firmware/efi/libstub/efi-stub-helper.c | 65 ++++++++++++++++++++
>>>    drivers/firmware/efi/libstub/efistub.h         | 12 ++++
>>>    drivers/firmware/efi/libstub/x86-stub.c        | 36 ++++++++++-
>>>    include/linux/efi.h                            |  1 +
>>>    5 files changed, 123 insertions(+), 7 deletions(-)
>>>
>>> diff --git a/drivers/firmware/efi/libstub/arm-stub.c b/drivers/firmware/efi/libstub/arm-stub.c
>>> index c7b091f50e55..1db943c1ba2b 100644
>>> --- a/drivers/firmware/efi/libstub/arm-stub.c
>>> +++ b/drivers/firmware/efi/libstub/arm-stub.c
>>> @@ -157,6 +157,7 @@ unsigned long efi_entry(void *handle, efi_system_table_t *sys_table_arg,
>>>        enum efi_secureboot_mode secure_boot;
>>>        struct screen_info *si;
>>>        efi_properties_table_t *prop_tbl;
>>> +     unsigned long max_addr;
>>>
>>>        sys_table = sys_table_arg;
>>>
>>> @@ -255,11 +256,18 @@ unsigned long efi_entry(void *handle, efi_system_table_t *sys_table_arg,
>>>        if (!fdt_addr)
>>>                pr_efi("Generating empty DTB\n");
>>>
>>> -     status = efi_load_initrd(image, ULONG_MAX,
>>> -                              efi_get_max_initrd_addr(dram_base, *image_addr),
>>> -                              &initrd_addr, &initrd_size);
>>> +     max_addr = efi_get_max_initrd_addr(dram_base, *image_addr);
>>> +     status = efi_load_initrd_devpath(&initrd_addr, &initrd_size, max_addr);
>>> +     if (status == EFI_SUCCESS)
>>> +             pr_efi("Loaded initrd from LINUX_EFI_INITRD_MEDIA_GUID device path\n");
>>> +     else if (status == EFI_NOT_FOUND) {
>>> +             status = efi_load_initrd(image, ULONG_MAX, max_addr,
>>> +                                      &initrd_addr, &initrd_size);
>>> +             if (status == EFI_SUCCESS)
>>> +                     pr_efi("Loaded initrd from command line option\n");
>>> +     }

As already mentioned in another mail: If the initrd intended to be
loaded by the the EFI_LOAD_FILE2_PROTOCOL, you fall back to the old
behavior. So the security is not increased by this patch.

>>>        if (status != EFI_SUCCESS)
>>> -             pr_efi_err("Failed initrd from command line!\n");
>>> +             pr_efi_err("Failed to load initrd!\n");
>>>
>>>        efi_random_get_seed();
>>>
>>> diff --git a/drivers/firmware/efi/libstub/efi-stub-helper.c b/drivers/firmware/efi/libstub/efi-stub-helper.c
>>> index 8e60a39df3c5..eaf45ea749b3 100644
>>> --- a/drivers/firmware/efi/libstub/efi-stub-helper.c
>>> +++ b/drivers/firmware/efi/libstub/efi-stub-helper.c
>>> @@ -323,3 +323,68 @@ void efi_char16_printk(efi_char16_t *str)
>>>        efi_call_proto(efi_table_attr(efi_system_table(), con_out),
>>>                       output_string, str);
>>>    }
>>> +
>>> +static const struct {
>>> +     struct efi_vendor_dev_path      vendor;
>>> +     struct efi_generic_dev_path     end;
>>> +} __packed initrd_devpath = {
>>> +     {
>>> +             EFI_DEV_MEDIA,
>>> +             EFI_DEV_MEDIA_VENDOR,
>>> +             sizeof(struct efi_vendor_dev_path),
>>> +             LINUX_EFI_INITRD_MEDIA_GUID
>>> +     }, {
>>> +             EFI_DEV_END_PATH,
>>> +             EFI_DEV_END_ENTIRE,
>>> +             sizeof(struct efi_generic_dev_path)
>>> +     }
>>> +};
>>> +
>>> +efi_status_t efi_load_initrd_devpath(unsigned long *load_addr,
>>> +                                  unsigned long *load_size,
>>> +                                  unsigned long max)
>>> +{
>>> +     efi_guid_t lf2_proto_guid = EFI_LOAD_FILE2_PROTOCOL_GUID;
>>> +     efi_device_path_protocol_t *dp;
>>> +     efi_load_file2_protocol_t *lf2;
>>> +     unsigned long initrd_addr;
>>> +     unsigned long initrd_size;
>>> +     efi_handle_t handle;
>>> +     efi_status_t status;
>>> +
>>> +     if (!load_addr || !load_size)
>>> +             return EFI_INVALID_PARAMETER;
>>> +
>>> +     dp = (efi_device_path_protocol_t *)&initrd_devpath;
>>> +     status = efi_bs_call(locate_device_path, &lf2_proto_guid, &dp, &handle);
>>> +     if (status != EFI_SUCCESS)
>>> +             return status;
>>> +
>>> +     status = efi_bs_call(handle_protocol, handle, &lf2_proto_guid,
>>> +                          (void **)&lf2);
>>> +     if (status != EFI_SUCCESS)
>>> +             return status;
>>
>> You require here that there is a handle exposing the device path
>> protocol with the initrd specific device path. On the same handle the
>> EFI_LOAD_FILE2_PROTOCOL must be installed which will load the initrd
>> file when called with the same device path.
>>
>
> Exactly.
>
>> An alternative implementation would simple loop over all instances of
>> the EFI_LOAD_FILE2_PROTOCOL and try to load the initrd.
>>
>
> How do I distinguish the initrd from other EFI_LOAD_FILE2_PROTOCOL
> instances? For instance, PCI option ROMs are also exposed as
> EFI_LOAD_FILE2_PROTOCOL.

These would respond with EFI_NOT_FOUND. The advantage of looping over
all instances could be that the same handle could be used to provide
multiple files. But how this is implemented is the least of my worries.

Best regards

Heinrich

>
>> It would be worthwhile to describe the requirements on the
>> implementation of the EFI_LOAD_FILE2_PROTOCOL in a code comment and in
>> the documentation.
>>
>> Unfortunately the documentation of UEFI has been duplicated into two files:
>>
>> Documentation/arm/uefi.rst
>> Documentation/x86/x86_64/uefi.rst
>>
>
> Yes, that is a good point. I will work on factoring out the generic
> parts and share them.
>
>
> Thanks for the review,
> Ard.
>
>
>
>>
>>> +
>>> +     initrd_size = 0;
>>> +     status = efi_call_proto(lf2, load_file,
>>> +                             (efi_device_path_protocol_t *)&initrd_devpath,
>>> +                             false, &initrd_size, NULL);
>>> +     if (status != EFI_BUFFER_TOO_SMALL)
>>> +             return EFI_LOAD_ERROR;
>>> +
>>> +     status = efi_allocate_pages(initrd_size, &initrd_addr, max);
>>> +     if (status != EFI_SUCCESS)
>>> +             return status;
>>> +
>>> +     status = efi_call_proto(lf2, load_file,
>>> +                             (efi_device_path_protocol_t *)&initrd_devpath,
>>> +                             false, &initrd_size, (void *)initrd_addr);
>>> +     if (status != EFI_SUCCESS) {
>>> +             efi_free(initrd_size, initrd_addr);
>>> +             return status;
>>> +     }
>>> +
>>> +     *load_addr = initrd_addr;
>>> +     *load_size = initrd_size;
>>> +     return EFI_SUCCESS;
>>> +}
>>> diff --git a/drivers/firmware/efi/libstub/efistub.h b/drivers/firmware/efi/libstub/efistub.h
>>> index 99e93fd76ec5..fbf9f9442eed 100644
>>> --- a/drivers/firmware/efi/libstub/efistub.h
>>> +++ b/drivers/firmware/efi/libstub/efistub.h
>>> @@ -566,6 +566,14 @@ union efi_load_file_protocol {
>>>        } mixed_mode;
>>>    };
>>>
>>> +struct efi_vendor_dev_path {
>>> +     u8              type;
>>> +     u8              sub_type;
>>> +     u16             length;
>>> +     efi_guid_t      vendorguid;
>>> +     u8              vendordata[];
>>> +} __packed;
>>> +
>>>    void efi_pci_disable_bridge_busmaster(void);
>>>
>>>    typedef efi_status_t (*efi_exit_boot_map_processing)(
>>> @@ -651,4 +659,8 @@ efi_status_t efi_load_initrd(efi_loaded_image_t *image,
>>>                             unsigned long *load_addr,
>>>                             unsigned long *load_size);
>>>
>>> +efi_status_t efi_load_initrd_devpath(unsigned long *load_addr,
>>> +                                  unsigned long *load_size,
>>> +                                  unsigned long max);
>>> +
>>>    #endif
>>> diff --git a/drivers/firmware/efi/libstub/x86-stub.c b/drivers/firmware/efi/libstub/x86-stub.c
>>> index f3e2ff31b624..7f38f95676dd 100644
>>> --- a/drivers/firmware/efi/libstub/x86-stub.c
>>> +++ b/drivers/firmware/efi/libstub/x86-stub.c
>>> @@ -419,9 +419,20 @@ efi_status_t __efiapi efi_pe_entry(efi_handle_t handle,
>>>        if (status != EFI_SUCCESS)
>>>                goto fail2;
>>>
>>> -     status = efi_load_initrd(image, hdr->initrd_addr_max,
>>> -                              above4g ? ULONG_MAX : hdr->initrd_addr_max,
>>> -                              &ramdisk_addr, &ramdisk_size);
>>> +     /*
>>> +      * The initrd loaded from the Linux initrd vendor device
>>> +      * path should take precedence, as we don't want the
>>> +      * [unverified] command line to override the initrd
>>> +      * supplied by the [potentially verified] firmware.
>>> +      */
>>> +     status = efi_load_initrd_devpath(&ramdisk_addr, &ramdisk_size,
>>> +                                      above4g ? ULONG_MAX
>>> +                                              : hdr->initrd_addr_max);
>>> +     if (status == EFI_NOT_FOUND)
>>> +             status = efi_load_initrd(image, hdr->initrd_addr_max,
>>> +                                      above4g ? ULONG_MAX
>>> +                                              : hdr->initrd_addr_max,
>>> +                                      &ramdisk_addr, &ramdisk_size);
>>>        if (status != EFI_SUCCESS)
>>>                goto fail2;
>>>        hdr->ramdisk_image = ramdisk_addr & 0xffffffff;
>>> @@ -732,6 +743,25 @@ struct boot_params *efi_main(efi_handle_t handle,
>>>                         ((u64)boot_params->ext_cmd_line_ptr << 32));
>>>        efi_parse_options((char *)cmdline_paddr);
>>>
>>> +     if (!hdr->ramdisk_size && !boot_params->ext_ramdisk_size) {
>>> +             unsigned long max = hdr->initrd_addr_max;
>>> +             unsigned long addr, size;
>>> +
>>> +             if (hdr->xloadflags & XLF_CAN_BE_LOADED_ABOVE_4G)
>>> +                     max = ULONG_MAX;
>>> +
>>> +             status = efi_load_initrd_devpath(&addr, &size, max);
>>> +             if (status == EFI_SUCCESS) {
>>> +                     hdr->ramdisk_image              = (u32)addr;
>>> +                     hdr->ramdisk_size               = (u32)size;
>>> +                     boot_params->ext_ramdisk_image  = (u64)addr >> 32;
>>> +                     boot_params->ext_ramdisk_size   = (u64)size >> 32;
>>> +             } else if (status != EFI_NOT_FOUND) {
>>> +                     efi_printk("efi_load_initrd_devpath() failed!\n");
>>> +                     goto fail;
>>> +             }
>>> +     }
>>> +
>>>        /*
>>>         * If the boot loader gave us a value for secure_boot then we use that,
>>>         * otherwise we ask the BIOS.
>>> diff --git a/include/linux/efi.h b/include/linux/efi.h
>>> index 9ccf313fe9de..75c83c322c40 100644
>>> --- a/include/linux/efi.h
>>> +++ b/include/linux/efi.h
>>> @@ -353,6 +353,7 @@ void efi_native_runtime_setup(void);
>>>    #define LINUX_EFI_TPM_EVENT_LOG_GUID                EFI_GUID(0xb7799cb0, 0xeca2, 0x4943,  0x96, 0x67, 0x1f, 0xae, 0x07, 0xb7, 0x47, 0xfa)
>>>    #define LINUX_EFI_TPM_FINAL_LOG_GUID                EFI_GUID(0x1e2ed096, 0x30e2, 0x4254,  0xbd, 0x89, 0x86, 0x3b, 0xbe, 0xf8, 0x23, 0x25)
>>>    #define LINUX_EFI_MEMRESERVE_TABLE_GUID             EFI_GUID(0x888eb0c6, 0x8ede, 0x4ff5,  0xa8, 0xf0, 0x9a, 0xee, 0x5c, 0xb9, 0x77, 0xc2)
>>> +#define LINUX_EFI_INITRD_MEDIA_GUID          EFI_GUID(0x5568e427, 0x68fc, 0x4f3d,  0xac, 0x74, 0xca, 0x55, 0x52, 0x31, 0xcc, 0x68)
>>>
>>>    /* OEM GUIDs */
>>>    #define DELLEMC_EFI_RCI2_TABLE_GUID         EFI_GUID(0x2d9f28a2, 0xa886, 0x456a,  0x97, 0xa8, 0xf1, 0x1e, 0xf2, 0x4f, 0xf4, 0x55)
>>>
>>


^ permalink raw reply	[flat|nested] 39+ messages in thread

* Re: [PATCH 1/2] efi/libstub: add support for loading the initrd from a device path
  2020-02-07  0:01       ` Heinrich Schuchardt
@ 2020-02-07  0:21         ` Ard Biesheuvel
  2020-02-07  0:57           ` Heinrich Schuchardt
  0 siblings, 1 reply; 39+ messages in thread
From: Ard Biesheuvel @ 2020-02-07  0:21 UTC (permalink / raw)
  To: Heinrich Schuchardt
  Cc: Ard Biesheuvel, linux-efi, linux-arm-kernel, Laszlo Ersek,
	Leif Lindholm, Peter Jones, Matthew Garrett, Alexander Graf,
	Ilias Apalodimas, Daniel Kiper

On Fri, 7 Feb 2020 at 00:01, Heinrich Schuchardt <xypron.glpk@gmx.de> wrote:
>
> On 2/6/20 11:35 PM, Ard Biesheuvel wrote:
> > On Thu, 6 Feb 2020 at 18:26, Heinrich Schuchardt <xypron.glpk@gmx.de> wrote:
> >>
> >> On 2/6/20 3:03 PM, Ard Biesheuvel wrote:
> >>> There are currently two ways to specify the initrd to be passed to the
> >>> Linux kernel when booting via the EFI stub:
> >>> - it can be passed as a initrd= command line option when doing a pure PE
> >>>     boot (as opposed to the EFI handover protocol that exists for x86)
> >>> - otherwise, the bootloader or firmware can load the initrd into memory,
> >>>     and pass the address and size via the bootparams struct (x86) or
> >>>     device tree (ARM)
> >>>
> >>> In the first case, we are limited to loading from the same file system
> >>> that the kernel was loaded from, and it is also problematic in a trusted
> >>
> >> Hello Ard,
> >>
> >> "same file system" is not a limitation of using a command line
> >> parameter. Any device path can be passed as a string.
> >>
> >
> > What do you mean? The current implementation opens the volume via the
> > loaded_image_info struct, and finds the file based on its filename,
> > not on a device path. So how can it access files on other file
> > systems?
> >
>
> I talked aobut the information can be transmitted in a parameter not
> about the restrictions the current Linux command line implementation has.
>
> If you would pass a complete device path as parameter, you could use the
> UEFI API to find the device with block file system and use the
> EFI_SIMPLE_FILE_SYSTEM_PROTOCOL to load the file.
>

Yes, but that still requires the bootloader to expose the file system
via EFI protocols, and it requires some degree of trust in the
contents of the command line. The command line in Linux is a bit hairy
in any case, but allowing it to override the very first thing that
gets executed is something we should try to avoid.


> >
> >>> boot context, given that we cannot easily protect the command line from
> >>> tampering without either adding complicated white/blacklisting of boot
> >>> arguments or locking down the command line altogether.
> >>
> >> Not relying on the command line for finding the initrd image does not
> >> secure the integrity and the validity of the initrd image.
> >>
> >
> > It does. It ensures that [signed] bootloader code is in charge of
> > providing the initrd at the point during the boot where the kernel is
> > ready to consume this data.
> >
> >> A signature on the initrd image could solve the security problem you
> >> describe. It would not require non-Linux software to be changed, and
> >> would provide much better security.
> >>
> >
> > A signed initrd would be useful, too, but it doesn't fix the whole problem.
> >
> > Linux does not support signed initrds today, and this feature permits
> > a firmware implementation to do something very similar, i.e., it
> > permits the bootloader to perform the verification as it is passed to
> > the kernel.
>
> One source of changed initrds is update-initramfs called in an operating
> system update process.
>
> How shall a bootloader verify the new initrd if we do not define what a
> signed initrd looks like?
>

Initrd signing is probably not feasible for this reason. But measuring
it, and using PCR value prediction to reseal keys when they change is
something we should be able to do in this context.

For that reason, it would be preferable for the command line not to
have control over whether or not an initrd gets loaded, and which.

> Would a bootloader be in a better position to verify an intird then
> Linux which already has code for file verification used when loading
> modules?
>

The bootloader already has code for file verification when loading the
kernel, so I fail to see your point here.

> >
> >
> >>>
> >>> In the second case, we force the bootloader to duplicate knowledge about
> >>> the boot protocol which is already encoded in the stub, and which may be
> >>> subject to change over time, e.g., bootparams struct definitions, memory
> >>> allocation/alignment requirements for the placement of the initrd etc etc.
> >>> In the ARM case, it also requires the bootloader to modify the hardware
> >>> description provided by the firmware, as it is passed in the same file.
> >>> On systems where the initrd is measured after loading, it creates a time
> >>> window where the initrd contents might be manipulated in memory before
> >>> handing over to the kernel.
> >>>
> >>> Address these concerns by adding support for loading the initrd into
> >>> memory by invoking the EFI LoadFile2 protocol installed on a vendor
> >>> GUIDed device path that specifically designates a Linux initrd.
> >>> This addresses the above concerns, by putting the EFI stub in charge of
> >>> placement in memory and of passing the base and size to the kernel proper
> >>> (via whatever means it desires) while still leaving it up to the firmware
> >>> or bootloader to obtain the file contents, potentially from other file
> >>> systems than the one the kernel itself was loaded from. On platforms that
> >>> implement measured boot, it permits the firmware to take the measurement
> >>> right before the kernel actually consumes the contents.
> >>
> >> A firmware implementing the UEFI standard will not be aware of any
> >> initrd image as such an object does not exist in the standard. It was a
> >> wise decision that the UEFI standard is operating system agnostic
> >> (accomodating BSD, Linux, Windows, etc.). So the firmware (EDK2, U-Boot,
> >> etc.) seems to be out of scope for providing a Linux specific
> >> EFI_LOAD_FILE2_PROTOCOL.
> >>
> >
> > You know we are currently patching bootparams struct and DTs to
> > provide the initrd information, right? And we have code that knows
> > about low/high memory limits, alignment, etc, that is different per
> > architecture.
> >
> >> When booting via GRUB it will be GRUB knowing which initrd to load.
> >>
> >
> > Exactly, which is why GRUB will implement this protocol. That way, it
> > does not have to touch the DT at all, or create a bootparams struct
> > from setup data and inspect the various flags about placement,
> > alignment, preferred addresses, etc.
> >
> >> Please, indicate which software you expect to expose the initrd related
> >> EFI_LOAD_FILE2_PROTOCOL.
> >>
> >
> > The primary use case is GRUB and other intermediate loaders, since it
> > would remove any need for these components to know any such details.
> > My aim is to make the next architecture that gets added to GRUB for
> > EFI boot 100% generic.
> >
> >> Using an UEFI variable for passing the initrd device path would be a
> >> leaner solution on the bootloader side than requiring an extra
> >> EFI_LOAD_FILE2_PROTOCOL implementation.
> >>
> >
> > This would also require kernel changes, since we don't currently load
> > initrds from arbitrary device paths. The EFI_FILE_PROTOCOL is much
> > more complicated than needed, and it doesn't work well with mixed
> > mode. It also requires GRUB to expose the filesystem it loads the
> > initrd from via EFI protocols, which is currently unnecessary and
> > therefore not implemented.
>
> This means you move the complexity of EFI_FILE_PROTOCOL from Linux to GRUB.
>

No. I am not interested in EFI_FILE_PROTOCOL, only in LoadFile2, which
is a single method that needs to be implemented.

> I would not have a problem if this would only touch GRUB. But if listen
> to Ilias we are replacing one implementation in Linux by one in GRUB and
> one in U-Boot and one in EDK2 and one in any other firmware.
>

If u-boot will be used to boot RISC-V in EFI mode without GRUB, then I
expect that we will need an implementation of this in u-boot.

> >
> > Also, using an EFI variable defeats the purpose. The whole point of
> > this is making it more likely that the kernel loaded the initrd that
> > the bootloader or firmware intended it to load, and having a piece of
> > simple [signed] code that implements this is the easiest way to
> > achieve that.
>
> At least on my Debian system it is the operating system creating initrd
> and defining which initrd matches which kernel. GRUB simply assumes that
> files ending on the same version number match. Therefore I would say
> Linux hopes that GRUB loads what Linux intended.
>
> The chain of trust would not be broken if the kernel were responsible
> for loading the initrd and for checking if it matches the kernel. Linux
> already does this for the kernel modules in initrd.
>

We can still sign the initrd and Linux can verify the signature. What
I am after is an interface that does not require the initrd to
originate from a EFI file system protocol, and which doesn't require
the loaded initrd to sit in memory for an unspecified amount of time
and its information passed via DT properties or bootparams structs.

So invoking EFI_FILE_PROTOCOL directly is not going to work,
regardless of whether we get the devicepath from the command line or
from a EFI variable.

> >
> > For u-boot, it should be trivial to implement a simple LoadFile2
> > protocol wrapper around EFI_FILE_PROTOCOL that can be installed on a
> > handle that also carries EFI_FILE_PROTOCOL.
> >
>
> A U-Boot implementation of the EFI_LOAD_FILE2_PROTOCOL would need a
> device path variable to find the block device and to open the
> EFI_SIMPLE_FILE_SYSTEM_PROTOCOL before accessing the file.
>
> Linux would not be needing more lines and we would not repeat the same
> code in GRUB, U-Boot, EDK2, etc.
>
> As said Linux updates the initrd often. If that file is not signed by
> Linux in a well defined way, do not expect any security at all.
>

It is not only about security. The primary goal is to remove the need
for arch specific knowledge in the firmware about DT, bootparams and
initrd allocation policies without being forced to load the initrd
from a filesystem that is exposed via a EFI protocol.

> If Linux does not tell which kernel matches which initrd, how should
> U-Boot know? - How do you plan to transfer this piece of information?
>

How does u-boot know today? If you can tell it which initrd to load
into memory, why is it difficult to put that same data into a buffer
in response to a protocol invocation?


> >>>
> >>> Signed-off-by: Ard Biesheuvel <ardb@kernel.org>
> >>> ---
> >>>    drivers/firmware/efi/libstub/arm-stub.c        | 16 +++--
> >>>    drivers/firmware/efi/libstub/efi-stub-helper.c | 65 ++++++++++++++++++++
> >>>    drivers/firmware/efi/libstub/efistub.h         | 12 ++++
> >>>    drivers/firmware/efi/libstub/x86-stub.c        | 36 ++++++++++-
> >>>    include/linux/efi.h                            |  1 +
> >>>    5 files changed, 123 insertions(+), 7 deletions(-)
> >>>
> >>> diff --git a/drivers/firmware/efi/libstub/arm-stub.c b/drivers/firmware/efi/libstub/arm-stub.c
> >>> index c7b091f50e55..1db943c1ba2b 100644
> >>> --- a/drivers/firmware/efi/libstub/arm-stub.c
> >>> +++ b/drivers/firmware/efi/libstub/arm-stub.c
> >>> @@ -157,6 +157,7 @@ unsigned long efi_entry(void *handle, efi_system_table_t *sys_table_arg,
> >>>        enum efi_secureboot_mode secure_boot;
> >>>        struct screen_info *si;
> >>>        efi_properties_table_t *prop_tbl;
> >>> +     unsigned long max_addr;
> >>>
> >>>        sys_table = sys_table_arg;
> >>>
> >>> @@ -255,11 +256,18 @@ unsigned long efi_entry(void *handle, efi_system_table_t *sys_table_arg,
> >>>        if (!fdt_addr)
> >>>                pr_efi("Generating empty DTB\n");
> >>>
> >>> -     status = efi_load_initrd(image, ULONG_MAX,
> >>> -                              efi_get_max_initrd_addr(dram_base, *image_addr),
> >>> -                              &initrd_addr, &initrd_size);
> >>> +     max_addr = efi_get_max_initrd_addr(dram_base, *image_addr);
> >>> +     status = efi_load_initrd_devpath(&initrd_addr, &initrd_size, max_addr);
> >>> +     if (status == EFI_SUCCESS)
> >>> +             pr_efi("Loaded initrd from LINUX_EFI_INITRD_MEDIA_GUID device path\n");
> >>> +     else if (status == EFI_NOT_FOUND) {
> >>> +             status = efi_load_initrd(image, ULONG_MAX, max_addr,
> >>> +                                      &initrd_addr, &initrd_size);
> >>> +             if (status == EFI_SUCCESS)
> >>> +                     pr_efi("Loaded initrd from command line option\n");
> >>> +     }
>
> As already mentioned in another mail: If the initrd intended to be
> loaded by the the EFI_LOAD_FILE2_PROTOCOL, you fall back to the old
> behavior. So the security is not increased by this patch.
>

If the bootloader is signed, it is justified to place a higher degree
of trust in the code we run than in the [unsigned] data we pass as the
command line.

> >>>        if (status != EFI_SUCCESS)
> >>> -             pr_efi_err("Failed initrd from command line!\n");
> >>> +             pr_efi_err("Failed to load initrd!\n");
> >>>
> >>>        efi_random_get_seed();
> >>>
> >>> diff --git a/drivers/firmware/efi/libstub/efi-stub-helper.c b/drivers/firmware/efi/libstub/efi-stub-helper.c
> >>> index 8e60a39df3c5..eaf45ea749b3 100644
> >>> --- a/drivers/firmware/efi/libstub/efi-stub-helper.c
> >>> +++ b/drivers/firmware/efi/libstub/efi-stub-helper.c
> >>> @@ -323,3 +323,68 @@ void efi_char16_printk(efi_char16_t *str)
> >>>        efi_call_proto(efi_table_attr(efi_system_table(), con_out),
> >>>                       output_string, str);
> >>>    }
> >>> +
> >>> +static const struct {
> >>> +     struct efi_vendor_dev_path      vendor;
> >>> +     struct efi_generic_dev_path     end;
> >>> +} __packed initrd_devpath = {
> >>> +     {
> >>> +             EFI_DEV_MEDIA,
> >>> +             EFI_DEV_MEDIA_VENDOR,
> >>> +             sizeof(struct efi_vendor_dev_path),
> >>> +             LINUX_EFI_INITRD_MEDIA_GUID
> >>> +     }, {
> >>> +             EFI_DEV_END_PATH,
> >>> +             EFI_DEV_END_ENTIRE,
> >>> +             sizeof(struct efi_generic_dev_path)
> >>> +     }
> >>> +};
> >>> +
> >>> +efi_status_t efi_load_initrd_devpath(unsigned long *load_addr,
> >>> +                                  unsigned long *load_size,
> >>> +                                  unsigned long max)
> >>> +{
> >>> +     efi_guid_t lf2_proto_guid = EFI_LOAD_FILE2_PROTOCOL_GUID;
> >>> +     efi_device_path_protocol_t *dp;
> >>> +     efi_load_file2_protocol_t *lf2;
> >>> +     unsigned long initrd_addr;
> >>> +     unsigned long initrd_size;
> >>> +     efi_handle_t handle;
> >>> +     efi_status_t status;
> >>> +
> >>> +     if (!load_addr || !load_size)
> >>> +             return EFI_INVALID_PARAMETER;
> >>> +
> >>> +     dp = (efi_device_path_protocol_t *)&initrd_devpath;
> >>> +     status = efi_bs_call(locate_device_path, &lf2_proto_guid, &dp, &handle);
> >>> +     if (status != EFI_SUCCESS)
> >>> +             return status;
> >>> +
> >>> +     status = efi_bs_call(handle_protocol, handle, &lf2_proto_guid,
> >>> +                          (void **)&lf2);
> >>> +     if (status != EFI_SUCCESS)
> >>> +             return status;
> >>
> >> You require here that there is a handle exposing the device path
> >> protocol with the initrd specific device path. On the same handle the
> >> EFI_LOAD_FILE2_PROTOCOL must be installed which will load the initrd
> >> file when called with the same device path.
> >>
> >
> > Exactly.
> >
> >> An alternative implementation would simple loop over all instances of
> >> the EFI_LOAD_FILE2_PROTOCOL and try to load the initrd.
> >>
> >
> > How do I distinguish the initrd from other EFI_LOAD_FILE2_PROTOCOL
> > instances? For instance, PCI option ROMs are also exposed as
> > EFI_LOAD_FILE2_PROTOCOL.
>
> These would respond with EFI_NOT_FOUND. The advantage of looping over
> all instances could be that the same handle could be used to provide
> multiple files. But how this is implemented is the least of my worries.
>
> Best regards
>
> Heinrich
>
> >
> >> It would be worthwhile to describe the requirements on the
> >> implementation of the EFI_LOAD_FILE2_PROTOCOL in a code comment and in
> >> the documentation.
> >>
> >> Unfortunately the documentation of UEFI has been duplicated into two files:
> >>
> >> Documentation/arm/uefi.rst
> >> Documentation/x86/x86_64/uefi.rst
> >>
> >
> > Yes, that is a good point. I will work on factoring out the generic
> > parts and share them.
> >
> >
> > Thanks for the review,
> > Ard.
> >
> >
> >
> >>
> >>> +
> >>> +     initrd_size = 0;
> >>> +     status = efi_call_proto(lf2, load_file,
> >>> +                             (efi_device_path_protocol_t *)&initrd_devpath,
> >>> +                             false, &initrd_size, NULL);
> >>> +     if (status != EFI_BUFFER_TOO_SMALL)
> >>> +             return EFI_LOAD_ERROR;
> >>> +
> >>> +     status = efi_allocate_pages(initrd_size, &initrd_addr, max);
> >>> +     if (status != EFI_SUCCESS)
> >>> +             return status;
> >>> +
> >>> +     status = efi_call_proto(lf2, load_file,
> >>> +                             (efi_device_path_protocol_t *)&initrd_devpath,
> >>> +                             false, &initrd_size, (void *)initrd_addr);
> >>> +     if (status != EFI_SUCCESS) {
> >>> +             efi_free(initrd_size, initrd_addr);
> >>> +             return status;
> >>> +     }
> >>> +
> >>> +     *load_addr = initrd_addr;
> >>> +     *load_size = initrd_size;
> >>> +     return EFI_SUCCESS;
> >>> +}
> >>> diff --git a/drivers/firmware/efi/libstub/efistub.h b/drivers/firmware/efi/libstub/efistub.h
> >>> index 99e93fd76ec5..fbf9f9442eed 100644
> >>> --- a/drivers/firmware/efi/libstub/efistub.h
> >>> +++ b/drivers/firmware/efi/libstub/efistub.h
> >>> @@ -566,6 +566,14 @@ union efi_load_file_protocol {
> >>>        } mixed_mode;
> >>>    };
> >>>
> >>> +struct efi_vendor_dev_path {
> >>> +     u8              type;
> >>> +     u8              sub_type;
> >>> +     u16             length;
> >>> +     efi_guid_t      vendorguid;
> >>> +     u8              vendordata[];
> >>> +} __packed;
> >>> +
> >>>    void efi_pci_disable_bridge_busmaster(void);
> >>>
> >>>    typedef efi_status_t (*efi_exit_boot_map_processing)(
> >>> @@ -651,4 +659,8 @@ efi_status_t efi_load_initrd(efi_loaded_image_t *image,
> >>>                             unsigned long *load_addr,
> >>>                             unsigned long *load_size);
> >>>
> >>> +efi_status_t efi_load_initrd_devpath(unsigned long *load_addr,
> >>> +                                  unsigned long *load_size,
> >>> +                                  unsigned long max);
> >>> +
> >>>    #endif
> >>> diff --git a/drivers/firmware/efi/libstub/x86-stub.c b/drivers/firmware/efi/libstub/x86-stub.c
> >>> index f3e2ff31b624..7f38f95676dd 100644
> >>> --- a/drivers/firmware/efi/libstub/x86-stub.c
> >>> +++ b/drivers/firmware/efi/libstub/x86-stub.c
> >>> @@ -419,9 +419,20 @@ efi_status_t __efiapi efi_pe_entry(efi_handle_t handle,
> >>>        if (status != EFI_SUCCESS)
> >>>                goto fail2;
> >>>
> >>> -     status = efi_load_initrd(image, hdr->initrd_addr_max,
> >>> -                              above4g ? ULONG_MAX : hdr->initrd_addr_max,
> >>> -                              &ramdisk_addr, &ramdisk_size);
> >>> +     /*
> >>> +      * The initrd loaded from the Linux initrd vendor device
> >>> +      * path should take precedence, as we don't want the
> >>> +      * [unverified] command line to override the initrd
> >>> +      * supplied by the [potentially verified] firmware.
> >>> +      */
> >>> +     status = efi_load_initrd_devpath(&ramdisk_addr, &ramdisk_size,
> >>> +                                      above4g ? ULONG_MAX
> >>> +                                              : hdr->initrd_addr_max);
> >>> +     if (status == EFI_NOT_FOUND)
> >>> +             status = efi_load_initrd(image, hdr->initrd_addr_max,
> >>> +                                      above4g ? ULONG_MAX
> >>> +                                              : hdr->initrd_addr_max,
> >>> +                                      &ramdisk_addr, &ramdisk_size);
> >>>        if (status != EFI_SUCCESS)
> >>>                goto fail2;
> >>>        hdr->ramdisk_image = ramdisk_addr & 0xffffffff;
> >>> @@ -732,6 +743,25 @@ struct boot_params *efi_main(efi_handle_t handle,
> >>>                         ((u64)boot_params->ext_cmd_line_ptr << 32));
> >>>        efi_parse_options((char *)cmdline_paddr);
> >>>
> >>> +     if (!hdr->ramdisk_size && !boot_params->ext_ramdisk_size) {
> >>> +             unsigned long max = hdr->initrd_addr_max;
> >>> +             unsigned long addr, size;
> >>> +
> >>> +             if (hdr->xloadflags & XLF_CAN_BE_LOADED_ABOVE_4G)
> >>> +                     max = ULONG_MAX;
> >>> +
> >>> +             status = efi_load_initrd_devpath(&addr, &size, max);
> >>> +             if (status == EFI_SUCCESS) {
> >>> +                     hdr->ramdisk_image              = (u32)addr;
> >>> +                     hdr->ramdisk_size               = (u32)size;
> >>> +                     boot_params->ext_ramdisk_image  = (u64)addr >> 32;
> >>> +                     boot_params->ext_ramdisk_size   = (u64)size >> 32;
> >>> +             } else if (status != EFI_NOT_FOUND) {
> >>> +                     efi_printk("efi_load_initrd_devpath() failed!\n");
> >>> +                     goto fail;
> >>> +             }
> >>> +     }
> >>> +
> >>>        /*
> >>>         * If the boot loader gave us a value for secure_boot then we use that,
> >>>         * otherwise we ask the BIOS.
> >>> diff --git a/include/linux/efi.h b/include/linux/efi.h
> >>> index 9ccf313fe9de..75c83c322c40 100644
> >>> --- a/include/linux/efi.h
> >>> +++ b/include/linux/efi.h
> >>> @@ -353,6 +353,7 @@ void efi_native_runtime_setup(void);
> >>>    #define LINUX_EFI_TPM_EVENT_LOG_GUID                EFI_GUID(0xb7799cb0, 0xeca2, 0x4943,  0x96, 0x67, 0x1f, 0xae, 0x07, 0xb7, 0x47, 0xfa)
> >>>    #define LINUX_EFI_TPM_FINAL_LOG_GUID                EFI_GUID(0x1e2ed096, 0x30e2, 0x4254,  0xbd, 0x89, 0x86, 0x3b, 0xbe, 0xf8, 0x23, 0x25)
> >>>    #define LINUX_EFI_MEMRESERVE_TABLE_GUID             EFI_GUID(0x888eb0c6, 0x8ede, 0x4ff5,  0xa8, 0xf0, 0x9a, 0xee, 0x5c, 0xb9, 0x77, 0xc2)
> >>> +#define LINUX_EFI_INITRD_MEDIA_GUID          EFI_GUID(0x5568e427, 0x68fc, 0x4f3d,  0xac, 0x74, 0xca, 0x55, 0x52, 0x31, 0xcc, 0x68)
> >>>
> >>>    /* OEM GUIDs */
> >>>    #define DELLEMC_EFI_RCI2_TABLE_GUID         EFI_GUID(0x2d9f28a2, 0xa886, 0x456a,  0x97, 0xa8, 0xf1, 0x1e, 0xf2, 0x4f, 0xf4, 0x55)
> >>>
> >>
>

^ permalink raw reply	[flat|nested] 39+ messages in thread

* Re: [PATCH 1/2] efi/libstub: add support for loading the initrd from a device path
  2020-02-07  0:21         ` Ard Biesheuvel
@ 2020-02-07  0:57           ` Heinrich Schuchardt
  2020-02-07  8:12             ` Ard Biesheuvel
  0 siblings, 1 reply; 39+ messages in thread
From: Heinrich Schuchardt @ 2020-02-07  0:57 UTC (permalink / raw)
  To: Ard Biesheuvel
  Cc: Ard Biesheuvel, linux-efi, linux-arm-kernel, Laszlo Ersek,
	Leif Lindholm, Peter Jones, Matthew Garrett, Alexander Graf,
	Ilias Apalodimas, Daniel Kiper

On 2/7/20 1:21 AM, Ard Biesheuvel wrote:
> On Fri, 7 Feb 2020 at 00:01, Heinrich Schuchardt <xypron.glpk@gmx.de> wrote:
>>
>> On 2/6/20 11:35 PM, Ard Biesheuvel wrote:
>>> On Thu, 6 Feb 2020 at 18:26, Heinrich Schuchardt <xypron.glpk@gmx.de> wrote:
>>>>
>>>> On 2/6/20 3:03 PM, Ard Biesheuvel wrote:
>>>>> There are currently two ways to specify the initrd to be passed to the
>>>>> Linux kernel when booting via the EFI stub:
>>>>> - it can be passed as a initrd= command line option when doing a pure PE
>>>>>      boot (as opposed to the EFI handover protocol that exists for x86)
>>>>> - otherwise, the bootloader or firmware can load the initrd into memory,
>>>>>      and pass the address and size via the bootparams struct (x86) or
>>>>>      device tree (ARM)
>>>>>
>>>>> In the first case, we are limited to loading from the same file system
>>>>> that the kernel was loaded from, and it is also problematic in a trusted
>>>>
>>>> Hello Ard,
>>>>
>>>> "same file system" is not a limitation of using a command line
>>>> parameter. Any device path can be passed as a string.
>>>>
>>>
>>> What do you mean? The current implementation opens the volume via the
>>> loaded_image_info struct, and finds the file based on its filename,
>>> not on a device path. So how can it access files on other file
>>> systems?
>>>
>>
>> I talked aobut the information can be transmitted in a parameter not
>> about the restrictions the current Linux command line implementation has.
>>
>> If you would pass a complete device path as parameter, you could use the
>> UEFI API to find the device with block file system and use the
>> EFI_SIMPLE_FILE_SYSTEM_PROTOCOL to load the file.
>>
>
> Yes, but that still requires the bootloader to expose the file system
> via EFI protocols, and it requires some degree of trust in the
> contents of the command line. The command line in Linux is a bit hairy
> in any case, but allowing it to override the very first thing that
> gets executed is something we should try to avoid.
>
>
>>>
>>>>> boot context, given that we cannot easily protect the command line from
>>>>> tampering without either adding complicated white/blacklisting of boot
>>>>> arguments or locking down the command line altogether.
>>>>
>>>> Not relying on the command line for finding the initrd image does not
>>>> secure the integrity and the validity of the initrd image.
>>>>
>>>
>>> It does. It ensures that [signed] bootloader code is in charge of
>>> providing the initrd at the point during the boot where the kernel is
>>> ready to consume this data.
>>>
>>>> A signature on the initrd image could solve the security problem you
>>>> describe. It would not require non-Linux software to be changed, and
>>>> would provide much better security.
>>>>
>>>
>>> A signed initrd would be useful, too, but it doesn't fix the whole problem.
>>>
>>> Linux does not support signed initrds today, and this feature permits
>>> a firmware implementation to do something very similar, i.e., it
>>> permits the bootloader to perform the verification as it is passed to
>>> the kernel.
>>
>> One source of changed initrds is update-initramfs called in an operating
>> system update process.
>>
>> How shall a bootloader verify the new initrd if we do not define what a
>> signed initrd looks like?
>>
>
> Initrd signing is probably not feasible for this reason. But measuring
> it, and using PCR value prediction to reseal keys when they change is
> something we should be able to do in this context.
>
> For that reason, it would be preferable for the command line not to
> have control over whether or not an initrd gets loaded, and which.
>
>> Would a bootloader be in a better position to verify an intird then
>> Linux which already has code for file verification used when loading
>> modules?
>>
>
> The bootloader already has code for file verification when loading the
> kernel, so I fail to see your point here.
>
>>>
>>>
>>>>>
>>>>> In the second case, we force the bootloader to duplicate knowledge about
>>>>> the boot protocol which is already encoded in the stub, and which may be
>>>>> subject to change over time, e.g., bootparams struct definitions, memory
>>>>> allocation/alignment requirements for the placement of the initrd etc etc.
>>>>> In the ARM case, it also requires the bootloader to modify the hardware
>>>>> description provided by the firmware, as it is passed in the same file.
>>>>> On systems where the initrd is measured after loading, it creates a time
>>>>> window where the initrd contents might be manipulated in memory before
>>>>> handing over to the kernel.
>>>>>
>>>>> Address these concerns by adding support for loading the initrd into
>>>>> memory by invoking the EFI LoadFile2 protocol installed on a vendor
>>>>> GUIDed device path that specifically designates a Linux initrd.
>>>>> This addresses the above concerns, by putting the EFI stub in charge of
>>>>> placement in memory and of passing the base and size to the kernel proper
>>>>> (via whatever means it desires) while still leaving it up to the firmware
>>>>> or bootloader to obtain the file contents, potentially from other file
>>>>> systems than the one the kernel itself was loaded from. On platforms that
>>>>> implement measured boot, it permits the firmware to take the measurement
>>>>> right before the kernel actually consumes the contents.
>>>>
>>>> A firmware implementing the UEFI standard will not be aware of any
>>>> initrd image as such an object does not exist in the standard. It was a
>>>> wise decision that the UEFI standard is operating system agnostic
>>>> (accomodating BSD, Linux, Windows, etc.). So the firmware (EDK2, U-Boot,
>>>> etc.) seems to be out of scope for providing a Linux specific
>>>> EFI_LOAD_FILE2_PROTOCOL.
>>>>
>>>
>>> You know we are currently patching bootparams struct and DTs to
>>> provide the initrd information, right? And we have code that knows
>>> about low/high memory limits, alignment, etc, that is different per
>>> architecture.
>>>
>>>> When booting via GRUB it will be GRUB knowing which initrd to load.
>>>>
>>>
>>> Exactly, which is why GRUB will implement this protocol. That way, it
>>> does not have to touch the DT at all, or create a bootparams struct
>>> from setup data and inspect the various flags about placement,
>>> alignment, preferred addresses, etc.
>>>
>>>> Please, indicate which software you expect to expose the initrd related
>>>> EFI_LOAD_FILE2_PROTOCOL.
>>>>
>>>
>>> The primary use case is GRUB and other intermediate loaders, since it
>>> would remove any need for these components to know any such details.
>>> My aim is to make the next architecture that gets added to GRUB for
>>> EFI boot 100% generic.
>>>
>>>> Using an UEFI variable for passing the initrd device path would be a
>>>> leaner solution on the bootloader side than requiring an extra
>>>> EFI_LOAD_FILE2_PROTOCOL implementation.
>>>>
>>>
>>> This would also require kernel changes, since we don't currently load
>>> initrds from arbitrary device paths. The EFI_FILE_PROTOCOL is much
>>> more complicated than needed, and it doesn't work well with mixed
>>> mode. It also requires GRUB to expose the filesystem it loads the
>>> initrd from via EFI protocols, which is currently unnecessary and
>>> therefore not implemented.
>>
>> This means you move the complexity of EFI_FILE_PROTOCOL from Linux to GRUB.
>>
>
> No. I am not interested in EFI_FILE_PROTOCOL, only in LoadFile2, which
> is a single method that needs to be implemented.

I said you move complexity because GRUB will need to use the
EFI_FILE_PROTOCOL to do the job that you do not want to do in Linux.

>
>> I would not have a problem if this would only touch GRUB. But if listen
>> to Ilias we are replacing one implementation in Linux by one in GRUB and
>> one in U-Boot and one in EDK2 and one in any other firmware.
>>
>
> If u-boot will be used to boot RISC-V in EFI mode without GRUB, then I
> expect that we will need an implementation of this in u-boot.

What sets RISC-V apart? GRUB for RISC-V is available.

>
>>>
>>> Also, using an EFI variable defeats the purpose. The whole point of
>>> this is making it more likely that the kernel loaded the initrd that
>>> the bootloader or firmware intended it to load, and having a piece of
>>> simple [signed] code that implements this is the easiest way to
>>> achieve that.
>>
>> At least on my Debian system it is the operating system creating initrd
>> and defining which initrd matches which kernel. GRUB simply assumes that
>> files ending on the same version number match. Therefore I would say
>> Linux hopes that GRUB loads what Linux intended.
>>
>> The chain of trust would not be broken if the kernel were responsible
>> for loading the initrd and for checking if it matches the kernel. Linux
>> already does this for the kernel modules in initrd.
>>
>
> We can still sign the initrd and Linux can verify the signature. What
> I am after is an interface that does not require the initrd to
> originate from a EFI file system protocol, and which doesn't require
> the loaded initrd to sit in memory for an unspecified amount of time
> and its information passed via DT properties or bootparams structs.
>
> So invoking EFI_FILE_PROTOCOL directly is not going to work,
> regardless of whether we get the devicepath from the command line or
> from a EFI variable.

What do you mean by "is not going to work"?

With the device path you can find the handle implementing the
EFI_SIMPLE_FIL_SYSTEM_PROTOCOL.

>
>>>
>>> For u-boot, it should be trivial to implement a simple LoadFile2
>>> protocol wrapper around EFI_FILE_PROTOCOL that can be installed on a
>>> handle that also carries EFI_FILE_PROTOCOL.
>>>
>>
>> A U-Boot implementation of the EFI_LOAD_FILE2_PROTOCOL would need a
>> device path variable to find the block device and to open the
>> EFI_SIMPLE_FILE_SYSTEM_PROTOCOL before accessing the file.
>>
>> Linux would not be needing more lines and we would not repeat the same
>> code in GRUB, U-Boot, EDK2, etc.
>>
>> As said Linux updates the initrd often. If that file is not signed by
>> Linux in a well defined way, do not expect any security at all.
>>
>
> It is not only about security. The primary goal is to remove the need
> for arch specific knowledge in the firmware about DT, bootparams and
> initrd allocation policies without being forced to load the initrd
> from a filesystem that is exposed via a EFI protocol.

Where are device-trees touched by this patch?

When booting via UEFI there is no need for knowledge of initrd
allocation policies in U-Boot because up to now Linux or GRUB or iPXE
load initrd.

Furthermore I need no knowledge of bootparams in U-Boot once we properly
we support UEFI variables at runtime because grub-update will pass the
command line in one of the Bootxxxx UEFI variables.

But most importantly I do not have to implement anything Linux specific
in U-Boot for booting via UEFI up to now.

>
>> If Linux does not tell which kernel matches which initrd, how should
>> U-Boot know? - How do you plan to transfer this piece of information?
>>
>
> How does u-boot know today? If you can tell it which initrd to load
> into memory, why is it difficult to put that same data into a buffer
> in response to a protocol invocation?
>

When booting via UEFI U-Boot never touches initrd.

When booting via GRUB, GRUB will know what to load and take care.

When calling Linux directly you have to provide a script boot.scr to set
the bootargs environment variable that will be passed to Linux via the
loaded file image protocol. And you will need a script in an
update-initramfs hook to update boot.scr everytime that you create a new
initrd.

Or you define a UEFI variable Bootxxxx and the bootmanager in U-Boot
will use that value. Unfortunately runtime variable support is still
missing so you currently cannot use efibootmgr to update Bootxxxx in U-Boot.

Essentially for UEFI booting Linux is always the source of the kernel
command line if you suppress editing in U-Boot and GRUB.

>
>>>>>
>>>>> Signed-off-by: Ard Biesheuvel <ardb@kernel.org>
>>>>> ---
>>>>>     drivers/firmware/efi/libstub/arm-stub.c        | 16 +++--
>>>>>     drivers/firmware/efi/libstub/efi-stub-helper.c | 65 ++++++++++++++++++++
>>>>>     drivers/firmware/efi/libstub/efistub.h         | 12 ++++
>>>>>     drivers/firmware/efi/libstub/x86-stub.c        | 36 ++++++++++-
>>>>>     include/linux/efi.h                            |  1 +
>>>>>     5 files changed, 123 insertions(+), 7 deletions(-)
>>>>>
>>>>> diff --git a/drivers/firmware/efi/libstub/arm-stub.c b/drivers/firmware/efi/libstub/arm-stub.c
>>>>> index c7b091f50e55..1db943c1ba2b 100644
>>>>> --- a/drivers/firmware/efi/libstub/arm-stub.c
>>>>> +++ b/drivers/firmware/efi/libstub/arm-stub.c
>>>>> @@ -157,6 +157,7 @@ unsigned long efi_entry(void *handle, efi_system_table_t *sys_table_arg,
>>>>>         enum efi_secureboot_mode secure_boot;
>>>>>         struct screen_info *si;
>>>>>         efi_properties_table_t *prop_tbl;
>>>>> +     unsigned long max_addr;
>>>>>
>>>>>         sys_table = sys_table_arg;
>>>>>
>>>>> @@ -255,11 +256,18 @@ unsigned long efi_entry(void *handle, efi_system_table_t *sys_table_arg,
>>>>>         if (!fdt_addr)
>>>>>                 pr_efi("Generating empty DTB\n");
>>>>>
>>>>> -     status = efi_load_initrd(image, ULONG_MAX,
>>>>> -                              efi_get_max_initrd_addr(dram_base, *image_addr),
>>>>> -                              &initrd_addr, &initrd_size);
>>>>> +     max_addr = efi_get_max_initrd_addr(dram_base, *image_addr);
>>>>> +     status = efi_load_initrd_devpath(&initrd_addr, &initrd_size, max_addr);
>>>>> +     if (status == EFI_SUCCESS)
>>>>> +             pr_efi("Loaded initrd from LINUX_EFI_INITRD_MEDIA_GUID device path\n");
>>>>> +     else if (status == EFI_NOT_FOUND) {
>>>>> +             status = efi_load_initrd(image, ULONG_MAX, max_addr,
>>>>> +                                      &initrd_addr, &initrd_size);
>>>>> +             if (status == EFI_SUCCESS)
>>>>> +                     pr_efi("Loaded initrd from command line option\n");
>>>>> +     }
>>
>> As already mentioned in another mail: If the initrd intended to be
>> loaded by the the EFI_LOAD_FILE2_PROTOCOL, you fall back to the old
>> behavior. So the security is not increased by this patch.
>>
>
> If the bootloader is signed, it is justified to place a higher degree
> of trust in the code we run than in the [unsigned] data we pass as the
> command line.

Your patch claims to fend off a specific threat scenario: A user puts an
untrusted initrd on the disk and references it in the Linux command line.

If he is able to do so with your current bootloader (signed or not
signed), he most probably will also be able to delete a good initrd from
the filesystem and thus force your code into the unsafe path.

That is why I say that with the current fallback logic this patch
achieves no increase in security. Of cause you could remove the fallback
logic. But in this case your Linux will not boot with any legacy
bootloader or firmware.

Best regards

Heinrich

>
>>>>>         if (status != EFI_SUCCESS)
>>>>> -             pr_efi_err("Failed initrd from command line!\n");
>>>>> +             pr_efi_err("Failed to load initrd!\n");
>>>>>
>>>>>         efi_random_get_seed();
>>>>>
>>>>> diff --git a/drivers/firmware/efi/libstub/efi-stub-helper.c b/drivers/firmware/efi/libstub/efi-stub-helper.c
>>>>> index 8e60a39df3c5..eaf45ea749b3 100644
>>>>> --- a/drivers/firmware/efi/libstub/efi-stub-helper.c
>>>>> +++ b/drivers/firmware/efi/libstub/efi-stub-helper.c
>>>>> @@ -323,3 +323,68 @@ void efi_char16_printk(efi_char16_t *str)
>>>>>         efi_call_proto(efi_table_attr(efi_system_table(), con_out),
>>>>>                        output_string, str);
>>>>>     }
>>>>> +
>>>>> +static const struct {
>>>>> +     struct efi_vendor_dev_path      vendor;
>>>>> +     struct efi_generic_dev_path     end;
>>>>> +} __packed initrd_devpath = {
>>>>> +     {
>>>>> +             EFI_DEV_MEDIA,
>>>>> +             EFI_DEV_MEDIA_VENDOR,
>>>>> +             sizeof(struct efi_vendor_dev_path),
>>>>> +             LINUX_EFI_INITRD_MEDIA_GUID
>>>>> +     }, {
>>>>> +             EFI_DEV_END_PATH,
>>>>> +             EFI_DEV_END_ENTIRE,
>>>>> +             sizeof(struct efi_generic_dev_path)
>>>>> +     }
>>>>> +};
>>>>> +
>>>>> +efi_status_t efi_load_initrd_devpath(unsigned long *load_addr,
>>>>> +                                  unsigned long *load_size,
>>>>> +                                  unsigned long max)
>>>>> +{
>>>>> +     efi_guid_t lf2_proto_guid = EFI_LOAD_FILE2_PROTOCOL_GUID;
>>>>> +     efi_device_path_protocol_t *dp;
>>>>> +     efi_load_file2_protocol_t *lf2;
>>>>> +     unsigned long initrd_addr;
>>>>> +     unsigned long initrd_size;
>>>>> +     efi_handle_t handle;
>>>>> +     efi_status_t status;
>>>>> +
>>>>> +     if (!load_addr || !load_size)
>>>>> +             return EFI_INVALID_PARAMETER;
>>>>> +
>>>>> +     dp = (efi_device_path_protocol_t *)&initrd_devpath;
>>>>> +     status = efi_bs_call(locate_device_path, &lf2_proto_guid, &dp, &handle);
>>>>> +     if (status != EFI_SUCCESS)
>>>>> +             return status;
>>>>> +
>>>>> +     status = efi_bs_call(handle_protocol, handle, &lf2_proto_guid,
>>>>> +                          (void **)&lf2);
>>>>> +     if (status != EFI_SUCCESS)
>>>>> +             return status;
>>>>
>>>> You require here that there is a handle exposing the device path
>>>> protocol with the initrd specific device path. On the same handle the
>>>> EFI_LOAD_FILE2_PROTOCOL must be installed which will load the initrd
>>>> file when called with the same device path.
>>>>
>>>
>>> Exactly.
>>>
>>>> An alternative implementation would simple loop over all instances of
>>>> the EFI_LOAD_FILE2_PROTOCOL and try to load the initrd.
>>>>
>>>
>>> How do I distinguish the initrd from other EFI_LOAD_FILE2_PROTOCOL
>>> instances? For instance, PCI option ROMs are also exposed as
>>> EFI_LOAD_FILE2_PROTOCOL.
>>
>> These would respond with EFI_NOT_FOUND. The advantage of looping over
>> all instances could be that the same handle could be used to provide
>> multiple files. But how this is implemented is the least of my worries.
>>
>> Best regards
>>
>> Heinrich
>>
>>>
>>>> It would be worthwhile to describe the requirements on the
>>>> implementation of the EFI_LOAD_FILE2_PROTOCOL in a code comment and in
>>>> the documentation.
>>>>
>>>> Unfortunately the documentation of UEFI has been duplicated into two files:
>>>>
>>>> Documentation/arm/uefi.rst
>>>> Documentation/x86/x86_64/uefi.rst
>>>>
>>>
>>> Yes, that is a good point. I will work on factoring out the generic
>>> parts and share them.
>>>
>>>
>>> Thanks for the review,
>>> Ard.
>>>
>>>
>>>
>>>>
>>>>> +
>>>>> +     initrd_size = 0;
>>>>> +     status = efi_call_proto(lf2, load_file,
>>>>> +                             (efi_device_path_protocol_t *)&initrd_devpath,
>>>>> +                             false, &initrd_size, NULL);
>>>>> +     if (status != EFI_BUFFER_TOO_SMALL)
>>>>> +             return EFI_LOAD_ERROR;
>>>>> +
>>>>> +     status = efi_allocate_pages(initrd_size, &initrd_addr, max);
>>>>> +     if (status != EFI_SUCCESS)
>>>>> +             return status;
>>>>> +
>>>>> +     status = efi_call_proto(lf2, load_file,
>>>>> +                             (efi_device_path_protocol_t *)&initrd_devpath,
>>>>> +                             false, &initrd_size, (void *)initrd_addr);
>>>>> +     if (status != EFI_SUCCESS) {
>>>>> +             efi_free(initrd_size, initrd_addr);
>>>>> +             return status;
>>>>> +     }
>>>>> +
>>>>> +     *load_addr = initrd_addr;
>>>>> +     *load_size = initrd_size;
>>>>> +     return EFI_SUCCESS;
>>>>> +}
>>>>> diff --git a/drivers/firmware/efi/libstub/efistub.h b/drivers/firmware/efi/libstub/efistub.h
>>>>> index 99e93fd76ec5..fbf9f9442eed 100644
>>>>> --- a/drivers/firmware/efi/libstub/efistub.h
>>>>> +++ b/drivers/firmware/efi/libstub/efistub.h
>>>>> @@ -566,6 +566,14 @@ union efi_load_file_protocol {
>>>>>         } mixed_mode;
>>>>>     };
>>>>>
>>>>> +struct efi_vendor_dev_path {
>>>>> +     u8              type;
>>>>> +     u8              sub_type;
>>>>> +     u16             length;
>>>>> +     efi_guid_t      vendorguid;
>>>>> +     u8              vendordata[];
>>>>> +} __packed;
>>>>> +
>>>>>     void efi_pci_disable_bridge_busmaster(void);
>>>>>
>>>>>     typedef efi_status_t (*efi_exit_boot_map_processing)(
>>>>> @@ -651,4 +659,8 @@ efi_status_t efi_load_initrd(efi_loaded_image_t *image,
>>>>>                              unsigned long *load_addr,
>>>>>                              unsigned long *load_size);
>>>>>
>>>>> +efi_status_t efi_load_initrd_devpath(unsigned long *load_addr,
>>>>> +                                  unsigned long *load_size,
>>>>> +                                  unsigned long max);
>>>>> +
>>>>>     #endif
>>>>> diff --git a/drivers/firmware/efi/libstub/x86-stub.c b/drivers/firmware/efi/libstub/x86-stub.c
>>>>> index f3e2ff31b624..7f38f95676dd 100644
>>>>> --- a/drivers/firmware/efi/libstub/x86-stub.c
>>>>> +++ b/drivers/firmware/efi/libstub/x86-stub.c
>>>>> @@ -419,9 +419,20 @@ efi_status_t __efiapi efi_pe_entry(efi_handle_t handle,
>>>>>         if (status != EFI_SUCCESS)
>>>>>                 goto fail2;
>>>>>
>>>>> -     status = efi_load_initrd(image, hdr->initrd_addr_max,
>>>>> -                              above4g ? ULONG_MAX : hdr->initrd_addr_max,
>>>>> -                              &ramdisk_addr, &ramdisk_size);
>>>>> +     /*
>>>>> +      * The initrd loaded from the Linux initrd vendor device
>>>>> +      * path should take precedence, as we don't want the
>>>>> +      * [unverified] command line to override the initrd
>>>>> +      * supplied by the [potentially verified] firmware.
>>>>> +      */
>>>>> +     status = efi_load_initrd_devpath(&ramdisk_addr, &ramdisk_size,
>>>>> +                                      above4g ? ULONG_MAX
>>>>> +                                              : hdr->initrd_addr_max);
>>>>> +     if (status == EFI_NOT_FOUND)
>>>>> +             status = efi_load_initrd(image, hdr->initrd_addr_max,
>>>>> +                                      above4g ? ULONG_MAX
>>>>> +                                              : hdr->initrd_addr_max,
>>>>> +                                      &ramdisk_addr, &ramdisk_size);
>>>>>         if (status != EFI_SUCCESS)
>>>>>                 goto fail2;
>>>>>         hdr->ramdisk_image = ramdisk_addr & 0xffffffff;
>>>>> @@ -732,6 +743,25 @@ struct boot_params *efi_main(efi_handle_t handle,
>>>>>                          ((u64)boot_params->ext_cmd_line_ptr << 32));
>>>>>         efi_parse_options((char *)cmdline_paddr);
>>>>>
>>>>> +     if (!hdr->ramdisk_size && !boot_params->ext_ramdisk_size) {
>>>>> +             unsigned long max = hdr->initrd_addr_max;
>>>>> +             unsigned long addr, size;
>>>>> +
>>>>> +             if (hdr->xloadflags & XLF_CAN_BE_LOADED_ABOVE_4G)
>>>>> +                     max = ULONG_MAX;
>>>>> +
>>>>> +             status = efi_load_initrd_devpath(&addr, &size, max);
>>>>> +             if (status == EFI_SUCCESS) {
>>>>> +                     hdr->ramdisk_image              = (u32)addr;
>>>>> +                     hdr->ramdisk_size               = (u32)size;
>>>>> +                     boot_params->ext_ramdisk_image  = (u64)addr >> 32;
>>>>> +                     boot_params->ext_ramdisk_size   = (u64)size >> 32;
>>>>> +             } else if (status != EFI_NOT_FOUND) {
>>>>> +                     efi_printk("efi_load_initrd_devpath() failed!\n");
>>>>> +                     goto fail;
>>>>> +             }
>>>>> +     }
>>>>> +
>>>>>         /*
>>>>>          * If the boot loader gave us a value for secure_boot then we use that,
>>>>>          * otherwise we ask the BIOS.
>>>>> diff --git a/include/linux/efi.h b/include/linux/efi.h
>>>>> index 9ccf313fe9de..75c83c322c40 100644
>>>>> --- a/include/linux/efi.h
>>>>> +++ b/include/linux/efi.h
>>>>> @@ -353,6 +353,7 @@ void efi_native_runtime_setup(void);
>>>>>     #define LINUX_EFI_TPM_EVENT_LOG_GUID                EFI_GUID(0xb7799cb0, 0xeca2, 0x4943,  0x96, 0x67, 0x1f, 0xae, 0x07, 0xb7, 0x47, 0xfa)
>>>>>     #define LINUX_EFI_TPM_FINAL_LOG_GUID                EFI_GUID(0x1e2ed096, 0x30e2, 0x4254,  0xbd, 0x89, 0x86, 0x3b, 0xbe, 0xf8, 0x23, 0x25)
>>>>>     #define LINUX_EFI_MEMRESERVE_TABLE_GUID             EFI_GUID(0x888eb0c6, 0x8ede, 0x4ff5,  0xa8, 0xf0, 0x9a, 0xee, 0x5c, 0xb9, 0x77, 0xc2)
>>>>> +#define LINUX_EFI_INITRD_MEDIA_GUID          EFI_GUID(0x5568e427, 0x68fc, 0x4f3d,  0xac, 0x74, 0xca, 0x55, 0x52, 0x31, 0xcc, 0x68)
>>>>>
>>>>>     /* OEM GUIDs */
>>>>>     #define DELLEMC_EFI_RCI2_TABLE_GUID         EFI_GUID(0x2d9f28a2, 0xa886, 0x456a,  0x97, 0xa8, 0xf1, 0x1e, 0xf2, 0x4f, 0xf4, 0x55)
>>>>>
>>>>
>>


^ permalink raw reply	[flat|nested] 39+ messages in thread

* Re: [PATCH 1/2] efi/libstub: add support for loading the initrd from a device path
  2020-02-06 22:49           ` Heinrich Schuchardt
@ 2020-02-07  7:35             ` Ilias Apalodimas
  0 siblings, 0 replies; 39+ messages in thread
From: Ilias Apalodimas @ 2020-02-07  7:35 UTC (permalink / raw)
  To: Heinrich Schuchardt
  Cc: Ard Biesheuvel, linux-efi, linux-arm-kernel, lersek, leif,
	pjones, mjg59, agraf, daniel.kiper

Hi Heinrich

[...]
> > > 
> > > If you don't have an environment or boot script how would
> > > update-initramfs set the path of the initrd when it is updated?
> > 
> > The path isn't hardcoded in any code here is it?
> > This specifies a way for the linux stub to load the actual file. It's pretty a
> > callback to the firmware. Were the firmware will find and how it will load it
> > eventually is implementation specific.
> 
> "Implementation specific" - This does not sound like anything you would
> want to have in mainline Linux, U-Boot, or EDK2.
> 

And it isn't. The *only* thing that's specific to the firmware itself, is
how/where to find the file. The whole handover mechanism after 
that is generic for everyone.

> > 
> > > 
> > > Using a UEFI variable seems to be the natural choice.
> > > 
> > 
> > You might as well use that to specify were you should load the file from.
> > The Loadfile2 (with the specified guid)  implementation of the firmware will
> > take care of that.
> > 
> 
> If we have a UEFI variable, the Linux kernel can use it to find the
> handle with the file system and load initrd via the
> EFI_SIMPLE_FILE_SYSTEM_PROTOCOL.
> 
> This way we stay within the existing UEFI specification and avoid
> anything "implementation specific" in the firmware.

Is this going to limit the directories we can place the file or not?

> 
> If you want extra security, Linux can use an authenticated variable.
> 
> > > > 
> > > > > > 
> > > > > > In the second case, we force the bootloader to duplicate knowledge about
> > > > > > the boot protocol which is already encoded in the stub, and which may be
> > > > > > subject to change over time, e.g., bootparams struct definitions, memory
> > > > > > allocation/alignment requirements for the placement of the initrd etc etc.
> > > > > > In the ARM case, it also requires the bootloader to modify the hardware
> > > > > > description provided by the firmware, as it is passed in the same file.
> > > > > > On systems where the initrd is measured after loading, it creates a time
> > > > > > window where the initrd contents might be manipulated in memory before
> > > > > > handing over to the kernel.
> > > > > > 
> > > > > > Address these concerns by adding support for loading the initrd into
> > > > > > memory by invoking the EFI LoadFile2 protocol installed on a vendor
> > > > > > GUIDed device path that specifically designates a Linux initrd.
> > > > > > This addresses the above concerns, by putting the EFI stub in charge of
> > > > > > placement in memory and of passing the base and size to the kernel proper
> > > > > > (via whatever means it desires) while still leaving it up to the firmware
> > > > > > or bootloader to obtain the file contents, potentially from other file
> > > > > > systems than the one the kernel itself was loaded from. On platforms that
> > > > > > implement measured boot, it permits the firmware to take the measurement
> > > > > > right before the kernel actually consumes the contents.
> > > > > 
> > > > > A firmware implementing the UEFI standard will not be aware of any
> > > > > initrd image as such an object does not exist in the standard. It was a
> > > > > wise decision that the UEFI standard is operating system agnostic
> > > > > (accomodating BSD, Linux, Windows, etc.). So the firmware (EDK2, U-Boot,
> > > > > etc.) seems to be out of scope for providing a Linux specific
> > > > > EFI_LOAD_FILE2_PROTOCOL.
> > > > > 
> > > > > When booting via GRUB it will be GRUB knowing which initrd to load.
> > > > 
> > > > What about booting the kernel directly?
> > > > 
> > > > > 
> > > > > Please, indicate which software you expect to expose the initrd related
> > > > > EFI_LOAD_FILE2_PROTOCOL.
> > > > 
> > > > I have an implementation for this on U-Boot which works. The file and device are
> > > > hardcoded at the moment, but the rest of the functionality works fine. I'll
> > > > share it with you once I clean it up a bit.
> > > 
> > > Using a UEFI variable for passing the intird device path to Linux does
> > > not require any change in U-Boot and is compatible with the UEFI
> > > implementations of existing hardware like the laptop on which I am
> > > writing this email.
> > 
> > This still has the same issues we have now, uefi variable, kernel command line
> > or whatever, it won't be common across architectures.
> 
> This would be a bad design choice by Linux. I cannot see why a UEFI
> variable should not be interpreted in a consistent way inside Linux to
> load a file via the EFI_SIMPLE_FILE_PROTOCOL.
> 
> > 
> > Thanks
> > /Ilias
> > > 
> > > Best regards
> > > 
> > > Heinrich
> > > 
> > > > 
> > > > > > -	status = efi_load_initrd(image, ULONG_MAX,
> > > > > > -				 efi_get_max_initrd_addr(dram_base, *image_addr),
> > > > > > -				 &initrd_addr, &initrd_size);
> > > > > > +	max_addr = efi_get_max_initrd_addr(dram_base, *image_addr);
> > > > > > +	status = efi_load_initrd_devpath(&initrd_addr, &initrd_size, max_addr);
> > > > > > +	if (status == EFI_SUCCESS)
> > > > > > +		pr_efi("Loaded initrd from LINUX_EFI_INITRD_MEDIA_GUID device path\n");
> > > > > > +	else if (status == EFI_NOT_FOUND) {
> > > > > > +		status = efi_load_initrd(image, ULONG_MAX, max_addr,
> > > > > > +					 &initrd_addr, &initrd_size);
> 
> If I delete the initrd that otherwise would be loaded by the
> EFI_LOAD_FILE2_PROTOCOL I end up with the old behavior. So where is the
> security gain provided by this patch?

If you delete the initrd, there is no initrd to load :)

Thanks
/Ilias

^ permalink raw reply	[flat|nested] 39+ messages in thread

* Re: [PATCH 1/2] efi/libstub: add support for loading the initrd from a device path
  2020-02-07  0:57           ` Heinrich Schuchardt
@ 2020-02-07  8:12             ` Ard Biesheuvel
  2020-02-07 13:30               ` Heinrich Schuchardt
  0 siblings, 1 reply; 39+ messages in thread
From: Ard Biesheuvel @ 2020-02-07  8:12 UTC (permalink / raw)
  To: Heinrich Schuchardt
  Cc: Ard Biesheuvel, linux-efi, linux-arm-kernel, Laszlo Ersek,
	Leif Lindholm, Peter Jones, Matthew Garrett, Alexander Graf,
	Ilias Apalodimas, Daniel Kiper

On Fri, 7 Feb 2020 at 00:58, Heinrich Schuchardt <xypron.glpk@gmx.de> wrote:
>
> On 2/7/20 1:21 AM, Ard Biesheuvel wrote:
> > On Fri, 7 Feb 2020 at 00:01, Heinrich Schuchardt <xypron.glpk@gmx.de> wrote:
> >>
> >> On 2/6/20 11:35 PM, Ard Biesheuvel wrote:
> >>> On Thu, 6 Feb 2020 at 18:26, Heinrich Schuchardt <xypron.glpk@gmx.de> wrote:
...
> >>>> Please, indicate which software you expect to expose the initrd related
> >>>> EFI_LOAD_FILE2_PROTOCOL.
> >>>>
> >>>
> >>> The primary use case is GRUB and other intermediate loaders, since it
> >>> would remove any need for these components to know any such details.
> >>> My aim is to make the next architecture that gets added to GRUB for
> >>> EFI boot 100% generic.
> >>>
> >>>> Using an UEFI variable for passing the initrd device path would be a
> >>>> leaner solution on the bootloader side than requiring an extra
> >>>> EFI_LOAD_FILE2_PROTOCOL implementation.
> >>>>
> >>>
> >>> This would also require kernel changes, since we don't currently load
> >>> initrds from arbitrary device paths. The EFI_FILE_PROTOCOL is much
> >>> more complicated than needed, and it doesn't work well with mixed
> >>> mode. It also requires GRUB to expose the filesystem it loads the
> >>> initrd from via EFI protocols, which is currently unnecessary and
> >>> therefore not implemented.
> >>
> >> This means you move the complexity of EFI_FILE_PROTOCOL from Linux to GRUB.
> >>
> >
> > No. I am not interested in EFI_FILE_PROTOCOL, only in LoadFile2, which
> > is a single method that needs to be implemented.
>
> I said you move complexity because GRUB will need to use the
> EFI_FILE_PROTOCOL to do the job that you do not want to do in Linux.
>
> >
> >> I would not have a problem if this would only touch GRUB. But if listen
> >> to Ilias we are replacing one implementation in Linux by one in GRUB and
> >> one in U-Boot and one in EDK2 and one in any other firmware.
> >>
> >
> > If u-boot will be used to boot RISC-V in EFI mode without GRUB, then I
> > expect that we will need an implementation of this in u-boot.
>
> What sets RISC-V apart? GRUB for RISC-V is available.
>

RISC-V EFI boot is not supported yet in upstream Linux.

> >
> >>>
> >>> Also, using an EFI variable defeats the purpose. The whole point of
> >>> this is making it more likely that the kernel loaded the initrd that
> >>> the bootloader or firmware intended it to load, and having a piece of
> >>> simple [signed] code that implements this is the easiest way to
> >>> achieve that.
> >>
> >> At least on my Debian system it is the operating system creating initrd
> >> and defining which initrd matches which kernel. GRUB simply assumes that
> >> files ending on the same version number match. Therefore I would say
> >> Linux hopes that GRUB loads what Linux intended.
> >>
> >> The chain of trust would not be broken if the kernel were responsible
> >> for loading the initrd and for checking if it matches the kernel. Linux
> >> already does this for the kernel modules in initrd.
> >>
> >
> > We can still sign the initrd and Linux can verify the signature. What
> > I am after is an interface that does not require the initrd to
> > originate from a EFI file system protocol, and which doesn't require
> > the loaded initrd to sit in memory for an unspecified amount of time
> > and its information passed via DT properties or bootparams structs.
> >
> > So invoking EFI_FILE_PROTOCOL directly is not going to work,
> > regardless of whether we get the devicepath from the command line or
> > from a EFI variable.
>
> What do you mean by "is not going to work"?
>
> With the device path you can find the handle implementing the
> EFI_SIMPLE_FIL_SYSTEM_PROTOCOL.
>
> >
> >>>
> >>> For u-boot, it should be trivial to implement a simple LoadFile2
> >>> protocol wrapper around EFI_FILE_PROTOCOL that can be installed on a
> >>> handle that also carries EFI_FILE_PROTOCOL.
> >>>
> >>
> >> A U-Boot implementation of the EFI_LOAD_FILE2_PROTOCOL would need a
> >> device path variable to find the block device and to open the
> >> EFI_SIMPLE_FILE_SYSTEM_PROTOCOL before accessing the file.
> >>
> >> Linux would not be needing more lines and we would not repeat the same
> >> code in GRUB, U-Boot, EDK2, etc.
> >>
> >> As said Linux updates the initrd often. If that file is not signed by
> >> Linux in a well defined way, do not expect any security at all.
> >>
> >
> > It is not only about security. The primary goal is to remove the need
> > for arch specific knowledge in the firmware about DT, bootparams and
> > initrd allocation policies without being forced to load the initrd
> > from a filesystem that is exposed via a EFI protocol.
>
> Where are device-trees touched by this patch?
>
> When booting via UEFI there is no need for knowledge of initrd
> allocation policies in U-Boot because up to now Linux or GRUB or iPXE
> load initrd.
>
> Furthermore I need no knowledge of bootparams in U-Boot once we properly
> we support UEFI variables at runtime because grub-update will pass the
> command line in one of the Bootxxxx UEFI variables.
>
> But most importantly I do not have to implement anything Linux specific
> in U-Boot for booting via UEFI up to now.
>

Adding Linux specific stuff to u-boot is arguably more appropriate
than adding architecture specific stuff to EFI loaders that could
otherwise be entirely generic.

...
>
> Your patch claims to fend off a specific threat scenario: A user puts an
> untrusted initrd on the disk and references it in the Linux command line.
>
> If he is able to do so with your current bootloader (signed or not
> signed), he most probably will also be able to delete a good initrd from
> the filesystem and thus force your code into the unsafe path.
>
> That is why I say that with the current fallback logic this patch
> achieves no increase in security. Of cause you could remove the fallback
> logic. But in this case your Linux will not boot with any legacy
> bootloader or firmware.
>

If there is a better way to expose the initrd that
a) does not require the initrd to reside on a file system that is
accessible via EFI protocols, and
b) does not require the loader to know about arch specific policies
regarding the placement of the initrd in memory, and
c) does not leave a time window between the time that the initrd is
loaded/verified/measured by the firmware and the time that the kernel
gets handed the buffer

then I am happy to discuss it. This proposal is the best I could come
up with to achieve the above.

-- 
Ard.

^ permalink raw reply	[flat|nested] 39+ messages in thread

* Re: [PATCH 0/2] arch-agnostic initrd loading method for EFI systems
  2020-02-06 14:03 [PATCH 0/2] arch-agnostic initrd loading method for EFI systems Ard Biesheuvel
  2020-02-06 14:03 ` [PATCH 1/2] efi/libstub: add support for loading the initrd from a device path Ard Biesheuvel
  2020-02-06 14:03 ` [PATCH 2/2] efi/libstub: take noinitrd cmdline argument into account for devpath initrd Ard Biesheuvel
@ 2020-02-07  9:09 ` Laszlo Ersek
  2020-02-07  9:22   ` Laszlo Ersek
  2020-02-07 18:45 ` Arvind Sankar
  3 siblings, 1 reply; 39+ messages in thread
From: Laszlo Ersek @ 2020-02-07  9:09 UTC (permalink / raw)
  To: Ard Biesheuvel, linux-efi
  Cc: linux-arm-kernel, leif, pjones, mjg59, agraf, ilias.apalodimas,
	xypron.glpk, daniel.kiper

On 02/06/20 15:03, Ard Biesheuvel wrote:
> This series introduces an arch agnostic way of loading the initrd into
> memory from the EFI stub. This addresses a number of shortcomings that
> affect the current implementations that exist across architectures:
> 
> - The initrd=<file> command line option can only load files that reside
>   on the same file system that the kernel itself was loaded from, which
>   requires the bootloader or firmware to expose that file system via the
>   appropriate EFI protocol, which is not always feasible. From the kernel
>   side, this protocol is problematic since it is incompatible with mixed
>   mode on x86 (this is due to the fact that some of its methods have
>   prototypes that are difficult to marshall)
> 
> - The approach that is ordinarily taken by GRUB is to load the initrd into
>   memory, and pass it to the kernel proper via the bootparams structure or
>   via the device tree. This requires the boot loader to have an understanding
>   of those structures, which are not always set in stone, and of the policies
>   around where the initrd may be loaded into memory. In the ARM case, it
>   requires GRUB to modify the hardware description provided by the firmware,
>   given that the initrd base and offset in memory are passed via the same
>   data structure. It also creates a time window where the initrd data sits
>   in memory, and can potentially be corrupted before the kernel is booted.
> 
> Considering that we will soon have new users of these interfaces (EFI for
> kvmtool on ARM, RISC-V in u-boot, etc), it makes sense to add a generic
> interface now, before having another wave of bespoke arch specific code
> coming in.
> 
> Another aspect to take into account is that support for UEFI secure boot
> and measured boot is being taken into the upstream, and being able to
> rely on the PE entry point for booting any architecture makes the GRUB
> vs shim story much cleaner, as we should be able to rely on LoadImage
> and StartImage on all architectures, while retaining the ability to
> load initrds from anywhere.
> 
> Note that these patches depend on a fair amount of cleanup work that I
> am targetting for v5.7. Branch can be found at:
> https://git.kernel.org/pub/scm/linux/kernel/git/ardb/linux.git/log/?h=efistub-unification2
> 
> An implementation for ArmVirtQemu (OVMF for ARM aka AAVMF) can be found
> at https://github.com/ardbiesheuvel/edk2/commits/linux-efi-generic.
> The change is for ARM only, but the exact same code could be used on x86.

I like this ArmVirtQemu feature, but I think it should be implemented as
an addition, rather than a replacement. Older kernels (older EFI stubs)
will try to fetch the initrd from the same fs where grub loaded the
kernel from (exactly as you describe in the blurb).

For example, virt-install's "--location" option "can recognize certain
distribution trees and fetches a bootable kernel/initrd pair to launch
the install". It would be nice to keep that working for older distros.

I think LoadFile[2] can co-exist with SimpleFs.

I also think that the "try SimpleFs first, fall back to LoadFile[2]
second" requirement applies only to the UEFI boot manager, and not to
the kernel's EFI stub. IOW in the new approach the kernel is free to
ignore (abandon) the old approach for good.

Thanks
Laszlo

> 
> Cc: lersek@redhat.com
> Cc: leif@nuviainc.com
> Cc: pjones@redhat.com
> Cc: mjg59@google.com
> Cc: agraf@csgraf.de
> Cc: ilias.apalodimas@linaro.org
> Cc: xypron.glpk@gmx.de 
> Cc: daniel.kiper@oracle.com
> 
> Ard Biesheuvel (2):
>   efi/libstub: add support for loading the initrd from a device path
>   efi/libstub: take noinitrd cmdline argument into account for devpath
>     initrd
> 
>  drivers/firmware/efi/libstub/arm-stub.c       | 21 ++++--
>  .../firmware/efi/libstub/efi-stub-helper.c    | 74 +++++++++++++++++++
>  drivers/firmware/efi/libstub/efistub.h        | 13 ++++
>  drivers/firmware/efi/libstub/x86-stub.c       | 51 ++++++++++---
>  include/linux/efi.h                           |  1 +
>  5 files changed, 146 insertions(+), 14 deletions(-)
> 


^ permalink raw reply	[flat|nested] 39+ messages in thread

* Re: [PATCH 0/2] arch-agnostic initrd loading method for EFI systems
  2020-02-07  9:09 ` [PATCH 0/2] arch-agnostic initrd loading method for EFI systems Laszlo Ersek
@ 2020-02-07  9:22   ` Laszlo Ersek
  2020-02-07 12:23     ` Ard Biesheuvel
  0 siblings, 1 reply; 39+ messages in thread
From: Laszlo Ersek @ 2020-02-07  9:22 UTC (permalink / raw)
  To: Ard Biesheuvel, linux-efi
  Cc: linux-arm-kernel, leif, pjones, mjg59, agraf, ilias.apalodimas,
	xypron.glpk, daniel.kiper

On 02/07/20 10:09, Laszlo Ersek wrote:
> On 02/06/20 15:03, Ard Biesheuvel wrote:
>> This series introduces an arch agnostic way of loading the initrd into
>> memory from the EFI stub. This addresses a number of shortcomings that
>> affect the current implementations that exist across architectures:
>>
>> - The initrd=<file> command line option can only load files that reside
>>   on the same file system that the kernel itself was loaded from, which
>>   requires the bootloader or firmware to expose that file system via the
>>   appropriate EFI protocol, which is not always feasible. From the kernel
>>   side, this protocol is problematic since it is incompatible with mixed
>>   mode on x86 (this is due to the fact that some of its methods have
>>   prototypes that are difficult to marshall)
>>
>> - The approach that is ordinarily taken by GRUB is to load the initrd into
>>   memory, and pass it to the kernel proper via the bootparams structure or
>>   via the device tree. This requires the boot loader to have an understanding
>>   of those structures, which are not always set in stone, and of the policies
>>   around where the initrd may be loaded into memory. In the ARM case, it
>>   requires GRUB to modify the hardware description provided by the firmware,
>>   given that the initrd base and offset in memory are passed via the same
>>   data structure. It also creates a time window where the initrd data sits
>>   in memory, and can potentially be corrupted before the kernel is booted.
>>
>> Considering that we will soon have new users of these interfaces (EFI for
>> kvmtool on ARM, RISC-V in u-boot, etc), it makes sense to add a generic
>> interface now, before having another wave of bespoke arch specific code
>> coming in.
>>
>> Another aspect to take into account is that support for UEFI secure boot
>> and measured boot is being taken into the upstream, and being able to
>> rely on the PE entry point for booting any architecture makes the GRUB
>> vs shim story much cleaner, as we should be able to rely on LoadImage
>> and StartImage on all architectures, while retaining the ability to
>> load initrds from anywhere.
>>
>> Note that these patches depend on a fair amount of cleanup work that I
>> am targetting for v5.7. Branch can be found at:
>> https://git.kernel.org/pub/scm/linux/kernel/git/ardb/linux.git/log/?h=efistub-unification2
>>
>> An implementation for ArmVirtQemu (OVMF for ARM aka AAVMF) can be found
>> at https://github.com/ardbiesheuvel/edk2/commits/linux-efi-generic.
>> The change is for ARM only, but the exact same code could be used on x86.
> 
> I like this ArmVirtQemu feature, but I think it should be implemented as
> an addition, rather than a replacement. Older kernels (older EFI stubs)
> will try to fetch the initrd from the same fs where grub loaded the
> kernel from (exactly as you describe in the blurb).
> 
> For example, virt-install's "--location" option "can recognize certain
> distribution trees and fetches a bootable kernel/initrd pair to launch
> the install". It would be nice to keep that working for older distros.
> 
> I think LoadFile[2] can co-exist with SimpleFs.
> 
> I also think that the "try SimpleFs first, fall back to LoadFile[2]
> second" requirement applies only to the UEFI boot manager, and not to
> the kernel's EFI stub. IOW in the new approach the kernel is free to
> ignore (abandon) the old approach for good.

... But that might not be good for compatibility with grub and/or the
platform firmware, from the kernel's own perspective, perhaps?...

Who is supposed to produce LoadFile2 with the new VenMedia devpath?

Thanks
Laszlo

>>
>> Cc: lersek@redhat.com
>> Cc: leif@nuviainc.com
>> Cc: pjones@redhat.com
>> Cc: mjg59@google.com
>> Cc: agraf@csgraf.de
>> Cc: ilias.apalodimas@linaro.org
>> Cc: xypron.glpk@gmx.de 
>> Cc: daniel.kiper@oracle.com
>>
>> Ard Biesheuvel (2):
>>   efi/libstub: add support for loading the initrd from a device path
>>   efi/libstub: take noinitrd cmdline argument into account for devpath
>>     initrd
>>
>>  drivers/firmware/efi/libstub/arm-stub.c       | 21 ++++--
>>  .../firmware/efi/libstub/efi-stub-helper.c    | 74 +++++++++++++++++++
>>  drivers/firmware/efi/libstub/efistub.h        | 13 ++++
>>  drivers/firmware/efi/libstub/x86-stub.c       | 51 ++++++++++---
>>  include/linux/efi.h                           |  1 +
>>  5 files changed, 146 insertions(+), 14 deletions(-)
>>
> 


^ permalink raw reply	[flat|nested] 39+ messages in thread

* Re: [PATCH 1/2] efi/libstub: add support for loading the initrd from a device path
  2020-02-06 14:03 ` [PATCH 1/2] efi/libstub: add support for loading the initrd from a device path Ard Biesheuvel
  2020-02-06 18:26   ` Heinrich Schuchardt
@ 2020-02-07  9:48   ` Laszlo Ersek
  2020-02-07 12:36     ` Ard Biesheuvel
  2020-02-09  6:39   ` Lukas Wunner
  2 siblings, 1 reply; 39+ messages in thread
From: Laszlo Ersek @ 2020-02-07  9:48 UTC (permalink / raw)
  To: Ard Biesheuvel, linux-efi
  Cc: linux-arm-kernel, leif, pjones, mjg59, agraf, ilias.apalodimas,
	xypron.glpk, daniel.kiper

On 02/06/20 15:03, Ard Biesheuvel wrote:
> There are currently two ways to specify the initrd to be passed to the
> Linux kernel when booting via the EFI stub:
> - it can be passed as a initrd= command line option when doing a pure PE
>   boot (as opposed to the EFI handover protocol that exists for x86)
> - otherwise, the bootloader or firmware can load the initrd into memory,
>   and pass the address and size via the bootparams struct (x86) or
>   device tree (ARM)
> 
> In the first case, we are limited to loading from the same file system
> that the kernel was loaded from, and it is also problematic in a trusted
> boot context, given that we cannot easily protect the command line from
> tampering without either adding complicated white/blacklisting of boot
> arguments or locking down the command line altogether.
> 
> In the second case, we force the bootloader to duplicate knowledge about
> the boot protocol which is already encoded in the stub, and which may be
> subject to change over time, e.g., bootparams struct definitions, memory
> allocation/alignment requirements for the placement of the initrd etc etc.
> In the ARM case, it also requires the bootloader to modify the hardware
> description provided by the firmware, as it is passed in the same file.
> On systems where the initrd is measured after loading, it creates a time
> window where the initrd contents might be manipulated in memory before
> handing over to the kernel.
> 
> Address these concerns by adding support for loading the initrd into
> memory by invoking the EFI LoadFile2 protocol installed on a vendor
> GUIDed device path that specifically designates a Linux initrd.
> This addresses the above concerns, by putting the EFI stub in charge of
> placement in memory and of passing the base and size to the kernel proper
> (via whatever means it desires) while still leaving it up to the firmware
> or bootloader to obtain the file contents, potentially from other file
> systems than the one the kernel itself was loaded from. On platforms that
> implement measured boot, it permits the firmware to take the measurement
> right before the kernel actually consumes the contents.
> 
> Signed-off-by: Ard Biesheuvel <ardb@kernel.org>
> ---
>  drivers/firmware/efi/libstub/arm-stub.c        | 16 +++--
>  drivers/firmware/efi/libstub/efi-stub-helper.c | 65 ++++++++++++++++++++
>  drivers/firmware/efi/libstub/efistub.h         | 12 ++++
>  drivers/firmware/efi/libstub/x86-stub.c        | 36 ++++++++++-
>  include/linux/efi.h                            |  1 +
>  5 files changed, 123 insertions(+), 7 deletions(-)
> 
> diff --git a/drivers/firmware/efi/libstub/arm-stub.c b/drivers/firmware/efi/libstub/arm-stub.c
> index c7b091f50e55..1db943c1ba2b 100644
> --- a/drivers/firmware/efi/libstub/arm-stub.c
> +++ b/drivers/firmware/efi/libstub/arm-stub.c
> @@ -157,6 +157,7 @@ unsigned long efi_entry(void *handle, efi_system_table_t *sys_table_arg,
>  	enum efi_secureboot_mode secure_boot;
>  	struct screen_info *si;
>  	efi_properties_table_t *prop_tbl;
> +	unsigned long max_addr;
>  
>  	sys_table = sys_table_arg;
>  
> @@ -255,11 +256,18 @@ unsigned long efi_entry(void *handle, efi_system_table_t *sys_table_arg,
>  	if (!fdt_addr)
>  		pr_efi("Generating empty DTB\n");
>  
> -	status = efi_load_initrd(image, ULONG_MAX,
> -				 efi_get_max_initrd_addr(dram_base, *image_addr),
> -				 &initrd_addr, &initrd_size);
> +	max_addr = efi_get_max_initrd_addr(dram_base, *image_addr);
> +	status = efi_load_initrd_devpath(&initrd_addr, &initrd_size, max_addr);
> +	if (status == EFI_SUCCESS)
> +		pr_efi("Loaded initrd from LINUX_EFI_INITRD_MEDIA_GUID device path\n");
> +	else if (status == EFI_NOT_FOUND) {
> +		status = efi_load_initrd(image, ULONG_MAX, max_addr,
> +					 &initrd_addr, &initrd_size);

- So this seems to be fallback#1, for ARM, which looks good.

- Are you sure you only want to fall back to the old method on
EFI_NOT_FOUND? Wouldn't other return values from
efi_load_initrd_devpath() justify that too?

... After checking the boot services called in
efi_load_initrd_devpath(), this idea seems reasonable, but then I'd
suggest documenting the significance of returning EFI_NOT_FOUND near the
efi_load_initrd_devpath() function declaration, in "efistub.h".

> +		if (status == EFI_SUCCESS)
> +			pr_efi("Loaded initrd from command line option\n");
> +	}
>  	if (status != EFI_SUCCESS)
> -		pr_efi_err("Failed initrd from command line!\n");
> +		pr_efi_err("Failed to load initrd!\n");
>  
>  	efi_random_get_seed();
>  
> diff --git a/drivers/firmware/efi/libstub/efi-stub-helper.c b/drivers/firmware/efi/libstub/efi-stub-helper.c
> index 8e60a39df3c5..eaf45ea749b3 100644
> --- a/drivers/firmware/efi/libstub/efi-stub-helper.c
> +++ b/drivers/firmware/efi/libstub/efi-stub-helper.c
> @@ -323,3 +323,68 @@ void efi_char16_printk(efi_char16_t *str)
>  	efi_call_proto(efi_table_attr(efi_system_table(), con_out),
>  		       output_string, str);
>  }
> +
> +static const struct {
> +	struct efi_vendor_dev_path	vendor;
> +	struct efi_generic_dev_path	end;
> +} __packed initrd_devpath = {
> +	{
> +		EFI_DEV_MEDIA,
> +		EFI_DEV_MEDIA_VENDOR,
> +		sizeof(struct efi_vendor_dev_path),
> +		LINUX_EFI_INITRD_MEDIA_GUID
> +	}, {
> +		EFI_DEV_END_PATH,
> +		EFI_DEV_END_ENTIRE,
> +		sizeof(struct efi_generic_dev_path)
> +	}
> +};
> +
> +efi_status_t efi_load_initrd_devpath(unsigned long *load_addr,
> +				     unsigned long *load_size,
> +				     unsigned long max)
> +{
> +	efi_guid_t lf2_proto_guid = EFI_LOAD_FILE2_PROTOCOL_GUID;
> +	efi_device_path_protocol_t *dp;
> +	efi_load_file2_protocol_t *lf2;
> +	unsigned long initrd_addr;
> +	unsigned long initrd_size;
> +	efi_handle_t handle;
> +	efi_status_t status;
> +
> +	if (!load_addr || !load_size)
> +		return EFI_INVALID_PARAMETER;
> +
> +	dp = (efi_device_path_protocol_t *)&initrd_devpath;
> +	status = efi_bs_call(locate_device_path, &lf2_proto_guid, &dp, &handle);
> +	if (status != EFI_SUCCESS)
> +		return status;
> +
> +	status = efi_bs_call(handle_protocol, handle, &lf2_proto_guid,
> +			     (void **)&lf2);
> +	if (status != EFI_SUCCESS)
> +		return status;
> +
> +	initrd_size = 0;
> +	status = efi_call_proto(lf2, load_file,
> +				(efi_device_path_protocol_t *)&initrd_devpath,
> +				false, &initrd_size, NULL);

The second argument to EFI_LOAD_FILE2_PROTOCOL.LoadFile() is "FilePath",
specified as "The device specific path of the file to load". This means
it is supposed to be a (possibly empty) sequence of FILEPATH_DEVICE_PATH
nodes, terminated by and "End Entire Device Path" node. See

- 10.3.1 Generic Device Path Structures
- 10.3.5.4 File Path Media Device Path

in UEFI-2.8.

And "initrd_devpath" is not a device path like that; instead it's the
VenMedia device path that's installed on the handle that also carries
our LoadFile2 instance.

Now, I do see that this all theoretical here, as we don't expect the
LoadFile2 instance that we've found via our special
LINUX_EFI_INITRD_MEDIA_GUID VenMedia devpath to do *any* device-specific
filename / pathname parsing.

But in that case (i.e., given that the FilePath argument is totally
irrelevant), I think it's much clearer if we simply pass an empty device
path -- one that consists of a single "End Entire Device Path" node.

I've checked, and your ArmVirtQemu patch ignores the FilePath argument
too -- justifiedly so. I just think it's better to pass in a well-formed
device path, rather than NULL. Because, the FilePath parameter is not
marked OPTIONAL in the spec.

> +	if (status != EFI_BUFFER_TOO_SMALL)
> +		return EFI_LOAD_ERROR;
> +
> +	status = efi_allocate_pages(initrd_size, &initrd_addr, max);
> +	if (status != EFI_SUCCESS)
> +		return status;
> +
> +	status = efi_call_proto(lf2, load_file,
> +				(efi_device_path_protocol_t *)&initrd_devpath,
> +				false, &initrd_size, (void *)initrd_addr);

Same here.

> +	if (status != EFI_SUCCESS) {
> +		efi_free(initrd_size, initrd_addr);
> +		return status;
> +	}
> +
> +	*load_addr = initrd_addr;
> +	*load_size = initrd_size;
> +	return EFI_SUCCESS;
> +}
> diff --git a/drivers/firmware/efi/libstub/efistub.h b/drivers/firmware/efi/libstub/efistub.h
> index 99e93fd76ec5..fbf9f9442eed 100644
> --- a/drivers/firmware/efi/libstub/efistub.h
> +++ b/drivers/firmware/efi/libstub/efistub.h
> @@ -566,6 +566,14 @@ union efi_load_file_protocol {
>  	} mixed_mode;
>  };
>  
> +struct efi_vendor_dev_path {
> +	u8		type;
> +	u8		sub_type;
> +	u16		length;
> +	efi_guid_t	vendorguid;
> +	u8		vendordata[];
> +} __packed;
> +
>  void efi_pci_disable_bridge_busmaster(void);
>  
>  typedef efi_status_t (*efi_exit_boot_map_processing)(
> @@ -651,4 +659,8 @@ efi_status_t efi_load_initrd(efi_loaded_image_t *image,
>  			     unsigned long *load_addr,
>  			     unsigned long *load_size);
>  
> +efi_status_t efi_load_initrd_devpath(unsigned long *load_addr,
> +				     unsigned long *load_size,
> +				     unsigned long max);
> +
>  #endif
> diff --git a/drivers/firmware/efi/libstub/x86-stub.c b/drivers/firmware/efi/libstub/x86-stub.c
> index f3e2ff31b624..7f38f95676dd 100644
> --- a/drivers/firmware/efi/libstub/x86-stub.c
> +++ b/drivers/firmware/efi/libstub/x86-stub.c
> @@ -419,9 +419,20 @@ efi_status_t __efiapi efi_pe_entry(efi_handle_t handle,
>  	if (status != EFI_SUCCESS)
>  		goto fail2;
>  
> -	status = efi_load_initrd(image, hdr->initrd_addr_max,
> -				 above4g ? ULONG_MAX : hdr->initrd_addr_max,
> -				 &ramdisk_addr, &ramdisk_size);
> +	/*
> +	 * The initrd loaded from the Linux initrd vendor device
> +	 * path should take precedence, as we don't want the
> +	 * [unverified] command line to override the initrd
> +	 * supplied by the [potentially verified] firmware.
> +	 */
> +	status = efi_load_initrd_devpath(&ramdisk_addr, &ramdisk_size,
> +					 above4g ? ULONG_MAX
> +						 : hdr->initrd_addr_max);
> +	if (status == EFI_NOT_FOUND)
> +		status = efi_load_initrd(image, hdr->initrd_addr_max,
> +					 above4g ? ULONG_MAX
> +						 : hdr->initrd_addr_max,
> +					 &ramdisk_addr, &ramdisk_size);

Fallback#2, for x86, also looks good.

>  	if (status != EFI_SUCCESS)
>  		goto fail2;
>  	hdr->ramdisk_image = ramdisk_addr & 0xffffffff;
> @@ -732,6 +743,25 @@ struct boot_params *efi_main(efi_handle_t handle,
>  			 ((u64)boot_params->ext_cmd_line_ptr << 32));
>  	efi_parse_options((char *)cmdline_paddr);
>  
> +	if (!hdr->ramdisk_size && !boot_params->ext_ramdisk_size) {
> +		unsigned long max = hdr->initrd_addr_max;
> +		unsigned long addr, size;
> +
> +		if (hdr->xloadflags & XLF_CAN_BE_LOADED_ABOVE_4G)
> +			max = ULONG_MAX;
> +
> +		status = efi_load_initrd_devpath(&addr, &size, max);
> +		if (status == EFI_SUCCESS) {
> +			hdr->ramdisk_image		= (u32)addr;
> +			hdr->ramdisk_size 		= (u32)size;
> +			boot_params->ext_ramdisk_image	= (u64)addr >> 32;
> +			boot_params->ext_ramdisk_size 	= (u64)size >> 32;
> +		} else if (status != EFI_NOT_FOUND) {
> +			efi_printk("efi_load_initrd_devpath() failed!\n");
> +			goto fail;
> +		}
> +	}
> +

No fallback here; this is not a replacement for efi_load_initrd(), but a
brand new call. Why? (It's probably justified, I just don't know enough
about the kernel.)

>  	/*
>  	 * If the boot loader gave us a value for secure_boot then we use that,
>  	 * otherwise we ask the BIOS.
> diff --git a/include/linux/efi.h b/include/linux/efi.h
> index 9ccf313fe9de..75c83c322c40 100644
> --- a/include/linux/efi.h
> +++ b/include/linux/efi.h
> @@ -353,6 +353,7 @@ void efi_native_runtime_setup(void);
>  #define LINUX_EFI_TPM_EVENT_LOG_GUID		EFI_GUID(0xb7799cb0, 0xeca2, 0x4943,  0x96, 0x67, 0x1f, 0xae, 0x07, 0xb7, 0x47, 0xfa)
>  #define LINUX_EFI_TPM_FINAL_LOG_GUID		EFI_GUID(0x1e2ed096, 0x30e2, 0x4254,  0xbd, 0x89, 0x86, 0x3b, 0xbe, 0xf8, 0x23, 0x25)
>  #define LINUX_EFI_MEMRESERVE_TABLE_GUID		EFI_GUID(0x888eb0c6, 0x8ede, 0x4ff5,  0xa8, 0xf0, 0x9a, 0xee, 0x5c, 0xb9, 0x77, 0xc2)
> +#define LINUX_EFI_INITRD_MEDIA_GUID		EFI_GUID(0x5568e427, 0x68fc, 0x4f3d,  0xac, 0x74, 0xca, 0x55, 0x52, 0x31, 0xcc, 0x68)
>  
>  /* OEM GUIDs */
>  #define DELLEMC_EFI_RCI2_TABLE_GUID		EFI_GUID(0x2d9f28a2, 0xa886, 0x456a,  0x97, 0xa8, 0xf1, 0x1e, 0xf2, 0x4f, 0xf4, 0x55)
> 

Thanks,
Laszlo


^ permalink raw reply	[flat|nested] 39+ messages in thread

* Re: [PATCH 1/2] efi/libstub: add support for loading the initrd from a device path
  2020-02-06 18:26   ` Heinrich Schuchardt
  2020-02-06 18:46     ` Ilias Apalodimas
  2020-02-06 22:35     ` Ard Biesheuvel
@ 2020-02-07 11:03     ` Laszlo Ersek
  2 siblings, 0 replies; 39+ messages in thread
From: Laszlo Ersek @ 2020-02-07 11:03 UTC (permalink / raw)
  To: Heinrich Schuchardt, Ard Biesheuvel, linux-efi
  Cc: linux-arm-kernel, leif, pjones, mjg59, agraf, ilias.apalodimas,
	daniel.kiper

On 02/06/20 19:26, Heinrich Schuchardt wrote:
> On 2/6/20 3:03 PM, Ard Biesheuvel wrote:

>> +    status = efi_bs_call(handle_protocol, handle, &lf2_proto_guid,
>> +                 (void **)&lf2);
>> +    if (status != EFI_SUCCESS)
>> +        return status;
> 
> You require here that there is a handle exposing the device path
> protocol with the initrd specific device path. On the same handle the
> EFI_LOAD_FILE2_PROTOCOL must be installed which will load the initrd
> file when called with the same device path.
> 
> An alternative implementation would simple loop over all instances of
> the EFI_LOAD_FILE2_PROTOCOL and try to load the initrd.

That's not a great idea IMO. EFI_LOAD_FILE2_PROTOCOL instances take
device-specific filenames / pathnames to load. If you pass any
particular pathname (e.g. "initrd" or "\\initrd") to random
EFI_LOAD_FILE2_PROTOCOL instance in the protocol database, there could
be undesired results / side effects. (It could cause network activity,
for example.)

Sticking with a VenMedia (i.e. GUID-ed) devpath is much safer; it
practically lets us define our own device-specific filename / pathname
space. (And in my other email, I suggested "use an empty devpath" for
device-specific pathname, because that's the simplest, it looks
spec-conformat, and it's safe, because our GUID makes the load attempt
unique already.)

I do agree with your question though: "Please, indicate which software
you expect to expose the initrd related EFI_LOAD_FILE2_PROTOCOL."

Thanks
Laszlo


^ permalink raw reply	[flat|nested] 39+ messages in thread

* Re: [PATCH 1/2] efi/libstub: add support for loading the initrd from a device path
  2020-02-06 22:35     ` Ard Biesheuvel
  2020-02-07  0:01       ` Heinrich Schuchardt
@ 2020-02-07 11:09       ` Laszlo Ersek
  1 sibling, 0 replies; 39+ messages in thread
From: Laszlo Ersek @ 2020-02-07 11:09 UTC (permalink / raw)
  To: Ard Biesheuvel, Heinrich Schuchardt
  Cc: Ard Biesheuvel, linux-efi, linux-arm-kernel, Leif Lindholm,
	Peter Jones, Matthew Garrett, Alexander Graf, Ilias Apalodimas,
	Daniel Kiper

On 02/06/20 23:35, Ard Biesheuvel wrote:
> On Thu, 6 Feb 2020 at 18:26, Heinrich Schuchardt <xypron.glpk@gmx.de> wrote:

>> When booting via GRUB it will be GRUB knowing which initrd to load.
>>
> 
> Exactly, which is why GRUB will implement this protocol. That way, it
> does not have to touch the DT at all, or create a bootparams struct
> from setup data and inspect the various flags about placement,
> alignment, preferred addresses, etc.
> 
>> Please, indicate which software you expect to expose the initrd related
>> EFI_LOAD_FILE2_PROTOCOL.
>>
> 
> The primary use case is GRUB and other intermediate loaders, since it
> would remove any need for these components to know any such details.
> My aim is to make the next architecture that gets added to GRUB for
> EFI boot 100% generic.

Understood, thanks. It sounds plausible to me.

Laszlo


^ permalink raw reply	[flat|nested] 39+ messages in thread

* Re: [PATCH 0/2] arch-agnostic initrd loading method for EFI systems
  2020-02-07  9:22   ` Laszlo Ersek
@ 2020-02-07 12:23     ` Ard Biesheuvel
  2020-02-07 16:20       ` James Bottomley
  0 siblings, 1 reply; 39+ messages in thread
From: Ard Biesheuvel @ 2020-02-07 12:23 UTC (permalink / raw)
  To: Laszlo Ersek
  Cc: Ard Biesheuvel, linux-efi, linux-arm-kernel, Leif Lindholm,
	Peter Jones, Matthew Garrett, Alexander Graf, Ilias Apalodimas,
	Heinrich Schuchardt, Daniel Kiper

On Fri, 7 Feb 2020 at 09:22, Laszlo Ersek <lersek@redhat.com> wrote:
>
> On 02/07/20 10:09, Laszlo Ersek wrote:
> > On 02/06/20 15:03, Ard Biesheuvel wrote:
> >> This series introduces an arch agnostic way of loading the initrd into
> >> memory from the EFI stub. This addresses a number of shortcomings that
> >> affect the current implementations that exist across architectures:
> >>
> >> - The initrd=<file> command line option can only load files that reside
> >>   on the same file system that the kernel itself was loaded from, which
> >>   requires the bootloader or firmware to expose that file system via the
> >>   appropriate EFI protocol, which is not always feasible. From the kernel
> >>   side, this protocol is problematic since it is incompatible with mixed
> >>   mode on x86 (this is due to the fact that some of its methods have
> >>   prototypes that are difficult to marshall)
> >>
> >> - The approach that is ordinarily taken by GRUB is to load the initrd into
> >>   memory, and pass it to the kernel proper via the bootparams structure or
> >>   via the device tree. This requires the boot loader to have an understanding
> >>   of those structures, which are not always set in stone, and of the policies
> >>   around where the initrd may be loaded into memory. In the ARM case, it
> >>   requires GRUB to modify the hardware description provided by the firmware,
> >>   given that the initrd base and offset in memory are passed via the same
> >>   data structure. It also creates a time window where the initrd data sits
> >>   in memory, and can potentially be corrupted before the kernel is booted.
> >>
> >> Considering that we will soon have new users of these interfaces (EFI for
> >> kvmtool on ARM, RISC-V in u-boot, etc), it makes sense to add a generic
> >> interface now, before having another wave of bespoke arch specific code
> >> coming in.
> >>
> >> Another aspect to take into account is that support for UEFI secure boot
> >> and measured boot is being taken into the upstream, and being able to
> >> rely on the PE entry point for booting any architecture makes the GRUB
> >> vs shim story much cleaner, as we should be able to rely on LoadImage
> >> and StartImage on all architectures, while retaining the ability to
> >> load initrds from anywhere.
> >>
> >> Note that these patches depend on a fair amount of cleanup work that I
> >> am targetting for v5.7. Branch can be found at:
> >> https://git.kernel.org/pub/scm/linux/kernel/git/ardb/linux.git/log/?h=efistub-unification2
> >>
> >> An implementation for ArmVirtQemu (OVMF for ARM aka AAVMF) can be found
> >> at https://github.com/ardbiesheuvel/edk2/commits/linux-efi-generic.
> >> The change is for ARM only, but the exact same code could be used on x86.
> >
> > I like this ArmVirtQemu feature, but I think it should be implemented as
> > an addition, rather than a replacement. Older kernels (older EFI stubs)
> > will try to fetch the initrd from the same fs where grub loaded the
> > kernel from (exactly as you describe in the blurb).
> >

Agreed. The ArmVirtQemu change is not intended for merging, but
primarily as a test rig for the kernel changes.

> > For example, virt-install's "--location" option "can recognize certain
> > distribution trees and fetches a bootable kernel/initrd pair to launch
> > the install". It would be nice to keep that working for older distros.
> >
> > I think LoadFile[2] can co-exist with SimpleFs.
> >
> > I also think that the "try SimpleFs first, fall back to LoadFile[2]
> > second" requirement applies only to the UEFI boot manager, and not to
> > the kernel's EFI stub. IOW in the new approach the kernel is free to
> > ignore (abandon) the old approach for good.
>
> ... But that might not be good for compatibility with grub and/or the
> platform firmware, from the kernel's own perspective, perhaps?...
>
> Who is supposed to produce LoadFile2 with the new VenMedia devpath?
>

What I am ultimately after is a generic GRUB that uses
LoadImage+Startimage for starting the kernel on all architectures, and
is able to load the initrd from anywhere in an arch agnostic manner.

Additionally, we might have
- an implementation for OVMF/AAVMF,
- a EDK2 UEFI Shell command that takes a shell file path to provide
the Linux initrd
- a uboot implementation that passes the initrd this way.

This series is the first step, to align between all the stakeholders
on the approach for this aspect, before taking it any further.

^ permalink raw reply	[flat|nested] 39+ messages in thread

* Re: [PATCH 1/2] efi/libstub: add support for loading the initrd from a device path
  2020-02-07  9:48   ` Laszlo Ersek
@ 2020-02-07 12:36     ` Ard Biesheuvel
  2020-02-10 14:26       ` Laszlo Ersek
  0 siblings, 1 reply; 39+ messages in thread
From: Ard Biesheuvel @ 2020-02-07 12:36 UTC (permalink / raw)
  To: Laszlo Ersek
  Cc: Ard Biesheuvel, linux-efi, linux-arm-kernel, Leif Lindholm,
	Peter Jones, Matthew Garrett, Alexander Graf, Ilias Apalodimas,
	Heinrich Schuchardt, Daniel Kiper

On Fri, 7 Feb 2020 at 09:48, Laszlo Ersek <lersek@redhat.com> wrote:
>
> On 02/06/20 15:03, Ard Biesheuvel wrote:
> > There are currently two ways to specify the initrd to be passed to the
> > Linux kernel when booting via the EFI stub:
> > - it can be passed as a initrd= command line option when doing a pure PE
> >   boot (as opposed to the EFI handover protocol that exists for x86)
> > - otherwise, the bootloader or firmware can load the initrd into memory,
> >   and pass the address and size via the bootparams struct (x86) or
> >   device tree (ARM)
> >
> > In the first case, we are limited to loading from the same file system
> > that the kernel was loaded from, and it is also problematic in a trusted
> > boot context, given that we cannot easily protect the command line from
> > tampering without either adding complicated white/blacklisting of boot
> > arguments or locking down the command line altogether.
> >
> > In the second case, we force the bootloader to duplicate knowledge about
> > the boot protocol which is already encoded in the stub, and which may be
> > subject to change over time, e.g., bootparams struct definitions, memory
> > allocation/alignment requirements for the placement of the initrd etc etc.
> > In the ARM case, it also requires the bootloader to modify the hardware
> > description provided by the firmware, as it is passed in the same file.
> > On systems where the initrd is measured after loading, it creates a time
> > window where the initrd contents might be manipulated in memory before
> > handing over to the kernel.
> >
> > Address these concerns by adding support for loading the initrd into
> > memory by invoking the EFI LoadFile2 protocol installed on a vendor
> > GUIDed device path that specifically designates a Linux initrd.
> > This addresses the above concerns, by putting the EFI stub in charge of
> > placement in memory and of passing the base and size to the kernel proper
> > (via whatever means it desires) while still leaving it up to the firmware
> > or bootloader to obtain the file contents, potentially from other file
> > systems than the one the kernel itself was loaded from. On platforms that
> > implement measured boot, it permits the firmware to take the measurement
> > right before the kernel actually consumes the contents.
> >
> > Signed-off-by: Ard Biesheuvel <ardb@kernel.org>
> > ---
> >  drivers/firmware/efi/libstub/arm-stub.c        | 16 +++--
> >  drivers/firmware/efi/libstub/efi-stub-helper.c | 65 ++++++++++++++++++++
> >  drivers/firmware/efi/libstub/efistub.h         | 12 ++++
> >  drivers/firmware/efi/libstub/x86-stub.c        | 36 ++++++++++-
> >  include/linux/efi.h                            |  1 +
> >  5 files changed, 123 insertions(+), 7 deletions(-)
> >
> > diff --git a/drivers/firmware/efi/libstub/arm-stub.c b/drivers/firmware/efi/libstub/arm-stub.c
> > index c7b091f50e55..1db943c1ba2b 100644
> > --- a/drivers/firmware/efi/libstub/arm-stub.c
> > +++ b/drivers/firmware/efi/libstub/arm-stub.c
> > @@ -157,6 +157,7 @@ unsigned long efi_entry(void *handle, efi_system_table_t *sys_table_arg,
> >       enum efi_secureboot_mode secure_boot;
> >       struct screen_info *si;
> >       efi_properties_table_t *prop_tbl;
> > +     unsigned long max_addr;
> >
> >       sys_table = sys_table_arg;
> >
> > @@ -255,11 +256,18 @@ unsigned long efi_entry(void *handle, efi_system_table_t *sys_table_arg,
> >       if (!fdt_addr)
> >               pr_efi("Generating empty DTB\n");
> >
> > -     status = efi_load_initrd(image, ULONG_MAX,
> > -                              efi_get_max_initrd_addr(dram_base, *image_addr),
> > -                              &initrd_addr, &initrd_size);
> > +     max_addr = efi_get_max_initrd_addr(dram_base, *image_addr);
> > +     status = efi_load_initrd_devpath(&initrd_addr, &initrd_size, max_addr);
> > +     if (status == EFI_SUCCESS)
> > +             pr_efi("Loaded initrd from LINUX_EFI_INITRD_MEDIA_GUID device path\n");
> > +     else if (status == EFI_NOT_FOUND) {
> > +             status = efi_load_initrd(image, ULONG_MAX, max_addr,
> > +                                      &initrd_addr, &initrd_size);
>
> - So this seems to be fallback#1, for ARM, which looks good.
>
> - Are you sure you only want to fall back to the old method on
> EFI_NOT_FOUND? Wouldn't other return values from
> efi_load_initrd_devpath() justify that too?
>
> ... After checking the boot services called in
> efi_load_initrd_devpath(), this idea seems reasonable, but then I'd
> suggest documenting the significance of returning EFI_NOT_FOUND near the
> efi_load_initrd_devpath() function declaration, in "efistub.h".
>

Good point.

> > +             if (status == EFI_SUCCESS)
> > +                     pr_efi("Loaded initrd from command line option\n");
> > +     }
> >       if (status != EFI_SUCCESS)
> > -             pr_efi_err("Failed initrd from command line!\n");
> > +             pr_efi_err("Failed to load initrd!\n");
> >
> >       efi_random_get_seed();
> >
> > diff --git a/drivers/firmware/efi/libstub/efi-stub-helper.c b/drivers/firmware/efi/libstub/efi-stub-helper.c
> > index 8e60a39df3c5..eaf45ea749b3 100644
> > --- a/drivers/firmware/efi/libstub/efi-stub-helper.c
> > +++ b/drivers/firmware/efi/libstub/efi-stub-helper.c
> > @@ -323,3 +323,68 @@ void efi_char16_printk(efi_char16_t *str)
> >       efi_call_proto(efi_table_attr(efi_system_table(), con_out),
> >                      output_string, str);
> >  }
> > +
> > +static const struct {
> > +     struct efi_vendor_dev_path      vendor;
> > +     struct efi_generic_dev_path     end;
> > +} __packed initrd_devpath = {
> > +     {
> > +             EFI_DEV_MEDIA,
> > +             EFI_DEV_MEDIA_VENDOR,
> > +             sizeof(struct efi_vendor_dev_path),
> > +             LINUX_EFI_INITRD_MEDIA_GUID
> > +     }, {
> > +             EFI_DEV_END_PATH,
> > +             EFI_DEV_END_ENTIRE,
> > +             sizeof(struct efi_generic_dev_path)
> > +     }
> > +};
> > +
> > +efi_status_t efi_load_initrd_devpath(unsigned long *load_addr,
> > +                                  unsigned long *load_size,
> > +                                  unsigned long max)
> > +{
> > +     efi_guid_t lf2_proto_guid = EFI_LOAD_FILE2_PROTOCOL_GUID;
> > +     efi_device_path_protocol_t *dp;
> > +     efi_load_file2_protocol_t *lf2;
> > +     unsigned long initrd_addr;
> > +     unsigned long initrd_size;
> > +     efi_handle_t handle;
> > +     efi_status_t status;
> > +
> > +     if (!load_addr || !load_size)
> > +             return EFI_INVALID_PARAMETER;
> > +
> > +     dp = (efi_device_path_protocol_t *)&initrd_devpath;
> > +     status = efi_bs_call(locate_device_path, &lf2_proto_guid, &dp, &handle);
> > +     if (status != EFI_SUCCESS)
> > +             return status;
> > +
> > +     status = efi_bs_call(handle_protocol, handle, &lf2_proto_guid,
> > +                          (void **)&lf2);
> > +     if (status != EFI_SUCCESS)
> > +             return status;
> > +
> > +     initrd_size = 0;
> > +     status = efi_call_proto(lf2, load_file,
> > +                             (efi_device_path_protocol_t *)&initrd_devpath,
> > +                             false, &initrd_size, NULL);
>
> The second argument to EFI_LOAD_FILE2_PROTOCOL.LoadFile() is "FilePath",
> specified as "The device specific path of the file to load". This means
> it is supposed to be a (possibly empty) sequence of FILEPATH_DEVICE_PATH
> nodes, terminated by and "End Entire Device Path" node. See
>
> - 10.3.1 Generic Device Path Structures
> - 10.3.5.4 File Path Media Device Path
>
> in UEFI-2.8.
>
> And "initrd_devpath" is not a device path like that; instead it's the
> VenMedia device path that's installed on the handle that also carries
> our LoadFile2 instance.
>

OK, so you are saying this could be used to disambiguate which of
several files you may want to load from the initrd GUIDed device path?

> Now, I do see that this all theoretical here, as we don't expect the
> LoadFile2 instance that we've found via our special
> LINUX_EFI_INITRD_MEDIA_GUID VenMedia devpath to do *any* device-specific
> filename / pathname parsing.
>
> But in that case (i.e., given that the FilePath argument is totally
> irrelevant), I think it's much clearer if we simply pass an empty device
> path -- one that consists of a single "End Entire Device Path" node.
>
> I've checked, and your ArmVirtQemu patch ignores the FilePath argument
> too -- justifiedly so. I just think it's better to pass in a well-formed
> device path, rather than NULL. Because, the FilePath parameter is not
> marked OPTIONAL in the spec.
>

One thing that occurred to me is that we have to decide whether we
want to support the '10.3.5.8 Relative Offset Range' device path node
for this file, so that you could potentially load subranges of the
file. I don't see a use case for it right now, though.

But for my understanding, would the FilePath passed to LoadFile2 be
'Offset(...)+EndEntire' in that case? Or should it include the GUID
device path node as well?

> > +     if (status != EFI_BUFFER_TOO_SMALL)
> > +             return EFI_LOAD_ERROR;
> > +
> > +     status = efi_allocate_pages(initrd_size, &initrd_addr, max);
> > +     if (status != EFI_SUCCESS)
> > +             return status;
> > +
> > +     status = efi_call_proto(lf2, load_file,
> > +                             (efi_device_path_protocol_t *)&initrd_devpath,
> > +                             false, &initrd_size, (void *)initrd_addr);
>
> Same here.
>
> > +     if (status != EFI_SUCCESS) {
> > +             efi_free(initrd_size, initrd_addr);
> > +             return status;
> > +     }
> > +
> > +     *load_addr = initrd_addr;
> > +     *load_size = initrd_size;
> > +     return EFI_SUCCESS;
> > +}
> > diff --git a/drivers/firmware/efi/libstub/efistub.h b/drivers/firmware/efi/libstub/efistub.h
> > index 99e93fd76ec5..fbf9f9442eed 100644
> > --- a/drivers/firmware/efi/libstub/efistub.h
> > +++ b/drivers/firmware/efi/libstub/efistub.h
> > @@ -566,6 +566,14 @@ union efi_load_file_protocol {
> >       } mixed_mode;
> >  };
> >
> > +struct efi_vendor_dev_path {
> > +     u8              type;
> > +     u8              sub_type;
> > +     u16             length;
> > +     efi_guid_t      vendorguid;
> > +     u8              vendordata[];
> > +} __packed;
> > +
> >  void efi_pci_disable_bridge_busmaster(void);
> >
> >  typedef efi_status_t (*efi_exit_boot_map_processing)(
> > @@ -651,4 +659,8 @@ efi_status_t efi_load_initrd(efi_loaded_image_t *image,
> >                            unsigned long *load_addr,
> >                            unsigned long *load_size);
> >
> > +efi_status_t efi_load_initrd_devpath(unsigned long *load_addr,
> > +                                  unsigned long *load_size,
> > +                                  unsigned long max);
> > +
> >  #endif
> > diff --git a/drivers/firmware/efi/libstub/x86-stub.c b/drivers/firmware/efi/libstub/x86-stub.c
> > index f3e2ff31b624..7f38f95676dd 100644
> > --- a/drivers/firmware/efi/libstub/x86-stub.c
> > +++ b/drivers/firmware/efi/libstub/x86-stub.c
> > @@ -419,9 +419,20 @@ efi_status_t __efiapi efi_pe_entry(efi_handle_t handle,
> >       if (status != EFI_SUCCESS)
> >               goto fail2;
> >
> > -     status = efi_load_initrd(image, hdr->initrd_addr_max,
> > -                              above4g ? ULONG_MAX : hdr->initrd_addr_max,
> > -                              &ramdisk_addr, &ramdisk_size);
> > +     /*
> > +      * The initrd loaded from the Linux initrd vendor device
> > +      * path should take precedence, as we don't want the
> > +      * [unverified] command line to override the initrd
> > +      * supplied by the [potentially verified] firmware.
> > +      */
> > +     status = efi_load_initrd_devpath(&ramdisk_addr, &ramdisk_size,
> > +                                      above4g ? ULONG_MAX
> > +                                              : hdr->initrd_addr_max);
> > +     if (status == EFI_NOT_FOUND)
> > +             status = efi_load_initrd(image, hdr->initrd_addr_max,
> > +                                      above4g ? ULONG_MAX
> > +                                              : hdr->initrd_addr_max,
> > +                                      &ramdisk_addr, &ramdisk_size);
>
> Fallback#2, for x86, also looks good.
>
> >       if (status != EFI_SUCCESS)
> >               goto fail2;
> >       hdr->ramdisk_image = ramdisk_addr & 0xffffffff;
> > @@ -732,6 +743,25 @@ struct boot_params *efi_main(efi_handle_t handle,
> >                        ((u64)boot_params->ext_cmd_line_ptr << 32));
> >       efi_parse_options((char *)cmdline_paddr);
> >
> > +     if (!hdr->ramdisk_size && !boot_params->ext_ramdisk_size) {
> > +             unsigned long max = hdr->initrd_addr_max;
> > +             unsigned long addr, size;
> > +
> > +             if (hdr->xloadflags & XLF_CAN_BE_LOADED_ABOVE_4G)
> > +                     max = ULONG_MAX;
> > +
> > +             status = efi_load_initrd_devpath(&addr, &size, max);
> > +             if (status == EFI_SUCCESS) {
> > +                     hdr->ramdisk_image              = (u32)addr;
> > +                     hdr->ramdisk_size               = (u32)size;
> > +                     boot_params->ext_ramdisk_image  = (u64)addr >> 32;
> > +                     boot_params->ext_ramdisk_size   = (u64)size >> 32;
> > +             } else if (status != EFI_NOT_FOUND) {
> > +                     efi_printk("efi_load_initrd_devpath() failed!\n");
> > +                     goto fail;
> > +             }
> > +     }
> > +
>
> No fallback here; this is not a replacement for efi_load_initrd(), but a
> brand new call. Why? (It's probably justified, I just don't know enough
> about the kernel.)
>

Yes, it is. efi_main() is called after efi_pe_entry() in the native PE
boot case, and it is the only one being called when using the EFI
handover protocol.

So the expected new flow would be for efi_pe_entry() to run first and
load the initrd, in which case this code would not run. However, it
would be better to still support the EFI handover protocol when using
this method of loading the initrd, not only for convenience, but also
because we cannot call efi_pe_entry() when running in mixed mode.

Interestingly, LoadImage() can be used to load 64-bit images on 32-bit
EDK2 firmware today (and I have to check whether that holds for mixed
mode Mac hardware as well), only StartImage() will fail. It all
depends on whether we still care about mixed mode in the secure boot
context going forward, but if we do, we may be able to use LoadImage()
but then jump to this entry point (via startup_32 in head_32.S)

So the bottom line is that we have to keep this entry point alive for
the time being, but there is no reason to add initrd loading to it
using the old method, since we can't support it very well in mixed
mode (due to the prototypes of the protocols) and we don't support
that now anyway.

> >       /*
> >        * If the boot loader gave us a value for secure_boot then we use that,
> >        * otherwise we ask the BIOS.
> > diff --git a/include/linux/efi.h b/include/linux/efi.h
> > index 9ccf313fe9de..75c83c322c40 100644
> > --- a/include/linux/efi.h
> > +++ b/include/linux/efi.h
> > @@ -353,6 +353,7 @@ void efi_native_runtime_setup(void);
> >  #define LINUX_EFI_TPM_EVENT_LOG_GUID         EFI_GUID(0xb7799cb0, 0xeca2, 0x4943,  0x96, 0x67, 0x1f, 0xae, 0x07, 0xb7, 0x47, 0xfa)
> >  #define LINUX_EFI_TPM_FINAL_LOG_GUID         EFI_GUID(0x1e2ed096, 0x30e2, 0x4254,  0xbd, 0x89, 0x86, 0x3b, 0xbe, 0xf8, 0x23, 0x25)
> >  #define LINUX_EFI_MEMRESERVE_TABLE_GUID              EFI_GUID(0x888eb0c6, 0x8ede, 0x4ff5,  0xa8, 0xf0, 0x9a, 0xee, 0x5c, 0xb9, 0x77, 0xc2)
> > +#define LINUX_EFI_INITRD_MEDIA_GUID          EFI_GUID(0x5568e427, 0x68fc, 0x4f3d,  0xac, 0x74, 0xca, 0x55, 0x52, 0x31, 0xcc, 0x68)
> >
> >  /* OEM GUIDs */
> >  #define DELLEMC_EFI_RCI2_TABLE_GUID          EFI_GUID(0x2d9f28a2, 0xa886, 0x456a,  0x97, 0xa8, 0xf1, 0x1e, 0xf2, 0x4f, 0xf4, 0x55)
> >
>
> Thanks,
> Laszlo
>

^ permalink raw reply	[flat|nested] 39+ messages in thread

* Re: [PATCH 1/2] efi/libstub: add support for loading the initrd from a device path
  2020-02-07  8:12             ` Ard Biesheuvel
@ 2020-02-07 13:30               ` Heinrich Schuchardt
  2020-02-07 13:58                 ` Ard Biesheuvel
  0 siblings, 1 reply; 39+ messages in thread
From: Heinrich Schuchardt @ 2020-02-07 13:30 UTC (permalink / raw)
  To: Ard Biesheuvel
  Cc: Ard Biesheuvel, linux-efi, linux-arm-kernel, Laszlo Ersek,
	Leif Lindholm, Peter Jones, Matthew Garrett, Alexander Graf,
	Ilias Apalodimas, Daniel Kiper



On 2/7/20 9:12 AM, Ard Biesheuvel wrote:
> On Fri, 7 Feb 2020 at 00:58, Heinrich Schuchardt <xypron.glpk@gmx.de> wrote:
>>
>> On 2/7/20 1:21 AM, Ard Biesheuvel wrote:
>>> On Fri, 7 Feb 2020 at 00:01, Heinrich Schuchardt <xypron.glpk@gmx.de> wrote:
>>>>
>>>> On 2/6/20 11:35 PM, Ard Biesheuvel wrote:
>>>>> On Thu, 6 Feb 2020 at 18:26, Heinrich Schuchardt <xypron.glpk@gmx.de> wrote:
> ...
>>>>>> Please, indicate which software you expect to expose the initrd related
>>>>>> EFI_LOAD_FILE2_PROTOCOL.
>>>>>>
>>>>>
>>>>> The primary use case is GRUB and other intermediate loaders, since it
>>>>> would remove any need for these components to know any such details.
>>>>> My aim is to make the next architecture that gets added to GRUB for
>>>>> EFI boot 100% generic.
>>>>>
>>>>>> Using an UEFI variable for passing the initrd device path would be a
>>>>>> leaner solution on the bootloader side than requiring an extra
>>>>>> EFI_LOAD_FILE2_PROTOCOL implementation.
>>>>>>
>>>>>
>>>>> This would also require kernel changes, since we don't currently load
>>>>> initrds from arbitrary device paths. The EFI_FILE_PROTOCOL is much
>>>>> more complicated than needed, and it doesn't work well with mixed
>>>>> mode. It also requires GRUB to expose the filesystem it loads the
>>>>> initrd from via EFI protocols, which is currently unnecessary and
>>>>> therefore not implemented.
>>>>
>>>> This means you move the complexity of EFI_FILE_PROTOCOL from Linux to GRUB.
>>>>
>>>
>>> No. I am not interested in EFI_FILE_PROTOCOL, only in LoadFile2, which
>>> is a single method that needs to be implemented.
>>
>> I said you move complexity because GRUB will need to use the
>> EFI_FILE_PROTOCOL to do the job that you do not want to do in Linux.
>>
>>>
>>>> I would not have a problem if this would only touch GRUB. But if listen
>>>> to Ilias we are replacing one implementation in Linux by one in GRUB and
>>>> one in U-Boot and one in EDK2 and one in any other firmware.
>>>>
>>>
>>> If u-boot will be used to boot RISC-V in EFI mode without GRUB, then I
>>> expect that we will need an implementation of this in u-boot.
>>
>> What sets RISC-V apart? GRUB for RISC-V is available.
  >>
>
> RISC-V EFI boot is not supported yet in upstream Linux.

It is currently prepared Atish Patra of WDC.

>
>>>
>>>>>
>>>>> Also, using an EFI variable defeats the purpose. The whole point of
>>>>> this is making it more likely that the kernel loaded the initrd that
>>>>> the bootloader or firmware intended it to load, and having a piece of
>>>>> simple [signed] code that implements this is the easiest way to
>>>>> achieve that.
>>>>
>>>> At least on my Debian system it is the operating system creating initrd
>>>> and defining which initrd matches which kernel. GRUB simply assumes that
>>>> files ending on the same version number match. Therefore I would say
>>>> Linux hopes that GRUB loads what Linux intended.
>>>>
>>>> The chain of trust would not be broken if the kernel were responsible
>>>> for loading the initrd and for checking if it matches the kernel. Linux
>>>> already does this for the kernel modules in initrd.
>>>>
>>>
>>> We can still sign the initrd and Linux can verify the signature. What
>>> I am after is an interface that does not require the initrd to
>>> originate from a EFI file system protocol, and which doesn't require
>>> the loaded initrd to sit in memory for an unspecified amount of time
>>> and its information passed via DT properties or bootparams structs.
>>>
>>> So invoking EFI_FILE_PROTOCOL directly is not going to work,
>>> regardless of whether we get the devicepath from the command line or
>>> from a EFI variable.
>>
>> What do you mean by "is not going to work"?
>>
>> With the device path you can find the handle implementing the
>> EFI_SIMPLE_FIL_SYSTEM_PROTOCOL.
>>
>>>
>>>>>
>>>>> For u-boot, it should be trivial to implement a simple LoadFile2
>>>>> protocol wrapper around EFI_FILE_PROTOCOL that can be installed on a
>>>>> handle that also carries EFI_FILE_PROTOCOL.
>>>>>
>>>>
>>>> A U-Boot implementation of the EFI_LOAD_FILE2_PROTOCOL would need a
>>>> device path variable to find the block device and to open the
>>>> EFI_SIMPLE_FILE_SYSTEM_PROTOCOL before accessing the file.
>>>>
>>>> Linux would not be needing more lines and we would not repeat the same
>>>> code in GRUB, U-Boot, EDK2, etc.
>>>>
>>>> As said Linux updates the initrd often. If that file is not signed by
>>>> Linux in a well defined way, do not expect any security at all.
>>>>
>>>
>>> It is not only about security. The primary goal is to remove the need
>>> for arch specific knowledge in the firmware about DT, bootparams and
>>> initrd allocation policies without being forced to load the initrd
>>> from a filesystem that is exposed via a EFI protocol.
>>
>> Where are device-trees touched by this patch?
>>
>> When booting via UEFI there is no need for knowledge of initrd
>> allocation policies in U-Boot because up to now Linux or GRUB or iPXE
>> load initrd.
>>
>> Furthermore I need no knowledge of bootparams in U-Boot once we properly
>> we support UEFI variables at runtime because grub-update will pass the
>> command line in one of the Bootxxxx UEFI variables.
>>
>> But most importantly I do not have to implement anything Linux specific
>> in U-Boot for booting via UEFI up to now.
>>
>
> Adding Linux specific stuff to u-boot is arguably more appropriate
> than adding architecture specific stuff to EFI loaders that could
> otherwise be entirely generic.
>
> ...
>>
>> Your patch claims to fend off a specific threat scenario: A user puts an
>> untrusted initrd on the disk and references it in the Linux command line.
>>
>> If he is able to do so with your current bootloader (signed or not
>> signed), he most probably will also be able to delete a good initrd from
>> the filesystem and thus force your code into the unsafe path.
>>
>> That is why I say that with the current fallback logic this patch
>> achieves no increase in security. Of cause you could remove the fallback
>> logic. But in this case your Linux will not boot with any legacy
>> bootloader or firmware.
>>
>
> If there is a better way to expose the initrd that
> a) does not require the initrd to reside on a file system that is
> accessible via EFI protocols, and
> b) does not require the loader to know about arch specific policies
> regarding the placement of the initrd in memory, and
> c) does not leave a time window between the time that the initrd is
> loaded/verified/measured by the firmware and the time that the kernel
> gets handed the buffer
>
> then I am happy to discuss it. This proposal is the best I could come
> up with to achieve the above.
>

Hello Ard,

I think part of our different views is that we are thinking about two
different use cases which both have their relevance:

If I understand you correctly, you are thinking about an embedded device
where the kernel and the initrd is essentially part of the firmware
provided by the device.

I am thinking of a system running a standard Linux distribution like
Debian where the initrd is generated by the operating system

In both use cases verifying the initrd is of importance.

Now concerning the requirements:

a) In U-Boot all file systems on block devices can be made accessible
via EFI protocols. Are you thinking about initrds that are not in a file
system?

b) My suggestion to use a UEFI variable for communicating the device
path would not require any arch specific policies either.

c) I proposed that the kernel does the verification. So there would be
equally nothing in between loading the file and its verification. Yet
responsibilities would be changed.

But possibly I missed some requirements you have in mind that I should
consider.

Best regards

Heinrich


^ permalink raw reply	[flat|nested] 39+ messages in thread

* Re: [PATCH 1/2] efi/libstub: add support for loading the initrd from a device path
  2020-02-07 13:30               ` Heinrich Schuchardt
@ 2020-02-07 13:58                 ` Ard Biesheuvel
  2020-02-07 14:18                   ` Alexander Graf
  0 siblings, 1 reply; 39+ messages in thread
From: Ard Biesheuvel @ 2020-02-07 13:58 UTC (permalink / raw)
  To: Heinrich Schuchardt
  Cc: Ard Biesheuvel, linux-efi, linux-arm-kernel, Laszlo Ersek,
	Leif Lindholm, Peter Jones, Matthew Garrett, Alexander Graf,
	Ilias Apalodimas, Daniel Kiper

On Fri, 7 Feb 2020 at 13:30, Heinrich Schuchardt <xypron.glpk@gmx.de> wrote:
>
>
>
> On 2/7/20 9:12 AM, Ard Biesheuvel wrote:
> > On Fri, 7 Feb 2020 at 00:58, Heinrich Schuchardt <xypron.glpk@gmx.de> wrote:
> >>
> >> On 2/7/20 1:21 AM, Ard Biesheuvel wrote:
> >>> On Fri, 7 Feb 2020 at 00:01, Heinrich Schuchardt <xypron.glpk@gmx.de> wrote:
> >>>>
> >>>> On 2/6/20 11:35 PM, Ard Biesheuvel wrote:
> >>>>> On Thu, 6 Feb 2020 at 18:26, Heinrich Schuchardt <xypron.glpk@gmx.de> wrote:
> > ...
> >>>>>> Please, indicate which software you expect to expose the initrd related
> >>>>>> EFI_LOAD_FILE2_PROTOCOL.
> >>>>>>
> >>>>>
> >>>>> The primary use case is GRUB and other intermediate loaders, since it
> >>>>> would remove any need for these components to know any such details.
> >>>>> My aim is to make the next architecture that gets added to GRUB for
> >>>>> EFI boot 100% generic.
> >>>>>
> >>>>>> Using an UEFI variable for passing the initrd device path would be a
> >>>>>> leaner solution on the bootloader side than requiring an extra
> >>>>>> EFI_LOAD_FILE2_PROTOCOL implementation.
> >>>>>>
> >>>>>
> >>>>> This would also require kernel changes, since we don't currently load
> >>>>> initrds from arbitrary device paths. The EFI_FILE_PROTOCOL is much
> >>>>> more complicated than needed, and it doesn't work well with mixed
> >>>>> mode. It also requires GRUB to expose the filesystem it loads the
> >>>>> initrd from via EFI protocols, which is currently unnecessary and
> >>>>> therefore not implemented.
> >>>>
> >>>> This means you move the complexity of EFI_FILE_PROTOCOL from Linux to GRUB.
> >>>>
> >>>
> >>> No. I am not interested in EFI_FILE_PROTOCOL, only in LoadFile2, which
> >>> is a single method that needs to be implemented.
> >>
> >> I said you move complexity because GRUB will need to use the
> >> EFI_FILE_PROTOCOL to do the job that you do not want to do in Linux.
> >>
> >>>
> >>>> I would not have a problem if this would only touch GRUB. But if listen
> >>>> to Ilias we are replacing one implementation in Linux by one in GRUB and
> >>>> one in U-Boot and one in EDK2 and one in any other firmware.
> >>>>
> >>>
> >>> If u-boot will be used to boot RISC-V in EFI mode without GRUB, then I
> >>> expect that we will need an implementation of this in u-boot.
> >>
> >> What sets RISC-V apart? GRUB for RISC-V is available.
>   >>
> >
> > RISC-V EFI boot is not supported yet in upstream Linux.
>
> It is currently prepared Atish Patra of WDC.
>

Exactly. So it is not in the upstream yet, and I want to converge on a
sane generic interface before it gets merged.

> >
> >>>
> >>>>>
> >>>>> Also, using an EFI variable defeats the purpose. The whole point of
> >>>>> this is making it more likely that the kernel loaded the initrd that
> >>>>> the bootloader or firmware intended it to load, and having a piece of
> >>>>> simple [signed] code that implements this is the easiest way to
> >>>>> achieve that.
> >>>>
> >>>> At least on my Debian system it is the operating system creating initrd
> >>>> and defining which initrd matches which kernel. GRUB simply assumes that
> >>>> files ending on the same version number match. Therefore I would say
> >>>> Linux hopes that GRUB loads what Linux intended.
> >>>>
> >>>> The chain of trust would not be broken if the kernel were responsible
> >>>> for loading the initrd and for checking if it matches the kernel. Linux
> >>>> already does this for the kernel modules in initrd.
> >>>>
> >>>
> >>> We can still sign the initrd and Linux can verify the signature. What
> >>> I am after is an interface that does not require the initrd to
> >>> originate from a EFI file system protocol, and which doesn't require
> >>> the loaded initrd to sit in memory for an unspecified amount of time
> >>> and its information passed via DT properties or bootparams structs.
> >>>
> >>> So invoking EFI_FILE_PROTOCOL directly is not going to work,
> >>> regardless of whether we get the devicepath from the command line or
> >>> from a EFI variable.
> >>
> >> What do you mean by "is not going to work"?
> >>
> >> With the device path you can find the handle implementing the
> >> EFI_SIMPLE_FIL_SYSTEM_PROTOCOL.
> >>
> >>>
> >>>>>
> >>>>> For u-boot, it should be trivial to implement a simple LoadFile2
> >>>>> protocol wrapper around EFI_FILE_PROTOCOL that can be installed on a
> >>>>> handle that also carries EFI_FILE_PROTOCOL.
> >>>>>
> >>>>
> >>>> A U-Boot implementation of the EFI_LOAD_FILE2_PROTOCOL would need a
> >>>> device path variable to find the block device and to open the
> >>>> EFI_SIMPLE_FILE_SYSTEM_PROTOCOL before accessing the file.
> >>>>
> >>>> Linux would not be needing more lines and we would not repeat the same
> >>>> code in GRUB, U-Boot, EDK2, etc.
> >>>>
> >>>> As said Linux updates the initrd often. If that file is not signed by
> >>>> Linux in a well defined way, do not expect any security at all.
> >>>>
> >>>
> >>> It is not only about security. The primary goal is to remove the need
> >>> for arch specific knowledge in the firmware about DT, bootparams and
> >>> initrd allocation policies without being forced to load the initrd
> >>> from a filesystem that is exposed via a EFI protocol.
> >>
> >> Where are device-trees touched by this patch?
> >>
> >> When booting via UEFI there is no need for knowledge of initrd
> >> allocation policies in U-Boot because up to now Linux or GRUB or iPXE
> >> load initrd.
> >>
> >> Furthermore I need no knowledge of bootparams in U-Boot once we properly
> >> we support UEFI variables at runtime because grub-update will pass the
> >> command line in one of the Bootxxxx UEFI variables.
> >>
> >> But most importantly I do not have to implement anything Linux specific
> >> in U-Boot for booting via UEFI up to now.
> >>
> >
> > Adding Linux specific stuff to u-boot is arguably more appropriate
> > than adding architecture specific stuff to EFI loaders that could
> > otherwise be entirely generic.
> >
> > ...
> >>
> >> Your patch claims to fend off a specific threat scenario: A user puts an
> >> untrusted initrd on the disk and references it in the Linux command line.
> >>
> >> If he is able to do so with your current bootloader (signed or not
> >> signed), he most probably will also be able to delete a good initrd from
> >> the filesystem and thus force your code into the unsafe path.
> >>
> >> That is why I say that with the current fallback logic this patch
> >> achieves no increase in security. Of cause you could remove the fallback
> >> logic. But in this case your Linux will not boot with any legacy
> >> bootloader or firmware.
> >>
> >
> > If there is a better way to expose the initrd that
> > a) does not require the initrd to reside on a file system that is
> > accessible via EFI protocols, and
> > b) does not require the loader to know about arch specific policies
> > regarding the placement of the initrd in memory, and
> > c) does not leave a time window between the time that the initrd is
> > loaded/verified/measured by the firmware and the time that the kernel
> > gets handed the buffer
> >
> > then I am happy to discuss it. This proposal is the best I could come
> > up with to achieve the above.
> >
>
> Hello Ard,
>
> I think part of our different views is that we are thinking about two
> different use cases which both have their relevance:
>
> If I understand you correctly, you are thinking about an embedded device
> where the kernel and the initrd is essentially part of the firmware
> provided by the device.
>
> I am thinking of a system running a standard Linux distribution like
> Debian where the initrd is generated by the operating system
>
> In both use cases verifying the initrd is of importance.
>
> Now concerning the requirements:
>
> a) In U-Boot all file systems on block devices can be made accessible
> via EFI protocols. Are you thinking about initrds that are not in a file
> system?
>

The typical GRUB deployment keeps the core GRUB itself (or the entire
thing if it is built as standalone) in the ESP, and the GRUB modules,
kernel images and initrds are in /boot, which is typically not a file
system that EFI understands. So in that case, initrd= does not work,
which is why GRUB loads the initrd into memory directly and passes the
base address and size via DT or bootparams structure.

> b) My suggestion to use a UEFI variable for communicating the device
> path would not require any arch specific policies either.
>

Passing the EFI device path is not going to help us since the initrd
may not be representable as a device path. That is the whole point,
actually - this series makes the initrd representable as a device
path, but in a simple way that doesn't rely on EFI_FILE_PROTOCOL but
only on EFI_LOAD_FILE2_PROTOCOL, which is *much* simpler.

> c) I proposed that the kernel does the verification. So there would be
> equally nothing in between loading the file and its verification. Yet
> responsibilities would be changed.
>
> But possibly I missed some requirements you have in mind that I should
> consider.
>

1) The assumption that the initrd can always be loaded from a EFI
device path directly does not hold.
2) Loading the initrd into memory and passing the address and size is
not acceptable.
3) Having a special device path that designates the initrd
specifically (which will be treated in a special way by the kernel)
makes it very easy for the boot firmware to attach policy to the
loading of the initrd that can be enforced right when the file is
being passed into the kernel.

Putting an arbitrary device path in a EFI variable doesn't address 1),
and it complicates 3), since you cannot easily distinguish whether the
file load that is occurring is the EFI stub loading the initrd at
boot.

^ permalink raw reply	[flat|nested] 39+ messages in thread

* Re: [PATCH 1/2] efi/libstub: add support for loading the initrd from a device path
  2020-02-07 13:58                 ` Ard Biesheuvel
@ 2020-02-07 14:18                   ` Alexander Graf
  2020-02-07 15:30                     ` Ard Biesheuvel
  2020-02-07 15:35                     ` Heinrich Schuchardt
  0 siblings, 2 replies; 39+ messages in thread
From: Alexander Graf @ 2020-02-07 14:18 UTC (permalink / raw)
  To: Ard Biesheuvel, Heinrich Schuchardt
  Cc: Ard Biesheuvel, linux-efi, linux-arm-kernel, Laszlo Ersek,
	Leif Lindholm, Peter Jones, Matthew Garrett, Ilias Apalodimas,
	Daniel Kiper


On 07.02.20 13:58, Ard Biesheuvel wrote:
> On Fri, 7 Feb 2020 at 13:30, Heinrich Schuchardt <xypron.glpk@gmx.de> wrote:
>>
>>
>> On 2/7/20 9:12 AM, Ard Biesheuvel wrote:
>>> On Fri, 7 Feb 2020 at 00:58, Heinrich Schuchardt <xypron.glpk@gmx.de> wrote:
>>>> On 2/7/20 1:21 AM, Ard Biesheuvel wrote:
>>>>> On Fri, 7 Feb 2020 at 00:01, Heinrich Schuchardt <xypron.glpk@gmx.de> wrote:
>>>>>> On 2/6/20 11:35 PM, Ard Biesheuvel wrote:
>>>>>>> On Thu, 6 Feb 2020 at 18:26, Heinrich Schuchardt <xypron.glpk@gmx.de> wrote:
>>> ...
>>>>>>>> Please, indicate which software you expect to expose the initrd related
>>>>>>>> EFI_LOAD_FILE2_PROTOCOL.
>>>>>>>>
>>>>>>> The primary use case is GRUB and other intermediate loaders, since it
>>>>>>> would remove any need for these components to know any such details.
>>>>>>> My aim is to make the next architecture that gets added to GRUB for
>>>>>>> EFI boot 100% generic.
>>>>>>>
>>>>>>>> Using an UEFI variable for passing the initrd device path would be a
>>>>>>>> leaner solution on the bootloader side than requiring an extra
>>>>>>>> EFI_LOAD_FILE2_PROTOCOL implementation.
>>>>>>>>
>>>>>>> This would also require kernel changes, since we don't currently load
>>>>>>> initrds from arbitrary device paths. The EFI_FILE_PROTOCOL is much
>>>>>>> more complicated than needed, and it doesn't work well with mixed
>>>>>>> mode. It also requires GRUB to expose the filesystem it loads the
>>>>>>> initrd from via EFI protocols, which is currently unnecessary and
>>>>>>> therefore not implemented.
>>>>>> This means you move the complexity of EFI_FILE_PROTOCOL from Linux to GRUB.
>>>>>>
>>>>> No. I am not interested in EFI_FILE_PROTOCOL, only in LoadFile2, which
>>>>> is a single method that needs to be implemented.
>>>> I said you move complexity because GRUB will need to use the
>>>> EFI_FILE_PROTOCOL to do the job that you do not want to do in Linux.
>>>>
>>>>>> I would not have a problem if this would only touch GRUB. But if listen
>>>>>> to Ilias we are replacing one implementation in Linux by one in GRUB and
>>>>>> one in U-Boot and one in EDK2 and one in any other firmware.
>>>>>>
>>>>> If u-boot will be used to boot RISC-V in EFI mode without GRUB, then I
>>>>> expect that we will need an implementation of this in u-boot.
>>>> What sets RISC-V apart? GRUB for RISC-V is available.
>>    >>
>>> RISC-V EFI boot is not supported yet in upstream Linux.
>> It is currently prepared Atish Patra of WDC.
>>
> Exactly. So it is not in the upstream yet, and I want to converge on a
> sane generic interface before it gets merged.
>
>>>>>>> Also, using an EFI variable defeats the purpose. The whole point of
>>>>>>> this is making it more likely that the kernel loaded the initrd that
>>>>>>> the bootloader or firmware intended it to load, and having a piece of
>>>>>>> simple [signed] code that implements this is the easiest way to
>>>>>>> achieve that.
>>>>>> At least on my Debian system it is the operating system creating initrd
>>>>>> and defining which initrd matches which kernel. GRUB simply assumes that
>>>>>> files ending on the same version number match. Therefore I would say
>>>>>> Linux hopes that GRUB loads what Linux intended.
>>>>>>
>>>>>> The chain of trust would not be broken if the kernel were responsible
>>>>>> for loading the initrd and for checking if it matches the kernel. Linux
>>>>>> already does this for the kernel modules in initrd.
>>>>>>
>>>>> We can still sign the initrd and Linux can verify the signature. What
>>>>> I am after is an interface that does not require the initrd to
>>>>> originate from a EFI file system protocol, and which doesn't require
>>>>> the loaded initrd to sit in memory for an unspecified amount of time
>>>>> and its information passed via DT properties or bootparams structs.
>>>>>
>>>>> So invoking EFI_FILE_PROTOCOL directly is not going to work,
>>>>> regardless of whether we get the devicepath from the command line or
>>>>> from a EFI variable.
>>>> What do you mean by "is not going to work"?
>>>>
>>>> With the device path you can find the handle implementing the
>>>> EFI_SIMPLE_FIL_SYSTEM_PROTOCOL.
>>>>
>>>>>>> For u-boot, it should be trivial to implement a simple LoadFile2
>>>>>>> protocol wrapper around EFI_FILE_PROTOCOL that can be installed on a
>>>>>>> handle that also carries EFI_FILE_PROTOCOL.
>>>>>>>
>>>>>> A U-Boot implementation of the EFI_LOAD_FILE2_PROTOCOL would need a
>>>>>> device path variable to find the block device and to open the
>>>>>> EFI_SIMPLE_FILE_SYSTEM_PROTOCOL before accessing the file.
>>>>>>
>>>>>> Linux would not be needing more lines and we would not repeat the same
>>>>>> code in GRUB, U-Boot, EDK2, etc.
>>>>>>
>>>>>> As said Linux updates the initrd often. If that file is not signed by
>>>>>> Linux in a well defined way, do not expect any security at all.
>>>>>>
>>>>> It is not only about security. The primary goal is to remove the need
>>>>> for arch specific knowledge in the firmware about DT, bootparams and
>>>>> initrd allocation policies without being forced to load the initrd
>>>>> from a filesystem that is exposed via a EFI protocol.
>>>> Where are device-trees touched by this patch?
>>>>
>>>> When booting via UEFI there is no need for knowledge of initrd
>>>> allocation policies in U-Boot because up to now Linux or GRUB or iPXE
>>>> load initrd.
>>>>
>>>> Furthermore I need no knowledge of bootparams in U-Boot once we properly
>>>> we support UEFI variables at runtime because grub-update will pass the
>>>> command line in one of the Bootxxxx UEFI variables.
>>>>
>>>> But most importantly I do not have to implement anything Linux specific
>>>> in U-Boot for booting via UEFI up to now.
>>>>
>>> Adding Linux specific stuff to u-boot is arguably more appropriate
>>> than adding architecture specific stuff to EFI loaders that could
>>> otherwise be entirely generic.
>>>
>>> ...
>>>> Your patch claims to fend off a specific threat scenario: A user puts an
>>>> untrusted initrd on the disk and references it in the Linux command line.
>>>>
>>>> If he is able to do so with your current bootloader (signed or not
>>>> signed), he most probably will also be able to delete a good initrd from
>>>> the filesystem and thus force your code into the unsafe path.
>>>>
>>>> That is why I say that with the current fallback logic this patch
>>>> achieves no increase in security. Of cause you could remove the fallback
>>>> logic. But in this case your Linux will not boot with any legacy
>>>> bootloader or firmware.
>>>>
>>> If there is a better way to expose the initrd that
>>> a) does not require the initrd to reside on a file system that is
>>> accessible via EFI protocols, and
>>> b) does not require the loader to know about arch specific policies
>>> regarding the placement of the initrd in memory, and
>>> c) does not leave a time window between the time that the initrd is
>>> loaded/verified/measured by the firmware and the time that the kernel
>>> gets handed the buffer
>>>
>>> then I am happy to discuss it. This proposal is the best I could come
>>> up with to achieve the above.
>>>
>> Hello Ard,
>>
>> I think part of our different views is that we are thinking about two
>> different use cases which both have their relevance:
>>
>> If I understand you correctly, you are thinking about an embedded device
>> where the kernel and the initrd is essentially part of the firmware
>> provided by the device.
>>
>> I am thinking of a system running a standard Linux distribution like
>> Debian where the initrd is generated by the operating system
>>
>> In both use cases verifying the initrd is of importance.
>>
>> Now concerning the requirements:
>>
>> a) In U-Boot all file systems on block devices can be made accessible
>> via EFI protocols. Are you thinking about initrds that are not in a file
>> system?
>>
> The typical GRUB deployment keeps the core GRUB itself (or the entire
> thing if it is built as standalone) in the ESP, and the GRUB modules,
> kernel images and initrds are in /boot, which is typically not a file
> system that EFI understands. So in that case, initrd= does not work,
> which is why GRUB loads the initrd into memory directly and passes the
> base address and size via DT or bootparams structure.
>
>> b) My suggestion to use a UEFI variable for communicating the device
>> path would not require any arch specific policies either.
>>
> Passing the EFI device path is not going to help us since the initrd
> may not be representable as a device path. That is the whole point,
> actually - this series makes the initrd representable as a device
> path, but in a simple way that doesn't rely on EFI_FILE_PROTOCOL but
> only on EFI_LOAD_FILE2_PROTOCOL, which is *much* simpler.


So if we had support in grub to just export its own file systems as UEFI 
protocols, that problem would disappear, right? What other reasons are 
left to not just use normal file load operations from the Linux EFI stub?

IIRC you mentioned that the fwcfg -kernel and -initrd parameters are 
already exposed as pseudo filesystem inside AAVMF, so that one is solved.

I think that only leaves the UEFI shell case? But if you have a UEFI 
shell, then you can only load from an existing file system as well, no?

What I can't quite grasp yet is how you would handle multiple initrds 
with a single device path. How would that work?


>
>> c) I proposed that the kernel does the verification. So there would be
>> equally nothing in between loading the file and its verification. Yet
>> responsibilities would be changed.
>>
>> But possibly I missed some requirements you have in mind that I should
>> consider.
>>
> 1) The assumption that the initrd can always be loaded from a EFI
> device path directly does not hold.


Can you think of good reasons why this is true? I understand the grub 
one, but that's solvable. What other cases are there?


> 2) Loading the initrd into memory and passing the address and size is
> not acceptable.


This would basically be the option to pass the initrd as configuration 
table, right? The only reason that definitely goes against that one that 
I can think of right now is to avoid double copying?


> 3) Having a special device path that designates the initrd
> specifically (which will be treated in a special way by the kernel)
> makes it very easy for the boot firmware to attach policy to the
> loading of the initrd that can be enforced right when the file is
> being passed into the kernel.


I don't fully understand this one. Can you give examples for such 
policies? :)


> Putting an arbitrary device path in a EFI variable doesn't address 1),
> and it complicates 3), since you cannot easily distinguish whether the
> file load that is occurring is the EFI stub loading the initrd at
> boot.


Why do we need to distinguish? I'm missing creativity for the use case 
right now. For 1), we just need to make sure that boot loaders that 
implement their own file systems also expose those file systems as UEFI 
protocols, right?


That said, I don't think the proposed approach of using 
EFI_LOAD_FILE2_PROTOCOL is bad. Whatever we do, we always will need to 
treat initrds special in one way or another; at least by exposing a 
command to specify which one(s) you want to load.


Alex



^ permalink raw reply	[flat|nested] 39+ messages in thread

* Re: [PATCH 1/2] efi/libstub: add support for loading the initrd from a device path
  2020-02-07 14:18                   ` Alexander Graf
@ 2020-02-07 15:30                     ` Ard Biesheuvel
  2020-02-07 15:35                     ` Heinrich Schuchardt
  1 sibling, 0 replies; 39+ messages in thread
From: Ard Biesheuvel @ 2020-02-07 15:30 UTC (permalink / raw)
  To: Alexander Graf
  Cc: Heinrich Schuchardt, Ard Biesheuvel, linux-efi, linux-arm-kernel,
	Laszlo Ersek, Leif Lindholm, Peter Jones, Matthew Garrett,
	Ilias Apalodimas, Daniel Kiper

On Fri, 7 Feb 2020 at 14:18, Alexander Graf <agraf@csgraf.de> wrote:
>
>
> On 07.02.20 13:58, Ard Biesheuvel wrote:
> > On Fri, 7 Feb 2020 at 13:30, Heinrich Schuchardt <xypron.glpk@gmx.de> wrote:
> >>
> >>
> >> On 2/7/20 9:12 AM, Ard Biesheuvel wrote:
> >>> On Fri, 7 Feb 2020 at 00:58, Heinrich Schuchardt <xypron.glpk@gmx.de> wrote:
> >>>> On 2/7/20 1:21 AM, Ard Biesheuvel wrote:
> >>>>> On Fri, 7 Feb 2020 at 00:01, Heinrich Schuchardt <xypron.glpk@gmx.de> wrote:
> >>>>>> On 2/6/20 11:35 PM, Ard Biesheuvel wrote:
> >>>>>>> On Thu, 6 Feb 2020 at 18:26, Heinrich Schuchardt <xypron.glpk@gmx.de> wrote:
> >>> ...
> >>>>>>>> Please, indicate which software you expect to expose the initrd related
> >>>>>>>> EFI_LOAD_FILE2_PROTOCOL.
> >>>>>>>>
> >>>>>>> The primary use case is GRUB and other intermediate loaders, since it
> >>>>>>> would remove any need for these components to know any such details.
> >>>>>>> My aim is to make the next architecture that gets added to GRUB for
> >>>>>>> EFI boot 100% generic.
> >>>>>>>
> >>>>>>>> Using an UEFI variable for passing the initrd device path would be a
> >>>>>>>> leaner solution on the bootloader side than requiring an extra
> >>>>>>>> EFI_LOAD_FILE2_PROTOCOL implementation.
> >>>>>>>>
> >>>>>>> This would also require kernel changes, since we don't currently load
> >>>>>>> initrds from arbitrary device paths. The EFI_FILE_PROTOCOL is much
> >>>>>>> more complicated than needed, and it doesn't work well with mixed
> >>>>>>> mode. It also requires GRUB to expose the filesystem it loads the
> >>>>>>> initrd from via EFI protocols, which is currently unnecessary and
> >>>>>>> therefore not implemented.
> >>>>>> This means you move the complexity of EFI_FILE_PROTOCOL from Linux to GRUB.
> >>>>>>
> >>>>> No. I am not interested in EFI_FILE_PROTOCOL, only in LoadFile2, which
> >>>>> is a single method that needs to be implemented.
> >>>> I said you move complexity because GRUB will need to use the
> >>>> EFI_FILE_PROTOCOL to do the job that you do not want to do in Linux.
> >>>>
> >>>>>> I would not have a problem if this would only touch GRUB. But if listen
> >>>>>> to Ilias we are replacing one implementation in Linux by one in GRUB and
> >>>>>> one in U-Boot and one in EDK2 and one in any other firmware.
> >>>>>>
> >>>>> If u-boot will be used to boot RISC-V in EFI mode without GRUB, then I
> >>>>> expect that we will need an implementation of this in u-boot.
> >>>> What sets RISC-V apart? GRUB for RISC-V is available.
> >>    >>
> >>> RISC-V EFI boot is not supported yet in upstream Linux.
> >> It is currently prepared Atish Patra of WDC.
> >>
> > Exactly. So it is not in the upstream yet, and I want to converge on a
> > sane generic interface before it gets merged.
> >
> >>>>>>> Also, using an EFI variable defeats the purpose. The whole point of
> >>>>>>> this is making it more likely that the kernel loaded the initrd that
> >>>>>>> the bootloader or firmware intended it to load, and having a piece of
> >>>>>>> simple [signed] code that implements this is the easiest way to
> >>>>>>> achieve that.
> >>>>>> At least on my Debian system it is the operating system creating initrd
> >>>>>> and defining which initrd matches which kernel. GRUB simply assumes that
> >>>>>> files ending on the same version number match. Therefore I would say
> >>>>>> Linux hopes that GRUB loads what Linux intended.
> >>>>>>
> >>>>>> The chain of trust would not be broken if the kernel were responsible
> >>>>>> for loading the initrd and for checking if it matches the kernel. Linux
> >>>>>> already does this for the kernel modules in initrd.
> >>>>>>
> >>>>> We can still sign the initrd and Linux can verify the signature. What
> >>>>> I am after is an interface that does not require the initrd to
> >>>>> originate from a EFI file system protocol, and which doesn't require
> >>>>> the loaded initrd to sit in memory for an unspecified amount of time
> >>>>> and its information passed via DT properties or bootparams structs.
> >>>>>
> >>>>> So invoking EFI_FILE_PROTOCOL directly is not going to work,
> >>>>> regardless of whether we get the devicepath from the command line or
> >>>>> from a EFI variable.
> >>>> What do you mean by "is not going to work"?
> >>>>
> >>>> With the device path you can find the handle implementing the
> >>>> EFI_SIMPLE_FIL_SYSTEM_PROTOCOL.
> >>>>
> >>>>>>> For u-boot, it should be trivial to implement a simple LoadFile2
> >>>>>>> protocol wrapper around EFI_FILE_PROTOCOL that can be installed on a
> >>>>>>> handle that also carries EFI_FILE_PROTOCOL.
> >>>>>>>
> >>>>>> A U-Boot implementation of the EFI_LOAD_FILE2_PROTOCOL would need a
> >>>>>> device path variable to find the block device and to open the
> >>>>>> EFI_SIMPLE_FILE_SYSTEM_PROTOCOL before accessing the file.
> >>>>>>
> >>>>>> Linux would not be needing more lines and we would not repeat the same
> >>>>>> code in GRUB, U-Boot, EDK2, etc.
> >>>>>>
> >>>>>> As said Linux updates the initrd often. If that file is not signed by
> >>>>>> Linux in a well defined way, do not expect any security at all.
> >>>>>>
> >>>>> It is not only about security. The primary goal is to remove the need
> >>>>> for arch specific knowledge in the firmware about DT, bootparams and
> >>>>> initrd allocation policies without being forced to load the initrd
> >>>>> from a filesystem that is exposed via a EFI protocol.
> >>>> Where are device-trees touched by this patch?
> >>>>
> >>>> When booting via UEFI there is no need for knowledge of initrd
> >>>> allocation policies in U-Boot because up to now Linux or GRUB or iPXE
> >>>> load initrd.
> >>>>
> >>>> Furthermore I need no knowledge of bootparams in U-Boot once we properly
> >>>> we support UEFI variables at runtime because grub-update will pass the
> >>>> command line in one of the Bootxxxx UEFI variables.
> >>>>
> >>>> But most importantly I do not have to implement anything Linux specific
> >>>> in U-Boot for booting via UEFI up to now.
> >>>>
> >>> Adding Linux specific stuff to u-boot is arguably more appropriate
> >>> than adding architecture specific stuff to EFI loaders that could
> >>> otherwise be entirely generic.
> >>>
> >>> ...
> >>>> Your patch claims to fend off a specific threat scenario: A user puts an
> >>>> untrusted initrd on the disk and references it in the Linux command line.
> >>>>
> >>>> If he is able to do so with your current bootloader (signed or not
> >>>> signed), he most probably will also be able to delete a good initrd from
> >>>> the filesystem and thus force your code into the unsafe path.
> >>>>
> >>>> That is why I say that with the current fallback logic this patch
> >>>> achieves no increase in security. Of cause you could remove the fallback
> >>>> logic. But in this case your Linux will not boot with any legacy
> >>>> bootloader or firmware.
> >>>>
> >>> If there is a better way to expose the initrd that
> >>> a) does not require the initrd to reside on a file system that is
> >>> accessible via EFI protocols, and
> >>> b) does not require the loader to know about arch specific policies
> >>> regarding the placement of the initrd in memory, and
> >>> c) does not leave a time window between the time that the initrd is
> >>> loaded/verified/measured by the firmware and the time that the kernel
> >>> gets handed the buffer
> >>>
> >>> then I am happy to discuss it. This proposal is the best I could come
> >>> up with to achieve the above.
> >>>
> >> Hello Ard,
> >>
> >> I think part of our different views is that we are thinking about two
> >> different use cases which both have their relevance:
> >>
> >> If I understand you correctly, you are thinking about an embedded device
> >> where the kernel and the initrd is essentially part of the firmware
> >> provided by the device.
> >>
> >> I am thinking of a system running a standard Linux distribution like
> >> Debian where the initrd is generated by the operating system
> >>
> >> In both use cases verifying the initrd is of importance.
> >>
> >> Now concerning the requirements:
> >>
> >> a) In U-Boot all file systems on block devices can be made accessible
> >> via EFI protocols. Are you thinking about initrds that are not in a file
> >> system?
> >>
> > The typical GRUB deployment keeps the core GRUB itself (or the entire
> > thing if it is built as standalone) in the ESP, and the GRUB modules,
> > kernel images and initrds are in /boot, which is typically not a file
> > system that EFI understands. So in that case, initrd= does not work,
> > which is why GRUB loads the initrd into memory directly and passes the
> > base address and size via DT or bootparams structure.
> >
> >> b) My suggestion to use a UEFI variable for communicating the device
> >> path would not require any arch specific policies either.
> >>
> > Passing the EFI device path is not going to help us since the initrd
> > may not be representable as a device path. That is the whole point,
> > actually - this series makes the initrd representable as a device
> > path, but in a simple way that doesn't rely on EFI_FILE_PROTOCOL but
> > only on EFI_LOAD_FILE2_PROTOCOL, which is *much* simpler.
>
>
> So if we had support in grub to just export its own file systems as UEFI
> protocols, that problem would disappear, right? What other reasons are
> left to not just use normal file load operations from the Linux EFI stub?
>

1) complexity

"""
typedef struct {
  u64     size;
  u64     file_size;
  u64     phys_size;
  efi_time_t    create_time;
  efi_time_t    last_access_time;
  efi_time_t    modification_time;
  __aligned_u64   attribute;
  efi_char16_t    filename[];
} efi_file_info_t;

typedef struct efi_file_protocol efi_file_protocol_t;

struct efi_file_protocol {
  u64   revision;
  efi_status_t  (__efiapi *open)  (efi_file_protocol_t *,
             efi_file_protocol_t **,
             efi_char16_t *, u64, u64);
  efi_status_t  (__efiapi *close) (efi_file_protocol_t *);
  efi_status_t  (__efiapi *delete)  (efi_file_protocol_t *);
  efi_status_t  (__efiapi *read)  (efi_file_protocol_t *,
             unsigned long *, void *);
  efi_status_t  (__efiapi *write) (efi_file_protocol_t *,
             unsigned long, void *);
  efi_status_t  (__efiapi *get_position)(efi_file_protocol_t *, u64 *);
  efi_status_t  (__efiapi *set_position)(efi_file_protocol_t *, u64);
  efi_status_t  (__efiapi *get_info)  (efi_file_protocol_t *,
             efi_guid_t *, unsigned long *,
             void *);
  efi_status_t  (__efiapi *set_info)  (efi_file_protocol_t *,
             efi_guid_t *, unsigned long,
             void *);
  efi_status_t  (__efiapi *flush) (efi_file_protocol_t *);
};

typedef struct efi_simple_file_system_protocol
efi_simple_file_system_protocol_t;

struct efi_simple_file_system_protocol {
  u64 revision;
  int (__efiapi *open_volume)(efi_simple_file_system_protocol_t *,
          efi_file_protocol_t **);
};

#define EFI_FILE_MODE_READ  0x0000000000000001
#define EFI_FILE_MODE_WRITE 0x0000000000000002
#define EFI_FILE_MODE_CREATE  0x8000000000000000
"""

versus

"""
union efi_load_file_protocol {
struct {
  efi_status_t (__efiapi *load_file)(efi_load_file_protocol_t *,
       efi_device_path_protocol_t *,
       bool, unsigned long *, void *);
  };
};
"""

2) mixed mode is problematic due to the use of u64 arguments in the prototypes
3) you have to trust that the device path that was specified was
intended to be loaded as an initrd
4) if you want to authenticate or measure the initrd from the firmware
as it is read, you have to disambiguate EFI_FILE_PROTOCOL calls
involving the initrd from arbitrary other calls.


> IIRC you mentioned that the fwcfg -kernel and -initrd parameters are
> already exposed as pseudo filesystem inside AAVMF, so that one is solved.
>

Yes, but this only works if you use the PE entry point, which rules
out mixed mode.

> I think that only leaves the UEFI shell case? But if you have a UEFI
> shell, then you can only load from an existing file system as well, no?
>

Yes.

> What I can't quite grasp yet is how you would handle multiple initrds
> with a single device path. How would that work?
>

The core kernel does not care about multiple initrds, they have to be
concatenated and passed as one anyway. In this case, that means the
implementation of LoadFile2 is in charge of this.

>
> >
> >> c) I proposed that the kernel does the verification. So there would be
> >> equally nothing in between loading the file and its verification. Yet
> >> responsibilities would be changed.
> >>
> >> But possibly I missed some requirements you have in mind that I should
> >> consider.
> >>
> > 1) The assumption that the initrd can always be loaded from a EFI
> > device path directly does not hold.
>
>
> Can you think of good reasons why this is true? I understand the grub
> one, but that's solvable. What other cases are there?
>

Thin EFI implementations in enclaves, LinuxBoot on arm64, ...

>
> > 2) Loading the initrd into memory and passing the address and size is
> > not acceptable.
>
>
> This would basically be the option to pass the initrd as configuration
> table, right? The only reason that definitely goes against that one that
> I can think of right now is to avoid double copying?
>

That basically forces the stub to reallocate it, unless we put the
[arch specific] allocation policy in the loader. initrds can be large,
and so having to copy them can be a problem.

>
> > 3) Having a special device path that designates the initrd
> > specifically (which will be treated in a special way by the kernel)
> > makes it very easy for the boot firmware to attach policy to the
> > loading of the initrd that can be enforced right when the file is
> > being passed into the kernel.
>
>
> I don't fully understand this one. Can you give examples for such
> policies? :)
>

If you want to measure the initrd into PCR x, it gives you a natural
place to do it, since the data is measured right at the point where
the stub consumes it, and the protocol will never be invoked if the
initrd is not requested (e.g., if the boot environment exposes a
initrd but 'noinitrd' is passed on the command line, in which case we
would prefer not to measure the initrd at all)

>
> > Putting an arbitrary device path in a EFI variable doesn't address 1),
> > and it complicates 3), since you cannot easily distinguish whether the
> > file load that is occurring is the EFI stub loading the initrd at
> > boot.
>
>
> Why do we need to distinguish? I'm missing creativity for the use case
> right now. For 1), we just need to make sure that boot loaders that
> implement their own file systems also expose those file systems as UEFI
> protocols, right?
>

Because an arbitrary file read call cannot be identified as being the
one issued by the stub to load and invoke the initrd.

>
> That said, I don't think the proposed approach of using
> EFI_LOAD_FILE2_PROTOCOL is bad. Whatever we do, we always will need to
> treat initrds special in one way or another; at least by exposing a
> command to specify which one(s) you want to load.
>

^ permalink raw reply	[flat|nested] 39+ messages in thread

* Re: [PATCH 1/2] efi/libstub: add support for loading the initrd from a device path
  2020-02-07 14:18                   ` Alexander Graf
  2020-02-07 15:30                     ` Ard Biesheuvel
@ 2020-02-07 15:35                     ` Heinrich Schuchardt
  1 sibling, 0 replies; 39+ messages in thread
From: Heinrich Schuchardt @ 2020-02-07 15:35 UTC (permalink / raw)
  To: Alexander Graf, Ard Biesheuvel
  Cc: Ard Biesheuvel, linux-efi, linux-arm-kernel, Laszlo Ersek,
	Leif Lindholm, Peter Jones, Matthew Garrett, Ilias Apalodimas,
	Daniel Kiper

On 2/7/20 3:18 PM, Alexander Graf wrote:
>
> On 07.02.20 13:58, Ard Biesheuvel wrote:
>> On Fri, 7 Feb 2020 at 13:30, Heinrich Schuchardt <xypron.glpk@gmx.de>
>> wrote:
>>>
>>>
>>> On 2/7/20 9:12 AM, Ard Biesheuvel wrote:
>>>> On Fri, 7 Feb 2020 at 00:58, Heinrich Schuchardt
>>>> <xypron.glpk@gmx.de> wrote:
>>>>> On 2/7/20 1:21 AM, Ard Biesheuvel wrote:
>>>>>> On Fri, 7 Feb 2020 at 00:01, Heinrich Schuchardt
>>>>>> <xypron.glpk@gmx.de> wrote:
>>>>>>> On 2/6/20 11:35 PM, Ard Biesheuvel wrote:
>>>>>>>> On Thu, 6 Feb 2020 at 18:26, Heinrich Schuchardt
>>>>>>>> <xypron.glpk@gmx.de> wrote:
>>>> ...
>>>>>>>>> Please, indicate which software you expect to expose the initrd
>>>>>>>>> related
>>>>>>>>> EFI_LOAD_FILE2_PROTOCOL.
>>>>>>>>>
>>>>>>>> The primary use case is GRUB and other intermediate loaders,
>>>>>>>> since it
>>>>>>>> would remove any need for these components to know any such
>>>>>>>> details.
>>>>>>>> My aim is to make the next architecture that gets added to GRUB for
>>>>>>>> EFI boot 100% generic.
>>>>>>>>
>>>>>>>>> Using an UEFI variable for passing the initrd device path would
>>>>>>>>> be a
>>>>>>>>> leaner solution on the bootloader side than requiring an extra
>>>>>>>>> EFI_LOAD_FILE2_PROTOCOL implementation.
>>>>>>>>>
>>>>>>>> This would also require kernel changes, since we don't currently
>>>>>>>> load
>>>>>>>> initrds from arbitrary device paths. The EFI_FILE_PROTOCOL is much
>>>>>>>> more complicated than needed, and it doesn't work well with mixed
>>>>>>>> mode. It also requires GRUB to expose the filesystem it loads the
>>>>>>>> initrd from via EFI protocols, which is currently unnecessary and
>>>>>>>> therefore not implemented.
>>>>>>> This means you move the complexity of EFI_FILE_PROTOCOL from
>>>>>>> Linux to GRUB.
>>>>>>>
>>>>>> No. I am not interested in EFI_FILE_PROTOCOL, only in LoadFile2,
>>>>>> which
>>>>>> is a single method that needs to be implemented.
>>>>> I said you move complexity because GRUB will need to use the
>>>>> EFI_FILE_PROTOCOL to do the job that you do not want to do in Linux.
>>>>>
>>>>>>> I would not have a problem if this would only touch GRUB. But if
>>>>>>> listen
>>>>>>> to Ilias we are replacing one implementation in Linux by one in
>>>>>>> GRUB and
>>>>>>> one in U-Boot and one in EDK2 and one in any other firmware.
>>>>>>>
>>>>>> If u-boot will be used to boot RISC-V in EFI mode without GRUB,
>>>>>> then I
>>>>>> expect that we will need an implementation of this in u-boot.
>>>>> What sets RISC-V apart? GRUB for RISC-V is available.
>>>    >>
>>>> RISC-V EFI boot is not supported yet in upstream Linux.
>>> It is currently prepared Atish Patra of WDC.
>>>
>> Exactly. So it is not in the upstream yet, and I want to converge on a
>> sane generic interface before it gets merged.
>>
>>>>>>>> Also, using an EFI variable defeats the purpose. The whole point of
>>>>>>>> this is making it more likely that the kernel loaded the initrd
>>>>>>>> that
>>>>>>>> the bootloader or firmware intended it to load, and having a
>>>>>>>> piece of
>>>>>>>> simple [signed] code that implements this is the easiest way to
>>>>>>>> achieve that.
>>>>>>> At least on my Debian system it is the operating system creating
>>>>>>> initrd
>>>>>>> and defining which initrd matches which kernel. GRUB simply
>>>>>>> assumes that
>>>>>>> files ending on the same version number match. Therefore I would say
>>>>>>> Linux hopes that GRUB loads what Linux intended.
>>>>>>>
>>>>>>> The chain of trust would not be broken if the kernel were
>>>>>>> responsible
>>>>>>> for loading the initrd and for checking if it matches the kernel.
>>>>>>> Linux
>>>>>>> already does this for the kernel modules in initrd.
>>>>>>>
>>>>>> We can still sign the initrd and Linux can verify the signature. What
>>>>>> I am after is an interface that does not require the initrd to
>>>>>> originate from a EFI file system protocol, and which doesn't require
>>>>>> the loaded initrd to sit in memory for an unspecified amount of time
>>>>>> and its information passed via DT properties or bootparams structs.
>>>>>>
>>>>>> So invoking EFI_FILE_PROTOCOL directly is not going to work,
>>>>>> regardless of whether we get the devicepath from the command line or
>>>>>> from a EFI variable.
>>>>> What do you mean by "is not going to work"?
>>>>>
>>>>> With the device path you can find the handle implementing the
>>>>> EFI_SIMPLE_FIL_SYSTEM_PROTOCOL.
>>>>>
>>>>>>>> For u-boot, it should be trivial to implement a simple LoadFile2
>>>>>>>> protocol wrapper around EFI_FILE_PROTOCOL that can be installed
>>>>>>>> on a
>>>>>>>> handle that also carries EFI_FILE_PROTOCOL.
>>>>>>>>
>>>>>>> A U-Boot implementation of the EFI_LOAD_FILE2_PROTOCOL would need a
>>>>>>> device path variable to find the block device and to open the
>>>>>>> EFI_SIMPLE_FILE_SYSTEM_PROTOCOL before accessing the file.
>>>>>>>
>>>>>>> Linux would not be needing more lines and we would not repeat the
>>>>>>> same
>>>>>>> code in GRUB, U-Boot, EDK2, etc.
>>>>>>>
>>>>>>> As said Linux updates the initrd often. If that file is not
>>>>>>> signed by
>>>>>>> Linux in a well defined way, do not expect any security at all.
>>>>>>>
>>>>>> It is not only about security. The primary goal is to remove the need
>>>>>> for arch specific knowledge in the firmware about DT, bootparams and
>>>>>> initrd allocation policies without being forced to load the initrd
>>>>>> from a filesystem that is exposed via a EFI protocol.
>>>>> Where are device-trees touched by this patch?
>>>>>
>>>>> When booting via UEFI there is no need for knowledge of initrd
>>>>> allocation policies in U-Boot because up to now Linux or GRUB or iPXE
>>>>> load initrd.
>>>>>
>>>>> Furthermore I need no knowledge of bootparams in U-Boot once we
>>>>> properly
>>>>> we support UEFI variables at runtime because grub-update will pass the
>>>>> command line in one of the Bootxxxx UEFI variables.
>>>>>
>>>>> But most importantly I do not have to implement anything Linux
>>>>> specific
>>>>> in U-Boot for booting via UEFI up to now.
>>>>>
>>>> Adding Linux specific stuff to u-boot is arguably more appropriate
>>>> than adding architecture specific stuff to EFI loaders that could
>>>> otherwise be entirely generic.
>>>>
>>>> ...
>>>>> Your patch claims to fend off a specific threat scenario: A user
>>>>> puts an
>>>>> untrusted initrd on the disk and references it in the Linux command
>>>>> line.
>>>>>
>>>>> If he is able to do so with your current bootloader (signed or not
>>>>> signed), he most probably will also be able to delete a good initrd
>>>>> from
>>>>> the filesystem and thus force your code into the unsafe path.
>>>>>
>>>>> That is why I say that with the current fallback logic this patch
>>>>> achieves no increase in security. Of cause you could remove the
>>>>> fallback
>>>>> logic. But in this case your Linux will not boot with any legacy
>>>>> bootloader or firmware.
>>>>>
>>>> If there is a better way to expose the initrd that
>>>> a) does not require the initrd to reside on a file system that is
>>>> accessible via EFI protocols, and
>>>> b) does not require the loader to know about arch specific policies
>>>> regarding the placement of the initrd in memory, and
>>>> c) does not leave a time window between the time that the initrd is
>>>> loaded/verified/measured by the firmware and the time that the kernel
>>>> gets handed the buffer
>>>>
>>>> then I am happy to discuss it. This proposal is the best I could come
>>>> up with to achieve the above.
>>>>
>>> Hello Ard,
>>>
>>> I think part of our different views is that we are thinking about two
>>> different use cases which both have their relevance:
>>>
>>> If I understand you correctly, you are thinking about an embedded device
>>> where the kernel and the initrd is essentially part of the firmware
>>> provided by the device.
>>>
>>> I am thinking of a system running a standard Linux distribution like
>>> Debian where the initrd is generated by the operating system
>>>
>>> In both use cases verifying the initrd is of importance.
>>>
>>> Now concerning the requirements:
>>>
>>> a) In U-Boot all file systems on block devices can be made accessible
>>> via EFI protocols. Are you thinking about initrds that are not in a file
>>> system?
>>>
>> The typical GRUB deployment keeps the core GRUB itself (or the entire
>> thing if it is built as standalone) in the ESP, and the GRUB modules,
>> kernel images and initrds are in /boot, which is typically not a file
>> system that EFI understands. So in that case, initrd= does not work,
>> which is why GRUB loads the initrd into memory directly and passes the
>> base address and size via DT or bootparams structure.
>>
>>> b) My suggestion to use a UEFI variable for communicating the device
>>> path would not require any arch specific policies either.
>>>
>> Passing the EFI device path is not going to help us since the initrd
>> may not be representable as a device path. That is the whole point,
>> actually - this series makes the initrd representable as a device
>> path, but in a simple way that doesn't rely on EFI_FILE_PROTOCOL but
>> only on EFI_LOAD_FILE2_PROTOCOL, which is *much* simpler.
>
>
> So if we had support in grub to just export its own file systems as UEFI
> protocols, that problem would disappear, right? What other reasons are
> left to not just use normal file load operations from the Linux EFI stub?
>
> IIRC you mentioned that the fwcfg -kernel and -initrd parameters are
> already exposed as pseudo filesystem inside AAVMF, so that one is solved.
>
> I think that only leaves the UEFI shell case? But if you have a UEFI
> shell, then you can only load from an existing file system as well, no?
>
> What I can't quite grasp yet is how you would handle multiple initrds
> with a single device path. How would that work?

I guess you refer to the feature described in
https://www.phoronix.com/scan.php?page=news_item&px=GRUB-Multiple-Early-Initrd

In the binary form of an EFI_DEVICE_PATH_PROTOCOL multiple device paths
can be concatenated using a separator node of type "End of Hardware
Device Path node" (0x7f) with sub-type "End This Instance of a Device
Path" (0x01).

Regards

Heinrich

>
>
>>
>>> c) I proposed that the kernel does the verification. So there would be
>>> equally nothing in between loading the file and its verification. Yet
>>> responsibilities would be changed.
>>>
>>> But possibly I missed some requirements you have in mind that I should
>>> consider.
>>>
>> 1) The assumption that the initrd can always be loaded from a EFI
>> device path directly does not hold.
>
>
> Can you think of good reasons why this is true? I understand the grub
> one, but that's solvable. What other cases are there?
>
>
>> 2) Loading the initrd into memory and passing the address and size is
>> not acceptable.
>
>
> This would basically be the option to pass the initrd as configuration
> table, right? The only reason that definitely goes against that one that
> I can think of right now is to avoid double copying?
>
>
>> 3) Having a special device path that designates the initrd
>> specifically (which will be treated in a special way by the kernel)
>> makes it very easy for the boot firmware to attach policy to the
>> loading of the initrd that can be enforced right when the file is
>> being passed into the kernel.
>
>
> I don't fully understand this one. Can you give examples for such
> policies? :)
>
>
>> Putting an arbitrary device path in a EFI variable doesn't address 1),
>> and it complicates 3), since you cannot easily distinguish whether the
>> file load that is occurring is the EFI stub loading the initrd at
>> boot.
>
>
> Why do we need to distinguish? I'm missing creativity for the use case
> right now. For 1), we just need to make sure that boot loaders that
> implement their own file systems also expose those file systems as UEFI
> protocols, right?
>
>
> That said, I don't think the proposed approach of using
> EFI_LOAD_FILE2_PROTOCOL is bad. Whatever we do, we always will need to
> treat initrds special in one way or another; at least by exposing a
> command to specify which one(s) you want to load.
>
>
> Alex
>
>


^ permalink raw reply	[flat|nested] 39+ messages in thread

* Re: [PATCH 0/2] arch-agnostic initrd loading method for EFI systems
  2020-02-07 12:23     ` Ard Biesheuvel
@ 2020-02-07 16:20       ` James Bottomley
  2020-02-07 18:31         ` Ard Biesheuvel
  0 siblings, 1 reply; 39+ messages in thread
From: James Bottomley @ 2020-02-07 16:20 UTC (permalink / raw)
  To: Ard Biesheuvel, Laszlo Ersek
  Cc: Ard Biesheuvel, linux-efi, linux-arm-kernel, Leif Lindholm,
	Peter Jones, Matthew Garrett, Alexander Graf, Ilias Apalodimas,
	Heinrich Schuchardt, Daniel Kiper

On Fri, 2020-02-07 at 12:23 +0000, Ard Biesheuvel wrote:
> On Fri, 7 Feb 2020 at 09:22, Laszlo Ersek <lersek@redhat.com> wrote:
> > 
> > On 02/07/20 10:09, Laszlo Ersek wrote:
[...]
> > > For example, virt-install's "--location" option "can recognize
> > > certain distribution trees and fetches a bootable kernel/initrd
> > > pair to launch the install". It would be nice to keep that
> > > working for older distros.
> > > 
> > > I think LoadFile[2] can co-exist with SimpleFs.
> > > 
> > > I also think that the "try SimpleFs first, fall back to
> > > LoadFile[2] second" requirement applies only to the UEFI boot
> > > manager, and not to the kernel's EFI stub. IOW in the new
> > > approach the kernel is free to ignore (abandon) the old approach
> > > for good.
> > 
> > ... But that might not be good for compatibility with grub and/or
> > the platform firmware, from the kernel's own perspective,
> > perhaps?...
> > 
> > Who is supposed to produce LoadFile2 with the new VenMedia devpath?
> > 
> 
> What I am ultimately after is a generic GRUB that uses
> LoadImage+Startimage for starting the kernel on all architectures, 

For most boots, we need to pivot to the MoK.  A long time ago, I
proposed updating the platform security policy to do an override to
allow MoK to become the security verifier (actually principally so I
could get the gummiboot bootloader to work with the MoK method):

https://git.kernel.org/pub/scm/linux/kernel/git/jejb/efitools.git/tree/lib/security_policy.c

And I believe all the pivot bootloaders now do this, but the fear was
always this looks a bit like hackery that might not work in some UEFI
implementations.  Since we don't really rely on it (shim link loads
after signature verification) we don't know whether the assumption does
break or not.  We'll need to get much more comfortable with the
security override before we can let grub do a simple load+start.

> and is able to load the initrd from anywhere in an arch agnostic
> manner.

I think the use case might not really be grub, it's gummiboot, or
systemd-boot as its now called:

https://wiki.archlinux.org/index.php/systemd-boot

The standard way of using grub and EFI is to put grub on the EFI
parition but have the kernel and the initrd on the root parition (which
won't be EFI readable).  This means we can keep the EFI partition small
and only needing modification when grub is updated, meaning it doesn't
even need mounting at all usually.

Don't get me wrong, I like the gummiboot way of doing the
LoadImage+StartImage: it's small and clean and doesn't need the shim
protocol, but people like the sophistication grub provides including
its ability to read kernel filesystems, so they're unlikely to change
that.

James


^ permalink raw reply	[flat|nested] 39+ messages in thread

* Re: [PATCH 0/2] arch-agnostic initrd loading method for EFI systems
  2020-02-07 16:20       ` James Bottomley
@ 2020-02-07 18:31         ` Ard Biesheuvel
  2020-02-07 19:54           ` James Bottomley
  0 siblings, 1 reply; 39+ messages in thread
From: Ard Biesheuvel @ 2020-02-07 18:31 UTC (permalink / raw)
  To: James Bottomley
  Cc: Laszlo Ersek, Ard Biesheuvel, linux-efi, linux-arm-kernel,
	Leif Lindholm, Peter Jones, Matthew Garrett, Alexander Graf,
	Ilias Apalodimas, Heinrich Schuchardt, Daniel Kiper

On Fri, 7 Feb 2020 at 16:20, James Bottomley
<James.Bottomley@hansenpartnership.com> wrote:
>
> On Fri, 2020-02-07 at 12:23 +0000, Ard Biesheuvel wrote:
> > On Fri, 7 Feb 2020 at 09:22, Laszlo Ersek <lersek@redhat.com> wrote:
> > >
> > > On 02/07/20 10:09, Laszlo Ersek wrote:
> [...]
> > > > For example, virt-install's "--location" option "can recognize
> > > > certain distribution trees and fetches a bootable kernel/initrd
> > > > pair to launch the install". It would be nice to keep that
> > > > working for older distros.
> > > >
> > > > I think LoadFile[2] can co-exist with SimpleFs.
> > > >
> > > > I also think that the "try SimpleFs first, fall back to
> > > > LoadFile[2] second" requirement applies only to the UEFI boot
> > > > manager, and not to the kernel's EFI stub. IOW in the new
> > > > approach the kernel is free to ignore (abandon) the old approach
> > > > for good.
> > >
> > > ... But that might not be good for compatibility with grub and/or
> > > the platform firmware, from the kernel's own perspective,
> > > perhaps?...
> > >
> > > Who is supposed to produce LoadFile2 with the new VenMedia devpath?
> > >
> >
> > What I am ultimately after is a generic GRUB that uses
> > LoadImage+Startimage for starting the kernel on all architectures,
>
> For most boots, we need to pivot to the MoK.  A long time ago, I
> proposed updating the platform security policy to do an override to
> allow MoK to become the security verifier (actually principally so I
> could get the gummiboot bootloader to work with the MoK method):
>
> https://git.kernel.org/pub/scm/linux/kernel/git/jejb/efitools.git/tree/lib/security_policy.c
>
> And I believe all the pivot bootloaders now do this, but the fear was
> always this looks a bit like hackery that might not work in some UEFI
> implementations.  Since we don't really rely on it (shim link loads
> after signature verification) we don't know whether the assumption does
> break or not.  We'll need to get much more comfortable with the
> security override before we can let grub do a simple load+start.
>

I'd like to do something much simpler: let shim override LoadImage and
StartImage, and in their implementations, fall back to the firmware
ones if necessary.

> > and is able to load the initrd from anywhere in an arch agnostic
> > manner.
>
> I think the use case might not really be grub, it's gummiboot, or
> systemd-boot as its now called:
>

No it is definitely GRUB. GRUB today needs to attach to the shim
protocol, perform the authentication, measure the payload etc etc,
which means it knows far too much about the internals of shim or the
fact that it even exists.

My ideal bootflow would be where the OS installer looks at the
firmware's db/dbx, doesn't bother to install shim if the OS vendor's
cert is there, and uses the exact same GRUB regardless of whether shim
is part of the bootflow or not.

One of the things impeding this is the fact that we cannot load the
initrd from anywhere when using loadimage+startimage.

> https://wiki.archlinux.org/index.php/systemd-boot
>
> The standard way of using grub and EFI is to put grub on the EFI
> parition but have the kernel and the initrd on the root parition (which
> won't be EFI readable).  This means we can keep the EFI partition small
> and only needing modification when grub is updated, meaning it doesn't
> even need mounting at all usually.
>
> Don't get me wrong, I like the gummiboot way of doing the
> LoadImage+StartImage: it's small and clean and doesn't need the shim
> protocol, but people like the sophistication grub provides including
> its ability to read kernel filesystems, so they're unlikely to change
> that.
>

^ permalink raw reply	[flat|nested] 39+ messages in thread

* Re: [PATCH 0/2] arch-agnostic initrd loading method for EFI systems
  2020-02-06 14:03 [PATCH 0/2] arch-agnostic initrd loading method for EFI systems Ard Biesheuvel
                   ` (2 preceding siblings ...)
  2020-02-07  9:09 ` [PATCH 0/2] arch-agnostic initrd loading method for EFI systems Laszlo Ersek
@ 2020-02-07 18:45 ` Arvind Sankar
  2020-02-07 19:47   ` Ard Biesheuvel
  3 siblings, 1 reply; 39+ messages in thread
From: Arvind Sankar @ 2020-02-07 18:45 UTC (permalink / raw)
  To: Ard Biesheuvel
  Cc: linux-efi, linux-arm-kernel, lersek, leif, pjones, mjg59, agraf,
	ilias.apalodimas, xypron.glpk, daniel.kiper

On Thu, Feb 06, 2020 at 02:03:50PM +0000, Ard Biesheuvel wrote:
>   data structure. It also creates a time window where the initrd data sits
>   in memory, and can potentially be corrupted before the kernel is booted.
> 

I don't quite understand the time window aspect -- can you expand on
that? It seems like the same time window exists between when the kernel
is loaded and when it actually runs, no? Why is this more important for
initrd?

^ permalink raw reply	[flat|nested] 39+ messages in thread

* Re: [PATCH 0/2] arch-agnostic initrd loading method for EFI systems
  2020-02-07 18:45 ` Arvind Sankar
@ 2020-02-07 19:47   ` Ard Biesheuvel
  2020-02-07 20:26     ` Arvind Sankar
  0 siblings, 1 reply; 39+ messages in thread
From: Ard Biesheuvel @ 2020-02-07 19:47 UTC (permalink / raw)
  To: Arvind Sankar
  Cc: Ard Biesheuvel, linux-efi, linux-arm-kernel, Laszlo Ersek,
	Leif Lindholm, Peter Jones, Matthew Garrett, Alexander Graf,
	Ilias Apalodimas, Heinrich Schuchardt, Daniel Kiper

On Fri, 7 Feb 2020 at 18:45, Arvind Sankar <nivedita@alum.mit.edu> wrote:
>
> On Thu, Feb 06, 2020 at 02:03:50PM +0000, Ard Biesheuvel wrote:
> >   data structure. It also creates a time window where the initrd data sits
> >   in memory, and can potentially be corrupted before the kernel is booted.
> >
>
> I don't quite understand the time window aspect -- can you expand on
> that? It seems like the same time window exists between when the kernel
> is loaded and when it actually runs, no? Why is this more important for
> initrd?

When using loadimage+startimage, the authentication and measurement of
the kernel image occur during the call to loadimage(), even if the
source of the load is memory itself, and startimage() is typically
called right after.

The assumption is that it may help to make this time as short as
possible for the initrd as well.

^ permalink raw reply	[flat|nested] 39+ messages in thread

* Re: [PATCH 0/2] arch-agnostic initrd loading method for EFI systems
  2020-02-07 18:31         ` Ard Biesheuvel
@ 2020-02-07 19:54           ` James Bottomley
  2020-02-07 20:03             ` Ard Biesheuvel
  0 siblings, 1 reply; 39+ messages in thread
From: James Bottomley @ 2020-02-07 19:54 UTC (permalink / raw)
  To: Ard Biesheuvel
  Cc: Laszlo Ersek, Ard Biesheuvel, linux-efi, linux-arm-kernel,
	Leif Lindholm, Peter Jones, Matthew Garrett, Alexander Graf,
	Ilias Apalodimas, Heinrich Schuchardt, Daniel Kiper

On Fri, 2020-02-07 at 18:31 +0000, Ard Biesheuvel wrote:
> On Fri, 7 Feb 2020 at 16:20, James Bottomley
> <James.Bottomley@hansenpartnership.com> wrote:
> > 
> > On Fri, 2020-02-07 at 12:23 +0000, Ard Biesheuvel wrote:
> > > On Fri, 7 Feb 2020 at 09:22, Laszlo Ersek <lersek@redhat.com>
> > > wrote:
> > > > 
> > > > On 02/07/20 10:09, Laszlo Ersek wrote:
> > 
> > [...]
> > > > > For example, virt-install's "--location" option "can
> > > > > recognize certain distribution trees and fetches a bootable
> > > > > kernel/initrd pair to launch the install". It would be nice
> > > > > to keep that working for older distros.
> > > > > 
> > > > > I think LoadFile[2] can co-exist with SimpleFs.
> > > > > 
> > > > > I also think that the "try SimpleFs first, fall back to
> > > > > LoadFile[2] second" requirement applies only to the UEFI boot
> > > > > manager, and not to the kernel's EFI stub. IOW in the new
> > > > > approach the kernel is free to ignore (abandon) the old
> > > > > approach for good.
> > > > 
> > > > ... But that might not be good for compatibility with grub
> > > > and/or the platform firmware, from the kernel's own
> > > > perspective, perhaps?...
> > > > 
> > > > Who is supposed to produce LoadFile2 with the new VenMedia
> > > > devpath?
> > > > 
> > > 
> > > What I am ultimately after is a generic GRUB that uses
> > > LoadImage+Startimage for starting the kernel on all
> > > architectures,
> > 
> > For most boots, we need to pivot to the MoK.  A long time ago, I
> > proposed updating the platform security policy to do an override to
> > allow MoK to become the security verifier (actually principally so
> > I could get the gummiboot bootloader to work with the MoK method):
> > 
> > https://git.kernel.org/pub/scm/linux/kernel/git/jejb/efitools.git/t
> > ree/lib/security_policy.c
> > 
> > And I believe all the pivot bootloaders now do this, but the fear
> > was always this looks a bit like hackery that might not work in
> > some UEFI implementations.  Since we don't really rely on it (shim
> > link loads after signature verification) we don't know whether the
> > assumption does break or not.  We'll need to get much more
> > comfortable with the security override before we can let grub do a
> > simple load+start.
> > 
> 
> I'd like to do something much simpler: let shim override LoadImage
> and StartImage,

Actually, the non-shim bootloaders really don't want to do that: the
whole point of being able to use LoadImage is that you don't need to
know how to load a PECOFF binary or check its signature.  Overriding
the security protocol allows updating the signature check, but if you
look at the current efitools implementation it uses the pkcs7 protocol
to avoid having to include crypto code.

I've got the pecoff code they'd need in my uefi library:

https://git.kernel.org/pub/scm/linux/kernel/git/jejb/efitools.git/tree/lib/pecoff.c

But it's a lot of code for things that pride themselves on being tiny.

>  and in their implementations, fall back to the firmware
> ones if necessary.
> 
> > > and is able to load the initrd from anywhere in an arch agnostic
> > > manner.
> > 
> > I think the use case might not really be grub, it's gummiboot, or
> > systemd-boot as its now called:
> > 
> 
> No it is definitely GRUB. GRUB today needs to attach to the shim
> protocol, perform the authentication, measure the payload etc etc,
> which means it knows far too much about the internals of shim or the
> fact that it even exists.

The shim protocol and shim are fairly separate.  I agree it means grub
has to load and know the two entry points for context and verify but
they're very far removed for the inner workings of shim.  Obviously, my
non-shim loader has to supply them for grub, so this is the
implementation:

https://git.kernel.org/pub/scm/linux/kernel/git/jejb/efitools.git/tree/lib/shim_protocol.c

It's only 50 lines.

The other thing to consider is that crypto code is huge.  Shim
currently includes it (although it could avoid this by using the pkcs7
verifier protocol trick I use ... I should push that harder) and it
adds about 1M of static code.  Grub does not have this code, so either
grub uses shim and its code to do the signature verification or grub
will have to include the additional 1M as well ... I think using shim
via the protocol is preferable.

> My ideal bootflow would be where the OS installer looks at the
> firmware's db/dbx, doesn't bother to install shim if the OS vendor's
> cert is there, and uses the exact same GRUB regardless of whether
> shim is part of the bootflow or not.

That's not enough.  The whole point of MoK is that the user may have
done their own key addition, so you could be in the situation where the
vendor cert is present in db but the user has a MoK override for boot
and if you assume presence of the vendor cert means you can use
loadimage, this will fail because the MoK cert isn't in db ... unless
you've added the MoK key via the security protocol override.

> One of the things impeding this is the fact that we cannot load the
> initrd from anywhere when using loadimage+startimage.

unless initrd becomes a PECOFF binary, it can never be loaded by
loadimage ... I thought you were still letting the kernel load it via
LoadFile2?  (assuming you are and that the above is just a typo).

James


^ permalink raw reply	[flat|nested] 39+ messages in thread

* Re: [PATCH 0/2] arch-agnostic initrd loading method for EFI systems
  2020-02-07 19:54           ` James Bottomley
@ 2020-02-07 20:03             ` Ard Biesheuvel
  0 siblings, 0 replies; 39+ messages in thread
From: Ard Biesheuvel @ 2020-02-07 20:03 UTC (permalink / raw)
  To: James Bottomley
  Cc: Laszlo Ersek, Ard Biesheuvel, linux-efi, linux-arm-kernel,
	Leif Lindholm, Peter Jones, Matthew Garrett, Alexander Graf,
	Ilias Apalodimas, Heinrich Schuchardt, Daniel Kiper

On Fri, 7 Feb 2020 at 19:54, James Bottomley
<James.Bottomley@hansenpartnership.com> wrote:
>
> On Fri, 2020-02-07 at 18:31 +0000, Ard Biesheuvel wrote:
> > On Fri, 7 Feb 2020 at 16:20, James Bottomley
> > <James.Bottomley@hansenpartnership.com> wrote:
> > >
> > > On Fri, 2020-02-07 at 12:23 +0000, Ard Biesheuvel wrote:
> > > > On Fri, 7 Feb 2020 at 09:22, Laszlo Ersek <lersek@redhat.com>
> > > > wrote:
> > > > >
> > > > > On 02/07/20 10:09, Laszlo Ersek wrote:
> > >
> > > [...]
> > > > > > For example, virt-install's "--location" option "can
> > > > > > recognize certain distribution trees and fetches a bootable
> > > > > > kernel/initrd pair to launch the install". It would be nice
> > > > > > to keep that working for older distros.
> > > > > >
> > > > > > I think LoadFile[2] can co-exist with SimpleFs.
> > > > > >
> > > > > > I also think that the "try SimpleFs first, fall back to
> > > > > > LoadFile[2] second" requirement applies only to the UEFI boot
> > > > > > manager, and not to the kernel's EFI stub. IOW in the new
> > > > > > approach the kernel is free to ignore (abandon) the old
> > > > > > approach for good.
> > > > >
> > > > > ... But that might not be good for compatibility with grub
> > > > > and/or the platform firmware, from the kernel's own
> > > > > perspective, perhaps?...
> > > > >
> > > > > Who is supposed to produce LoadFile2 with the new VenMedia
> > > > > devpath?
> > > > >
> > > >
> > > > What I am ultimately after is a generic GRUB that uses
> > > > LoadImage+Startimage for starting the kernel on all
> > > > architectures,
> > >
> > > For most boots, we need to pivot to the MoK.  A long time ago, I
> > > proposed updating the platform security policy to do an override to
> > > allow MoK to become the security verifier (actually principally so
> > > I could get the gummiboot bootloader to work with the MoK method):
> > >
> > > https://git.kernel.org/pub/scm/linux/kernel/git/jejb/efitools.git/t
> > > ree/lib/security_policy.c
> > >
> > > And I believe all the pivot bootloaders now do this, but the fear
> > > was always this looks a bit like hackery that might not work in
> > > some UEFI implementations.  Since we don't really rely on it (shim
> > > link loads after signature verification) we don't know whether the
> > > assumption does break or not.  We'll need to get much more
> > > comfortable with the security override before we can let grub do a
> > > simple load+start.
> > >
> >
> > I'd like to do something much simpler: let shim override LoadImage
> > and StartImage,
>
> Actually, the non-shim bootloaders really don't want to do that: the
> whole point of being able to use LoadImage is that you don't need to
> know how to load a PECOFF binary or check its signature.  Overriding
> the security protocol allows updating the signature check, but if you
> look at the current efitools implementation it uses the pkcs7 protocol
> to avoid having to include crypto code.
>
> I've got the pecoff code they'd need in my uefi library:
>
> https://git.kernel.org/pub/scm/linux/kernel/git/jejb/efitools.git/tree/lib/pecoff.c
>
> But it's a lot of code for things that pride themselves on being tiny.
>

I think you are missing the point. GRUB will only use
loadimage+startimage, no matter what is backing it (the firmware or
shim). The same applies to gummiboot or even the uefi shell if you
wanted to. So all loaders use LoadImage/StartImage as usual, but shim
inserts itself into the call chain if it was loaded first.


> >  and in their implementations, fall back to the firmware
> > ones if necessary.
> >
> > > > and is able to load the initrd from anywhere in an arch agnostic
> > > > manner.
> > >
> > > I think the use case might not really be grub, it's gummiboot, or
> > > systemd-boot as its now called:
> > >
> >
> > No it is definitely GRUB. GRUB today needs to attach to the shim
> > protocol, perform the authentication, measure the payload etc etc,
> > which means it knows far too much about the internals of shim or the
> > fact that it even exists.
>
> The shim protocol and shim are fairly separate.  I agree it means grub
> has to load and know the two entry points for context and verify but
> they're very far removed for the inner workings of shim.  Obviously, my
> non-shim loader has to supply them for grub, so this is the
> implementation:
>
> https://git.kernel.org/pub/scm/linux/kernel/git/jejb/efitools.git/tree/lib/shim_protocol.c
>
> It's only 50 lines.
>
> The other thing to consider is that crypto code is huge.  Shim
> currently includes it (although it could avoid this by using the pkcs7
> verifier protocol trick I use ... I should push that harder) and it
> adds about 1M of static code.  Grub does not have this code, so either
> grub uses shim and its code to do the signature verification or grub
> will have to include the additional 1M as well ... I think using shim
> via the protocol is preferable.
>

No. GRUB will call loadimage+startimage, and will end up hitting the
implementation exposed by shim.

> > My ideal bootflow would be where the OS installer looks at the
> > firmware's db/dbx, doesn't bother to install shim if the OS vendor's
> > cert is there, and uses the exact same GRUB regardless of whether
> > shim is part of the bootflow or not.
>
> That's not enough.  The whole point of MoK is that the user may have
> done their own key addition, so you could be in the situation where the
> vendor cert is present in db but the user has a MoK override for boot
> and if you assume presence of the vendor cert means you can use
> loadimage, this will fail because the MoK cert isn't in db ... unless
> you've added the MoK key via the security protocol override.
>

No. The LoadImage you are hitting is shim's loadimage not the
firmware's loadimage in this case.

> > One of the things impeding this is the fact that we cannot load the
> > initrd from anywhere when using loadimage+startimage.
>
> unless initrd becomes a PECOFF binary, it can never be loaded by
> loadimage ... I thought you were still letting the kernel load it via
> LoadFile2?  (assuming you are and that the above is just a typo).
>

No it is not a typo.

If you load the kernel vis LoadImage, you need to use initrd= to load
the initrd, which required that file to be hosted on a file system
that EFI understands. The alternative is to load the initrd into
memory, store the address and size into a bootparams structure or DT,
and invoke the kernel via some other entry point that allows you to
carry this metadata.

I want to get rid of the latter, which means I need a way to load the
initrd that is not limited to loading from the same [EFI supported]
file system as the kernel. *That* is what this series is about.

^ permalink raw reply	[flat|nested] 39+ messages in thread

* Re: [PATCH 0/2] arch-agnostic initrd loading method for EFI systems
  2020-02-07 19:47   ` Ard Biesheuvel
@ 2020-02-07 20:26     ` Arvind Sankar
  0 siblings, 0 replies; 39+ messages in thread
From: Arvind Sankar @ 2020-02-07 20:26 UTC (permalink / raw)
  To: Ard Biesheuvel
  Cc: Arvind Sankar, Ard Biesheuvel, linux-efi, linux-arm-kernel,
	Laszlo Ersek, Leif Lindholm, Peter Jones, Matthew Garrett,
	Alexander Graf, Ilias Apalodimas, Heinrich Schuchardt,
	Daniel Kiper

On Fri, Feb 07, 2020 at 07:47:46PM +0000, Ard Biesheuvel wrote:
> On Fri, 7 Feb 2020 at 18:45, Arvind Sankar <nivedita@alum.mit.edu> wrote:
> >
> > On Thu, Feb 06, 2020 at 02:03:50PM +0000, Ard Biesheuvel wrote:
> > >   data structure. It also creates a time window where the initrd data sits
> > >   in memory, and can potentially be corrupted before the kernel is booted.
> > >
> >
> > I don't quite understand the time window aspect -- can you expand on
> > that? It seems like the same time window exists between when the kernel
> > is loaded and when it actually runs, no? Why is this more important for
> > initrd?
> 
> When using loadimage+startimage, the authentication and measurement of
> the kernel image occur during the call to loadimage(), even if the
> source of the load is memory itself, and startimage() is typically
> called right after.
> 
> The assumption is that it may help to make this time as short as
> possible for the initrd as well.

Ok, this is for when we can use LoadImage, that makes sense.

^ permalink raw reply	[flat|nested] 39+ messages in thread

* Re: [PATCH 1/2] efi/libstub: add support for loading the initrd from a device path
  2020-02-06 14:03 ` [PATCH 1/2] efi/libstub: add support for loading the initrd from a device path Ard Biesheuvel
  2020-02-06 18:26   ` Heinrich Schuchardt
  2020-02-07  9:48   ` Laszlo Ersek
@ 2020-02-09  6:39   ` Lukas Wunner
  2020-02-09 11:35     ` Ard Biesheuvel
  2 siblings, 1 reply; 39+ messages in thread
From: Lukas Wunner @ 2020-02-09  6:39 UTC (permalink / raw)
  To: Ard Biesheuvel
  Cc: linux-efi, linux-arm-kernel, lersek, leif, pjones, mjg59, agraf,
	ilias.apalodimas, xypron.glpk, daniel.kiper

On Thu, Feb 06, 2020 at 02:03:51PM +0000, Ard Biesheuvel wrote:
> --- a/drivers/firmware/efi/libstub/efistub.h
> +++ b/drivers/firmware/efi/libstub/efistub.h
> @@ -566,6 +566,14 @@ union efi_load_file_protocol {
>  	} mixed_mode;
>  };
>  
> +struct efi_vendor_dev_path {
> +	u8		type;
> +	u8		sub_type;
> +	u16		length;
> +	efi_guid_t	vendorguid;
> +	u8		vendordata[];
> +} __packed;

In commit 46cd4b75cd0e ("efi: Add device path parser") I introduced a
struct efi_dev_path with a union for the type-specific data.  Maybe
you can make use of it instead of introducing a new type?


> --- a/drivers/firmware/efi/libstub/efi-stub-helper.c
> +++ b/drivers/firmware/efi/libstub/efi-stub-helper.c
> @@ -323,3 +323,68 @@ void efi_char16_printk(efi_char16_t *str)
>  	efi_call_proto(efi_table_attr(efi_system_table(), con_out),
>  		       output_string, str);
>  }
> +
> +static const struct {
> +	struct efi_vendor_dev_path	vendor;
> +	struct efi_generic_dev_path	end;
> +} __packed initrd_devpath = {

Nit:  Can we consistently stick to "dev_path" in lieu of "devpath"?

Thanks,

Lukas

^ permalink raw reply	[flat|nested] 39+ messages in thread

* Re: [PATCH 1/2] efi/libstub: add support for loading the initrd from a device path
  2020-02-09  6:39   ` Lukas Wunner
@ 2020-02-09 11:35     ` Ard Biesheuvel
  0 siblings, 0 replies; 39+ messages in thread
From: Ard Biesheuvel @ 2020-02-09 11:35 UTC (permalink / raw)
  To: Lukas Wunner
  Cc: Ard Biesheuvel, linux-efi, linux-arm-kernel, Laszlo Ersek,
	Leif Lindholm, Peter Jones, Matthew Garrett, Alexander Graf,
	Ilias Apalodimas, Heinrich Schuchardt, Daniel Kiper

On Sun, 9 Feb 2020 at 07:50, Lukas Wunner <lukas@wunner.de> wrote:
>
> On Thu, Feb 06, 2020 at 02:03:51PM +0000, Ard Biesheuvel wrote:
> > --- a/drivers/firmware/efi/libstub/efistub.h
> > +++ b/drivers/firmware/efi/libstub/efistub.h
> > @@ -566,6 +566,14 @@ union efi_load_file_protocol {
> >       } mixed_mode;
> >  };
> >
> > +struct efi_vendor_dev_path {
> > +     u8              type;
> > +     u8              sub_type;
> > +     u16             length;
> > +     efi_guid_t      vendorguid;
> > +     u8              vendordata[];
> > +} __packed;
>
> In commit 46cd4b75cd0e ("efi: Add device path parser") I introduced a
> struct efi_dev_path with a union for the type-specific data.  Maybe
> you can make use of it instead of introducing a new type?
>

That would make sense, but how do you take the size then? Perhaps it
would be better to have a generic struct definition for the shared
fields, struct definitions for the different types that use the
generic type as the first fields, and a separate union that ties them
altogether for the parser.

>
> > --- a/drivers/firmware/efi/libstub/efi-stub-helper.c
> > +++ b/drivers/firmware/efi/libstub/efi-stub-helper.c
> > @@ -323,3 +323,68 @@ void efi_char16_printk(efi_char16_t *str)
> >       efi_call_proto(efi_table_attr(efi_system_table(), con_out),
> >                      output_string, str);
> >  }
> > +
> > +static const struct {
> > +     struct efi_vendor_dev_path      vendor;
> > +     struct efi_generic_dev_path     end;
> > +} __packed initrd_devpath = {
>
> Nit:  Can we consistently stick to "dev_path" in lieu of "devpath"?
>

Sure.

^ permalink raw reply	[flat|nested] 39+ messages in thread

* Re: [PATCH 1/2] efi/libstub: add support for loading the initrd from a device path
  2020-02-07 12:36     ` Ard Biesheuvel
@ 2020-02-10 14:26       ` Laszlo Ersek
  0 siblings, 0 replies; 39+ messages in thread
From: Laszlo Ersek @ 2020-02-10 14:26 UTC (permalink / raw)
  To: Ard Biesheuvel
  Cc: Ard Biesheuvel, linux-efi, linux-arm-kernel, Leif Lindholm,
	Peter Jones, Matthew Garrett, Alexander Graf, Ilias Apalodimas,
	Heinrich Schuchardt, Daniel Kiper

On 02/07/20 13:36, Ard Biesheuvel wrote:
> On Fri, 7 Feb 2020 at 09:48, Laszlo Ersek <lersek@redhat.com> wrote:
>> On 02/06/20 15:03, Ard Biesheuvel wrote:

>>> +efi_status_t efi_load_initrd_devpath(unsigned long *load_addr,
>>> +                                  unsigned long *load_size,
>>> +                                  unsigned long max)
>>> +{
>>> +     efi_guid_t lf2_proto_guid = EFI_LOAD_FILE2_PROTOCOL_GUID;
>>> +     efi_device_path_protocol_t *dp;
>>> +     efi_load_file2_protocol_t *lf2;
>>> +     unsigned long initrd_addr;
>>> +     unsigned long initrd_size;
>>> +     efi_handle_t handle;
>>> +     efi_status_t status;
>>> +
>>> +     if (!load_addr || !load_size)
>>> +             return EFI_INVALID_PARAMETER;
>>> +
>>> +     dp = (efi_device_path_protocol_t *)&initrd_devpath;
>>> +     status = efi_bs_call(locate_device_path, &lf2_proto_guid, &dp, &handle);
>>> +     if (status != EFI_SUCCESS)
>>> +             return status;
>>> +
>>> +     status = efi_bs_call(handle_protocol, handle, &lf2_proto_guid,
>>> +                          (void **)&lf2);
>>> +     if (status != EFI_SUCCESS)
>>> +             return status;
>>> +
>>> +     initrd_size = 0;
>>> +     status = efi_call_proto(lf2, load_file,
>>> +                             (efi_device_path_protocol_t *)&initrd_devpath,
>>> +                             false, &initrd_size, NULL);
>>
>> The second argument to EFI_LOAD_FILE2_PROTOCOL.LoadFile() is "FilePath",
>> specified as "The device specific path of the file to load". This means
>> it is supposed to be a (possibly empty) sequence of FILEPATH_DEVICE_PATH
>> nodes, terminated by and "End Entire Device Path" node. See
>>
>> - 10.3.1 Generic Device Path Structures
>> - 10.3.5.4 File Path Media Device Path
>>
>> in UEFI-2.8.
>>
>> And "initrd_devpath" is not a device path like that; instead it's the
>> VenMedia device path that's installed on the handle that also carries
>> our LoadFile2 instance.
>>
> 
> OK, so you are saying this could be used to disambiguate which of
> several files you may want to load from the initrd GUIDed device path?

Yes, exactly.

>> Now, I do see that this all theoretical here, as we don't expect the
>> LoadFile2 instance that we've found via our special
>> LINUX_EFI_INITRD_MEDIA_GUID VenMedia devpath to do *any* device-specific
>> filename / pathname parsing.
>>
>> But in that case (i.e., given that the FilePath argument is totally
>> irrelevant), I think it's much clearer if we simply pass an empty device
>> path -- one that consists of a single "End Entire Device Path" node.
>>
>> I've checked, and your ArmVirtQemu patch ignores the FilePath argument
>> too -- justifiedly so. I just think it's better to pass in a well-formed
>> device path, rather than NULL. Because, the FilePath parameter is not
>> marked OPTIONAL in the spec.
>>
> 
> One thing that occurred to me is that we have to decide whether we
> want to support the '10.3.5.8 Relative Offset Range' device path node
> for this file, so that you could potentially load subranges of the
> file. I don't see a use case for it right now, though.

Agreed, it doesn't seem necessary / justified.

> But for my understanding, would the FilePath passed to LoadFile2 be
> 'Offset(...)+EndEntire' in that case? Or should it include the GUID
> device path node as well?

I see the only specified, concrete use case for the Offset() devpath
node in "14.4.2.1 PCI Bus Driver Responsibilities". I think it doesn't
apply at all to our use case.

Also, according to "10.3.5.8 Relative Offset Range",

    This device path node specifies a range of offsets relative to the
    first byte available on the device.

In that sense, it seems like a (mutually exclusive) alternative to
FilePath. Given a device, one would specify *either* an offset range
(which is relative to the start of the device, when the device is viewed
as a range of bytes), *or* a FilePath (which is "relative" to the device
when viewed as a store of named files, but still not as a full-blown
random-access filesystem).

In brief, Offset() doesn't seem to apply in connection with LoadFile2,
at all. Certainly not in our particular use case, I'd suggest.

[...]

Thanks!
Laszlo


^ permalink raw reply	[flat|nested] 39+ messages in thread

* Re: [PATCH 2/2] efi/libstub: take noinitrd cmdline argument into account for devpath initrd
  2020-02-06 14:03 ` [PATCH 2/2] efi/libstub: take noinitrd cmdline argument into account for devpath initrd Ard Biesheuvel
  2020-02-06 18:33   ` Heinrich Schuchardt
@ 2020-02-12 16:01   ` Peter Jones
  1 sibling, 0 replies; 39+ messages in thread
From: Peter Jones @ 2020-02-12 16:01 UTC (permalink / raw)
  To: Ard Biesheuvel
  Cc: linux-efi, linux-arm-kernel, lersek, leif, mjg59, agraf,
	ilias.apalodimas, xypron.glpk, daniel.kiper

On Thu, Feb 06, 2020 at 02:03:52PM +0000, Ard Biesheuvel wrote:
> One of the advantages of using what basically amounts to a callback
> interface into the bootloader for loading the initrd is that it provides
> a natural place for the bootloader or firmware to measure the initrd
> contents while they are being passed to the kernel.
> 
> Unfortunately, this is not a guarantee that the initrd will in fact be
> loaded and its /init invoked by the kernel, since the command line may
> contain the 'noinitrd' option, in which case the initrd is ignored, but
> this will not be reflected in the PCR that covers the initrd measurement.
> 
> This could be addressed by measuring the command line as well, and
> including that PCR in the attestation policy, but this locks down the
> command line completely, which may be too restrictive.

In practice I think we need to be measuring the command line anyway.  In
current existing deployments, we measure kernel and initramfs into PCR9,
and measure the kernel command line into PCR8 (both are reserved in the
TIS for OS use).  This allows users farther down the stack to choose
whether which things they seal against, based on their requirements.

> So let's take the noinitrd argument into account in the stub, too. This
> forces the PCR that covers the initrd to assume a different value when
> noinitrd is passed, allowing an attestation policy to disregard the
> command line if there is no need to take its measurement into account
> for other reasons.

I think we also need to log a capping EV_SEPARATOR event with an log entry
that says it's for noinitrd into PCR9, in order to prevent any
scenarios where an attacker prevents the normal initramfs from loading,
and then replays the events from a prior log in order to unseal secrets.

-- 
  Peter


^ permalink raw reply	[flat|nested] 39+ messages in thread

end of thread, back to index

Thread overview: 39+ messages (download: mbox.gz / follow: Atom feed)
-- links below jump to the message on this page --
2020-02-06 14:03 [PATCH 0/2] arch-agnostic initrd loading method for EFI systems Ard Biesheuvel
2020-02-06 14:03 ` [PATCH 1/2] efi/libstub: add support for loading the initrd from a device path Ard Biesheuvel
2020-02-06 18:26   ` Heinrich Schuchardt
2020-02-06 18:46     ` Ilias Apalodimas
2020-02-06 19:15       ` Heinrich Schuchardt
2020-02-06 20:09         ` Ilias Apalodimas
2020-02-06 22:49           ` Heinrich Schuchardt
2020-02-07  7:35             ` Ilias Apalodimas
2020-02-06 22:35     ` Ard Biesheuvel
2020-02-07  0:01       ` Heinrich Schuchardt
2020-02-07  0:21         ` Ard Biesheuvel
2020-02-07  0:57           ` Heinrich Schuchardt
2020-02-07  8:12             ` Ard Biesheuvel
2020-02-07 13:30               ` Heinrich Schuchardt
2020-02-07 13:58                 ` Ard Biesheuvel
2020-02-07 14:18                   ` Alexander Graf
2020-02-07 15:30                     ` Ard Biesheuvel
2020-02-07 15:35                     ` Heinrich Schuchardt
2020-02-07 11:09       ` Laszlo Ersek
2020-02-07 11:03     ` Laszlo Ersek
2020-02-07  9:48   ` Laszlo Ersek
2020-02-07 12:36     ` Ard Biesheuvel
2020-02-10 14:26       ` Laszlo Ersek
2020-02-09  6:39   ` Lukas Wunner
2020-02-09 11:35     ` Ard Biesheuvel
2020-02-06 14:03 ` [PATCH 2/2] efi/libstub: take noinitrd cmdline argument into account for devpath initrd Ard Biesheuvel
2020-02-06 18:33   ` Heinrich Schuchardt
2020-02-06 23:44     ` Ard Biesheuvel
2020-02-12 16:01   ` Peter Jones
2020-02-07  9:09 ` [PATCH 0/2] arch-agnostic initrd loading method for EFI systems Laszlo Ersek
2020-02-07  9:22   ` Laszlo Ersek
2020-02-07 12:23     ` Ard Biesheuvel
2020-02-07 16:20       ` James Bottomley
2020-02-07 18:31         ` Ard Biesheuvel
2020-02-07 19:54           ` James Bottomley
2020-02-07 20:03             ` Ard Biesheuvel
2020-02-07 18:45 ` Arvind Sankar
2020-02-07 19:47   ` Ard Biesheuvel
2020-02-07 20:26     ` Arvind Sankar

Linux-EFI Archive on lore.kernel.org

Archives are clonable:
	git clone --mirror https://lore.kernel.org/linux-efi/0 linux-efi/git/0.git

	# If you have public-inbox 1.1+ installed, you may
	# initialize and index your mirror using the following commands:
	public-inbox-init -V2 linux-efi linux-efi/ https://lore.kernel.org/linux-efi \
		linux-efi@vger.kernel.org
	public-inbox-index linux-efi

Example config snippet for mirrors

Newsgroup available over NNTP:
	nntp://nntp.lore.kernel.org/org.kernel.vger.linux-efi


AGPL code for this site: git clone https://public-inbox.org/public-inbox.git