@@ -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
@@ -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
+```
@@ -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)
{
}
@@ -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)
@@ -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");
@@ -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;
@@ -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);
new file mode 100644
@@ -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 <kay@vrfy.org>
+ * Copyright (C) 2020 Trammell Hudson <hudson@trmm.net>
+ *
+ * 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;
+}