| /* |
| * 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; |
| } |