Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

IGVM: Embed firmware in IGVM file and parse OVMF metadata into IGVM parameter block #199

Merged
merged 4 commits into from
Jan 5, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
13 changes: 10 additions & 3 deletions Makefile
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,13 @@ KERNEL_ELF = "target/x86_64-unknown-none/${TARGET_PATH}/svsm"
TEST_KERNEL_ELF = target/x86_64-unknown-none/${TARGET_PATH}/svsm-test
FS_FILE ?= none

FW_FILE ?= none
ifneq ($(FW_FILE), none)
BUILD_FW = --firmware ${FW_FILE}
else
BUILD_FW =
endif

C_BIT_POS ?= 51

STAGE1_OBJS = stage1/stage1.o stage1/reset.o
Expand All @@ -29,12 +36,12 @@ all: stage1/kernel.elf svsm.bin igvm

igvm: $(IGVM_FILES)

$(IGVMBLD): igvmbld/igvmbld.c igvmbld/igvm_defs.h igvmbld/sev-snp.h
$(IGVMBLD): igvmbld/igvmbld.c igvmbld/ovmfmeta.c igvmbld/ovmfmeta.h igvmbld/igvm_defs.h igvmbld/sev-snp.h
mkdir -v -p bin
$(CC) -o $@ -O -Iigvmbld igvmbld/igvmbld.c
$(CC) -o $@ -O -Iigvmbld igvmbld/igvmbld.c igvmbld/ovmfmeta.c

bin/coconut-qemu.igvm: $(IGVMBLD) stage1/kernel.elf stage1/stage2.bin
$(IGVMBLD) --output $@ --stage2 stage1/stage2.bin --kernel stage1/kernel.elf --qemu
$(IGVMBLD) --output $@ --stage2 stage1/stage2.bin --kernel stage1/kernel.elf --qemu ${BUILD_FW}

bin/coconut-hyperv.igvm: $(IGVMBLD) stage1/kernel.elf stage1/stage2.bin
$(IGVMBLD) --output $@ --stage2 stage1/stage2.bin --kernel stage1/kernel.elf --hyperv --com-port 3
Expand Down
81 changes: 55 additions & 26 deletions bootlib/src/igvm_params.rs
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,58 @@ pub struct IgvmParamPage {
pub environment_info: u32,
}

/// An entry that represents an area of pre-validated memory defined by the
/// firmware in the IGVM file.
#[repr(C, packed)]
#[derive(Clone, Copy, Debug)]
pub struct IgvmParamBlockFwMem {
/// The base physical address of the prevalidated memory region.
pub base: u32,

/// The length of the prevalidated memory region in bytes.
pub size: u32,
}

/// The portion of the IGVM parameter block that describes metadata about
/// the firmware image embedded in the IGVM file.
#[repr(C, packed)]
#[derive(Clone, Copy, Debug)]
pub struct IgvmParamBlockFwInfo {
/// The guest physical address of the start of the guest firmware. The
/// permissions on the pages in the firmware range are adjusted to the guest
/// VMPL. If this field is zero then no firmware is launched after
/// initialization is complete.
pub start: u32,

/// The size of the guest firmware in bytes. If the firmware size is zero then
/// no firmware is launched after initialization is complete.
pub size: u32,

_reserved: u32,

/// The guest physical address at which the firmware expects to find the
/// secrets page.
pub secrets_page: u32,

/// The guest physical address at which the firmware expects to find the
/// calling area page.
pub caa_page: u32,

/// The guest physical address at which the firmware expects to find the
/// CPUID page.
pub cpuid_page: u32,

/// The guest physical address at which the firmware expects the reset
/// vector to be defined.
pub reset_addr: u32,

/// The number of prevalidated memory regions defined by the firmware.
pub prevalidated_count: u32,

/// The prevalidated memory regions defined by the firmware.
pub prevalidated: [IgvmParamBlockFwMem; 8],
}

