u-boot/lib/efi_loader/efi_load_initrd.c
Ilias Apalodimas ec80b4735a efi_loader: Implement FileLoad2 for initramfs loading
Following kernel's proposal for an arch-agnostic initrd loading
mechanism [1] let's implement the U-boot counterpart.
This new approach has a number of advantages compared to what we did up
to now. The file is loaded into memory only when requested limiting the
area of TOCTOU attacks. Users will be allowed to place the initramfs
file on any u-boot accessible partition instead of just the ESP one.
Finally this is an attempt of a generic interface across architectures
in the linux kernel so it makes sense to support that.

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

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

Signed-off-by: Ilias Apalodimas <ilias.apalodimas@linaro.org>
Signed-off-by: Heinrich Schuchardt <xypron.glpk@gmx.de>
2020-02-28 19:37:14 +01:00

198 lines
4.3 KiB
C

// SPDX-License-Identifier: GPL-2.0+
/*
* Copyright (c) 2020, Linaro Limited
*/
#include <common.h>
#include <env.h>
#include <malloc.h>
#include <mapmem.h>
#include <dm.h>
#include <fs.h>
#include <efi_loader.h>
#include <efi_load_initrd.h>
static const efi_guid_t efi_guid_load_file2_protocol =
EFI_LOAD_FILE2_PROTOCOL_GUID;
static efi_status_t EFIAPI
efi_load_file2_initrd(struct efi_load_file_protocol *this,
struct efi_device_path *file_path, bool boot_policy,
efi_uintn_t *buffer_size, void *buffer);
static const struct efi_load_file_protocol efi_lf2_protocol = {
.load_file = efi_load_file2_initrd,
};
/*
* Device path defined by Linux to identify the handle providing the
* EFI_LOAD_FILE2_PROTOCOL used for loading the initial ramdisk.
*/
static const struct efi_initrd_dp dp = {
.vendor = {
{
DEVICE_PATH_TYPE_MEDIA_DEVICE,
DEVICE_PATH_SUB_TYPE_VENDOR_PATH,
sizeof(dp.vendor),
},
EFI_INITRD_MEDIA_GUID,
},
.end = {
DEVICE_PATH_TYPE_END,
DEVICE_PATH_SUB_TYPE_END,
sizeof(dp.end),
}
};
/**
* get_file_size() - retrieve the size of initramfs, set efi status on error
*
* @dev: device to read from. i.e "mmc"
* @part: device partition. i.e "0:1"
* @file: name fo file
* @status: EFI exit code in case of failure
*
* Return: size of file
*/
static loff_t get_file_size(const char *dev, const char *part, const char *file,
efi_status_t *status)
{
loff_t sz = 0;
int ret;
ret = fs_set_blk_dev(dev, part, FS_TYPE_ANY);
if (ret) {
*status = EFI_NO_MEDIA;
goto out;
}
ret = fs_size(file, &sz);
if (ret) {
sz = 0;
*status = EFI_NOT_FOUND;
goto out;
}
out:
return sz;
}
/**
* load_file2() - get information about random number generation
*
* This function implement the LoadFile2() service in order to load an initram
* disk requested by the Linux kernel stub.
* See the UEFI spec for details.
*
* @this: loadfile2 protocol instance
* @file_path: relative path of the file. "" in this case
* @boot_policy: must be false for Loadfile2
* @buffer_size: size of allocated buffer
* @buffer: buffer to load the file
*
* Return: status code
*/
static efi_status_t EFIAPI
efi_load_file2_initrd(struct efi_load_file_protocol *this,
struct efi_device_path *file_path, bool boot_policy,
efi_uintn_t *buffer_size, void *buffer)
{
const char *filespec = CONFIG_EFI_INITRD_FILESPEC;
efi_status_t status = EFI_NOT_FOUND;
loff_t file_sz = 0, read_sz = 0;
char *dev, *part, *file;
char *s;
int ret;
EFI_ENTRY("%p, %p, %d, %p, %p", this, file_path, boot_policy,
buffer_size, buffer);
s = strdup(filespec);
if (!s)
goto out;
if (!this || this != &efi_lf2_protocol ||
!buffer_size) {
status = EFI_INVALID_PARAMETER;
goto out;
}
if (file_path->type != dp.end.type ||
file_path->sub_type != dp.end.sub_type) {
status = EFI_INVALID_PARAMETER;
goto out;
}
if (boot_policy) {
status = EFI_UNSUPPORTED;
goto out;
}
/* expect something like 'mmc 0:1 initrd.cpio.gz' */
dev = strsep(&s, " ");
if (!dev)
goto out;
part = strsep(&s, " ");
if (!part)
goto out;
file = strsep(&s, " ");
if (!file)
goto out;
file_sz = get_file_size(dev, part, file, &status);
if (!file_sz)
goto out;
if (!buffer || *buffer_size < file_sz) {
status = EFI_BUFFER_TOO_SMALL;
*buffer_size = file_sz;
} else {
ret = fs_set_blk_dev(dev, part, FS_TYPE_ANY);
if (ret) {
status = EFI_NO_MEDIA;
goto out;
}
ret = fs_read(file, map_to_sysmem(buffer), 0, *buffer_size,
&read_sz);
if (ret || read_sz != file_sz)
goto out;
*buffer_size = read_sz;
status = EFI_SUCCESS;
}
out:
free(s);
return EFI_EXIT(status);
}
/**
* efi_initrd_register() - Register a handle and loadfile2 protocol
*
* This function creates a new handle and installs a linux specific GUID
* to handle initram disk loading during boot.
* See the UEFI spec for details.
*
* Return: status code
*/
efi_status_t efi_initrd_register(void)
{
efi_handle_t efi_initrd_handle = NULL;
efi_status_t ret;
/*
* Set up the handle with the EFI_LOAD_FILE2_PROTOCOL which Linux may
* use to load the initial ramdisk.
*/
ret = EFI_CALL(efi_install_multiple_protocol_interfaces
(&efi_initrd_handle,
/* initramfs */
&efi_guid_device_path, &dp,
/* LOAD_FILE2 */
&efi_guid_load_file2_protocol,
(void *)&efi_lf2_protocol,
NULL));
return ret;
}