303 lines
8.7 KiB
C
303 lines
8.7 KiB
C
|
// SPDX-License-Identifier: GPL-2.0
|
||
|
|
||
|
#include <linux/efi.h>
|
||
|
#include <linux/pe.h>
|
||
|
#include <asm/efi.h>
|
||
|
#include <asm/unaligned.h>
|
||
|
|
||
|
#include "efistub.h"
|
||
|
|
||
|
static unsigned char zboot_heap[SZ_256K] __aligned(64);
|
||
|
static unsigned long free_mem_ptr, free_mem_end_ptr;
|
||
|
|
||
|
#define STATIC static
|
||
|
#if defined(CONFIG_KERNEL_GZIP)
|
||
|
#include "../../../../lib/decompress_inflate.c"
|
||
|
#elif defined(CONFIG_KERNEL_LZ4)
|
||
|
#include "../../../../lib/decompress_unlz4.c"
|
||
|
#elif defined(CONFIG_KERNEL_LZMA)
|
||
|
#include "../../../../lib/decompress_unlzma.c"
|
||
|
#elif defined(CONFIG_KERNEL_LZO)
|
||
|
#include "../../../../lib/decompress_unlzo.c"
|
||
|
#elif defined(CONFIG_KERNEL_XZ)
|
||
|
#undef memcpy
|
||
|
#define memcpy memcpy
|
||
|
#undef memmove
|
||
|
#define memmove memmove
|
||
|
#include "../../../../lib/decompress_unxz.c"
|
||
|
#elif defined(CONFIG_KERNEL_ZSTD)
|
||
|
#include "../../../../lib/decompress_unzstd.c"
|
||
|
#endif
|
||
|
|
||
|
extern char efi_zboot_header[];
|
||
|
extern char _gzdata_start[], _gzdata_end[];
|
||
|
|
||
|
static void log(efi_char16_t str[])
|
||
|
{
|
||
|
efi_call_proto(efi_table_attr(efi_system_table, con_out),
|
||
|
output_string, L"EFI decompressor: ");
|
||
|
efi_call_proto(efi_table_attr(efi_system_table, con_out),
|
||
|
output_string, str);
|
||
|
efi_call_proto(efi_table_attr(efi_system_table, con_out),
|
||
|
output_string, L"\n");
|
||
|
}
|
||
|
|
||
|
static void error(char *x)
|
||
|
{
|
||
|
log(L"error() called from decompressor library\n");
|
||
|
}
|
||
|
|
||
|
// Local version to avoid pulling in memcmp()
|
||
|
static bool guids_eq(const efi_guid_t *a, const efi_guid_t *b)
|
||
|
{
|
||
|
const u32 *l = (u32 *)a;
|
||
|
const u32 *r = (u32 *)b;
|
||
|
|
||
|
return l[0] == r[0] && l[1] == r[1] && l[2] == r[2] && l[3] == r[3];
|
||
|
}
|
||
|
|
||
|
static efi_status_t __efiapi
|
||
|
load_file(efi_load_file_protocol_t *this, efi_device_path_protocol_t *rem,
|
||
|
bool boot_policy, unsigned long *bufsize, void *buffer)
|
||
|
{
|
||
|
unsigned long compressed_size = _gzdata_end - _gzdata_start;
|
||
|
struct efi_vendor_dev_path *vendor_dp;
|
||
|
bool decompress = false;
|
||
|
unsigned long size;
|
||
|
int ret;
|
||
|
|
||
|
if (rem == NULL || bufsize == NULL)
|
||
|
return EFI_INVALID_PARAMETER;
|
||
|
|
||
|
if (boot_policy)
|
||
|
return EFI_UNSUPPORTED;
|
||
|
|
||
|
// Look for our vendor media device node in the remaining file path
|
||
|
if (rem->type == EFI_DEV_MEDIA &&
|
||
|
rem->sub_type == EFI_DEV_MEDIA_VENDOR) {
|
||
|
vendor_dp = container_of(rem, struct efi_vendor_dev_path, header);
|
||
|
if (!guids_eq(&vendor_dp->vendorguid, &LINUX_EFI_ZBOOT_MEDIA_GUID))
|
||
|
return EFI_NOT_FOUND;
|
||
|
|
||
|
decompress = true;
|
||
|
rem = (void *)(vendor_dp + 1);
|
||
|
}
|
||
|
|
||
|
if (rem->type != EFI_DEV_END_PATH ||
|
||
|
rem->sub_type != EFI_DEV_END_ENTIRE)
|
||
|
return EFI_NOT_FOUND;
|
||
|
|
||
|
// The uncompressed size of the payload is appended to the raw bit
|
||
|
// stream, and may therefore appear misaligned in memory
|
||
|
size = decompress ? get_unaligned_le32(_gzdata_end - 4)
|
||
|
: compressed_size;
|
||
|
if (buffer == NULL || *bufsize < size) {
|
||
|
*bufsize = size;
|
||
|
return EFI_BUFFER_TOO_SMALL;
|
||
|
}
|
||
|
|
||
|
if (decompress) {
|
||
|
ret = __decompress(_gzdata_start, compressed_size, NULL, NULL,
|
||
|
buffer, size, NULL, error);
|
||
|
if (ret < 0) {
|
||
|
log(L"Decompression failed");
|
||
|
return EFI_DEVICE_ERROR;
|
||
|
}
|
||
|
} else {
|
||
|
memcpy(buffer, _gzdata_start, compressed_size);
|
||
|
}
|
||
|
|
||
|
return EFI_SUCCESS;
|
||
|
}
|
||
|
|
||
|
// Return the length in bytes of the device path up to the first end node.
|
||
|
static int device_path_length(const efi_device_path_protocol_t *dp)
|
||
|
{
|
||
|
int len = 0;
|
||
|
|
||
|
while (dp->type != EFI_DEV_END_PATH) {
|
||
|
len += dp->length;
|
||
|
dp = (void *)((u8 *)dp + dp->length);
|
||
|
}
|
||
|
return len;
|
||
|
}
|
||
|
|
||
|
static void append_rel_offset_node(efi_device_path_protocol_t **dp,
|
||
|
unsigned long start, unsigned long end)
|
||
|
{
|
||
|
struct efi_rel_offset_dev_path *rodp = (void *)*dp;
|
||
|
|
||
|
rodp->header.type = EFI_DEV_MEDIA;
|
||
|
rodp->header.sub_type = EFI_DEV_MEDIA_REL_OFFSET;
|
||
|
rodp->header.length = sizeof(struct efi_rel_offset_dev_path);
|
||
|
rodp->reserved = 0;
|
||
|
rodp->starting_offset = start;
|
||
|
rodp->ending_offset = end;
|
||
|
|
||
|
*dp = (void *)(rodp + 1);
|
||
|
}
|
||
|
|
||
|
static void append_ven_media_node(efi_device_path_protocol_t **dp,
|
||
|
efi_guid_t *guid)
|
||
|
{
|
||
|
struct efi_vendor_dev_path *vmdp = (void *)*dp;
|
||
|
|
||
|
vmdp->header.type = EFI_DEV_MEDIA;
|
||
|
vmdp->header.sub_type = EFI_DEV_MEDIA_VENDOR;
|
||
|
vmdp->header.length = sizeof(struct efi_vendor_dev_path);
|
||
|
vmdp->vendorguid = *guid;
|
||
|
|
||
|
*dp = (void *)(vmdp + 1);
|
||
|
}
|
||
|
|
||
|
static void append_end_node(efi_device_path_protocol_t **dp)
|
||
|
{
|
||
|
(*dp)->type = EFI_DEV_END_PATH;
|
||
|
(*dp)->sub_type = EFI_DEV_END_ENTIRE;
|
||
|
(*dp)->length = sizeof(struct efi_generic_dev_path);
|
||
|
|
||
|
++*dp;
|
||
|
}
|
||
|
|
||
|
asmlinkage efi_status_t __efiapi
|
||
|
efi_zboot_entry(efi_handle_t handle, efi_system_table_t *systab)
|
||
|
{
|
||
|
struct efi_mem_mapped_dev_path mmdp = {
|
||
|
.header.type = EFI_DEV_HW,
|
||
|
.header.sub_type = EFI_DEV_MEM_MAPPED,
|
||
|
.header.length = sizeof(struct efi_mem_mapped_dev_path)
|
||
|
};
|
||
|
efi_device_path_protocol_t *parent_dp, *dpp, *lf2_dp, *li_dp;
|
||
|
efi_load_file2_protocol_t zboot_load_file2;
|
||
|
efi_loaded_image_t *parent, *child;
|
||
|
unsigned long exit_data_size;
|
||
|
efi_handle_t child_handle;
|
||
|
efi_handle_t zboot_handle;
|
||
|
efi_char16_t *exit_data;
|
||
|
efi_status_t status;
|
||
|
void *dp_alloc;
|
||
|
int dp_len;
|
||
|
|
||
|
WRITE_ONCE(efi_system_table, systab);
|
||
|
|
||
|
free_mem_ptr = (unsigned long)&zboot_heap;
|
||
|
free_mem_end_ptr = free_mem_ptr + sizeof(zboot_heap);
|
||
|
|
||
|
exit_data = NULL;
|
||
|
exit_data_size = 0;
|
||
|
|
||
|
status = efi_bs_call(handle_protocol, handle,
|
||
|
&LOADED_IMAGE_PROTOCOL_GUID, (void **)&parent);
|
||
|
if (status != EFI_SUCCESS) {
|
||
|
log(L"Failed to locate parent's loaded image protocol");
|
||
|
return status;
|
||
|
}
|
||
|
|
||
|
status = efi_bs_call(handle_protocol, handle,
|
||
|
&LOADED_IMAGE_DEVICE_PATH_PROTOCOL_GUID,
|
||
|
(void **)&parent_dp);
|
||
|
if (status != EFI_SUCCESS || parent_dp == NULL) {
|
||
|
// Create a MemoryMapped() device path node to describe
|
||
|
// the parent image if no device path was provided.
|
||
|
mmdp.memory_type = parent->image_code_type;
|
||
|
mmdp.starting_addr = (unsigned long)parent->image_base;
|
||
|
mmdp.ending_addr = (unsigned long)parent->image_base +
|
||
|
parent->image_size - 1;
|
||
|
parent_dp = &mmdp.header;
|
||
|
dp_len = sizeof(mmdp);
|
||
|
} else {
|
||
|
dp_len = device_path_length(parent_dp);
|
||
|
}
|
||
|
|
||
|
// Allocate some pool memory for device path protocol data
|
||
|
status = efi_bs_call(allocate_pool, EFI_LOADER_DATA,
|
||
|
2 * (dp_len + sizeof(struct efi_rel_offset_dev_path) +
|
||
|
sizeof(struct efi_generic_dev_path)) +
|
||
|
sizeof(struct efi_vendor_dev_path),
|
||
|
(void **)&dp_alloc);
|
||
|
if (status != EFI_SUCCESS) {
|
||
|
log(L"Failed to allocate device path pool memory");
|
||
|
return status;
|
||
|
}
|
||
|
|
||
|
// Create a device path describing the compressed payload in this image
|
||
|
// <...parent_dp...>/Offset(<start>, <end>)
|
||
|
lf2_dp = memcpy(dp_alloc, parent_dp, dp_len);
|
||
|
dpp = (void *)((u8 *)lf2_dp + dp_len);
|
||
|
append_rel_offset_node(&dpp,
|
||
|
(unsigned long)(_gzdata_start - efi_zboot_header),
|
||
|
(unsigned long)(_gzdata_end - efi_zboot_header - 1));
|
||
|
append_end_node(&dpp);
|
||
|
|
||
|
// Create a device path describing the decompressed payload in this image
|
||
|
// <...parent_dp...>/Offset(<start>, <end>)/VenMedia(ZBOOT_MEDIA_GUID)
|
||
|
dp_len += sizeof(struct efi_rel_offset_dev_path);
|
||
|
li_dp = memcpy(dpp, lf2_dp, dp_len);
|
||
|
dpp = (void *)((u8 *)li_dp + dp_len);
|
||
|
append_ven_media_node(&dpp, &LINUX_EFI_ZBOOT_MEDIA_GUID);
|
||
|
append_end_node(&dpp);
|
||
|
|
||
|
zboot_handle = NULL;
|
||
|
zboot_load_file2.load_file = load_file;
|
||
|
status = efi_bs_call(install_multiple_protocol_interfaces,
|
||
|
&zboot_handle,
|
||
|
&EFI_DEVICE_PATH_PROTOCOL_GUID, lf2_dp,
|
||
|
&EFI_LOAD_FILE2_PROTOCOL_GUID, &zboot_load_file2,
|
||
|
NULL);
|
||
|
if (status != EFI_SUCCESS) {
|
||
|
log(L"Failed to install LoadFile2 protocol and device path");
|
||
|
goto free_dpalloc;
|
||
|
}
|
||
|
|
||
|
status = efi_bs_call(load_image, false, handle, li_dp, NULL, 0,
|
||
|
&child_handle);
|
||
|
if (status != EFI_SUCCESS) {
|
||
|
log(L"Failed to load image");
|
||
|
goto uninstall_lf2;
|
||
|
}
|
||
|
|
||
|
status = efi_bs_call(handle_protocol, child_handle,
|
||
|
&LOADED_IMAGE_PROTOCOL_GUID, (void **)&child);
|
||
|
if (status != EFI_SUCCESS) {
|
||
|
log(L"Failed to locate child's loaded image protocol");
|
||
|
goto unload_image;
|
||
|
}
|
||
|
|
||
|
// Copy the kernel command line
|
||
|
child->load_options = parent->load_options;
|
||
|
child->load_options_size = parent->load_options_size;
|
||
|
|
||
|
status = efi_bs_call(start_image, child_handle, &exit_data_size,
|
||
|
&exit_data);
|
||
|
if (status != EFI_SUCCESS) {
|
||
|
log(L"StartImage() returned with error");
|
||
|
if (exit_data_size > 0)
|
||
|
log(exit_data);
|
||
|
|
||
|
// If StartImage() returns EFI_SECURITY_VIOLATION, the image is
|
||
|
// not unloaded so we need to do it by hand.
|
||
|
if (status == EFI_SECURITY_VIOLATION)
|
||
|
unload_image:
|
||
|
efi_bs_call(unload_image, child_handle);
|
||
|
}
|
||
|
|
||
|
uninstall_lf2:
|
||
|
efi_bs_call(uninstall_multiple_protocol_interfaces,
|
||
|
zboot_handle,
|
||
|
&EFI_DEVICE_PATH_PROTOCOL_GUID, lf2_dp,
|
||
|
&EFI_LOAD_FILE2_PROTOCOL_GUID, &zboot_load_file2,
|
||
|
NULL);
|
||
|
|
||
|
free_dpalloc:
|
||
|
efi_bs_call(free_pool, dp_alloc);
|
||
|
|
||
|
efi_bs_call(exit, handle, status, exit_data_size, exit_data);
|
||
|
|
||
|
// Free ExitData in case Exit() returned with a failure code,
|
||
|
// but return the original status code.
|
||
|
log(L"Exit() returned with failure code");
|
||
|
if (exit_data != NULL)
|
||
|
efi_bs_call(free_pool, exit_data);
|
||
|
return status;
|
||
|
}
|