/*
* Copyright (C) 2017 The Android Open Source Project
*
* Permission is hereby granted, free of charge, to any person
* obtaining a copy of this software and associated documentation
* files (the "Software"), to deal in the Software without
* restriction, including without limitation the rights to use, copy,
* modify, merge, publish, distribute, sublicense, and/or sell copies
* of the Software, and to permit persons to whom the Software is
* furnished to do so, subject to the following conditions:
*
* The above copyright notice and this permission notice shall be
* included in all copies or substantial portions of the Software.
*
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
* EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
* MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
* NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS
* BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN
* ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN
* CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
* SOFTWARE.
*/
#include <efi.h>
#include <efilib.h>
#include "bootimg.h"
#include "uefi_avb_boot.h"
#include "uefi_avb_util.h"
/* See Documentation/x86/boot.txt for this struct and more information
* about the boot/handover protocol.
*/
#define SETUP_MAGIC 0x53726448 /* "HdrS" */
struct SetupHeader {
UINT8 boot_sector[0x01f1];
UINT8 setup_secs;
UINT16 root_flags;
UINT32 sys_size;
UINT16 ram_size;
UINT16 video_mode;
UINT16 root_dev;
UINT16 signature;
UINT16 jump;
UINT32 header;
UINT16 version;
UINT16 su_switch;
UINT16 setup_seg;
UINT16 start_sys;
UINT16 kernel_ver;
UINT8 loader_id;
UINT8 load_flags;
UINT16 movesize;
UINT32 code32_start;
UINT32 ramdisk_start;
UINT32 ramdisk_len;
UINT32 bootsect_kludge;
UINT16 heap_end;
UINT8 ext_loader_ver;
UINT8 ext_loader_type;
UINT32 cmd_line_ptr;
UINT32 ramdisk_max;
UINT32 kernel_alignment;
UINT8 relocatable_kernel;
UINT8 min_alignment;
UINT16 xloadflags;
UINT32 cmdline_size;
UINT32 hardware_subarch;
UINT64 hardware_subarch_data;
UINT32 payload_offset;
UINT32 payload_length;
UINT64 setup_data;
UINT64 pref_address;
UINT32 init_size;
UINT32 handover_offset;
} __attribute__((packed));
#ifdef __x86_64__
typedef VOID (*handover_f)(VOID* image,
EFI_SYSTEM_TABLE* table,
struct SetupHeader* setup);
static inline VOID linux_efi_handover(EFI_HANDLE image,
struct SetupHeader* setup) {
handover_f handover;
asm volatile("cli");
handover =
(handover_f)((UINTN)setup->code32_start + 512 + setup->handover_offset);
handover(image, ST, setup);
}
#else
typedef VOID (*handover_f)(VOID* image,
EFI_SYSTEM_TABLE* table,
struct SetupHeader* setup)
__attribute__((regparm(0)));
static inline VOID linux_efi_handover(EFI_HANDLE image,
struct SetupHeader* setup) {
handover_f handover;
handover = (handover_f)((UINTN)setup->code32_start + setup->handover_offset);
handover(image, ST, setup);
}
#endif
static size_t round_up(size_t value, size_t size) {
size_t ret = value + size - 1;
ret /= size;
ret *= size;
return ret;
}
UEFIAvbBootKernelResult uefi_avb_boot_kernel(EFI_HANDLE efi_image_handle,
AvbSlotVerifyData* slot_data,
const char* cmdline_extra) {
UEFIAvbBootKernelResult ret;
const boot_img_hdr* header;
EFI_STATUS err;
UINT8* kernel_buf = NULL;
UINT8* initramfs_buf = NULL;
UINT8* cmdline_utf8 = NULL;
AvbPartitionData* boot;
size_t offset;
uint64_t total_size;
size_t initramfs_size;
size_t cmdline_first_len;
size_t cmdline_second_len;
size_t cmdline_extra_len;
size_t cmdline_utf8_len;
struct SetupHeader* image_setup;
struct SetupHeader* setup;
EFI_PHYSICAL_ADDRESS addr;
if (slot_data->num_loaded_partitions != 1) {
avb_error("No boot partition.\n");
ret = UEFI_AVB_BOOT_KERNEL_RESULT_ERROR_PARTITION_INVALID_FORMAT;
goto out;
}
boot = &slot_data->loaded_partitions[0];
if (avb_strcmp(boot->partition_name, "boot") != 0) {
avb_errorv(
"Unexpected partition name '", boot->partition_name, "'.\n", NULL);
ret = UEFI_AVB_BOOT_KERNEL_RESULT_ERROR_PARTITION_INVALID_FORMAT;
goto out;
}
header = (const boot_img_hdr*)boot->data;
/* Check boot image header magic field. */
if (avb_memcmp(BOOT_MAGIC, header->magic, BOOT_MAGIC_SIZE)) {
avb_error("Wrong boot image header magic.\n");
ret = UEFI_AVB_BOOT_KERNEL_RESULT_ERROR_PARTITION_INVALID_FORMAT;
goto out;
}
/* Sanity check header. */
total_size = header->kernel_size;
if (!avb_safe_add_to(&total_size, header->ramdisk_size) ||
!avb_safe_add_to(&total_size, header->second_size)) {
avb_error("Overflow while adding sizes of kernel and initramfs.\n");
ret = UEFI_AVB_BOOT_KERNEL_RESULT_ERROR_PARTITION_INVALID_FORMAT;
goto out;
}
if (total_size > boot->data_size) {
avb_error("Invalid kernel/initramfs sizes.\n");
ret = UEFI_AVB_BOOT_KERNEL_RESULT_ERROR_PARTITION_INVALID_FORMAT;
goto out;
}
/* The kernel has to be in its own specific memory pool. */
err = uefi_call_wrapper(BS->AllocatePool,
NUM_ARGS_ALLOCATE_POOL,
EfiLoaderCode,
header->kernel_size,
&kernel_buf);
if (EFI_ERROR(err)) {
avb_error("Could not allocate kernel buffer.\n");
ret = UEFI_AVB_BOOT_KERNEL_RESULT_ERROR_OOM;
goto out;
}
avb_memcpy(kernel_buf, boot->data + header->page_size, header->kernel_size);
/* Ditto for the initrd. */
initramfs_buf = NULL;
initramfs_size = header->ramdisk_size + header->second_size;
if (initramfs_size > 0) {
err = uefi_call_wrapper(BS->AllocatePool,
NUM_ARGS_ALLOCATE_POOL,
EfiLoaderCode,
initramfs_size,
&initramfs_buf);
if (EFI_ERROR(err)) {
avb_error("Could not allocate initrd buffer.\n");
ret = UEFI_AVB_BOOT_KERNEL_RESULT_ERROR_OOM;
goto out;
}
/* Concatente the first and second initramfs. */
offset = header->page_size;
offset += round_up(header->kernel_size, header->page_size);
avb_memcpy(initramfs_buf, boot->data + offset, header->ramdisk_size);
offset += round_up(header->ramdisk_size, header->page_size);
avb_memcpy(initramfs_buf, boot->data + offset, header->second_size);
}
/* Prepare the command-line. */
cmdline_first_len = avb_strlen((const char*)header->cmdline);
cmdline_second_len = avb_strlen(slot_data->cmdline);
cmdline_extra_len = cmdline_extra != NULL ? avb_strlen(cmdline_extra) : 0;
if (cmdline_extra_len > 0) {
cmdline_extra_len += 1;
}
cmdline_utf8_len =
cmdline_first_len + 1 + cmdline_second_len + 1 + cmdline_extra_len;
err = uefi_call_wrapper(BS->AllocatePool,
NUM_ARGS_ALLOCATE_POOL,
EfiLoaderCode,
cmdline_utf8_len,
&cmdline_utf8);
if (EFI_ERROR(err)) {
avb_error("Could not allocate kernel cmdline.\n");
ret = UEFI_AVB_BOOT_KERNEL_RESULT_ERROR_OOM;
goto out;
}
offset = 0;
avb_memcpy(cmdline_utf8, header->cmdline, cmdline_first_len);
offset += cmdline_first_len;
cmdline_utf8[offset] = ' ';
offset += 1;
avb_memcpy(cmdline_utf8 + offset, slot_data->cmdline, cmdline_second_len);
offset += cmdline_second_len;
if (cmdline_extra_len > 0) {
cmdline_utf8[offset] = ' ';
avb_memcpy(cmdline_utf8 + offset + 1, cmdline_extra, cmdline_extra_len - 1);
offset += cmdline_extra_len;
}
cmdline_utf8[offset] = '\0';
offset += 1;
avb_assert(offset == cmdline_utf8_len);
/* Now set up the EFI handover. */
image_setup = (struct SetupHeader*)kernel_buf;
if (image_setup->signature != 0xAA55 || image_setup->header != SETUP_MAGIC) {
avb_error("Wrong kernel header magic.\n");
ret = UEFI_AVB_BOOT_KERNEL_RESULT_ERROR_KERNEL_INVALID_FORMAT;
goto out;
}
if (image_setup->version < 0x20b) {
avb_error("Wrong version.\n");
ret = UEFI_AVB_BOOT_KERNEL_RESULT_ERROR_KERNEL_INVALID_FORMAT;
goto out;
}
if (!image_setup->relocatable_kernel) {
avb_error("Kernel is not relocatable.\n");
ret = UEFI_AVB_BOOT_KERNEL_RESULT_ERROR_KERNEL_INVALID_FORMAT;
goto out;
}
addr = 0x3fffffff;
err = uefi_call_wrapper(BS->AllocatePages,
4,
AllocateMaxAddress,
EfiLoaderData,
EFI_SIZE_TO_PAGES(0x4000),
&addr);
if (EFI_ERROR(err)) {
avb_error("Could not allocate setup buffer.\n");
ret = UEFI_AVB_BOOT_KERNEL_RESULT_ERROR_OOM;
goto out;
}
setup = (struct SetupHeader*)(UINTN)addr;
avb_memset(setup, '\0', 0x4000);
avb_memcpy(setup, image_setup, sizeof(struct SetupHeader));
setup->loader_id = 0xff;
setup->code32_start =
((uintptr_t)kernel_buf) + (image_setup->setup_secs + 1) * 512;
setup->cmd_line_ptr = (uintptr_t)cmdline_utf8;
setup->ramdisk_start = (uintptr_t)initramfs_buf;
setup->ramdisk_len = (uintptr_t)initramfs_size;
/* Jump to the kernel. */
linux_efi_handover(efi_image_handle, setup);
ret = UEFI_AVB_BOOT_KERNEL_RESULT_ERROR_START_KERNEL;
out:
return ret;
}
const char* uefi_avb_boot_kernel_result_to_string(
UEFIAvbBootKernelResult result) {
const char* ret = NULL;
switch (result) {
case UEFI_AVB_BOOT_KERNEL_RESULT_OK:
ret = "OK";
break;
case UEFI_AVB_BOOT_KERNEL_RESULT_ERROR_OOM:
ret = "ERROR_OEM";
break;
case UEFI_AVB_BOOT_KERNEL_RESULT_ERROR_IO:
ret = "ERROR_IO";
break;
case UEFI_AVB_BOOT_KERNEL_RESULT_ERROR_PARTITION_INVALID_FORMAT:
ret = "ERROR_PARTITION_INVALID_FORMAT";
break;
case UEFI_AVB_BOOT_KERNEL_RESULT_ERROR_KERNEL_INVALID_FORMAT:
ret = "ERROR_KERNEL_INVALID_FORMAT";
break;
case UEFI_AVB_BOOT_KERNEL_RESULT_ERROR_START_KERNEL:
ret = "ERROR_START_KERNEL";
break;
/* Do not add a 'default:' case here because of -Wswitch. */
}
if (ret == NULL) {
avb_error("Unknown UEFIAvbBootKernelResult value.\n");
ret = "(unknown)";
}
return ret;
}