arch-agnostic initrd loading method for EFI systems
Ard Biesheuvel
maintainercover arch-agnostic initrd loading method for EFI systems
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.
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(-)
Signature · 1 line
2.17.1
Ard Biesheuvel
maintainerPATCH 1/2 efi/libstub: add support for loading the initrd from a device path
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.
--- 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(-)
drivers/firmware/efi/libstub/arm-stub.c +12 -4
@@ -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 ();
drivers/firmware/efi/libstub/efi-stub-helper.c +65 -0
68 lines not on this page · raw message
drivers/firmware/efi/libstub/efistub.h +12 -0
@@ -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
drivers/firmware/efi/libstub/x86-stub.c +33 -3
@@ -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.
include/linux/efi.h +1 -0
@@ -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)
Signature · 1 line
2.17.1
Re: [PATCH 1/2] efi/libstub: add support for loading the initrd from a device path
Quoted 10 lines · Ard Biesheuvel
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.
Quoted 3 lines
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.
Quoted 11 lines
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.
Quoted 10 lines
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.
Quoted 84 lines
Signed-off-by Ard Biesheuvel <[email protected]>--- 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(-)drivers/firmware/efi/libstub/arm-stub.c +12
-4@@ -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 ();drivers/firmware/efi/libstub/efi-stub-helper.c +31
-0@@ -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 ;
drivers/firmware/efi/libstub/efi-stub-helper.c void efi_char16_printk(efi_char16_t *str) · line 366
Quoted 10 lines
drivers/firmware/efi/libstub/efi-stub-helper.c +10
-0+ + 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.rstDocumentation/x86/x86_64/uefi.rst
Best regards
Heinrich
Quoted 119 lines
drivers/firmware/efi/libstub/efi-stub-helper.c +24
-0+ + 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 ; +}drivers/firmware/efi/libstub/efistub.h +12
-0@@ -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 drivers/firmware/efi/libstub/x86-stub.c +33
-3@@ -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. include/linux/efi.h +1
-0@@ -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)
Ilias Apalodimas
reviewerRe: [PATCH 1/2] efi/libstub: add support for loading the initrd from a device path
Hi Heinrich,
I actually like the approach.
On Thu, Feb 06, 2020 at 07:26:20PM +0100, Heinrich Schuchardt wrote:
[...]
Quoted 6 lines
Quoted 3 lines
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.
Quoted 5 lines
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.
Quoted 22 lines
Quoted 11 lines
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.
Quoted 10 lines
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.
Quoted 8 lines
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?
Quoted 3 lines
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.
Quoted 5 lines
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_PROTOCOLimplementation.
Thanks
/Ilias
Quoted 236 lines
Quoted 84 lines
Signed-off-by Ard Biesheuvel <[email protected]>--- 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(-)drivers/firmware/efi/libstub/arm-stub.c +12
-4@@ -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 ();drivers/firmware/efi/libstub/efi-stub-helper.c +31
-0@@ -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 ;drivers/firmware/efi/libstub/efi-stub-helper.c void efi_char16_printk(efi_char16_t *str) · line 366
Quoted 10 lines
drivers/firmware/efi/libstub/efi-stub-helper.c +10
-0+ + 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_PROTOCOLmust 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_PROTOCOLand try to load the initrd.It would be worthwhile to describe the requirements on the implementation of the
EFI_LOAD_FILE2_PROTOCOLin a code comment and in the documentation.Unfortunately the documentation of UEFI has been duplicated into two files:
Documentation/arm/uefi.rstDocumentation/x86/x86_64/uefi.rstBest regards
Heinrich
Quoted 119 lines
drivers/firmware/efi/libstub/efi-stub-helper.c +24
-0+ + 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 ; +}drivers/firmware/efi/libstub/efistub.h +12
-0@@ -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 drivers/firmware/efi/libstub/x86-stub.c +33
-3@@ -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. include/linux/efi.h +1
-0@@ -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)
Re: [PATCH 1/2] efi/libstub: add support for loading the initrd from a device path
Quoted 10 lines · Ilias Apalodimas
Hi Heinrich,
I actually like the approach.
On Thu, Feb 06, 2020 at 07:26:20PM +0100, Heinrich Schuchardt wrote:
[...]Quoted 4 lines
Quoted 3 lines
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.
Quoted 6 lines
Quoted 2 lines
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?
Quoted 7 lines
Quoted 6 lines
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.
Quoted 7 lines
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.
Quoted 35 lines
Quoted 23 lines
Quoted 12 lines
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.
Quoted 10 lines
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.
Quoted 8 lines
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?
Quoted 1 line
Quoted 6 lines
Quoted 2 lines
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
Quoted 246 lines
Quoted 6 lines
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_PROTOCOLimplementation.Thanks
/IliasQuoted 236 lines
Quoted 84 lines
Signed-off-by Ard Biesheuvel <[email protected]>--- 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(-)drivers/firmware/efi/libstub/arm-stub.c +12
-4@@ -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 ();drivers/firmware/efi/libstub/efi-stub-helper.c +31
-0@@ -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 ;drivers/firmware/efi/libstub/efi-stub-helper.c void efi_char16_printk(efi_char16_t *str) · line 366
Quoted 10 lines
drivers/firmware/efi/libstub/efi-stub-helper.c +10
-0+ + 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_PROTOCOLmust 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_PROTOCOLand try to load the initrd.It would be worthwhile to describe the requirements on the implementation of the
EFI_LOAD_FILE2_PROTOCOLin a code comment and in the documentation.Unfortunately the documentation of UEFI has been duplicated into two files:
Documentation/arm/uefi.rstDocumentation/x86/x86_64/uefi.rstBest regards
Heinrich
Quoted 119 lines
drivers/firmware/efi/libstub/efi-stub-helper.c +24
-0+ + 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 ; +}drivers/firmware/efi/libstub/efistub.h +12
-0@@ -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 drivers/firmware/efi/libstub/x86-stub.c +33
-3@@ -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. include/linux/efi.h +1
-0@@ -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)
Ilias Apalodimas
reviewerRe: [PATCH 1/2] efi/libstub: add support for loading the initrd from a device path
Quoted 14 lines · Heinrich Schuchardt
Quoted 13 lines · Ilias Apalodimas
Hi Heinrich,
I actually like the approach.
On Thu, Feb 06, 2020 at 07:26:20PM +0100, Heinrich Schuchardt wrote:
[...]Quoted 6 lines
Quoted 3 lines
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.
Quoted 6 lines
Quoted 3 lines
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?
Quoted 12 lines
Quoted 12 lines
Quoted 7 lines
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.
Quoted 6 lines
Quoted 3 lines
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.
Quoted 3 lines
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.
Quoted 38 lines
Quoted 38 lines
Quoted 23 lines
Quoted 12 lines
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.
Quoted 10 lines
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.
Quoted 8 lines
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?
Quoted 3 lines
Please, indicate which software you expect to expose the initrd related
EFI_LOAD_FILE2_PROTOCOL.
Quoted 8 lines
Quoted 3 lines
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
Quoted 252 lines
Best regards
Heinrich
Quoted 246 lines
Quoted 6 lines
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_PROTOCOLimplementation.Thanks
/IliasQuoted 236 lines
Quoted 84 lines
Signed-off-by Ard Biesheuvel <[email protected]>--- 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(-)drivers/firmware/efi/libstub/arm-stub.c +12
-4@@ -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 ();drivers/firmware/efi/libstub/efi-stub-helper.c +31
-0@@ -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 ;drivers/firmware/efi/libstub/efi-stub-helper.c void efi_char16_printk(efi_char16_t *str) · line 366
Quoted 10 lines
drivers/firmware/efi/libstub/efi-stub-helper.c +10
-0+ + 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_PROTOCOLmust 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_PROTOCOLand try to load the initrd.It would be worthwhile to describe the requirements on the implementation of the
EFI_LOAD_FILE2_PROTOCOLin a code comment and in the documentation.Unfortunately the documentation of UEFI has been duplicated into two files:
Documentation/arm/uefi.rstDocumentation/x86/x86_64/uefi.rstBest regards
Heinrich
Quoted 119 lines
drivers/firmware/efi/libstub/efi-stub-helper.c +24
-0+ + 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 ; +}drivers/firmware/efi/libstub/efistub.h +12
-0@@ -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 drivers/firmware/efi/libstub/x86-stub.c +33
-3@@ -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. include/linux/efi.h +1
-0@@ -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)
Re: [PATCH 1/2] efi/libstub: add support for loading the initrd from a device path
Quoted 40 lines · Ilias Apalodimas
Quoted 14 lines · Heinrich Schuchardt
Quoted 13 lines · Ilias Apalodimas
Hi Heinrich,
I actually like the approach.
On Thu, Feb 06, 2020 at 07:26:20PM +0100, Heinrich Schuchardt wrote:
[...]Quoted 6 lines
Quoted 3 lines
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.
Quoted 6 lines
Quoted 3 lines
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?
Quoted 16 lines
Quoted 15 lines
Quoted 7 lines
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.
Quoted 7 lines
Quoted 2 lines
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.
Quoted 9 lines
Quoted 4 lines
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.
Quoted 42 lines
Quoted 42 lines
Quoted 41 lines
Quoted 23 lines
Quoted 12 lines
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.
Quoted 10 lines
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.
Quoted 8 lines
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?
Quoted 3 lines
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.
Quoted 7 lines
Quoted 4 lines
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.
Quoted 44 lines
Thanks
/IliasQuoted 41 lines
Best regards
Heinrich
Quoted 36 lines
Quoted 6 lines
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_PROTOCOLimplementation.Thanks
/IliasQuoted 26 lines
Quoted 26 lines
Signed-off-by Ard Biesheuvel <[email protected]>--- 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(-)drivers/firmware/efi/libstub/arm-stub.c +1
-0@@ -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" );
Quoted 10 lines
Quoted 10 lines
Quoted 10 lines
Quoted 10 lines
drivers/firmware/efi/libstub/arm-stub.c unsigned long efi_entry(void *handle, efi_system_table_t *sys_table_arg, · line 265
Quoted 10 lines
drivers/firmware/efi/libstub/arm-stub.c +7
-3- 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
Quoted 201 lines
Quoted 201 lines
Quoted 200 lines
Quoted 200 lines
Quoted 48 lines
drivers/firmware/efi/libstub/arm-stub.c +4
-1+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 ();drivers/firmware/efi/libstub/efi-stub-helper.c +31
-0@@ -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 ;drivers/firmware/efi/libstub/efi-stub-helper.c void efi_char16_printk(efi_char16_t *str) · line 366
Quoted 10 lines
drivers/firmware/efi/libstub/efi-stub-helper.c +10
-0+ + 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_PROTOCOLmust 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_PROTOCOLand try to load the initrd.It would be worthwhile to describe the requirements on the implementation of the
EFI_LOAD_FILE2_PROTOCOLin a code comment and in the documentation.Unfortunately the documentation of UEFI has been duplicated into two files:
Documentation/arm/uefi.rstDocumentation/x86/x86_64/uefi.rstBest regards
Heinrich
Quoted 119 lines
drivers/firmware/efi/libstub/efi-stub-helper.c +24
-0+ + 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 ; +}drivers/firmware/efi/libstub/efistub.h +12
-0@@ -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 drivers/firmware/efi/libstub/x86-stub.c +33
-3@@ -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. include/linux/efi.h +1
-0@@ -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)
Ilias Apalodimas
reviewerRe: [PATCH 1/2] efi/libstub: add support for loading the initrd from a device path
Hi Heinrich
[...]
Quoted 4 lines
Quoted 4 lines
Quoted 3 lines
If you don't have an environment or boot script how would update-initramfs set the path of the initrd when it is updated?
Quoted 8 lines
Quoted 4 lines
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.
Quoted 10 lines
Quoted 9 lines
Quoted 4 lines
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.
Quoted 6 lines
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?
Quoted 66 lines
If you want extra security, Linux can use an authenticated variable.
Quoted 42 lines
Quoted 42 lines
Quoted 41 lines
Quoted 23 lines
Quoted 12 lines
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.
Quoted 10 lines
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.
Quoted 8 lines
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?
Quoted 3 lines
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.
Quoted 7 lines
Quoted 4 lines
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.Quoted 9 lines
Thanks
/IliasQuoted 6 lines
Best regards
Heinrich
Quoted 1 line
Quoted 14 lines
Quoted 10 lines
Quoted 10 lines
Quoted 10 lines
Quoted 10 lines
drivers/firmware/efi/libstub/arm-stub.c unsigned long efi_entry(void *handle, efi_system_table_t *sys_table_arg, · line 265
Quoted 10 lines
drivers/firmware/efi/libstub/arm-stub.c +7
-3- 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_PROTOCOLI 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
Re: [PATCH 1/2] efi/libstub: add support for loading the initrd from a device path
In reply to Heinrich SchuchardtQuoted 10 lines · Heinrich Schuchardt
Quoted 8 lines · Ard Biesheuvel
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)
Quoted 8 lines
Quoted 2 lines
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?
Quoted 7 lines
Quoted 3 lines
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.
Quoted 4 lines
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.
Quoted 22 lines
Quoted 11 lines
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.
Quoted 10 lines
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.
Quoted 7 lines
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.
Quoted 2 lines
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.
Quoted 3 lines
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.
Quoted 4 lines
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_PROTOCOLimplementation.
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.
Quoted 91 lines
Quoted 91 lines
Signed-off-by Ard Biesheuvel <[email protected]>--- 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(-)drivers/firmware/efi/libstub/arm-stub.c +12
-4@@ -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 ();drivers/firmware/efi/libstub/efi-stub-helper.c +38
-0@@ -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,
Quoted 9 lines
drivers/firmware/efi/libstub/efi-stub-helper.c void efi_char16_printk(efi_char16_t *str) · line 366
Quoted 3 lines
drivers/firmware/efi/libstub/efi-stub-helper.c +3
-0+ (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_PROTOCOLmust be installed which will load the initrd file when called with the same device path.
Exactly.
Quoted 3 lines
An alternative implementation would simple loop over all instances of the
EFI_LOAD_FILE2_PROTOCOLand 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.
Quoted 9 lines
It would be worthwhile to describe the requirements on the implementation of the
EFI_LOAD_FILE2_PROTOCOLin a code comment and in the documentation.Unfortunately the documentation of UEFI has been duplicated into two files:
Documentation/arm/uefi.rstDocumentation/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.
Quoted 121 lines
Quoted 120 lines
drivers/firmware/efi/libstub/efi-stub-helper.c +24
-0+ + 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 ; +}drivers/firmware/efi/libstub/efistub.h +12
-0@@ -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 drivers/firmware/efi/libstub/x86-stub.c +33
-3@@ -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. include/linux/efi.h +1
-0@@ -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)
Re: [PATCH 1/2] efi/libstub: add support for loading the initrd from a device path
Quoted 16 lines · Ard Biesheuvel
Quoted 15 lines · Heinrich Schuchardt
Quoted 10 lines · Ard Biesheuvel
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,
Quoted 9 lines
Quoted 3 lines
"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_infostruct, 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.
Quoted 18 lines
Quoted 8 lines
Quoted 4 lines
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.
Quoted 4 lines
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.
Quoted 6 lines
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?
Quoted 59 lines
Quoted 24 lines
Quoted 13 lines
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.
Quoted 10 lines
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.
Quoted 7 lines
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.
Quoted 2 lines
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.
Quoted 3 lines
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.
Quoted 4 lines
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_PROTOCOLimplementation.
Quoted 6 lines
This would also require kernel changes, since we don't currently load initrds from arbitrary device paths. The
EFI_FILE_PROTOCOLis 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.
Quoted 6 lines
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.
Quoted 5 lines
For u-boot, it should be trivial to implement a simple LoadFile2 protocol wrapper around
EFI_FILE_PROTOCOLthat can be installed on a handle that also carriesEFI_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?
Quoted 26 lines
Quoted 26 lines
Quoted 26 lines
Signed-off-by Ard Biesheuvel <[email protected]>--- 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(-)drivers/firmware/efi/libstub/arm-stub.c +1
-0@@ -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" );
Quoted 13 lines
Quoted 5 lines
Quoted 5 lines
drivers/firmware/efi/libstub/arm-stub.c +2
-3- 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);Quoted 8 lines
drivers/firmware/efi/libstub/arm-stub.c unsigned long efi_entry(void *handle, efi_system_table_t *sys_table_arg, · line 268
Quoted 8 lines
drivers/firmware/efi/libstub/arm-stub.c +8
-0+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.
Quoted 64 lines
Quoted 52 lines
Quoted 52 lines
drivers/firmware/efi/libstub/arm-stub.c +1
-1if (status !=EFI_SUCCESS ) -pr_efi_err ("Failed initrd from command line!\n" ); +pr_efi_err ("Failed to load initrd!\n" );efi_random_get_seed ();drivers/firmware/efi/libstub/efi-stub-helper.c +38
-0@@ -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,Quoted 9 lines
drivers/firmware/efi/libstub/efi-stub-helper.c void efi_char16_printk(efi_char16_t *str) · line 366
Quoted 3 lines
drivers/firmware/efi/libstub/efi-stub-helper.c +3
-0+ (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_PROTOCOLmust be installed which will load the initrd file when called with the same device path.Exactly.
Quoted 7 lines
Quoted 3 lines
An alternative implementation would simple loop over all instances of the
EFI_LOAD_FILE2_PROTOCOLand try to load the initrd.How do I distinguish the initrd from other
EFI_LOAD_FILE2_PROTOCOLinstances? For instance, PCI option ROMs are also exposed asEFI_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
Quoted 141 lines
Quoted 10 lines
It would be worthwhile to describe the requirements on the implementation of the
EFI_LOAD_FILE2_PROTOCOLin a code comment and in the documentation.Unfortunately the documentation of UEFI has been duplicated into two files:
Documentation/arm/uefi.rstDocumentation/x86/x86_64/uefi.rstYes, that is a good point. I will work on factoring out the generic parts and share them.
Thanks for the review,
Ard.Quoted 121 lines
Quoted 120 lines
drivers/firmware/efi/libstub/efi-stub-helper.c +24
-0+ + 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 ; +}drivers/firmware/efi/libstub/efistub.h +12
-0@@ -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 drivers/firmware/efi/libstub/x86-stub.c +33
-3@@ -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. include/linux/efi.h +1
-0@@ -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)
Re: [PATCH 1/2] efi/libstub: add support for loading the initrd from a device path
Quoted 28 lines · Heinrich Schuchardt
Quoted 16 lines · Ard Biesheuvel
Quoted 15 lines · Heinrich Schuchardt
Quoted 10 lines · Ard Biesheuvel
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,
Quoted 9 lines
Quoted 3 lines
"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_infostruct, and finds the file based on its filename, not on a device path. So how can it access files on other file systems?
Quoted 7 lines
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_PROTOCOLto 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.
Quoted 25 lines
Quoted 18 lines
Quoted 8 lines
Quoted 4 lines
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.
Quoted 4 lines
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.
Quoted 6 lines
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.
Quoted 6 lines
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.
Quoted 4 lines
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.
Quoted 59 lines
Quoted 59 lines
Quoted 24 lines
Quoted 13 lines
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.
Quoted 10 lines
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.
Quoted 7 lines
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.
Quoted 2 lines
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.
Quoted 3 lines
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.
Quoted 4 lines
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_PROTOCOLimplementation.
Quoted 9 lines
Quoted 6 lines
This would also require kernel changes, since we don't currently load initrds from arbitrary device paths. The
EFI_FILE_PROTOCOLis 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_PROTOCOLfrom 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.
Quoted 4 lines
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.
Quoted 7 lines
Quoted 6 lines
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.
Quoted 9 lines
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.
Quoted 10 lines
Quoted 5 lines
For u-boot, it should be trivial to implement a simple LoadFile2 protocol wrapper around
EFI_FILE_PROTOCOLthat can be installed on a handle that also carriesEFI_FILE_PROTOCOL.A U-Boot implementation of the
EFI_LOAD_FILE2_PROTOCOLwould need a device path variable to find the block device and to open theEFI_SIMPLE_FILE_SYSTEM_PROTOCOLbefore accessing the file.
Quoted 6 lines
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.
Quoted 3 lines
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?
Quoted 35 lines
Quoted 35 lines
Quoted 35 lines
Quoted 35 lines
Signed-off-by Ard Biesheuvel <[email protected]>--- 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(-)drivers/firmware/efi/libstub/arm-stub.c +7
-3@@ -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,
Quoted 9 lines
Quoted 4 lines
Quoted 4 lines
drivers/firmware/efi/libstub/arm-stub.c unsigned long efi_entry(void *handle, efi_system_table_t *sys_table_arg, · line 268
Quoted 4 lines
drivers/firmware/efi/libstub/arm-stub.c +4
-0+ &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.
Quoted 222 lines
Quoted 64 lines
Quoted 52 lines
Quoted 52 lines
drivers/firmware/efi/libstub/arm-stub.c +1
-1if (status !=EFI_SUCCESS ) -pr_efi_err ("Failed initrd from command line!\n" ); +pr_efi_err ("Failed to load initrd!\n" );efi_random_get_seed ();drivers/firmware/efi/libstub/efi-stub-helper.c +38
-0@@ -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,Quoted 9 lines
drivers/firmware/efi/libstub/efi-stub-helper.c void efi_char16_printk(efi_char16_t *str) · line 366
Quoted 3 lines
drivers/firmware/efi/libstub/efi-stub-helper.c +3
-0+ (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_PROTOCOLmust be installed which will load the initrd file when called with the same device path.Exactly.
Quoted 7 lines
Quoted 3 lines
An alternative implementation would simple loop over all instances of the
EFI_LOAD_FILE2_PROTOCOLand try to load the initrd.How do I distinguish the initrd from other
EFI_LOAD_FILE2_PROTOCOLinstances? For instance, PCI option ROMs are also exposed asEFI_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
Quoted 141 lines
Quoted 10 lines
It would be worthwhile to describe the requirements on the implementation of the
EFI_LOAD_FILE2_PROTOCOLin a code comment and in the documentation.Unfortunately the documentation of UEFI has been duplicated into two files:
Documentation/arm/uefi.rstDocumentation/x86/x86_64/uefi.rstYes, that is a good point. I will work on factoring out the generic parts and share them.
Thanks for the review,
Ard.Quoted 121 lines
Quoted 120 lines
drivers/firmware/efi/libstub/efi-stub-helper.c +24
-0+ + 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 ; +}drivers/firmware/efi/libstub/efistub.h +12
-0@@ -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 drivers/firmware/efi/libstub/x86-stub.c +33
-3@@ -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. include/linux/efi.h +1
-0@@ -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)
Re: [PATCH 1/2] efi/libstub: add support for loading the initrd from a device path
Quoted 157 lines · Ard Biesheuvel
Quoted 28 lines · Heinrich Schuchardt
Quoted 16 lines · Ard Biesheuvel
Quoted 15 lines · Heinrich Schuchardt
Quoted 10 lines · Ard Biesheuvel
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,
Quoted 9 lines
Quoted 3 lines
"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_infostruct, and finds the file based on its filename, not on a device path. So how can it access files on other file systems?Quoted 7 lines
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_PROTOCOLto 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.
Quoted 25 lines
Quoted 18 lines
Quoted 8 lines
Quoted 4 lines
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.
Quoted 4 lines
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.
Quoted 6 lines
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.
Quoted 6 lines
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.
Quoted 4 lines
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.
Quoted 66 lines
Quoted 65 lines
Quoted 24 lines
Quoted 13 lines
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.
Quoted 10 lines
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.
Quoted 7 lines
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.
Quoted 2 lines
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.
Quoted 3 lines
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.
Quoted 4 lines
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_PROTOCOLimplementation.This would also require kernel changes, since we don't currently load initrds from arbitrary device paths. The
EFI_FILE_PROTOCOLis 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.
Quoted 5 lines
Quoted 2 lines
This means you move the complexity of
EFI_FILE_PROTOCOLfrom 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.
Quoted 8 lines
Quoted 5 lines
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.
Quoted 24 lines
Quoted 8 lines
Quoted 7 lines
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.
Quoted 9 lines
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.
Quoted 3 lines
So invoking
EFI_FILE_PROTOCOLdirectly 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.
Quoted 14 lines
Quoted 14 lines
Quoted 6 lines
For u-boot, it should be trivial to implement a simple LoadFile2 protocol wrapper around
EFI_FILE_PROTOCOLthat can be installed on a handle that also carriesEFI_FILE_PROTOCOL.A U-Boot implementation of the
EFI_LOAD_FILE2_PROTOCOLwould need a device path variable to find the block device and to open theEFI_SIMPLE_FILE_SYSTEM_PROTOCOLbefore accessing the file.Linux would not be needing more lines and we would not repeat the same code in GRUB, U-Boot, EDK2, etc.
Quoted 8 lines
Quoted 3 lines
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.
Quoted 9 lines
Quoted 4 lines
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.
Quoted 46 lines
Quoted 36 lines
Quoted 36 lines
Quoted 36 lines
Quoted 36 lines
Signed-off-by Ard Biesheuvel <[email protected]>--- 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(-)drivers/firmware/efi/libstub/arm-stub.c +7
-3@@ -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,Quoted 9 lines
Quoted 4 lines
Quoted 4 lines
drivers/firmware/efi/libstub/arm-stub.c unsigned long efi_entry(void *handle, efi_system_table_t *sys_table_arg, · line 268
Quoted 4 lines
drivers/firmware/efi/libstub/arm-stub.c +4
-0+ &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.
Quoted 3 lines
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
Quoted 223 lines
Quoted 223 lines
Quoted 65 lines
Quoted 53 lines
Quoted 53 lines
drivers/firmware/efi/libstub/arm-stub.c +1
-1if (status !=EFI_SUCCESS ) -pr_efi_err ("Failed initrd from command line!\n" ); +pr_efi_err ("Failed to load initrd!\n" );efi_random_get_seed ();drivers/firmware/efi/libstub/efi-stub-helper.c +38
-0@@ -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,Quoted 9 lines
drivers/firmware/efi/libstub/efi-stub-helper.c void efi_char16_printk(efi_char16_t *str) · line 366
Quoted 3 lines
drivers/firmware/efi/libstub/efi-stub-helper.c +3
-0+ (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_PROTOCOLmust be installed which will load the initrd file when called with the same device path.Exactly.
Quoted 7 lines
Quoted 3 lines
An alternative implementation would simple loop over all instances of the
EFI_LOAD_FILE2_PROTOCOLand try to load the initrd.How do I distinguish the initrd from other
EFI_LOAD_FILE2_PROTOCOLinstances? For instance, PCI option ROMs are also exposed asEFI_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
Quoted 141 lines
Quoted 10 lines
It would be worthwhile to describe the requirements on the implementation of the
EFI_LOAD_FILE2_PROTOCOLin a code comment and in the documentation.Unfortunately the documentation of UEFI has been duplicated into two files:
Documentation/arm/uefi.rstDocumentation/x86/x86_64/uefi.rstYes, that is a good point. I will work on factoring out the generic parts and share them.
Thanks for the review,
Ard.Quoted 121 lines
Quoted 120 lines
drivers/firmware/efi/libstub/efi-stub-helper.c +24
-0+ + 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 ; +}drivers/firmware/efi/libstub/efistub.h +12
-0@@ -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 drivers/firmware/efi/libstub/x86-stub.c +33
-3@@ -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. include/linux/efi.h +1
-0@@ -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)
Re: [PATCH 1/2] efi/libstub: add support for loading the initrd from a device path
Quoted 6 lines · Heinrich Schuchardt
Quoted 4 lines · Ard Biesheuvel
Quoted 3 lines · Heinrich Schuchardt
Quoted 1 line · Ard Biesheuvel
On Thu, 6 Feb 2020 at 18:26, Heinrich Schuchardt <[email protected]> wrote:
...
Quoted 36 lines
Quoted 21 lines
Quoted 21 lines
Quoted 20 lines
Quoted 3 lines
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.
Quoted 4 lines
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_PROTOCOLimplementation.This would also require kernel changes, since we don't currently load initrds from arbitrary device paths. The
EFI_FILE_PROTOCOLis 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.Quoted 5 lines
Quoted 2 lines
This means you move the complexity of
EFI_FILE_PROTOCOLfrom 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_PROTOCOLto do the job that you do not want to do in Linux.Quoted 6 lines
Quoted 5 lines
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.
Quoted 5 lines
Quoted 2 lines
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.
Quoted 62 lines
Quoted 24 lines
Quoted 8 lines
Quoted 7 lines
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.
Quoted 9 lines
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.
Quoted 3 lines
So invoking
EFI_FILE_PROTOCOLdirectly 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.Quoted 14 lines
Quoted 14 lines
Quoted 6 lines
For u-boot, it should be trivial to implement a simple LoadFile2 protocol wrapper around
EFI_FILE_PROTOCOLthat can be installed on a handle that also carriesEFI_FILE_PROTOCOL.A U-Boot implementation of the
EFI_LOAD_FILE2_PROTOCOLwould need a device path variable to find the block device and to open theEFI_SIMPLE_FILE_SYSTEM_PROTOCOLbefore accessing the file.Linux would not be needing more lines and we would not repeat the same code in GRUB, U-Boot, EDK2, etc.
Quoted 8 lines
Quoted 3 lines
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.
Quoted 7 lines
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.
...
Quoted 4 lines
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.
Quoted 9 lines
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.
Signature · 1 line
Ard.
Re: [PATCH 1/2] efi/libstub: add support for loading the initrd from a device path
Quoted 44 lines · Ard Biesheuvel
Quoted 6 lines · Heinrich Schuchardt
Quoted 4 lines · Ard Biesheuvel
Quoted 3 lines · Heinrich Schuchardt
Quoted 1 line · Ard Biesheuvel
On Thu, 6 Feb 2020 at 18:26, Heinrich Schuchardt <[email protected]> wrote:
...
Quoted 36 lines
Quoted 21 lines
Quoted 21 lines
Quoted 20 lines
Quoted 3 lines
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.
Quoted 4 lines
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_PROTOCOLimplementation.This would also require kernel changes, since we don't currently load initrds from arbitrary device paths. The
EFI_FILE_PROTOCOLis 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.Quoted 5 lines
Quoted 2 lines
This means you move the complexity of
EFI_FILE_PROTOCOLfrom 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_PROTOCOLto do the job that you do not want to do in Linux.Quoted 6 lines
Quoted 5 lines
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.
Quoted 7 lines
Quoted 5 lines
Quoted 2 lines
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.
Quoted 99 lines
Quoted 63 lines
Quoted 25 lines
Quoted 9 lines
Quoted 8 lines
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.
Quoted 9 lines
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.
Quoted 3 lines
So invoking
EFI_FILE_PROTOCOLdirectly 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.Quoted 14 lines
Quoted 14 lines
Quoted 6 lines
For u-boot, it should be trivial to implement a simple LoadFile2 protocol wrapper around
EFI_FILE_PROTOCOLthat can be installed on a handle that also carriesEFI_FILE_PROTOCOL.A U-Boot implementation of the
EFI_LOAD_FILE2_PROTOCOLwould need a device path variable to find the block device and to open theEFI_SIMPLE_FILE_SYSTEM_PROTOCOLbefore accessing the file.Linux would not be needing more lines and we would not repeat the same code in GRUB, U-Boot, EDK2, etc.
Quoted 8 lines
Quoted 3 lines
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.
Quoted 7 lines
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.
...
Quoted 4 lines
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.
Quoted 9 lines
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
Quoted 3 lines
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
Re: [PATCH 1/2] efi/libstub: add support for loading the initrd from a device path
Quoted 51 lines · Heinrich Schuchardt
Quoted 47 lines · Ard Biesheuvel
Quoted 6 lines · Heinrich Schuchardt
Quoted 4 lines · Ard Biesheuvel
Quoted 3 lines · Heinrich Schuchardt
Quoted 1 line · Ard Biesheuvel
On Thu, 6 Feb 2020 at 18:26, Heinrich Schuchardt <[email protected]> wrote:
...
Quoted 39 lines
Quoted 21 lines
Quoted 21 lines
Quoted 20 lines
Quoted 3 lines
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.
Quoted 4 lines
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_PROTOCOLimplementation.This would also require kernel changes, since we don't currently load initrds from arbitrary device paths. The
EFI_FILE_PROTOCOLis 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.Quoted 5 lines
Quoted 2 lines
This means you move the complexity of
EFI_FILE_PROTOCOLfrom 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_PROTOCOLto do the job that you do not want to do in Linux.Quoted 8 lines
Quoted 5 lines
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.
Quoted 7 lines
Quoted 4 lines
Quoted 2 lines
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.
Quoted 115 lines
Quoted 99 lines
Quoted 63 lines
Quoted 25 lines
Quoted 9 lines
Quoted 8 lines
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.
Quoted 9 lines
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.
Quoted 3 lines
So invoking
EFI_FILE_PROTOCOLdirectly 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.Quoted 14 lines
Quoted 14 lines
Quoted 6 lines
For u-boot, it should be trivial to implement a simple LoadFile2 protocol wrapper around
EFI_FILE_PROTOCOLthat can be installed on a handle that also carriesEFI_FILE_PROTOCOL.A U-Boot implementation of the
EFI_LOAD_FILE2_PROTOCOLwould need a device path variable to find the block device and to open theEFI_SIMPLE_FILE_SYSTEM_PROTOCOLbefore accessing the file.Linux would not be needing more lines and we would not repeat the same code in GRUB, U-Boot, EDK2, etc.
Quoted 8 lines
Quoted 3 lines
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.
Quoted 7 lines
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.
...
Quoted 4 lines
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.
Quoted 9 lines
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 bufferQuoted 3 lines
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
Quoted 8 lines
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.
Quoted 3 lines
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.
Quoted 7 lines
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.
Re: [PATCH 1/2] efi/libstub: add support for loading the initrd from a device path
Quoted 164 lines · Ard Biesheuvel
Quoted 38 lines · Heinrich Schuchardt
Quoted 35 lines · Ard Biesheuvel
Quoted 4 lines · Heinrich Schuchardt
Quoted 3 lines · Ard Biesheuvel
Quoted 2 lines · Heinrich Schuchardt
Quoted 1 line · Ard Biesheuvel
On Thu, 6 Feb 2020 at 18:26, Heinrich Schuchardt <[email protected]> wrote:
...
Quoted 29 lines
Quoted 18 lines
Quoted 18 lines
Quoted 18 lines
Quoted 3 lines
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.
Quoted 4 lines
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_PROTOCOLimplementation.This would also require kernel changes, since we don't currently load initrds from arbitrary device paths. The
EFI_FILE_PROTOCOLis 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.Quoted 4 lines
Quoted 2 lines
This means you move the complexity of
EFI_FILE_PROTOCOLfrom 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_PROTOCOLto do the job that you do not want to do in Linux.Quoted 4 lines
Quoted 4 lines
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.
Quoted 7 lines
Quoted 5 lines
Quoted 4 lines
Quoted 2 lines
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.
Quoted 100 lines
Quoted 85 lines
Quoted 52 lines
Quoted 20 lines
Quoted 5 lines
Quoted 5 lines
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.
Quoted 9 lines
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.
Quoted 3 lines
So invoking
EFI_FILE_PROTOCOLdirectly 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.Quoted 11 lines
Quoted 11 lines
Quoted 4 lines
For u-boot, it should be trivial to implement a simple LoadFile2 protocol wrapper around
EFI_FILE_PROTOCOLthat can be installed on a handle that also carriesEFI_FILE_PROTOCOL.A U-Boot implementation of the
EFI_LOAD_FILE2_PROTOCOLwould need a device path variable to find the block device and to open theEFI_SIMPLE_FILE_SYSTEM_PROTOCOLbefore accessing the file.Linux would not be needing more lines and we would not repeat the same code in GRUB, U-Boot, EDK2, etc.
Quoted 7 lines
Quoted 3 lines
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.
Quoted 7 lines
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.
...
Quoted 12 lines
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 bufferQuoted 3 lines
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
Quoted 8 lines
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.
Quoted 8 lines
Quoted 3 lines
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_PROTOCOLbut only onEFI_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?
Quoted 10 lines
Quoted 8 lines
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?
Quoted 2 lines
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?
Quoted 5 lines
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? :)
Quoted 4 lines
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
Re: [PATCH 1/2] efi/libstub: add support for loading the initrd from a device path
Quoted 177 lines · Alexander Graf
Quoted 164 lines · Ard Biesheuvel
Quoted 38 lines · Heinrich Schuchardt
Quoted 35 lines · Ard Biesheuvel
Quoted 4 lines · Heinrich Schuchardt
Quoted 3 lines · Ard Biesheuvel
Quoted 2 lines · Heinrich Schuchardt
Quoted 1 line · Ard Biesheuvel
On Thu, 6 Feb 2020 at 18:26, Heinrich Schuchardt <[email protected]> wrote:
...
Quoted 29 lines
Quoted 18 lines
Quoted 18 lines
Quoted 18 lines
Quoted 3 lines
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.
Quoted 4 lines
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_PROTOCOLimplementation.This would also require kernel changes, since we don't currently load initrds from arbitrary device paths. The
EFI_FILE_PROTOCOLis 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.Quoted 4 lines
Quoted 2 lines
This means you move the complexity of
EFI_FILE_PROTOCOLfrom 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_PROTOCOLto do the job that you do not want to do in Linux.Quoted 4 lines
Quoted 4 lines
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.
Quoted 7 lines
Quoted 5 lines
Quoted 4 lines
Quoted 2 lines
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.
Quoted 100 lines
Quoted 85 lines
Quoted 52 lines
Quoted 20 lines
Quoted 5 lines
Quoted 5 lines
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.
Quoted 9 lines
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.
Quoted 3 lines
So invoking
EFI_FILE_PROTOCOLdirectly 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.Quoted 11 lines
Quoted 11 lines
Quoted 4 lines
For u-boot, it should be trivial to implement a simple LoadFile2 protocol wrapper around
EFI_FILE_PROTOCOLthat can be installed on a handle that also carriesEFI_FILE_PROTOCOL.A U-Boot implementation of the
EFI_LOAD_FILE2_PROTOCOLwould need a device path variable to find the block device and to open theEFI_SIMPLE_FILE_SYSTEM_PROTOCOLbefore accessing the file.Linux would not be needing more lines and we would not repeat the same code in GRUB, U-Boot, EDK2, etc.
Quoted 7 lines
Quoted 3 lines
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.
Quoted 7 lines
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.
...
Quoted 12 lines
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 bufferQuoted 3 lines
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
Quoted 8 lines
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.
Quoted 8 lines
Quoted 3 lines
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_PROTOCOLbut only onEFI_LOAD_FILE2_PROTOCOL, which is *much* simpler.
Quoted 4 lines
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_protocolefi_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.
Quoted 3 lines
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.
Quoted 3 lines
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.
Quoted 3 lines
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.
Quoted 9 lines
Quoted 9 lines
Quoted 9 lines
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.
Quoted 7 lines
Quoted 2 lines
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, ...
Quoted 9 lines
Quoted 3 lines
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.
Quoted 11 lines
Quoted 6 lines
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)
Quoted 7 lines
Quoted 5 lines
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.
Quoted 5 lines
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.
Quoted 6 lines
That said, I don't think the proposed approach of using
EFI_LOAD_FILE2_PROTOCOLis 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.
Re: [PATCH 1/2] efi/libstub: add support for loading the initrd from a device path
In reply to Alexander GrafQuoted 209 lines · Alexander Graf
Quoted 193 lines · Ard Biesheuvel
Quoted 50 lines · Heinrich Schuchardt
Quoted 47 lines · Ard Biesheuvel
Quoted 6 lines · Heinrich Schuchardt
Quoted 5 lines · Ard Biesheuvel
Quoted 3 lines · Heinrich Schuchardt
Quoted 2 lines · Ard Biesheuvel
On Thu, 6 Feb 2020 at 18:26, Heinrich Schuchardt
<[email protected]> wrote:...
Quoted 38 lines
Quoted 23 lines
Quoted 23 lines
Quoted 23 lines
Quoted 4 lines
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.Quoted 5 lines
Using an UEFI variable for passing the initrd device path would be a
leaner solution on the bootloader side than requiring an extraEFI_LOAD_FILE2_PROTOCOLimplementation.This would also require kernel changes, since we don't currently load
initrds from arbitrary device paths. TheEFI_FILE_PROTOCOLis 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.Quoted 6 lines
Quoted 3 lines
This means you move the complexity of
EFI_FILE_PROTOCOLfrom 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_PROTOCOLto do the job that you do not want to do in Linux.Quoted 6 lines
Quoted 6 lines
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.Quoted 8 lines
Quoted 4 lines
Quoted 4 lines
Quoted 3 lines
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.
>>
Quoted 1 line
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.
Quoted 115 lines
Quoted 100 lines
Quoted 61 lines
Quoted 26 lines
Quoted 14 lines
Quoted 7 lines
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.Quoted 6 lines
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.
Quoted 3 lines
So invoking
EFI_FILE_PROTOCOLdirectly 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.Quoted 13 lines
Quoted 13 lines
Quoted 5 lines
For u-boot, it should be trivial to implement a simple LoadFile2 protocol wrapper around
EFI_FILE_PROTOCOLthat can be installed on a
handle that also carriesEFI_FILE_PROTOCOL.A U-Boot implementation of the
EFI_LOAD_FILE2_PROTOCOLwould need a device path variable to find the block device and to open theEFI_SIMPLE_FILE_SYSTEM_PROTOCOLbefore accessing the file.Linux would not be needing more lines and we would not repeat the same
code in GRUB, U-Boot, EDK2, etc.Quoted 8 lines
Quoted 4 lines
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.
Quoted 9 lines
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.
...
Quoted 10 lines
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.Quoted 6 lines
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 bufferQuoted 3 lines
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
Quoted 8 lines
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.
Quoted 8 lines
Quoted 3 lines
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_PROTOCOLbut only onEFI_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?
Quoted 8 lines
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
Quoted 50 lines
Quoted 7 lines
Quoted 7 lines
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.
Quoted 5 lines
Quoted 3 lines
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?
Quoted 2 lines
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?
Quoted 5 lines
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? :)
Quoted 4 lines
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?
Quoted 9 lines
That said, I don't think the proposed approach of using
EFI_LOAD_FILE2_PROTOCOLis 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
_______________________________________________
linux-arm-kernel mailing list
[email protected]
http://lists.infradead.org/mailman/listinfo/linux-arm-kernel
Re: [PATCH 1/2] efi/libstub: add support for loading the initrd from a device path
In reply to Ard BiesheuvelQuoted 10 lines · Ard Biesheuvel
Quoted 2 lines · Heinrich Schuchardt
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.
Quoted 8 lines
Quoted 3 lines
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
Re: [PATCH 1/2] efi/libstub: add support for loading the initrd from a device path
In reply to Heinrich SchuchardtQuoted 7 lines · Heinrich Schuchardt
Quoted 4 lines · Ard Biesheuvel
+ status =
efi_bs_call(handle_protocol, handle, &lf2_proto_guid, + (void **)&lf2);
+ if (status !=EFI_SUCCESS)
+ return status;
Quoted 7 lines
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_PROTOCOLmust 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_PROTOCOLand 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
Re: [PATCH 1/2] efi/libstub: add support for loading the initrd from a device path
In reply to Ard BiesheuvelQuoted 60 lines · Ard Biesheuvel
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 <[email protected]>--- 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(-)drivers/firmware/efi/libstub/arm-stub.c +1
-0@@ -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" );
drivers/firmware/efi/libstub/arm-stub.c unsigned long efi_entry(void *handle, efi_system_table_t *sys_table_arg, · line 265
Quoted 10 lines
drivers/firmware/efi/libstub/arm-stub.c +7
-3- 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".
Quoted 53 lines
drivers/firmware/efi/libstub/arm-stub.c +4
-1+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 ();drivers/firmware/efi/libstub/efi-stub-helper.c +36
-0@@ -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;
drivers/firmware/efi/libstub/efi-stub-helper.c void efi_char16_printk(efi_char16_t *str) · line 371
Quoted 10 lines
drivers/firmware/efi/libstub/efi-stub-helper.c +10
-0+ + 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.
drivers/firmware/efi/libstub/efi-stub-helper.c void efi_char16_printk(efi_char16_t *str) · line 381
Quoted 10 lines
drivers/firmware/efi/libstub/efi-stub-helper.c +10
-0+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.
Quoted 52 lines
drivers/firmware/efi/libstub/efi-stub-helper.c +9
-0+if (status !=EFI_SUCCESS ) { +efi_free (initrd_size, initrd_addr); +return status; + } + + *load_addr = initrd_addr; + *load_size = initrd_size; +return EFI_SUCCESS ; +}drivers/firmware/efi/libstub/efistub.h +12
-0@@ -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 drivers/firmware/efi/libstub/x86-stub.c +4
-3@@ -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
drivers/firmware/efi/libstub/x86-stub.c efi_status_t __efiapi efi_pe_entry(efi_handle_t handle, · line 435
Quoted 10 lines
drivers/firmware/efi/libstub/x86-stub.c +10
-0+* 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.
Quoted 16 lines
drivers/firmware/efi/libstub/x86-stub.c +9
-0if (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 ) {
drivers/firmware/efi/libstub/x86-stub.c struct boot_params *efi_main(efi_handle_t handle, · line 764
Quoted 10 lines
drivers/firmware/efi/libstub/x86-stub.c +10
-0+ 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.)
Quoted 9 lines
drivers/firmware/efi/libstub/x86-stub.c +0
-0/* * If the boot loader gave us a value for secure_boot then we use that, * otherwise we ask the BIOS. include/linux/efi.h +0
-0
include/linux/efi.h void efi_native_runtime_setup(void); · line 359
Quoted 7 lines
include/linux/efi.h +1
-0@@ -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
Re: [PATCH 1/2] efi/libstub: add support for loading the initrd from a device path
Quoted 75 lines · Laszlo Ersek
Quoted 60 lines · Ard Biesheuvel
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 <[email protected]>--- 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(-)drivers/firmware/efi/libstub/arm-stub.c +1
-0@@ -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" );drivers/firmware/efi/libstub/arm-stub.c unsigned long efi_entry(void *handle, efi_system_table_t *sys_table_arg, · line 265
Quoted 10 lines
drivers/firmware/efi/libstub/arm-stub.c +7
-3- 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.
Quoted 9 lines
- Are you sure you only want to fall back to the old method on
EFI_NOT_FOUND? Wouldn't other return values fromefi_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 returningEFI_NOT_FOUNDnear theefi_load_initrd_devpath()function declaration, in "efistub.h".
Good point.
Quoted 69 lines
Quoted 53 lines
drivers/firmware/efi/libstub/arm-stub.c +4
-1+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 ();drivers/firmware/efi/libstub/efi-stub-helper.c +36
-0@@ -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;drivers/firmware/efi/libstub/efi-stub-helper.c void efi_char16_printk(efi_char16_t *str) · line 371
Quoted 10 lines
drivers/firmware/efi/libstub/efi-stub-helper.c +10
-0+ + 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 ofFILEPATH_DEVICE_PATHnodes, terminated by and "End Entire Device Path" node. See
Quoted 9 lines
- 10.3.1 Generic Device Path Structures
- 10.3.5.4 File Path Media Device Pathin 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?
Quoted 5 lines
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_GUIDVenMedia devpath to do *any* device-specific filename / pathname parsing.
Quoted 9 lines
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?
Quoted 100 lines
drivers/firmware/efi/libstub/efi-stub-helper.c void efi_char16_printk(efi_char16_t *str) · line 381
Quoted 10 lines
drivers/firmware/efi/libstub/efi-stub-helper.c +10
-0+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.
Quoted 52 lines
drivers/firmware/efi/libstub/efi-stub-helper.c +9
-0+if (status !=EFI_SUCCESS ) { +efi_free (initrd_size, initrd_addr); +return status; + } + + *load_addr = initrd_addr; + *load_size = initrd_size; +return EFI_SUCCESS ; +}drivers/firmware/efi/libstub/efistub.h +12
-0@@ -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 drivers/firmware/efi/libstub/x86-stub.c +4
-3@@ -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 drivers/firmware/efi/libstub/x86-stub.c efi_status_t __efiapi efi_pe_entry(efi_handle_t handle, · line 435
Quoted 10 lines
drivers/firmware/efi/libstub/x86-stub.c +10
-0+* 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.
Quoted 22 lines
drivers/firmware/efi/libstub/x86-stub.c +15
-0if (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" );
Quoted 9 lines
drivers/firmware/efi/libstub/x86-stub.c struct boot_params *efi_main(efi_handle_t handle, · line 764
Quoted 4 lines
drivers/firmware/efi/libstub/x86-stub.c +4
-0+ 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.
Quoted 20 lines
Quoted 9 lines
drivers/firmware/efi/libstub/x86-stub.c +0
-0/* * If the boot loader gave us a value for secure_boot then we use that, * otherwise we ask the BIOS. include/linux/efi.h +0
-0include/linux/efi.h void efi_native_runtime_setup(void); · line 359
Quoted 7 lines
include/linux/efi.h +1
-0@@ -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
Re: [PATCH 1/2] efi/libstub: add support for loading the initrd from a device path
Quoted 43 lines · Ard Biesheuvel
Quoted 42 lines · Laszlo Ersek
Quoted 19 lines · Ard Biesheuvel
drivers/firmware/efi/libstub/efi-stub-helper.c +19
-0+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;drivers/firmware/efi/libstub/efi-stub-helper.c void efi_char16_printk(efi_char16_t *str) · line 371
Quoted 10 lines
drivers/firmware/efi/libstub/efi-stub-helper.c +10
-0+ + 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 ofFILEPATH_DEVICE_PATHnodes, terminated by and "End Entire Device Path" node. See- 10.3.1 Generic Device Path Structures
- 10.3.5.4 File Path Media Device Pathin UEFI-2.8.
Quoted 7 lines
Quoted 4 lines
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.
Quoted 15 lines
Quoted 5 lines
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_GUIDVenMedia devpath to do *any* device-specific filename / pathname parsing.Quoted 9 lines
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.
Quoted 4 lines
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.
Quoted 3 lines
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
Re: [PATCH 1/2] efi/libstub: add support for loading the initrd from a device path
In reply to Ard Biesheuveldrivers/firmware/efi/libstub/efistub.h union efi_load_file_protocol { · line 575
Quoted 13 lines · Ard Biesheuvel
drivers/firmware/efi/libstub/efistub.h +7
-0@@ -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?
drivers/firmware/efi/libstub/efi-stub-helper.c void efi_char16_printk(efi_char16_t *str) · line 330
Quoted 11 lines
drivers/firmware/efi/libstub/efi-stub-helper.c +5
-0@@ -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
Re: [PATCH 1/2] efi/libstub: add support for loading the initrd from a device path
Quoted 8 lines · Lukas Wunner
Quoted 6 lines · Ard Biesheuvel
drivers/firmware/efi/libstub/efistub.h +0
-0@@ -566,6 +566,14 @@ union efi_load_file_protocol { } mixed_mode; };
Quoted 12 lines
drivers/firmware/efi/libstub/efistub.h union efi_load_file_protocol { · line 575
Quoted 7 lines
drivers/firmware/efi/libstub/efistub.h +7
-0+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 structefi_dev_pathwith 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.
Quoted 15 lines
drivers/firmware/efi/libstub/efi-stub-helper.c void efi_char16_printk(efi_char16_t *str) · line 330
Quoted 12 lines
drivers/firmware/efi/libstub/efi-stub-helper.c +5
-0@@ -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.
Ard Biesheuvel
maintainerPATCH 2/2 efi/libstub: take noinitrd cmdline argument into account for devpath initrd
In reply to Ard BiesheuvelOne 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.
--- 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(-)
drivers/firmware/efi/libstub/arm-stub.c +13 -10
@@ -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 ();
drivers/firmware/efi/libstub/efi-stub-helper.c +9 -0
@@ -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;
drivers/firmware/efi/libstub/efistub.h +1 -0
@@ -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 );
drivers/firmware/efi/libstub/x86-stub.c +24 -21
57 lines not on this page · raw message
Signature · 1 line
2.17.1
Re: [PATCH 2/2] efi/libstub: take noinitrd cmdline argument into account for devpath initrd
Quoted 9 lines · Ard Biesheuvel
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?
Quoted 165 lines
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 <[email protected]>--- 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(-)drivers/firmware/efi/libstub/arm-stub.c +13
-10@@ -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 ();drivers/firmware/efi/libstub/efi-stub-helper.c +9
-0@@ -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;drivers/firmware/efi/libstub/efistub.h +1
-0@@ -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 );drivers/firmware/efi/libstub/x86-stub.c +24
-21@@ -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;
Re: [PATCH 2/2] efi/libstub: take noinitrd cmdline argument into account for devpath initrd
Quoted 7 lines · Heinrich Schuchardt
Quoted 5 lines · Ard Biesheuvel
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.
Quoted 7 lines
Quoted 4 lines
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.
Re: [PATCH 2/2] efi/libstub: take noinitrd cmdline argument into account for devpath initrd
In reply to Ard BiesheuvelQuoted 5 lines · Ard Biesheuvel
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.
Quoted 8 lines
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.
Quoted 5 lines
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.
Signature · 1 line
Peter
Quoted 34 lines · Ard Biesheuvel
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.
Quoted 7 lines
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-unification2An 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
Quoted 22 lines
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
initrddrivers/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(-)
Quoted 52 lines · Laszlo Ersek
Quoted 34 lines · Ard Biesheuvel
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.
Quoted 7 lines
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-unification2An 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.
Quoted 6 lines
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
Quoted 23 lines
Quoted 22 lines
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
initrddrivers/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(-)
Quoted 45 lines · Laszlo Ersek
Quoted 43 lines · Laszlo Ersek
Quoted 41 lines · Ard Biesheuvel
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-unification2An 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.
Quoted 5 lines
Quoted 5 lines
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.
Quoted 11 lines
Quoted 10 lines
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.
Quoted 5 lines
... 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.
Quoted 3 lines · Ard Biesheuvel
Quoted 2 lines · Laszlo Ersek
On 02/07/20 10:09, Laszlo Ersek wrote:
[...]
Quoted 17 lines
Quoted 17 lines
Quoted 5 lines
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.
Quoted 7 lines
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?...
Quoted 5 lines
Quoted 2 lines
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.
Quoted 2 lines
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
Quoted 34 lines · James Bottomley
Quoted 3 lines · Ard Biesheuvel
Quoted 2 lines · Laszlo Ersek
On 02/07/20 10:09, Laszlo Ersek wrote:
[...]
Quoted 17 lines
Quoted 17 lines
Quoted 5 lines
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.
Quoted 7 lines
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?...
Quoted 5 lines
Quoted 2 lines
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):
Quoted 9 lines
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.
Quoted 6 lines
Quoted 2 lines
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.
Quoted 14 lines
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.
Quoted 52 lines · Ard Biesheuvel
Quoted 41 lines · James Bottomley
Quoted 4 lines · Ard Biesheuvel
Quoted 2 lines · Laszlo Ersek
On 02/07/20 10:09, Laszlo Ersek wrote:
[...]
Quoted 17 lines
Quoted 17 lines
Quoted 5 lines
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.
Quoted 7 lines
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?...
Quoted 7 lines
Quoted 3 lines
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.cQuoted 8 lines
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.
Quoted 2 lines
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.
Quoted 6 lines
and in their implementations, fall back to the firmware
ones if necessary.Quoted 3 lines
Quoted 2 lines
and is able to load the initrd from anywhere in an arch agnostic manner.
Quoted 8 lines
Quoted 3 lines
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.
Quoted 4 lines
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.
Quoted 2 lines
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
Quoted 64 lines · James Bottomley
Quoted 52 lines · Ard Biesheuvel
Quoted 41 lines · James Bottomley
Quoted 4 lines · Ard Biesheuvel
Quoted 2 lines · Laszlo Ersek
On 02/07/20 10:09, Laszlo Ersek wrote:
[...]
Quoted 17 lines
Quoted 17 lines
Quoted 5 lines
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.
Quoted 7 lines
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?...
Quoted 7 lines
Quoted 3 lines
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.cQuoted 8 lines
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.
Quoted 2 lines
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.
Quoted 6 lines
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.
Quoted 25 lines
Quoted 6 lines
and in their implementations, fall back to the firmware
ones if necessary.Quoted 3 lines
Quoted 2 lines
and is able to load the initrd from anywhere in an arch agnostic manner.
Quoted 8 lines
Quoted 3 lines
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.
Quoted 8 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.
Quoted 5 lines
Quoted 4 lines
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.
Quoted 7 lines
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.
Quoted 7 lines
Quoted 2 lines
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.
Quoted 3 lines · Ard Biesheuvel
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?
Quoted 10 lines · Arvind Sankar
Quoted 3 lines · Ard Biesheuvel
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.
Quoted 12 lines · Ard Biesheuvel
Quoted 10 lines · Arvind Sankar
Quoted 3 lines · Ard Biesheuvel
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?
Quoted 7 lines
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, andstartimage()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.