/// The IGVM parameter block is a measured page constructed by the IGVM file
/// builder which describes where the additional IGVM parameter information
/// has been placed into the guest address space.
Expand Down Expand Up @@ -50,32 +102,9 @@ pub struct IgvmParamBlock {

_reserved: u16,

/// The guest physical address of the start of the guest firmware. The
/// permissions on the pages in the firmware range are adjusted to the guest
/// VMPL. If this field is zero then no firmware is launched after
/// initialization is complete.
pub fw_start: u32,

/// The size of the guest firmware in bytes. If the firmware size is zero then
/// no firmware is launched after initialization is complete.
pub fw_size: u32,

/// The guest physical address of the page that contains metadata that
/// corresponds to the firmware. The SVSM expects the page to contain
/// metadata in the format defined by OVMF. If this field is zero but
/// a firmware range has been provided then the firmware is launched
/// without parsing any metadata.
pub fw_metadata: u32,

/// The guest physical address at which the firmware expects to find the
/// secrets page.
pub fw_secrets_page: u32,

/// The guest physical address at which the firmware expects to find the
/// calling area page.
pub fw_caa_page: u32,

_reserved2: u32,
/// Metadata containing information about the firmware image embedded in the
/// IGVM file.
pub firmware: IgvmParamBlockFwInfo,

/// The amount of space that must be reserved at the base of the kernel
/// memory region (e.g. for VMSA contents).
Expand Down
35 changes: 35 additions & 0 deletions igvmbld/igvm_defs.h
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,9 @@
// Copyright (c) Microsoft Corporation
//
// Author: Jon Lange (jlange@microsoft.com)
#pragma once

#include <stdint.h>

typedef enum {
IGVM_VHT_SUPPORTED_PLATFORM = 0x1,
Expand Down Expand Up @@ -94,3 +97,35 @@ typedef struct {
uint16_t Reserved;
uint32_t padding;
} IGVM_VHS_VP_CONTEXT;

typedef struct {
uint32_t base;
uint32_t len;
} IgvmParamBlockFwMem;

typedef struct {
uint32_t start;
uint32_t size;
uint32_t _reserved;
uint32_t secrets_page;
uint32_t caa_page;
uint32_t cpuid_page;
uint32_t reset_addr;
uint32_t prevalidated_count;
IgvmParamBlockFwMem prevalidated[8];
} IgvmParamBlockFwInfo;

typedef struct {
uint32_t param_area_size;
uint32_t param_page_offset;
uint32_t memory_map_offset;
uint32_t cpuid_page;
uint32_t secrets_page;
uint16_t debug_serial_port;
uint16_t _reserved;
IgvmParamBlockFwInfo firmware;
uint32_t kernel_reserved_size;
uint32_t kernel_size;
uint64_t kernel_base;
} IgvmParamBlock;

139 changes: 118 additions & 21 deletions igvmbld/igvmbld.c
Original file line number Diff line number Diff line change
Expand Up @@ -16,30 +16,12 @@
#include <unistd.h>
#include "sev-snp.h"
#include "igvm_defs.h"
#include "ovmfmeta.h"

#define PAGE_SIZE 0x1000

#define FIELD_OFFSET(type, field) ((int)((uint8_t *)&((type *)NULL)->field - (uint8_t *)NULL))

typedef struct {
uint32_t param_area_size;
uint32_t param_page_offset;
uint32_t memory_map_offset;
uint32_t cpuid_page;
uint32_t secrets_page;
uint16_t debug_serial_port;
uint16_t _reserved1;
uint32_t fw_start;
uint32_t fw_size;
uint32_t fw_metadata;
uint32_t fw_secrets_page;
uint32_t fw_caa_page;
uint32_t _reserved2;
uint32_t kernel_reserved_size;
uint32_t kernel_size;
uint64_t kernel_base;
} IgvmParamBlock;

typedef struct {
uint32_t cpu_count;
uint32_t environment_info;
Expand Down Expand Up @@ -86,9 +68,13 @@ const char *stage2_filename;
const char *kernel_filename;
const char *filesystem_filename;
const char *output_filename;
const char *fw_filename;
uint32_t fw_size;
uint32_t fw_base;
int is_qemu;
int is_hyperv;
int com_port = 1;
int is_verbose;

const uint16_t com_io_ports[] = { 0x3f8, 0x2f8, 0x3e8, 0x2e8 };

Expand Down Expand Up @@ -688,6 +674,50 @@ int generate_igvm_file(const char *filename)
return 0;
}

static int check_firmware_options()
{
if (is_qemu)
{
FILE *fp;

if (!fw_filename)
{
// Firmware image is optional.
return 0;
}

// Get the firmware file size so we can determine the top and bottom address
// range.
fp = fopen(fw_filename, "rb");
if (!fp)
{
fprintf(stderr, "Firmware file cannot be opened: \"%s\"\n", fw_filename);
return 1;

}
if (fseek(fp, 0, SEEK_END) != 0)
{
fprintf(stderr, "Failed to read firmware file: \"%s\"\n", fw_filename);
fclose(fp);
return 1;
}
fw_size = ftell(fp);
fclose(fp);

// OVMF firmware must be aligned with the top at 4GB.
fw_base = 0xffffffff - fw_size + 1;
}
else
{
if (fw_filename)
{
fprintf(stderr, "The --firmware parameter is not currently supported for Hyper-V\n");
return 1;
}
}
return 0;
}

int parse_options(int argc, const char *argv[])
{
while (argc != 0)
Expand Down Expand Up @@ -785,6 +815,27 @@ int parse_options(int argc, const char *argv[])
{
is_hyperv = 1;
}
else if ((0 == strcmp(argv[0], "--verbose")) ||
(0 == strcmp(argv[0], "-v")))
{
is_verbose = 1;
}
else if (0 == strcmp(argv[0], "--firmware"))
{
if (fw_filename != NULL)
{
fprintf(stderr, "--firmware specified more than once\n");
return 1;
}
if (argc == 1)
{
fprintf(stderr, "missing argument for --firmware\n");
return 1;
}
fw_filename = argv[1];
argc -= 1;
argv += 1;
}
else
{
fprintf(stderr, "unknown option %s\n", argv[0]);
Expand Down Expand Up @@ -815,11 +866,35 @@ int parse_options(int argc, const char *argv[])
{
fprintf(stderr, "exactly one of --qemu and --hyperv must be specified\n");
return 1;
}
}

if (check_firmware_options() != 0)
{
return 1;
}

return 0;
}

static void print_fw_metadata(IgvmParamBlock *igvm_parameter_block)
{
uint32_t i;

printf("Firmware configuration\n======================\n");
printf(" start: %X\n", igvm_parameter_block->firmware.start);
printf(" size: %X\n", igvm_parameter_block->firmware.size);
printf(" secrets_page: %X\n", igvm_parameter_block->firmware.secrets_page);
printf(" caa_page: %X\n", igvm_parameter_block->firmware.caa_page);
printf(" cpuid_page: %X\n", igvm_parameter_block->firmware.cpuid_page);
printf(" reset_addr: %X\n", igvm_parameter_block->firmware.reset_addr);
printf(" prevalidated_count: %X\n", igvm_parameter_block->firmware.prevalidated_count);
for (i = 0; i < igvm_parameter_block->firmware.prevalidated_count; ++i)
{
printf(" prevalidated[%d].base: %X\n", i, igvm_parameter_block->firmware.prevalidated[i].base);
printf(" prevalidated[%d].len: %X\n", i, igvm_parameter_block->firmware.prevalidated[i].len);
}
}

int main(int argc, const char *argv[])
{
uint32_t address;
Expand Down Expand Up @@ -897,7 +972,7 @@ int main(int argc, const char *argv[])
return 1;
}
address = (kernel_data->address + kernel_data->size + PAGE_SIZE - 1) &
~(PAGE_SIZE - 1);
~(PAGE_SIZE - 1);

// If a filesystem image is present, then load it after the kernel. It is
// rounded up to the next page boundary to avoid overlapping with any of
Expand Down Expand Up @@ -984,6 +1059,28 @@ int main(int argc, const char *argv[])
address += PAGE_SIZE;
}

// If a firmware file has been specified then add it and set the relevant
// parameter block entries.
if (fw_filename)
{
igvm_parameter_block->firmware.size = fw_size;
igvm_parameter_block->firmware.start = fw_base;
if (!construct_file_data_object(fw_filename, fw_base))
{
return 1;
}

// If the firmware file is an OVMF binary then we can extract the OVMF
// metadata from it. If the firmware is not OVMF then this function has
// no effect.
parse_ovmf_metadata(fw_filename, igvm_parameter_block);

if (is_verbose)
{
print_fw_metadata(igvm_parameter_block);
}
}

// Generate a header to describe the memory that will be used as the
// SVSM range. This tells the loader that this GPA range must be populated
// or else the image will not run.
Expand Down
Loading
Loading