From 3dd317fdcd27bfa2215dc24a9b04b857ff5e058f Mon Sep 17 00:00:00 2001 From: Jon Lange Date: Fri, 5 Jan 2024 14:07:05 -0800 Subject: [PATCH 1/7] Fix strict compiler warnings Signed-off-by: Jon Lange --- igvmbld/igvmbld.c | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) diff --git a/igvmbld/igvmbld.c b/igvmbld/igvmbld.c index 10cb196d8..72c2d9b14 100644 --- a/igvmbld/igvmbld.c +++ b/igvmbld/igvmbld.c @@ -88,19 +88,20 @@ uint32_t total_file_size; static uint32_t _crc; -static uint32_t crc32b_init() { +static void crc32b_init() { _crc = 0xffffffff; } static void crc32b_update(uint8_t *message, uint32_t len) { - int32_t i, j; + uint32_t i; + int32_t j; uint32_t byte, mask; for (i = 0; i < len; ++i) { byte = message[i]; _crc = _crc ^ byte; for (j = 7; j >= 0; --j) { - mask = -(_crc & 1); + mask = 0 - (_crc & 1); _crc = (_crc >> 1) ^ (0xedb88320 & mask); } } From c82bd898fd9442ed9b520cd4a61afc49d2582b82 Mon Sep 17 00:00:00 2001 From: Jon Lange Date: Thu, 4 Jan 2024 19:35:23 -0800 Subject: [PATCH 2/7] Prevent memory overruns when writing data that is not aligned to a multiple of pages in size Signed-off-by: Jon Lange --- igvmbld/igvmbld.c | 15 ++++++++++++++- 1 file changed, 14 insertions(+), 1 deletion(-) diff --git a/igvmbld/igvmbld.c b/igvmbld/igvmbld.c index 72c2d9b14..9bbe22493 100644 --- a/igvmbld/igvmbld.c +++ b/igvmbld/igvmbld.c @@ -131,13 +131,26 @@ DATA_OBJ *insert_data_object(DATA_OBJ *data_object) DATA_OBJ *allocate_data_object(uint64_t address, uint32_t size, uint32_t data_size) { + uint32_t allocation_size; DATA_OBJ *data_object; data_object = malloc(sizeof(DATA_OBJ)); data_object->address = address; data_object->size = size; if (data_size != 0) - data_object->data = malloc(data_size); + { + // Make sure the allocation is rounded up to a page boundary because + // the data will be written in multiples of pages. + allocation_size = (data_size + PAGE_SIZE - 1) & ~(PAGE_SIZE - 1); + data_object->data = malloc(allocation_size); + + // Zero-initialize any allocation padding to sure that it is zero in + // the final file. + if (allocation_size != data_size) + { + memset((uint8_t *)data_object->data, data_size, allocation_size - data_size); + } + } else data_object->data = NULL; data_object->data_type = IGVM_VHT_PAGE_DATA; From fcb7ec86b673145e19a12f2038dee4cc14cb966d Mon Sep 17 00:00:00 2001 From: Jon Lange Date: Fri, 5 Jan 2024 14:06:07 -0800 Subject: [PATCH 3/7] Split common definitions into a separate header file Signed-off-by: Jon Lange --- Makefile | 2 +- igvmbld/igvm_defs.h | 34 ------------------------------ igvmbld/igvmbld.c | 15 ++------------ igvmbld/igvmbld.h | 50 +++++++++++++++++++++++++++++++++++++++++++++ igvmbld/ovmfmeta.c | 10 ++------- igvmbld/ovmfmeta.h | 12 ----------- 6 files changed, 55 insertions(+), 68 deletions(-) create mode 100644 igvmbld/igvmbld.h delete mode 100644 igvmbld/ovmfmeta.h diff --git a/Makefile b/Makefile index 5bd2c8d12..7ad76b771 100644 --- a/Makefile +++ b/Makefile @@ -36,7 +36,7 @@ all: stage1/kernel.elf svsm.bin igvm igvm: $(IGVM_FILES) -$(IGVMBLD): igvmbld/igvmbld.c igvmbld/ovmfmeta.c igvmbld/ovmfmeta.h igvmbld/igvm_defs.h igvmbld/sev-snp.h +$(IGVMBLD): igvmbld/igvmbld.c igvmbld/ovmfmeta.c igvmbld/igvmbld.h igvmbld/igvm_defs.h igvmbld/sev-snp.h mkdir -v -p bin $(CC) -o $@ -O -Iigvmbld igvmbld/igvmbld.c igvmbld/ovmfmeta.c diff --git a/igvmbld/igvm_defs.h b/igvmbld/igvm_defs.h index 4b6e84886..8836003e3 100644 --- a/igvmbld/igvm_defs.h +++ b/igvmbld/igvm_defs.h @@ -5,8 +5,6 @@ // Author: Jon Lange (jlange@microsoft.com) #pragma once -#include - typedef enum { IGVM_VHT_SUPPORTED_PLATFORM = 0x1, IGVM_VHT_PARAMETER_AREA = 0x301, @@ -97,35 +95,3 @@ 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; - diff --git a/igvmbld/igvmbld.c b/igvmbld/igvmbld.c index 9bbe22493..76d522f8d 100644 --- a/igvmbld/igvmbld.c +++ b/igvmbld/igvmbld.c @@ -9,18 +9,7 @@ // maintained until the Rust version is ready. // -#include -#include -#include -#include -#include -#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)) +#include "igvmbld.h" typedef struct { uint32_t cpu_count; @@ -905,7 +894,7 @@ static void print_fw_metadata(IgvmParamBlock *igvm_parameter_block) 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); + printf(" prevalidated[%d].size: %X\n", i, igvm_parameter_block->firmware.prevalidated[i].size); } } diff --git a/igvmbld/igvmbld.h b/igvmbld/igvmbld.h new file mode 100644 index 000000000..bd427ea30 --- /dev/null +++ b/igvmbld/igvmbld.h @@ -0,0 +1,50 @@ +// SPDX-License-Identifier: MIT OR Apache-2.0 +// +// Copyright (c) Microsoft Corporation +// +// Author: Jon Lange (jlange@microsoft.com) + +#include +#include +#include +#include +#include +#include "sev-snp.h" +#include "igvm_defs.h" + +#define PAGE_SIZE 0x1000 + +#define FIELD_OFFSET(type, field) ((int)((uint8_t *)&((type *)NULL)->field - (uint8_t *)NULL)) + +typedef struct { + uint32_t base; + uint32_t size; +} 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; + +int parse_ovmf_metadata(const char *ovmf_filename, IgvmParamBlock *params); diff --git a/igvmbld/ovmfmeta.c b/igvmbld/ovmfmeta.c index 7aa117c3a..3c4ca37e6 100644 --- a/igvmbld/ovmfmeta.c +++ b/igvmbld/ovmfmeta.c @@ -5,16 +5,10 @@ // Author: Joerg Roedel // Author: Roy Hopkins -#include "ovmfmeta.h" -#include "igvm_defs.h" -#include +#include "igvmbld.h" #include -#include -#include #include #include -#include -#include #define UUID_FMT "%02hhx%02hhx%02hhx%02hhx-" \ "%02hhx%02hhx-%02hhx%02hhx-" \ @@ -133,7 +127,7 @@ static void parse_sev_meta_data(void *data, uint8_t *buffer, size_t size, IgvmPa exit(1); } params->firmware.prevalidated[entry].base = meta->descs[i].base; - params->firmware.prevalidated[entry].len = meta->descs[i].len; + params->firmware.prevalidated[entry].size = meta->descs[i].len; ++params->firmware.prevalidated_count; break; } diff --git a/igvmbld/ovmfmeta.h b/igvmbld/ovmfmeta.h deleted file mode 100644 index c0729b176..000000000 --- a/igvmbld/ovmfmeta.h +++ /dev/null @@ -1,12 +0,0 @@ -// SPDX-License-Identifier: MIT OR Apache-2.0 -// -// Copyright (c) 2024 SUSE LLC -// -// Author: Roy Hopkins - -#pragma once - -#include -#include "igvm_defs.h" - -int parse_ovmf_metadata(const char *ovmf_filename, IgvmParamBlock *params); \ No newline at end of file From 1cc4b8eb3430e9b781a049360e4269cfd975d81c Mon Sep 17 00:00:00 2001 From: Jon Lange Date: Sat, 16 Dec 2023 17:44:20 -0800 Subject: [PATCH 4/7] Support firmware loaded at the base of memory Signed-off-by: Jon Lange --- bootlib/src/igvm_params.rs | 6 +++- igvmbld/igvmbld.h | 3 +- src/config.rs | 62 ++++++++++++++++++++++++++++++++++++-- src/igvm_params.rs | 31 +++++++++++++------ src/svsm.rs | 35 ++------------------- src/svsm_paging.rs | 9 ++++-- 6 files changed, 96 insertions(+), 50 deletions(-) diff --git a/bootlib/src/igvm_params.rs b/bootlib/src/igvm_params.rs index bcc9b7e9a..ec19bd991 100644 --- a/bootlib/src/igvm_params.rs +++ b/bootlib/src/igvm_params.rs @@ -48,7 +48,11 @@ pub struct IgvmParamBlockFwInfo { /// no firmware is launched after initialization is complete. pub size: u32, - _reserved: u32, + /// Indicates that the initial location of firmware is at the base of + /// memory and will not be loaded into the ROM range. + pub in_low_memory: u8, + + _reserved: [u8; 3], /// The guest physical address at which the firmware expects to find the /// secrets page. diff --git a/igvmbld/igvmbld.h b/igvmbld/igvmbld.h index bd427ea30..868070120 100644 --- a/igvmbld/igvmbld.h +++ b/igvmbld/igvmbld.h @@ -24,7 +24,8 @@ typedef struct { typedef struct { uint32_t start; uint32_t size; - uint32_t _reserved; + uint8_t in_low_memory; + uint8_t _reserved[3]; uint32_t secrets_page; uint32_t caa_page; uint32_t cpuid_page; diff --git a/src/config.rs b/src/config.rs index ad3ce8640..11e25a1e8 100644 --- a/src/config.rs +++ b/src/config.rs @@ -19,6 +19,44 @@ use crate::serial::SERIAL_PORT; use crate::utils::MemoryRegion; use alloc::vec::Vec; +fn check_ovmf_regions( + flash_regions: &[MemoryRegion], + kernel_region: &MemoryRegion, +) { + let flash_range = { + let one_gib = 1024 * 1024 * 1024usize; + let start = PhysAddr::from(3 * one_gib); + MemoryRegion::new(start, one_gib) + }; + + // Sanity-check flash regions. + for region in flash_regions.iter() { + // Make sure that the regions are between 3GiB and 4GiB. + if !region.overlap(&flash_range) { + panic!("flash region in unexpected region"); + } + + // Make sure that no regions overlap with the kernel. + if region.overlap(kernel_region) { + panic!("flash region overlaps with kernel"); + } + } + + // Make sure that regions don't overlap. + for (i, outer) in flash_regions.iter().enumerate() { + for inner in flash_regions[..i].iter() { + if outer.overlap(inner) { + panic!("flash regions overlap"); + } + } + // Make sure that one regions ends at 4GiB. + let one_region_ends_at_4gib = flash_regions + .iter() + .any(|region| region.end() == flash_range.end()); + assert!(one_region_ends_at_4gib); + } +} + #[derive(Debug)] pub enum SvsmConfig<'a> { FirmwareConfig(FwCfg<'a>), @@ -93,12 +131,30 @@ impl<'a> SvsmConfig<'a> { } } - pub fn get_fw_regions(&self) -> Result>, SvsmError> { + pub fn get_fw_regions( + &self, + kernel_region: &MemoryRegion, + ) -> Vec> { match self { SvsmConfig::FirmwareConfig(fw_cfg) => { - Ok(fw_cfg.iter_flash_regions().collect::>()) + let flash_regions = fw_cfg.iter_flash_regions().collect::>(); + check_ovmf_regions(&flash_regions, kernel_region); + flash_regions } - SvsmConfig::IgvmConfig(igvm_params) => igvm_params.get_fw_regions(), + SvsmConfig::IgvmConfig(igvm_params) => { + let flash_regions = igvm_params.get_fw_regions(); + if !igvm_params.fw_in_low_memory() { + check_ovmf_regions(&flash_regions, kernel_region); + } + flash_regions + } + } + } + + pub fn fw_in_low_memory(&self) -> bool { + match self { + SvsmConfig::FirmwareConfig(_) => false, + SvsmConfig::IgvmConfig(igvm_params) => igvm_params.fw_in_low_memory(), } } diff --git a/src/igvm_params.rs b/src/igvm_params.rs index 0bb5036a2..a4c58d9ed 100644 --- a/src/igvm_params.rs +++ b/src/igvm_params.rs @@ -13,7 +13,6 @@ use crate::error::SvsmError::Firmware; use crate::fw_meta::SevFWMetaData; use crate::mm::PAGE_SIZE; use crate::utils::MemoryRegion; -use alloc::vec; use alloc::vec::Vec; use bootlib::igvm_params::{IgvmParamBlock, IgvmParamPage}; @@ -22,6 +21,8 @@ use igvm_defs::{IgvmEnvironmentInfo, MemoryMapEntryType, IGVM_VHS_MEMORY_MAP_ENT const IGVM_MEMORY_ENTRIES_PER_PAGE: usize = PAGE_SIZE / size_of::(); +const STAGE2_END_ADDR: usize = 0xA0000; + #[derive(Clone, Debug)] #[repr(C, align(64))] pub struct IgvmMemoryMap { @@ -199,14 +200,26 @@ impl IgvmParams<'_> { } } - pub fn get_fw_regions(&self) -> Result>, SvsmError> { - if !self.should_launch_fw() { - Err(Firmware) - } else { - Ok(vec![MemoryRegion::new( - PhysAddr::new(self.igvm_param_block.firmware.start as usize), - self.igvm_param_block.firmware.size as usize, - )]) + pub fn get_fw_regions(&self) -> Vec> { + assert!(self.should_launch_fw()); + + let mut regions = Vec::new(); + + if self.igvm_param_block.firmware.in_low_memory != 0 { + // Add the stage 2 region to the firmware region list so + // permissions can be granted to the guest VMPL for that range. + regions.push(MemoryRegion::new(PhysAddr::new(0), STAGE2_END_ADDR)); } + + regions.push(MemoryRegion::new( + PhysAddr::new(self.igvm_param_block.firmware.start as usize), + self.igvm_param_block.firmware.size as usize, + )); + + regions + } + + pub fn fw_in_low_memory(&self) -> bool { + self.igvm_param_block.firmware.in_low_memory != 0 } } diff --git a/src/svsm.rs b/src/svsm.rs index 8dc076fe8..6bf71d55a 100755 --- a/src/svsm.rs +++ b/src/svsm.rs @@ -50,7 +50,7 @@ use svsm::svsm_console::SVSMIOPort; use svsm::svsm_paging::{init_page_table, invalidate_early_boot_memory}; use svsm::task::{create_task, TASK_FLAG_SHARE_PT}; use svsm::types::{PageSize, GUEST_VMPL, PAGE_SIZE}; -use svsm::utils::{halt, immut_after_init::ImmutAfterInitCell, zero_mem_region, MemoryRegion}; +use svsm::utils::{halt, immut_after_init::ImmutAfterInitCell, zero_mem_region}; use svsm::mm::validate::{init_valid_bitmap_ptr, migrate_valid_bitmap}; @@ -221,39 +221,8 @@ fn launch_fw() -> Result<(), SvsmError> { } fn validate_fw(config: &SvsmConfig, launch_info: &KernelLaunchInfo) -> Result<(), SvsmError> { - let flash_regions = config.get_fw_regions()?; let kernel_region = new_kernel_region(launch_info); - let flash_range = { - let one_gib = 1024 * 1024 * 1024usize; - let start = PhysAddr::from(3 * one_gib); - MemoryRegion::new(start, one_gib) - }; - - // Sanity-check flash regions. - for region in flash_regions.iter() { - // Make sure that the regions are between 3GiB and 4GiB. - if !region.overlap(&flash_range) { - panic!("flash region in unexpected region"); - } - - // Make sure that no regions overlap with the kernel. - if region.overlap(&kernel_region) { - panic!("flash region overlaps with kernel"); - } - } - // Make sure that regions don't overlap. - for (i, outer) in flash_regions.iter().enumerate() { - for inner in flash_regions[..i].iter() { - if outer.overlap(inner) { - panic!("flash regions overlap"); - } - } - } - // Make sure that one regions ends at 4GiB. - let one_region_ends_at_4gib = flash_regions - .iter() - .any(|region| region.end() == flash_range.end()); - assert!(one_region_ends_at_4gib); + let flash_regions = config.get_fw_regions(&kernel_region); for (i, region) in flash_regions.into_iter().enumerate() { log::info!( diff --git a/src/svsm_paging.rs b/src/svsm_paging.rs index 4f9095faa..bf7083cca 100644 --- a/src/svsm_paging.rs +++ b/src/svsm_paging.rs @@ -133,9 +133,12 @@ pub fn invalidate_early_boot_memory( ) -> Result<(), SvsmError> { // Early boot memory must be invalidated after changing to the SVSM page // page table to avoid invalidating page tables currently in use. Always - // invalidate stage 2 memory, and invalidate the boot data if required. - let stage2_region = MemoryRegion::new(PhysAddr::null(), 640 * 1024); - invalidate_boot_memory_region(config, stage2_region)?; + // invalidate stage 2 memory, unless firmware is loaded into low memory. + // Also invalidate the boot data if required. + if !config.fw_in_low_memory() { + let stage2_region = MemoryRegion::new(PhysAddr::null(), 640 * 1024); + invalidate_boot_memory_region(config, stage2_region)?; + } if config.invalidate_boot_data() { let kernel_elf_size = From b76004580e07dcf846624f5cadd3d3110c8607fa Mon Sep 17 00:00:00 2001 From: Jon Lange Date: Sun, 17 Dec 2023 21:32:08 -0800 Subject: [PATCH 5/7] Permit customization of the guest VMSA based on IGVM parameters Signed-off-by: Jon Lange --- bootlib/src/igvm_params.rs | 39 ++++++++++++++++++++- igvmbld/igvmbld.h | 3 +- src/config.rs | 8 +++++ src/igvm_params.rs | 71 +++++++++++++++++++++++++++++++++++++- src/svsm.rs | 6 ++-- 5 files changed, 122 insertions(+), 5 deletions(-) diff --git a/bootlib/src/igvm_params.rs b/bootlib/src/igvm_params.rs index ec19bd991..c49f30c01 100644 --- a/bootlib/src/igvm_params.rs +++ b/bootlib/src/igvm_params.rs @@ -95,6 +95,10 @@ pub struct IgvmParamBlock { /// of the memory map (which is in IGVM format). pub memory_map_offset: u32, + /// The offset, in bytes, of the guest context, or zero if no guest + /// context is present. + pub guest_context_offset: u32, + /// The guest physical address of the CPUID page. pub cpuid_page: u32, @@ -104,7 +108,7 @@ pub struct IgvmParamBlock { /// The port number of the serial port to use for debugging. pub debug_serial_port: u16, - _reserved: u16, + _reserved: [u16; 3], /// Metadata containing information about the firmware image embedded in the /// IGVM file. @@ -120,3 +124,36 @@ pub struct IgvmParamBlock { /// The guest physical address of the base of the kernel memory region. pub kernel_base: u64, } + +/// The IGVM context page is a measured page that is used to specify the start +/// context for the guest VMPL. If present, it overrides the processor state +/// initialized at reset. +#[derive(Copy, Debug, Clone)] +#[repr(C, packed)] +pub struct IgvmGuestContext { + pub cr0: u64, + pub cr3: u64, + pub cr4: u64, + pub efer: u64, + pub gdt_base: u64, + pub gdt_limit: u32, + pub code_selector: u16, + pub data_selector: u16, + pub rip: u64, + pub rax: u64, + pub rcx: u64, + pub rdx: u64, + pub rbx: u64, + pub rsp: u64, + pub rbp: u64, + pub rsi: u64, + pub rdi: u64, + pub r8: u64, + pub r9: u64, + pub r10: u64, + pub r11: u64, + pub r12: u64, + pub r13: u64, + pub r14: u64, + pub r15: u64, +} diff --git a/igvmbld/igvmbld.h b/igvmbld/igvmbld.h index 868070120..4b58e206f 100644 --- a/igvmbld/igvmbld.h +++ b/igvmbld/igvmbld.h @@ -38,10 +38,11 @@ typedef struct { uint32_t param_area_size; uint32_t param_page_offset; uint32_t memory_map_offset; + uint32_t guest_context_offset; uint32_t cpuid_page; uint32_t secrets_page; uint16_t debug_serial_port; - uint16_t _reserved; + uint16_t _reserved[3]; IgvmParamBlockFwInfo firmware; uint32_t kernel_reserved_size; uint32_t kernel_size; diff --git a/src/config.rs b/src/config.rs index 11e25a1e8..aa627178d 100644 --- a/src/config.rs +++ b/src/config.rs @@ -18,6 +18,7 @@ use crate::mm::{PerCPUPageMappingGuard, PAGE_SIZE, SIZE_1G}; use crate::serial::SERIAL_PORT; use crate::utils::MemoryRegion; use alloc::vec::Vec; +use cpuarch::vmsa::VMSA; fn check_ovmf_regions( flash_regions: &[MemoryRegion], @@ -164,4 +165,11 @@ impl<'a> SvsmConfig<'a> { SvsmConfig::IgvmConfig(_) => true, } } + + pub fn initialize_guest_vmsa(&self, vmsa: &mut VMSA) { + match self { + SvsmConfig::FirmwareConfig(_) => (), + SvsmConfig::IgvmConfig(igvm_params) => igvm_params.initialize_guest_vmsa(vmsa), + } + } } diff --git a/src/igvm_params.rs b/src/igvm_params.rs index a4c58d9ed..04dc5f0dc 100644 --- a/src/igvm_params.rs +++ b/src/igvm_params.rs @@ -8,14 +8,16 @@ extern crate alloc; use crate::acpi::tables::ACPICPUInfo; use crate::address::{PhysAddr, VirtAddr}; +use crate::cpu::efer::EFERFlags; use crate::error::SvsmError; use crate::error::SvsmError::Firmware; use crate::fw_meta::SevFWMetaData; use crate::mm::PAGE_SIZE; use crate::utils::MemoryRegion; use alloc::vec::Vec; +use cpuarch::vmsa::VMSA; -use bootlib::igvm_params::{IgvmParamBlock, IgvmParamPage}; +use bootlib::igvm_params::{IgvmGuestContext, IgvmParamBlock, IgvmParamPage}; use core::mem::size_of; use igvm_defs::{IgvmEnvironmentInfo, MemoryMapEntryType, IGVM_VHS_MEMORY_MAP_ENTRY}; @@ -34,6 +36,7 @@ pub struct IgvmParams<'a> { igvm_param_block: &'a IgvmParamBlock, igvm_param_page: &'a IgvmParamPage, igvm_memory_map: &'a IgvmMemoryMap, + igvm_guest_context_address: VirtAddr, } impl IgvmParams<'_> { @@ -43,11 +46,17 @@ impl IgvmParams<'_> { let param_page = unsafe { &*param_page_address.as_ptr::() }; let memory_map_address = addr + param_block.memory_map_offset.try_into().unwrap(); let memory_map = unsafe { &*memory_map_address.as_ptr::() }; + let guest_context_address = if param_block.guest_context_offset != 0 { + addr + param_block.guest_context_offset.try_into().unwrap() + } else { + VirtAddr::null() + }; Self { igvm_param_block: param_block, igvm_param_page: param_page, igvm_memory_map: memory_map, + igvm_guest_context_address: guest_context_address, } } @@ -222,4 +231,64 @@ impl IgvmParams<'_> { pub fn fw_in_low_memory(&self) -> bool { self.igvm_param_block.firmware.in_low_memory != 0 } + + pub fn initialize_guest_vmsa(&self, vmsa: &mut VMSA) { + if self.igvm_param_block.guest_context_offset != 0 { + let guest_context = + unsafe { &*self.igvm_guest_context_address.as_ptr::() }; + + // Copy the specified registers into the VMSA. + vmsa.cr0 = guest_context.cr0; + vmsa.cr3 = guest_context.cr3; + vmsa.cr4 = guest_context.cr4; + vmsa.efer = guest_context.efer; + vmsa.rip = guest_context.rip; + vmsa.rax = guest_context.rax; + vmsa.rcx = guest_context.rcx; + vmsa.rdx = guest_context.rdx; + vmsa.rbx = guest_context.rbx; + vmsa.rsp = guest_context.rsp; + vmsa.rbp = guest_context.rbp; + vmsa.rsi = guest_context.rsi; + vmsa.rdi = guest_context.rdi; + vmsa.r8 = guest_context.r8; + vmsa.r9 = guest_context.r9; + vmsa.r10 = guest_context.r10; + vmsa.r11 = guest_context.r11; + vmsa.r12 = guest_context.r12; + vmsa.r13 = guest_context.r13; + vmsa.r14 = guest_context.r14; + vmsa.r15 = guest_context.r15; + vmsa.gdt.base = guest_context.gdt_base; + vmsa.gdt.limit = guest_context.gdt_limit; + + // If a non-zero code selector is specified, then set the code + // segment attributes based on EFER.LMA. + if guest_context.code_selector != 0 { + vmsa.cs.selector = guest_context.code_selector; + let efer_lma = EFERFlags::LMA; + if (vmsa.efer & efer_lma.bits()) != 0 { + vmsa.cs.flags = 0xA9B; + } else { + vmsa.cs.flags = 0xC9B; + vmsa.cs.limit = 0xFFFFFFFF; + } + } + + let efer_svme = EFERFlags::SVME; + vmsa.efer &= !efer_svme.bits(); + + // If a non-zero data selector is specified, then modify the data + // segment attributes to be compatible with protected mode. + if guest_context.data_selector != 0 { + vmsa.ds.selector = guest_context.data_selector; + vmsa.ds.flags = 0xA93; + vmsa.ds.limit = 0xFFFFFFFF; + vmsa.ss = vmsa.ds; + vmsa.es = vmsa.ds; + vmsa.fs = vmsa.ds; + vmsa.gs = vmsa.ds; + } + } + } } diff --git a/src/svsm.rs b/src/svsm.rs index 6bf71d55a..79be4f60f 100755 --- a/src/svsm.rs +++ b/src/svsm.rs @@ -204,10 +204,12 @@ fn prepare_fw_launch(fw_meta: &SevFWMetaData) -> Result<(), SvsmError> { Ok(()) } -fn launch_fw() -> Result<(), SvsmError> { +fn launch_fw(config: &SvsmConfig) -> Result<(), SvsmError> { let vmsa_pa = this_cpu_mut().guest_vmsa_ref().vmsa_phys().unwrap(); let vmsa = this_cpu_mut().guest_vmsa(); + config.initialize_guest_vmsa(vmsa); + log::info!("VMSA PA: {:#x}", vmsa_pa); let sev_features = vmsa.sev_features; @@ -445,7 +447,7 @@ pub extern "C" fn svsm_main() { virt_log_usage(); if config.should_launch_fw() { - if let Err(e) = launch_fw() { + if let Err(e) = launch_fw(&config) { panic!("Failed to launch FW: {:#?}", e); } } From 5832f348624bad0750dfd00fbd4d22364f6e8904 Mon Sep 17 00:00:00 2001 From: Jon Lange Date: Mon, 18 Dec 2023 02:38:28 -0800 Subject: [PATCH 6/7] Configure guest vTOM when configured by IGVM parameters Signed-off-by: Jon Lange --- bootlib/src/igvm_params.rs | 3 +++ igvmbld/igvmbld.c | 14 +++++++++----- igvmbld/igvmbld.h | 1 + src/igvm_params.rs | 6 ++++++ 4 files changed, 19 insertions(+), 5 deletions(-) diff --git a/bootlib/src/igvm_params.rs b/bootlib/src/igvm_params.rs index c49f30c01..3c10f834d 100644 --- a/bootlib/src/igvm_params.rs +++ b/bootlib/src/igvm_params.rs @@ -123,6 +123,9 @@ pub struct IgvmParamBlock { /// The guest physical address of the base of the kernel memory region. pub kernel_base: u64, + + /// The value of vTOM used by the guest, or zero if not used. + pub vtom: u64, } /// The IGVM context page is a measured page that is used to specify the start diff --git a/igvmbld/igvmbld.c b/igvmbld/igvmbld.c index 76d522f8d..40bffde21 100644 --- a/igvmbld/igvmbld.c +++ b/igvmbld/igvmbld.c @@ -351,7 +351,7 @@ void generate_initial_vmsa(SEV_VMSA *vmsa) vmsa->sev_features = SevFeature_Snp | SevFeature_RestrictInj; } -void setup_igvm_platform_header(void) +IGVM_VHS_SUPPORTED_PLATFORM *setup_igvm_platform_header(void) { IGVM_VHS *header; IGVM_VHS_SUPPORTED_PLATFORM *platform; @@ -372,9 +372,7 @@ void setup_igvm_platform_header(void) platform->PlatformType = IgvmPlatformType_SevSnp; platform->PlatformVersion = 1; - // Set the GPA boundary at bit 46, below the lowest possible C-bit - // position. - platform->SharedGpaBoundary = 0x0000400000000000; + return platform; } void generate_required_memory_header(IgvmParamBlock *igvm_parameter_block) @@ -908,6 +906,7 @@ int main(int argc, const char *argv[]) IgvmParamBlock *igvm_parameter_block; DATA_OBJ *initial_stack; DATA_OBJ *kernel_data; + IGVM_VHS_SUPPORTED_PLATFORM *platform; DATA_OBJ *secrets_page; DATA_OBJ *stage2_data; Stage2Stack *stage2_stack; @@ -926,7 +925,7 @@ int main(int argc, const char *argv[]) var_hdr_offset = sizeof(IGVM_FIXED_HEADER); // Set up the platform compatibility header. - setup_igvm_platform_header(); + platform = setup_igvm_platform_header(); // Construct a set of ranges for the memory map: // 00000-0EFFF: zero-filled (must be pre-validated) @@ -1049,6 +1048,11 @@ int main(int argc, const char *argv[]) // as reserved. vmsa_address = igvm_parameter_block->kernel_base; igvm_parameter_block->kernel_reserved_size = 0x1000; + + // Set the shared GPA boundary at bit 46, below the lowest possible + // C-bit position. + igvm_parameter_block->vtom = 0x0000400000000000; + platform->SharedGpaBoundary = igvm_parameter_block->vtom; } else { diff --git a/igvmbld/igvmbld.h b/igvmbld/igvmbld.h index 4b58e206f..426425be8 100644 --- a/igvmbld/igvmbld.h +++ b/igvmbld/igvmbld.h @@ -47,6 +47,7 @@ typedef struct { uint32_t kernel_reserved_size; uint32_t kernel_size; uint64_t kernel_base; + uint64_t vtom; } IgvmParamBlock; int parse_ovmf_metadata(const char *ovmf_filename, IgvmParamBlock *params); diff --git a/src/igvm_params.rs b/src/igvm_params.rs index 04dc5f0dc..08cd52527 100644 --- a/src/igvm_params.rs +++ b/src/igvm_params.rs @@ -289,6 +289,12 @@ impl IgvmParams<'_> { vmsa.fs = vmsa.ds; vmsa.gs = vmsa.ds; } + + // Configure vTOM if reqested. + if self.igvm_param_block.vtom != 0 { + vmsa.vtom = self.igvm_param_block.vtom; + vmsa.sev_features |= 2; // VTOM feature + } } } } From b3580d86f9e319b8953ff75f90ee12e16626efd5 Mon Sep 17 00:00:00 2001 From: Jon Lange Date: Fri, 5 Jan 2024 14:07:37 -0800 Subject: [PATCH 7/7] Support loading IGVM-based Hyper-V firmware into the SVSM IGVM file Signed-off-by: Jon Lange --- Makefile | 4 +- igvmbld/igvm_defs.h | 4 + igvmbld/igvmbld.c | 105 ++++++---- igvmbld/igvmbld.h | 41 ++++ igvmbld/igvmcopy.c | 486 ++++++++++++++++++++++++++++++++++++++++++++ igvmbld/sev-snp.h | 1 + 6 files changed, 599 insertions(+), 42 deletions(-) create mode 100644 igvmbld/igvmcopy.c diff --git a/Makefile b/Makefile index 7ad76b771..066df3050 100644 --- a/Makefile +++ b/Makefile @@ -36,9 +36,9 @@ all: stage1/kernel.elf svsm.bin igvm igvm: $(IGVM_FILES) -$(IGVMBLD): igvmbld/igvmbld.c igvmbld/ovmfmeta.c igvmbld/igvmbld.h igvmbld/igvm_defs.h igvmbld/sev-snp.h +$(IGVMBLD): igvmbld/igvmbld.c igvmbld/ovmfmeta.c igvmbld/igvmcopy.c igvmbld/igvmbld.h igvmbld/igvm_defs.h igvmbld/sev-snp.h mkdir -v -p bin - $(CC) -o $@ -O -Iigvmbld igvmbld/igvmbld.c igvmbld/ovmfmeta.c + $(CC) -o $@ -O -Iigvmbld igvmbld/igvmbld.c igvmbld/ovmfmeta.c igvmbld/igvmcopy.c bin/coconut-qemu.igvm: $(IGVMBLD) stage1/kernel.elf stage1/stage2.bin $(IGVMBLD) --output $@ --stage2 stage1/stage2.bin --kernel stage1/kernel.elf --qemu ${BUILD_FW} diff --git a/igvmbld/igvm_defs.h b/igvmbld/igvm_defs.h index 8836003e3..3276fb76c 100644 --- a/igvmbld/igvm_defs.h +++ b/igvmbld/igvm_defs.h @@ -7,13 +7,17 @@ typedef enum { IGVM_VHT_SUPPORTED_PLATFORM = 0x1, + IGVM_VHT_SNP_POLICY = 0x101, IGVM_VHT_PARAMETER_AREA = 0x301, IGVM_VHT_PAGE_DATA = 0x302, IGVM_VHT_PARAMETER_INSERT = 0x303, IGVM_VHT_VP_CONTEXT = 0x304, IGVM_VHT_REQUIRED_MEMORY = 0x305, IGVM_VHT_VP_COUNT_PARMETER = 0x307, + IGVM_VHT_SRAT = 0x308, + IGVM_VHT_MADT = 0x309, IGVM_VHT_MEMORY_MAP = 0x30C, + IGVM_VHT_COMMAND_LINE = 0x30E, IGVM_VHT_ENVIRONMENT_INFO_PARAMETER = 0x313, } IGVM_VHT; diff --git a/igvmbld/igvmbld.c b/igvmbld/igvmbld.c index 40bffde21..881d918d6 100644 --- a/igvmbld/igvmbld.c +++ b/igvmbld/igvmbld.c @@ -25,34 +25,12 @@ typedef struct { uint32_t reserved; } Stage2Stack; -typedef enum { - parameter_page_general = 0, - parameter_page_memory_map, -} ParameterPageIndex; - -typedef struct _data_obj { - struct _data_obj *next; - void *data; - uint64_t address; - uint32_t size; - uint16_t page_type; - uint16_t data_type; - IGVM_VHS_PAGE_DATA *page_data_headers; -} DATA_OBJ; - typedef struct _param_page { struct _param_page *next; uint32_t address; ParameterPageIndex index; } PARAM_PAGE; -typedef struct _igvm_vhs { - struct _igvm_vhs *next; - IGVM_VHT header_type; - uint32_t header_size; - void *data; -} IGVM_VHS; - const char *stage2_filename; const char *kernel_filename; const char *filesystem_filename; @@ -144,6 +122,7 @@ DATA_OBJ *allocate_data_object(uint64_t address, uint32_t size, uint32_t data_si data_object->data = NULL; data_object->data_type = IGVM_VHT_PAGE_DATA; data_object->page_type = IgvmPageType_Normal; + data_object->page_data_flags = 0; return data_object; } @@ -508,6 +487,7 @@ void generate_data_headers(void) { page_data[i].GPA = address; page_data[i].CompatibilityMask = 1; + page_data[i].Flags = data_obj->page_data_flags; address += PAGE_SIZE; } @@ -708,14 +688,7 @@ static int check_firmware_options() // 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; } @@ -902,6 +875,7 @@ int main(int argc, const char *argv[]) DATA_OBJ *cpuid_page; int err; DATA_OBJ *filesystem_data; + FirmwareIgvmInfo fw_info; DATA_OBJ *igvm_parameter_object; IgvmParamBlock *igvm_parameter_block; DATA_OBJ *initial_stack; @@ -909,6 +883,7 @@ int main(int argc, const char *argv[]) IGVM_VHS_SUPPORTED_PLATFORM *platform; DATA_OBJ *secrets_page; DATA_OBJ *stage2_data; + uint32_t stage2_top; Stage2Stack *stage2_stack; uint64_t vmsa_address; DATA_OBJ *vmsa_data; @@ -948,25 +923,51 @@ int main(int argc, const char *argv[]) return 1; } - address = (stage2_data->address + stage2_data->size + PAGE_SIZE - 1) & - ~(PAGE_SIZE - 1); - if (address > 0x9F000) + stage2_top = (stage2_data->address + stage2_data->size + PAGE_SIZE - 1) & + ~(PAGE_SIZE - 1); + if (stage2_top > 0x9F000) { fprintf(stderr, "stage 2 image is too large\n"); return 1; } - else if (address < 0x9F000) + else if (stage2_top < 0x9F000) { - construct_empty_data_object(address, 0x9F000 - address); + construct_empty_data_object(stage2_top, 0x9F000 - stage2_top); } cpuid_page = construct_mem_data_object(0x9F000, 0x1000); cpuid_page->page_type = IgvmPageType_Cpuid; fill_cpuid_page((SNP_CPUID_PAGE *)cpuid_page->data); - // Load the kernel data at a base address of 1 MB. + // Plan to load the kernel image at a base address of 1 MB unless it must + // be relocated due to firmware. address = 1 << 20; + memset(&fw_info, 0, sizeof(FirmwareIgvmInfo)); + + // If a hyper-v firmware file was specified, then load it. + if (is_hyperv) + { + if (fw_filename != NULL) + { + if (is_hyperv) + { + err = read_hyperv_igvm_file(fw_filename, &fw_info); + if (err != 0) + { + return err; + } + + address = fw_info.fw_info.start + fw_info.fw_info.size; + } + else + { + fprintf(stderr, "--firmware only supported for hyperv targets\n"); + return 1; + } + } + } + // Construct a data object for the kernel. kernel_data = construct_file_data_object(kernel_filename, address); if (kernel_data == NULL) @@ -1032,6 +1033,14 @@ int main(int argc, const char *argv[]) construct_parameter_page(address, parameter_page_memory_map); address += PAGE_SIZE; + // If the firmware has supplied a guest context page, then assign it an address now. + if (fw_info.guest_context != NULL) + { + fw_info.guest_context->address = address; + igvm_parameter_block->guest_context_offset = address - (uint32_t)igvm_parameter_object->address; + address += fw_info.guest_context->size; + } + // Populate the rest of the parameter block. igvm_parameter_block->param_area_size = address - (uint32_t)igvm_parameter_object->address; igvm_parameter_block->cpuid_page = (uint32_t)cpuid_page->address; @@ -1049,9 +1058,25 @@ int main(int argc, const char *argv[]) vmsa_address = igvm_parameter_block->kernel_base; igvm_parameter_block->kernel_reserved_size = 0x1000; - // Set the shared GPA boundary at bit 46, below the lowest possible - // C-bit position. - igvm_parameter_block->vtom = 0x0000400000000000; + // Add additional information if firmware is being launched. + if (fw_info.fw_info.size != 0) + { + // Mark the range between the top of stage 2 and the base of + // memory as a range that needs to be validated. + fw_info.fw_info.prevalidated_count = 1; + fw_info.fw_info.prevalidated[0].base = stage2_top; + fw_info.fw_info.prevalidated[0].size = fw_info.fw_info.start - stage2_top; + + igvm_parameter_block->firmware = fw_info.fw_info; + igvm_parameter_block->vtom = fw_info.vtom; + } + else + { + // Set the shared GPA boundary at bit 46, below the lowest possible + // C-bit position. + igvm_parameter_block->vtom = 0x0000400000000000; + } + platform->SharedGpaBoundary = igvm_parameter_block->vtom; } else @@ -1068,7 +1093,7 @@ int main(int argc, const char *argv[]) // If a firmware file has been specified then add it and set the relevant // parameter block entries. - if (fw_filename) + if (fw_filename && is_qemu) { igvm_parameter_block->firmware.size = fw_size; igvm_parameter_block->firmware.start = fw_base; diff --git a/igvmbld/igvmbld.h b/igvmbld/igvmbld.h index 426425be8..f405eb39b 100644 --- a/igvmbld/igvmbld.h +++ b/igvmbld/igvmbld.h @@ -50,4 +50,45 @@ typedef struct { uint64_t vtom; } IgvmParamBlock; +typedef enum { + parameter_page_general = 0, + parameter_page_memory_map, + num_parameter_pages, +} ParameterPageIndex; + +typedef struct _igvm_vhs { + struct _igvm_vhs *next; + IGVM_VHT header_type; + uint32_t header_size; + void *data; +} IGVM_VHS; + +typedef struct _data_obj { + struct _data_obj *next; + void *data; + uint64_t address; + uint32_t size; + uint16_t page_type; + uint16_t data_type; + uint32_t page_data_flags; + IGVM_VHS_PAGE_DATA *page_data_headers; +} DATA_OBJ; + +typedef struct { + IgvmParamBlockFwInfo fw_info; + uint64_t vtom; + DATA_OBJ *guest_context; +} FirmwareIgvmInfo; + +IGVM_VHS *allocate_var_headers( + IGVM_VHT header_type, + uint32_t struct_size, + uint32_t header_size, + int count); + +DATA_OBJ *construct_empty_data_object(uint64_t address, uint32_t size); +DATA_OBJ *construct_mem_data_object(uint64_t address, uint32_t size); + +int read_hyperv_igvm_file(const char *file_name, FirmwareIgvmInfo *fw_info); + int parse_ovmf_metadata(const char *ovmf_filename, IgvmParamBlock *params); diff --git a/igvmbld/igvmcopy.c b/igvmbld/igvmcopy.c new file mode 100644 index 000000000..d863c083c --- /dev/null +++ b/igvmbld/igvmcopy.c @@ -0,0 +1,486 @@ +// SPDX-License-Identifier: MIT OR Apache-2.0 +// +// Copyright (c) Microsoft Corporation +// +// Author: Jon Lange (jlange@microsoft.com) + +#include "igvmbld.h" + +typedef struct { + uint64_t cr0; + uint64_t cr3; + uint64_t cr4; + uint64_t efer; + uint64_t gdt_base; + uint32_t gdt_limit; + uint16_t code_selector; + uint16_t data_selector; + uint64_t rip; + uint64_t gp_registers[16]; +} IgvmGuestContext; + +int get_next_var_hdr( + uint8_t *var_hdrs, + uint32_t *var_hdr_offset, + uint32_t var_hdr_size, + IGVM_VHS *found_header) +{ + uint32_t header_size; + IGVM_VAR_HEADER *var_hdr; + + // Make sure the variable header data is large enough to accommodate this + // variable header. + var_hdr = (IGVM_VAR_HEADER *)(var_hdrs + *var_hdr_offset); + if (*var_hdr_offset + sizeof(IGVM_VAR_HEADER) > var_hdr_size) + { + return 0; + } + + header_size = sizeof(IGVM_VAR_HEADER) + var_hdr->header_size; + header_size = (header_size + 7) & ~7; + if (*var_hdr_offset + header_size > var_hdr_size) + { + return 0; + } + + found_header->header_type = var_hdr->header_type; + found_header->header_size = var_hdr->header_size; + found_header->data = var_hdr + 1; + + *var_hdr_offset += header_size; + + return 1; +} + +void fill_guest_context(IgvmGuestContext *guest_context, SEV_VMSA *vmsa) +{ + int i; + + guest_context->cr0 = vmsa->cr0; + guest_context->cr3 = vmsa->cr3; + guest_context->cr4 = vmsa->cr4; + guest_context->efer = vmsa->efer; + guest_context->gdt_base = vmsa->segments[SevSegment_Gdt].base; + guest_context->gdt_limit = vmsa->segments[SevSegment_Gdt].limit; + guest_context->code_selector = vmsa->segments[SevSegment_Cs].selector; + guest_context->data_selector = vmsa->segments[SevSegment_Ds].selector; + guest_context->rip = vmsa->rip; + for (i = 0; i < 16; ++i) + { + guest_context->gp_registers[i] = vmsa->gp_registers[i]; + } +} + +int read_hyperv_igvm_file(const char *file_name, FirmwareIgvmInfo *fw_info) +{ + uint32_t compatibility_mask; + DATA_OBJ *data_obj; + FILE *file; + IGVM_FIXED_HEADER fixed_header; + IgvmGuestContext *guest_context; + IGVM_VHS *header; + uint64_t highest_gpa; + uint64_t lowest_gpa; + IGVM_VHS_PAGE_DATA page_data; + IGVM_VHS_PARAMETER *parameter; + IGVM_VHS_PARAMETER_AREA *parameter_area; + IGVM_VHS_PARAMETER_INSERT parameter_insert; + IGVM_VHS_SUPPORTED_PLATFORM platform_header; + uint64_t start_rip; + IGVM_VHS var_hdr; + uint32_t var_hdr_offset; + void *var_hdrs; + SEV_VMSA *vmsa; + IGVM_VHS_VP_CONTEXT vp_context; + + file = fopen(file_name, "r"); + if (file == NULL) + { + fprintf(stderr, "could not open %s\n", file_name); + return 1; + } + + var_hdrs = NULL; + + // Read the fixed header to determine where the variable headers are. + if (fread(&fixed_header, sizeof(IGVM_FIXED_HEADER), 1, file) != 1) + { +ReadError: + fprintf(stderr, "failed to read %s\n", file_name); + fclose(file); + if (var_hdrs != NULL) + { + free(var_hdrs); + } + return 1; + } + + if ((fixed_header.Magic != IGVM_MAGIC) || + (fixed_header.FormatVersion > 2) || + (fixed_header.VariableHeaderOffset < sizeof(IGVM_FIXED_HEADER))) + { + goto ReadError; + } + + // Support a maximum of 1 MB of variable headers in this implementation. + if (fixed_header.VariableHeaderSize >= (1 << 20)) + { + goto ReadError; + } + + // Make a local copy of the variable header data. + var_hdrs = malloc(fixed_header.VariableHeaderSize); + if (0 != fseek(file, fixed_header.VariableHeaderOffset, SEEK_SET)) + { + goto ReadError; + } + if (fread(var_hdrs, 1, fixed_header.VariableHeaderSize, file) != fixed_header.VariableHeaderSize) + { + goto ReadError; + } + + // Scan the variable headers looking for an SNP platform header. + var_hdr_offset = 0; + compatibility_mask = 0; + while (var_hdr_offset < fixed_header.VariableHeaderSize) + { + if (!get_next_var_hdr( + var_hdrs, + &var_hdr_offset, + fixed_header.VariableHeaderSize, + &var_hdr)) + { +IncompatibleFile: + fprintf(stderr, "%s is not a compatible IGVM file\n", file_name); + fclose(file); + free(var_hdrs); + return 1; + } + + if (var_hdr.header_type == IGVM_VHT_SUPPORTED_PLATFORM) + { + if (var_hdr.header_size < sizeof(IGVM_VHS_SUPPORTED_PLATFORM)) + { + goto IncompatibleFile; + } + + memcpy(&platform_header, var_hdr.data, sizeof(IGVM_VHS_SUPPORTED_PLATFORM)); + if ((platform_header.PlatformType == IgvmPlatformType_SevSnp) && + (platform_header.PlatformVersion == 1)) + { + if (platform_header.HighestVtl != 0) + { + goto IncompatibleFile; + } + + compatibility_mask = platform_header.CompatibilityMask; + break; + } + } + } + + if ((compatibility_mask == 0) || + ((compatibility_mask & (compatibility_mask - 1)) != 0)) + { + goto IncompatibleFile; + } + + // Now process all variable headers again to process the data. + highest_gpa = 0; + lowest_gpa = highest_gpa - 1; + guest_context = NULL; + + var_hdr_offset = 0; + while (var_hdr_offset < fixed_header.VariableHeaderSize) + { + if (!get_next_var_hdr( + var_hdrs, + &var_hdr_offset, + fixed_header.VariableHeaderSize, + &var_hdr)) + { + goto IncompatibleFile; + } + + switch (var_hdr.header_type) + { + case IGVM_VHT_SUPPORTED_PLATFORM: + // The platform header was processed earlier. + break; + + case IGVM_VHT_PARAMETER_AREA: + if (var_hdr.header_size != sizeof(IGVM_VHS_PARAMETER_AREA)) + { + goto IncompatibleFile; + } + + // Generate a new parameter area, offset by the number of + // parameter pages used by the SVSM. + header = allocate_var_headers( + IGVM_VHT_PARAMETER_AREA, + sizeof(IGVM_VHS_PARAMETER_AREA), + sizeof(IGVM_VHS_PARAMETER_AREA), + 1); + parameter_area = header->data; + memcpy(parameter_area, var_hdr.data, sizeof(IGVM_VHS_PARAMETER_AREA)); + + if (parameter_area->FileOffset != 0) + { + goto IncompatibleFile; + } + + parameter_area->ParameterPageIndex += num_parameter_pages; + break; + + case IGVM_VHT_PARAMETER_INSERT: + if (var_hdr.header_size != sizeof(IGVM_VHS_PARAMETER_INSERT)) + { + goto IncompatibleFile; + } + + // Generate an insertion directive with the correctly modified + // parameter area index, but only if the directive matches the + // compatibility mask. + memcpy(¶meter_insert, var_hdr.data, sizeof(IGVM_VHS_PARAMETER_INSERT)); + if (parameter_insert.CompatibilityMask & compatibility_mask) + { + parameter_insert.ParameterPageIndex += num_parameter_pages; + if (parameter_insert.GPA >= highest_gpa) + { + highest_gpa = parameter_insert.GPA + PAGE_SIZE; + } + if (parameter_insert.GPA < lowest_gpa) + { + lowest_gpa = parameter_insert.GPA; + } + header = allocate_var_headers( + IGVM_VHT_PARAMETER_INSERT, + sizeof(IGVM_VHS_PARAMETER_INSERT), + sizeof(IGVM_VHS_PARAMETER_INSERT), + 1); + memcpy(header->data, ¶meter_insert, sizeof(IGVM_VHS_PARAMETER_INSERT)); + } + + break; + + case IGVM_VHT_VP_COUNT_PARMETER: + case IGVM_VHT_MEMORY_MAP: + case IGVM_VHT_ENVIRONMENT_INFO_PARAMETER: + case IGVM_VHT_COMMAND_LINE: + case IGVM_VHT_MADT: + case IGVM_VHT_SRAT: + if (var_hdr.header_size != sizeof(IGVM_VHS_PARAMETER)) + { + goto IncompatibleFile; + } + + // Generate a directive with the correctly modified parameter area + // index. + header = allocate_var_headers( + var_hdr.header_type, + sizeof(IGVM_VHS_PARAMETER), + sizeof(IGVM_VHS_PARAMETER), + 1); + parameter = header->data; + memcpy(parameter, var_hdr.data, sizeof(IGVM_VHS_PARAMETER)); + parameter->ParameterPageIndex += num_parameter_pages; + break; + + case IGVM_VHT_PAGE_DATA: + if (var_hdr.header_size != sizeof(IGVM_VHS_PAGE_DATA)) + { + goto IncompatibleFile; + } + + // Determine whether this page data is selected for the correct + // platform. If so, the behavior depends on the type of page + // data. + memcpy(&page_data, var_hdr.data, sizeof(IGVM_VHS_PAGE_DATA)); + if (page_data.CompatibilityMask & compatibility_mask) + { + if ((page_data.Flags & ~2) != 0) + { + goto IncompatibleFile; + } + + // The page at zero is special: it includes logic to PVALIDATE + // the first 1 MB of memory. That should be skipped when + // running under an SVSM because that memory is validated by + // the SVSM itself. In this case, the page is read to extract + // the true starting RIP, but the page data itself is not + // inserted into the final IGVM file. + if (page_data.GPA == 0) + { + if (page_data.FileOffset == 0) + { + goto IncompatibleFile; + } + if (0 != fseek(file, page_data.FileOffset, SEEK_SET)) + { + goto ReadError; + } + if (fread(&start_rip, sizeof(uint64_t), 1, file) != 1) + { + goto ReadError; + } + continue; + } + + if (page_data.GPA >= highest_gpa) + { + highest_gpa = page_data.GPA + PAGE_SIZE; + } + if (page_data.GPA < lowest_gpa) + { + lowest_gpa = page_data.GPA; + } + + switch (page_data.DataType) + { + case IgvmPageType_Normal: + case IgvmPageType_Cpuid: + case IgvmPageType_CpuidExtendedFeatures: + // CPUID pages can be manifested directly in the firmware + // address space; they do not have to be pre-processed by + // the SVSM. + if (page_data.FileOffset == 0) + { + data_obj = construct_empty_data_object(page_data.GPA, PAGE_SIZE); + data_obj->page_type = page_data.DataType; + } + else + { + if (page_data.FileOffset + PAGE_SIZE > fixed_header.TotalFileSize) + { + goto ReadError; + } + data_obj = construct_mem_data_object(page_data.GPA, PAGE_SIZE); + data_obj->page_type = page_data.DataType; + + if (0 != fseek(file, page_data.FileOffset, SEEK_SET)) + { + goto ReadError; + } + if (fread(data_obj->data, 1, PAGE_SIZE, file) != PAGE_SIZE) + { + goto ReadError; + } + } + + data_obj->page_data_flags = page_data.Flags; + + break; + + case IgvmPageType_Secrets: + // The secrets page is not manifested in the final file. + // Instead, simply capture the location of the secrets + // page so it can be copied into the correct location by + // the SVSM. + fw_info->fw_info.secrets_page = (uint32_t)page_data.GPA; + if (fw_info->fw_info.secrets_page != page_data.GPA) + { + goto IncompatibleFile; + } + + // The Hyper-V firmware reserves the page following the + // secrets page for the calling area. + fw_info->fw_info.caa_page = fw_info->fw_info.secrets_page + PAGE_SIZE; + + break; + } + } + + break; + + case IGVM_VHT_VP_CONTEXT: + if (var_hdr.header_size < FIELD_OFFSET(IGVM_VHS_VP_CONTEXT, padding)) + { + goto IncompatibleFile; + } + + // Determine whether this VP context is selected for the correct + // platform. If so, the VP context will be extracted into a + // structure that will be included in the IGVM parameter block of + // the new IGVM file. + memcpy(&vp_context, var_hdr.data, sizeof(IGVM_VHS_VP_CONTEXT)); + if (vp_context.CompatibilityMask & compatibility_mask) { + if (vp_context.VpIndex != 0) + { + goto IncompatibleFile; + } + + vmsa = malloc(sizeof(SEV_VMSA)); + if (0 != fseek(file, vp_context.FileOffset, SEEK_SET)) + { + free(vmsa); + goto ReadError; + } + if (fread(vmsa, sizeof(SEV_VMSA), 1, file) != 1) + { + free(vmsa); + goto ReadError; + } + + // Construct this data object without an address; its address + // will be populated later. Note that the address specified + // in the VP context object here is not relevant, because the + // SVSM IGVM headers expect the guest context to be part of + // the IGVM parameter area. + data_obj = construct_mem_data_object(0, PAGE_SIZE); + guest_context = data_obj->data; + memset(guest_context, 0, PAGE_SIZE); + fill_guest_context(guest_context, vmsa); + + if (vmsa->sev_features & SevFeature_VTOM) + { + fw_info->vtom = vmsa->vTOM; + } + + free(vmsa); + fw_info->guest_context = data_obj; + } + + break; + + case IGVM_VHT_REQUIRED_MEMORY: + case IGVM_VHT_SNP_POLICY: + // This can be ignored when importing firmware files. + break; + + default: + goto IncompatibleFile; + } + } + + // The base of the firmware must be above 640K. + if (lowest_gpa < 0xA0000) + { + goto IncompatibleFile; + } + + fw_info->fw_info.start = (uint32_t)lowest_gpa; + fw_info->fw_info.size = (uint32_t)(highest_gpa - lowest_gpa); + fw_info->fw_info.in_low_memory = 1; + + if ((fw_info->fw_info.start != lowest_gpa) || + (fw_info->fw_info.start + fw_info->fw_info.size) != highest_gpa) + { + goto IncompatibleFile; + } + + // If the starting RIP was fetched from GPA zero, then place it into the + // initial context now. + if ((start_rip != 0) && (guest_context != NULL)) + { + if (start_rip != (uint32_t)start_rip) + { + goto IncompatibleFile; + } + guest_context->rip = start_rip; + } + + fclose(file); + free(var_hdrs); + + return 0; +} diff --git a/igvmbld/sev-snp.h b/igvmbld/sev-snp.h index 604f9a1ce..68a8dd1dc 100644 --- a/igvmbld/sev-snp.h +++ b/igvmbld/sev-snp.h @@ -77,4 +77,5 @@ enum { }; #define SevFeature_Snp 0x0001 +#define SevFeature_VTOM 0x0002 #define SevFeature_RestrictInj 0x0008