diff --git a/.gitignore b/.gitignore index 188495783e..f6865c9cd8 100644 --- a/.gitignore +++ b/.gitignore @@ -327,6 +327,7 @@ xen/arch/*/efi/boot.c xen/arch/*/efi/compat.c xen/arch/*/efi/ebmalloc.c xen/arch/*/efi/efi.h +xen/arch/*/efi/pe.c xen/arch/*/efi/runtime.c xen/common/config_data.S xen/common/config.gz diff --git a/docs/misc/efi.pandoc b/docs/misc/efi.pandoc index 23c1a2732d..ac3cd58cae 100644 --- a/docs/misc/efi.pandoc +++ b/docs/misc/efi.pandoc @@ -116,3 +116,52 @@ Filenames must be specified relative to the location of the EFI binary. Extra options to be passed to Xen can also be specified on the command line, following a `--` separator option. + +## Unified Xen kernel image + +The "Unified" kernel image can be generated by adding additional +sections to the Xen EFI executable with objcopy, similar to how +[systemd-boot uses the stub to add them to the Linux kernel](https://wiki.archlinux.org/index.php/systemd-boot#Preparing_a_unified_kernel_image) + +The sections for the xen configuration file, the dom0 kernel, dom0 initrd, +XSM and CPU microcode should be added after the Xen `.pad` section, the +ending address of which can be located with: + +``` +objdump -h xen.efi \ + | perl -ane '/\.pad/ && printf "0x%016x\n", hex($F[2]) + hex($F[3])' +``` + +For all the examples the `.pad` section ended at 0xffff82d041000000. +All the sections are optional (`.config`, `.kernel`, `.ramdisk`, `.xsm`, +`.ucode` (x86) and `.dtb` (ARM)) and the order does not matter. +The virtual addresses do not need to be contiguous, although they should not +be overlapping and should all be greater than the last virtual address of the +hypervisor components. + +``` +objcopy \ + --add-section .config=xen.cfg \ + --change-section-vma .config=0xffff82d041000000 + --add-section .ucode=ucode.bin \ + --change-section-vma .ucode=0xffff82d041010000 \ + --add-section .xsm=xsm.cfg \ + --change-section-vma .xsm=0xffff82d041080000 \ + --add-section .kernel=vmlinux \ + --change-section-vma .kernel=0xffff82d041100000 \ + --add-section .ramdisk=initrd.img \ + --change-section-vma .ramdisk=0xffff82d042000000 \ + xen.efi \ + xen.unified.efi +``` + +The unified executable can be signed with sbsigntool to make +it usable with UEFI secure boot: + +``` +sbsign \ + --key signing.key \ + --cert cert.pem \ + --output xen.signed.efi \ + xen.unified.efi +``` diff --git a/xen/arch/arm/efi/Makefile b/xen/arch/arm/efi/Makefile index 662892babd..1b1ed06fed 100644 --- a/xen/arch/arm/efi/Makefile +++ b/xen/arch/arm/efi/Makefile @@ -1,4 +1,4 @@ CFLAGS-y += -fshort-wchar -obj-y += boot.init.o ebmalloc.o runtime.o +obj-y += boot.init.o pe.init.o ebmalloc.o runtime.o obj-$(CONFIG_ACPI) += efi-dom0.init.o diff --git a/xen/arch/arm/efi/efi-boot.h b/xen/arch/arm/efi/efi-boot.h index c6200fda0e..f64a6604af 100644 --- a/xen/arch/arm/efi/efi-boot.h +++ b/xen/arch/arm/efi/efi-boot.h @@ -382,27 +382,36 @@ static void __init noreturn efi_arch_post_exit_boot(void) efi_xen_start(fdt, fdt_totalsize(fdt)); } -static void __init efi_arch_cfg_file_early(EFI_FILE_HANDLE dir_handle, char *section) +static void __init efi_arch_cfg_file_early(const EFI_LOADED_IMAGE *image, + EFI_FILE_HANDLE dir_handle, + const char *section) { union string name; /* * The DTB must be processed before any other entries in the configuration - * file, as the DTB is updated as modules are loaded. + * file, as the DTB is updated as modules are loaded. Prefer the one + * stored as a PE section in a unified image, and fall back to a file + * on disk if the section is not present. */ - name.s = get_value(&cfg, section, "dtb"); - if ( name.s ) + if ( !read_section(image, L"dtb", &dtbfile, NULL) ) { - split_string(name.s); - read_file(dir_handle, s2w(&name), &dtbfile, NULL); - efi_bs->FreePool(name.w); + name.s = get_value(&cfg, section, "dtb"); + if ( name.s ) + { + split_string(name.s); + read_file(dir_handle, s2w(&name), &dtbfile, NULL); + efi_bs->FreePool(name.w); + } } fdt = fdt_increase_size(&dtbfile, cfg.size + EFI_PAGE_SIZE); if ( !fdt ) blexit(L"Unable to create new FDT"); } -static void __init efi_arch_cfg_file_late(EFI_FILE_HANDLE dir_handle, char *section) +static void __init efi_arch_cfg_file_late(const EFI_LOADED_IMAGE *image, + EFI_FILE_HANDLE dir_handle, + const char *section) { } diff --git a/xen/arch/x86/efi/Makefile b/xen/arch/x86/efi/Makefile index 770438a029..e857c0f2cc 100644 --- a/xen/arch/x86/efi/Makefile +++ b/xen/arch/x86/efi/Makefile @@ -8,7 +8,7 @@ cmd_objcopy_o_ihex = $(OBJCOPY) -I ihex -O binary $< $@ boot.init.o: buildid.o -EFIOBJ := boot.init.o ebmalloc.o compat.o runtime.o +EFIOBJ := boot.init.o pe.init.o ebmalloc.o compat.o runtime.o $(call cc-option-add,cflags-stack-boundary,CC,-mpreferred-stack-boundary=4) $(EFIOBJ): CFLAGS-stack-boundary := $(cflags-stack-boundary) diff --git a/xen/arch/x86/efi/efi-boot.h b/xen/arch/x86/efi/efi-boot.h index 1025000afd..2541ba1f32 100644 --- a/xen/arch/x86/efi/efi-boot.h +++ b/xen/arch/x86/efi/efi-boot.h @@ -272,14 +272,21 @@ static void __init noreturn efi_arch_post_exit_boot(void) unreachable(); } -static void __init efi_arch_cfg_file_early(EFI_FILE_HANDLE dir_handle, char *section) +static void __init efi_arch_cfg_file_early(const EFI_LOADED_IMAGE *image, + EFI_FILE_HANDLE dir_handle, + const char *section) { } -static void __init efi_arch_cfg_file_late(EFI_FILE_HANDLE dir_handle, char *section) +static void __init efi_arch_cfg_file_late(const EFI_LOADED_IMAGE *image, + EFI_FILE_HANDLE dir_handle, + const char *section) { union string name; + if ( read_section(image, L"ucode", &ucode, NULL) ) + return; + name.s = get_value(&cfg, section, "ucode"); if ( !name.s ) name.s = get_value(&cfg, "global", "ucode"); diff --git a/xen/common/efi/boot.c b/xen/common/efi/boot.c index bd629eb658..bacd551bb5 100644 --- a/xen/common/efi/boot.c +++ b/xen/common/efi/boot.c @@ -122,6 +122,8 @@ static CHAR16 *s2w(union string *str); static char *w2s(const union string *str); static bool read_file(EFI_FILE_HANDLE dir_handle, CHAR16 *name, struct file *file, const char *options); +static bool read_section(const EFI_LOADED_IMAGE *image, const CHAR16 *name, + struct file *file, const char *options); static size_t wstrlen(const CHAR16 * s); static int set_color(u32 mask, int bpp, u8 *pos, u8 *sz); static bool match_guid(const EFI_GUID *guid1, const EFI_GUID *guid2); @@ -631,6 +633,20 @@ static bool __init read_file(EFI_FILE_HANDLE dir_handle, CHAR16 *name, return true; } +static bool __init read_section(const EFI_LOADED_IMAGE *image, + const CHAR16 *name, struct file *file, + const char *options) +{ + file->ptr = pe_find_section(image->ImageBase, image->ImageSize, + name, &file->size); + if ( !file->ptr ) + return false; + + handle_file_info(name, file, options); + + return true; +} + static void __init pre_parse(const struct file *cfg) { char *ptr = cfg->str, *end = ptr + cfg->size; @@ -1216,7 +1232,9 @@ efi_start(EFI_HANDLE ImageHandle, EFI_SYSTEM_TABLE *SystemTable) dir_handle = get_parent_handle(loaded_image, &file_name); /* Read and parse the config file. */ - if ( !cfg_file_name ) + if ( read_section(loaded_image, L"config", &cfg, NULL) ) + PrintStr(L"Using builtin config file\r\n"); + else if ( !cfg_file_name ) { CHAR16 *tail; @@ -1266,29 +1284,39 @@ efi_start(EFI_HANDLE ImageHandle, EFI_SYSTEM_TABLE *SystemTable) if ( !name.s ) blexit(L"No Dom0 kernel image specified."); - efi_arch_cfg_file_early(dir_handle, section.s); + efi_arch_cfg_file_early(loaded_image, dir_handle, section.s); option_str = split_string(name.s); - read_file(dir_handle, s2w(&name), &kernel, option_str); - efi_bs->FreePool(name.w); - if ( !EFI_ERROR(efi_bs->LocateProtocol(&shim_lock_guid, NULL, - (void **)&shim_lock)) && - (status = shim_lock->Verify(kernel.ptr, kernel.size)) != EFI_SUCCESS ) - PrintErrMesg(L"Dom0 kernel image could not be verified", status); - - name.s = get_value(&cfg, section.s, "ramdisk"); - if ( name.s ) + if ( !read_section(loaded_image, L"kernel", &kernel, option_str) ) { - read_file(dir_handle, s2w(&name), &ramdisk, NULL); + read_file(dir_handle, s2w(&name), &kernel, option_str); efi_bs->FreePool(name.w); + + if ( !EFI_ERROR(efi_bs->LocateProtocol(&shim_lock_guid, NULL, + (void **)&shim_lock)) && + (status = shim_lock->Verify(kernel.ptr, kernel.size)) != EFI_SUCCESS ) + PrintErrMesg(L"Dom0 kernel image could not be verified", status); } - name.s = get_value(&cfg, section.s, "xsm"); - if ( name.s ) + if ( !read_section(loaded_image, L"ramdisk", &ramdisk, NULL) ) { - read_file(dir_handle, s2w(&name), &xsm, NULL); - efi_bs->FreePool(name.w); + name.s = get_value(&cfg, section.s, "ramdisk"); + if ( name.s ) + { + read_file(dir_handle, s2w(&name), &ramdisk, NULL); + efi_bs->FreePool(name.w); + } + } + + if ( !read_section(loaded_image, L"xsm", &xsm, NULL) ) + { + name.s = get_value(&cfg, section.s, "xsm"); + if ( name.s ) + { + read_file(dir_handle, s2w(&name), &xsm, NULL); + efi_bs->FreePool(name.w); + } } /* @@ -1324,7 +1352,7 @@ efi_start(EFI_HANDLE ImageHandle, EFI_SYSTEM_TABLE *SystemTable) } } - efi_arch_cfg_file_late(dir_handle, section.s); + efi_arch_cfg_file_late(loaded_image, dir_handle, section.s); efi_bs->FreePages(cfg.addr, PFN_UP(cfg.size)); cfg.addr = 0; diff --git a/xen/common/efi/efi.h b/xen/common/efi/efi.h index 4845d84913..663a8b5000 100644 --- a/xen/common/efi/efi.h +++ b/xen/common/efi/efi.h @@ -47,3 +47,6 @@ const CHAR16 *wmemchr(const CHAR16 *s, CHAR16 c, UINTN n); /* EFI boot allocator. */ void *ebmalloc(size_t size); void free_ebmalloc_unused_mem(void); + +const void *pe_find_section(const void *image_base, const size_t image_size, + const CHAR16 *section_name, UINTN *size_out); diff --git a/xen/common/efi/pe.c b/xen/common/efi/pe.c new file mode 100644 index 0000000000..a84992df9a --- /dev/null +++ b/xen/common/efi/pe.c @@ -0,0 +1,152 @@ +/* + * xen/common/efi/pe.c + * + * PE executable header parser. + * + * Derived from https://github.com/systemd/systemd/blob/master/src/boot/efi/pe.c + * commit 07d5ed536ec0a76b08229c7a80b910cb9acaf6b1 + * + * Copyright (C) 2015 Kay Sievers + * Copyright (C) 2020 Trammell Hudson + * + * This program is free software; you can redistribute it and/or modify it + * under the terms of the GNU Lesser General Public License as published by + * the Free Software Foundation; either version 2.1 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, but + * WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + */ + + +#include "efi.h" + +struct DosFileHeader { + UINT8 Magic[2]; + UINT16 LastSize; + UINT16 nBlocks; + UINT16 nReloc; + UINT16 HdrSize; + UINT16 MinAlloc; + UINT16 MaxAlloc; + UINT16 ss; + UINT16 sp; + UINT16 Checksum; + UINT16 ip; + UINT16 cs; + UINT16 RelocPos; + UINT16 nOverlay; + UINT16 reserved[4]; + UINT16 OEMId; + UINT16 OEMInfo; + UINT16 reserved2[10]; + UINT32 ExeHeader; +}; + +#if defined(__arm__) || defined (__aarch64__) +#define PE_HEADER_MACHINE 0xaa64 +#elif defined(__x86_64__) +#define PE_HEADER_MACHINE 0x8664 +#else +#error "Unknown architecture" +#endif + +struct PeFileHeader { + UINT16 Machine; + UINT16 NumberOfSections; + UINT32 TimeDateStamp; + UINT32 PointerToSymbolTable; + UINT32 NumberOfSymbols; + UINT16 SizeOfOptionalHeader; + UINT16 Characteristics; +}; + +struct PeHeader { + UINT8 Magic[4]; + struct PeFileHeader FileHeader; +}; + +struct PeSectionHeader { + CHAR8 Name[8]; + UINT32 VirtualSize; + UINT32 VirtualAddress; + UINT32 SizeOfRawData; + UINT32 PointerToRawData; + UINT32 PointerToRelocations; + UINT32 PointerToLinenumbers; + UINT16 NumberOfRelocations; + UINT16 NumberOfLinenumbers; + UINT32 Characteristics; +}; + +static bool __init pe_name_compare(const struct PeSectionHeader *sect, + const CHAR16 *name) +{ + size_t i; + + if ( sect->Name[0] != '.' ) + return false; + + for ( i = 1; i < sizeof(sect->Name); i++ ) + { + const char c = sect->Name[i]; + + if ( c != name[i - 1] ) + return false; + if ( c == '\0' ) + return true; + } + + return name[i - 1] == L'\0'; +} + +const void *__init pe_find_section(const void *image, const UINTN image_size, + const CHAR16 *section_name, UINTN *size_out) +{ + const struct DosFileHeader *dos = image; + const struct PeHeader *pe; + const struct PeSectionHeader *sect; + UINTN offset, i; + + if ( image_size < sizeof(*dos) || + memcmp(dos->Magic, "MZ", 2) != 0 ) + return NULL; + + offset = dos->ExeHeader; + pe = image + offset; + + offset += sizeof(*pe); + if ( image_size < offset || + memcmp(pe->Magic, "PE\0\0", 4) != 0 ) + return NULL; + + if ( pe->FileHeader.Machine != PE_HEADER_MACHINE ) + return NULL; + + offset += pe->FileHeader.SizeOfOptionalHeader; + + for ( i = 0; i < pe->FileHeader.NumberOfSections; i++ ) + { + sect = image + offset; + if ( image_size < offset + sizeof(*sect) ) + return NULL; + + if ( !pe_name_compare(sect, section_name) ) + { + offset += sizeof(*sect); + continue; + } + + if ( image_size < sect->VirtualSize + sect->VirtualAddress ) + blexit(L"PE invalid section size + address"); + + if ( size_out ) + *size_out = sect->VirtualSize; + + return image + sect->VirtualAddress; + } + + return NULL; +}