Skip to content

Commit

Permalink
efi/boot: Add "unified Xen" support for SecureBoot
Browse files Browse the repository at this point in the history
During UEFI boot, Xen can look to see if it has built-in PE sections for its
configuration file, the Linux kernel, the initrd, and the XSM.  By
bundling all of these into a single file with the hypervisor, it is
easier to integrate with UEFI SecureBoot, which supports validating a single
signed EFI executable.

The PE executable parser and unifed kernel idea is copied from systemd-boot
and has been used by the safeboot project.

The scripts/unify-xen shows how to use objcopy to bundle all of the
components together; it requires better command line parsing to be
robust.

Signed-off-by: Trammell Hudson <[email protected]>
  • Loading branch information
osresearch committed Aug 4, 2020
1 parent 81fd0d3 commit 765f0fe
Show file tree
Hide file tree
Showing 2 changed files with 238 additions and 20 deletions.
190 changes: 170 additions & 20 deletions xen/common/efi/boot.c
Original file line number Diff line number Diff line change
Expand Up @@ -102,6 +102,7 @@ union string {

struct file {
UINTN size;
bool need_to_free;
union {
EFI_PHYSICAL_ADDRESS addr;
void *ptr;
Expand Down Expand Up @@ -330,13 +331,13 @@ static void __init noreturn blexit(const CHAR16 *str)
if ( !efi_bs )
efi_arch_halt();

if ( cfg.addr )
if ( cfg.addr && cfg.need_to_free)
efi_bs->FreePages(cfg.addr, PFN_UP(cfg.size));
if ( kernel.addr )
if ( kernel.addr && kernel.need_to_free)
efi_bs->FreePages(kernel.addr, PFN_UP(kernel.size));
if ( ramdisk.addr )
if ( ramdisk.addr && ramdisk.need_to_free)
efi_bs->FreePages(ramdisk.addr, PFN_UP(ramdisk.size));
if ( xsm.addr )
if ( xsm.addr && xsm.need_to_free)
efi_bs->FreePages(xsm.addr, PFN_UP(xsm.size));

efi_arch_blexit();
Expand Down Expand Up @@ -619,6 +620,7 @@ static bool __init read_file(EFI_FILE_HANDLE dir_handle, CHAR16 *name,
what = what ?: L"Seek";
else
{
file->need_to_free = true;
file->addr = min(1UL << (32 + PAGE_SHIFT),
HYPERVISOR_VIRT_END - DIRECTMAP_VIRT_START);
ret = efi_bs->AllocatePages(AllocateMaxAddress, EfiLoaderData,
Expand Down Expand Up @@ -665,6 +667,135 @@ static bool __init read_file(EFI_FILE_HANDLE dir_handle, CHAR16 *name,
return true;
}


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;
} __attribute__((packed));

#define PE_HEADER_MACHINE_I386 0x014c
#define PE_HEADER_MACHINE_X64 0x8664
#define PE_HEADER_MACHINE_ARM64 0xaa64

struct PeFileHeader {
UINT16 Machine;
UINT16 NumberOfSections;
UINT32 TimeDateStamp;
UINT32 PointerToSymbolTable;
UINT32 NumberOfSymbols;
UINT16 SizeOfOptionalHeader;
UINT16 Characteristics;
} __attribute__((packed));

struct PeHeader {
UINT8 Magic[4];
struct PeFileHeader FileHeader;
} __attribute__((packed));

struct PeSectionHeader {
UINT8 Name[8];
UINT32 VirtualSize;
UINT32 VirtualAddress;
UINT32 SizeOfRawData;
UINT32 PointerToRawData;
UINT32 PointerToRelocations;
UINT32 PointerToLinenumbers;
UINT16 NumberOfRelocations;
UINT16 NumberOfLinenumbers;
UINT32 Characteristics;
} __attribute__((packed));

static void * __init pe_find_section(const void * const image_base,
const char * section_name, UINTN * size_out)
{
const CHAR8 * const base = image_base;
const struct DosFileHeader * dos = (const void*) base;
const struct PeHeader * pe;
const UINTN name_len = strlen(section_name);
UINTN offset;

if (base == NULL)
return NULL;

if (memcmp(dos->Magic, "MZ", 2) != 0)
return NULL;

pe = (const void *) &base[dos->ExeHeader];
if (memcmp(pe->Magic, "PE\0\0", 4) != 0)
return NULL;

/* PE32+ Subsystem type */
if (pe->FileHeader.Machine != PE_HEADER_MACHINE_X64
&& pe->FileHeader.Machine != PE_HEADER_MACHINE_ARM64
&& pe->FileHeader.Machine != PE_HEADER_MACHINE_I386)
return NULL;

if (pe->FileHeader.NumberOfSections > 96)
return NULL;

offset = dos->ExeHeader + sizeof(*pe) + pe->FileHeader.SizeOfOptionalHeader;

for (UINTN i = 0; i < pe->FileHeader.NumberOfSections; i++)
{
const struct PeSectionHeader *const sect = (const struct PeSectionHeader *)&base[offset];
if (memcmp(sect->Name, section_name, name_len) == 0)
{
if (size_out)
*size_out = sect->VirtualSize;
return (void*)(sect->VirtualAddress + (uintptr_t) image_base);
}

offset += sizeof(*sect);
}

return NULL;
}

static bool __init read_section(const void * const image_base, char * const name,
struct file *file, char *options)
{
union string name_string = { .s = name + 1 };
if ( !image_base )
return false;

file->ptr = pe_find_section(image_base, name, &file->size);
if ( !file->ptr )
return false;

file->need_to_free = false;

if ( file == &cfg )
return true;

s2w(&name_string);
PrintStr(name_string.w);
PrintStr(L": ");
DisplayUint(file->addr, 2 * sizeof(file->addr));
PrintStr(L"-");
DisplayUint(file->addr + file->size, 2 * sizeof(file->addr));
PrintStr(newline);
efi_arch_handle_module(file, name_string.w, options);

return true;
}

static void __init pre_parse(const struct file *cfg)
{
char *ptr = cfg->ptr, *end = ptr + cfg->size;
Expand Down Expand Up @@ -1143,6 +1274,7 @@ efi_start(EFI_HANDLE ImageHandle, EFI_SYSTEM_TABLE *SystemTable)
static EFI_GUID __initdata loaded_image_guid = LOADED_IMAGE_PROTOCOL;
static EFI_GUID __initdata shim_lock_guid = SHIM_LOCK_PROTOCOL_GUID;
EFI_LOADED_IMAGE *loaded_image;
void * image_base = NULL;
EFI_STATUS status;
unsigned int i, argc;
CHAR16 **argv, *file_name, *cfg_file_name = NULL, *options = NULL;
Expand Down Expand Up @@ -1171,6 +1303,8 @@ efi_start(EFI_HANDLE ImageHandle, EFI_SYSTEM_TABLE *SystemTable)
PrintErrMesg(L"No Loaded Image Protocol", status);

efi_arch_load_addr_check(loaded_image);
if (loaded_image)
image_base = loaded_image->ImageBase;

if ( use_cfg_file )
{
Expand Down Expand Up @@ -1249,9 +1383,13 @@ efi_start(EFI_HANDLE ImageHandle, EFI_SYSTEM_TABLE *SystemTable)
/* Get the file system interface. */
dir_handle = get_parent_handle(loaded_image, &file_name);

/* Read and parse the config file. */
if ( !cfg_file_name )
if ( read_section(image_base, ".config", &cfg, NULL) )
{
PrintStr(L"Using unified config file\r\n");
}
else if ( !cfg_file_name )
{
/* Read and parse the config file. */
CHAR16 *tail;

while ( (tail = point_tail(file_name)) != NULL )
Expand All @@ -1269,6 +1407,7 @@ efi_start(EFI_HANDLE ImageHandle, EFI_SYSTEM_TABLE *SystemTable)
}
else if ( !read_file(dir_handle, cfg_file_name, &cfg, NULL) )
blexit(L"Configuration file not found.");

pre_parse(&cfg);

if ( section.w )
Expand Down Expand Up @@ -1303,26 +1442,37 @@ efi_start(EFI_HANDLE ImageHandle, EFI_SYSTEM_TABLE *SystemTable)
efi_arch_cfg_file_early(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(image_base, ".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(image_base, ".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(image_base, ".xsm", &xsm, NULL) )
{
name.s = get_value(&cfg, section.s, "xsm");
if ( name.s )
{
if (!xsm.ptr)
read_file(dir_handle, s2w(&name), &xsm, NULL);
efi_bs->FreePool(name.w);
}
}

/*
Expand Down
68 changes: 68 additions & 0 deletions xen/scripts/unify-xen
Original file line number Diff line number Diff line change
@@ -0,0 +1,68 @@
#!/bin/bash
# Merge a Linux kernel, initrd and commandline into xen.efi to produce a single signed
# EFI executable.
#
# turn off "expressions don't expand in single quotes"
# and "can't follow non-constant sources"
# shellcheck disable=SC2016 disable=SC1090
set -e -o pipefail
export LC_ALL=C

die() { echo "$@" >&2 ; exit 1 ; }
warn() { echo "$@" >&2 ; }
debug() { [ "$VERBOSE" == 1 ] && echo "$@" >&2 ; }

cleanup() {
rm -rf "$TMP"
}

TMP=$(mktemp -d)
TMP_MOUNT=n
trap cleanup EXIT

########################################

# Usage
# unify xen.efi xen.cfg bzimage initrd
# Xen goes up to a pad at 00400000

XEN="$1"
CONFIG="$2"
KERNEL="$3"
RAMDISK="$4"
# --change-section-vma .config=0x0500000 \
# --change-section-vma .kernel=0x0510000 \
# --change-section-vma .ramdisk=0x3000000 \

objcopy \
--add-section .kernel="$KERNEL" \
--add-section .ramdisk="$RAMDISK" \
--add-section .config="$CONFIG" \
--change-section-vma .config=0xffff82d041000000 \
--change-section-vma .kernel=0xffff82d041010000 \
--change-section-vma .ramdisk=0xffff82d042000000 \
"$XEN" \
"$TMP/xen.efi" \
|| die "$TMP/xen.efi: unable to create"

KEY_ENGINE=""
KEY="/etc/safeboot/signing.key"
CERT="/etc/safeboot/cert.pem"

for try in 1 2 3 ; do
warn "$TMP/xen.efi: Signing (ignore warnings about gaps)"
sbsign.safeboot \
$KEY_ENGINE \
--key "$KEY" \
--cert "$CERT" \
--output "xen.signed.efi" \
"$TMP/xen.efi" \
&& break

if [ "$try" == 3 ]; then
die "xen.signed.efi: failed after $try tries"
fi

warn "$OUTDIR/linux.efi: signature failed! Try $try."
done

0 comments on commit 765f0fe

Please sign in to comment.