Skip to content

Commit

Permalink
efi: Enable booting unified hypervisor/kernel/initrd images
Browse files Browse the repository at this point in the history
This patch adds support for bundling the xen.efi hypervisor, the xen.cfg
configuration file, the Linux kernel and initrd, as well as the XSM,
and architectural specific files into a single "unified" EFI executable.
This allows an administrator to update the components independently
without requiring rebuilding xen, as well as to replace the components
in an existing image.

The resulting EFI executable can be invoked directly from the UEFI Boot
Manager, removing the need to use a separate loader like grub as well
as removing dependencies on local filesystem access.  And since it is
a single file, it can be signed and validated by UEFI Secure Boot without
requring the shim protocol.

It is inspired by systemd-boot's unified kernel technique and borrows the
function to locate PE sections from systemd's LGPL'ed code.  During EFI
boot, Xen looks at its own loaded image to locate the PE sections for
the Xen configuration (`.config`), dom0 kernel (`.kernel`), dom0 initrd
(`.ramdisk`), and XSM config (`.xsm`), which are included after building
xen.efi using objcopy to add named sections for each input file.

For x86, the CPU ucode can be included in a section named `.ucode`,
which is loaded in the efi_arch_cfg_file_late() stage of the boot process.

On ARM systems the Device Tree can be included in a section named
`.dtb`, which is loaded during the efi_arch_cfg_file_early() stage of
the boot process.

Note that the system will fall back to loading files from disk if
the named sections do not exist. This allows distributions to continue
with the status quo if they want a signed kernel + config, while still
allowing a user provided initrd (which is how the shim protocol currently
works as well).

This patch also adds constness to the section parameter of
efi_arch_cfg_file_early() and efi_arch_cfg_file_late(),
changes pe_find_section() to use a const CHAR16 section name,
and adds pe_name_compare() to match section names.

Signed-off-by: Trammell Hudson <hudson@trmm.net>
Reviewed-by: Jan Beulich <jbeulich@suse.com>
[Fix ARM build by including pe.init.o]
Signed-off-by: Andrew Cooper <andrew.cooper3@citrix.com>
  • Loading branch information
osresearch authored and andyhhp committed Oct 9, 2020
1 parent 4dced5d commit 8a71d50
Show file tree
Hide file tree
Showing 9 changed files with 278 additions and 29 deletions.
1 change: 1 addition & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down
49 changes: 49 additions & 0 deletions docs/misc/efi.pandoc
Original file line number Diff line number Diff line change
Expand Up @@ -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
```
2 changes: 1 addition & 1 deletion xen/arch/arm/efi/Makefile
Original file line number Diff line number Diff line change
@@ -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
25 changes: 17 additions & 8 deletions xen/arch/arm/efi/efi-boot.h
Original file line number Diff line number Diff line change
Expand Up @@ -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)
{
}

Expand Down
2 changes: 1 addition & 1 deletion xen/arch/x86/efi/Makefile
Original file line number Diff line number Diff line change
Expand Up @@ -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)
Expand Down
11 changes: 9 additions & 2 deletions xen/arch/x86/efi/efi-boot.h
Original file line number Diff line number Diff line change
Expand Up @@ -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");
Expand Down
62 changes: 45 additions & 17 deletions xen/common/efi/boot.c
Original file line number Diff line number Diff line change
Expand Up @@ -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);
Expand Down Expand Up @@ -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;
Expand Down Expand Up @@ -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;

Expand Down Expand Up @@ -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);
}
}

/*
Expand Down Expand Up @@ -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;
Expand Down
3 changes: 3 additions & 0 deletions xen/common/efi/efi.h
Original file line number Diff line number Diff line change
Expand Up @@ -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);

0 comments on commit 8a71d50

Please sign in to comment.