From 7ce600fca8407bda3a0922fd119cf15eda19627d Mon Sep 17 00:00:00 2001 From: Adam Dunlap Date: Wed, 8 Jan 2025 10:24:15 -0800 Subject: [PATCH] stage2: Use IGVM memory map to resize SVSM kernel If the IGVM memory map has a HIDDEN entry that matches where the SVSM kernel should go according to the IgvmParamBlock, then use the size of that block for the size of the kernel (checked against a minimum and maximum size in the ParamBlock) This allows the hypervisor to dynamically resize the SVSM based on the machine shape without changing the IgvmParamBlock and thus the launch measurement. This is necessary because the SVSM uses more memory when there are more vCPUs (and, in the future, guest memory). The location and minium and maximum size can still be measured in the IgvmParamBlock to ensure that the kernel region will not overlap anything important or cause undersize problems. Signed-off-by: Adam Dunlap --- Cargo.lock | 8 ++++---- Cargo.toml | 4 ++-- bootlib/src/igvm_params.rs | 13 ++++++++++--- igvmbuilder/src/gpa_map.rs | 13 ++++++++----- igvmbuilder/src/igvm_builder.rs | 8 +++++--- kernel/src/igvm_params.rs | 32 +++++++++++++++++++++++++++++--- kernel/src/stage2.rs | 2 ++ 7 files changed, 60 insertions(+), 20 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index 82ed6e6a4..7ce766e7b 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -514,9 +514,9 @@ dependencies = [ [[package]] name = "igvm" -version = "0.3.3" +version = "0.3.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7984b10433b50e06a06bd50c69bca4888a5d7de8975f64ea4c2a7687eb99b09d" +checksum = "7b4ae8479aa3163c8a0fa716aa6ef08a6553e1097f8a89544f46fee695b5a162" dependencies = [ "bitfield-struct 0.7.0", "crc32fast", @@ -531,9 +531,9 @@ dependencies = [ [[package]] name = "igvm_defs" -version = "0.3.3" +version = "0.3.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b64ec5588c475372ae830475d3ee9a7bd255407dcb9f03faf6d493556eb6105a" +checksum = "e4f70c18b574e5c7fa6222c1f0ebd8bfe2e14b762573b799faf8697c044b0e2a" dependencies = [ "bitfield-struct 0.7.0", "open-enum", diff --git a/Cargo.toml b/Cargo.toml index fbc771473..541273881 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -48,8 +48,8 @@ bitflags = "2.4" clap = { version = "4.4.14", default-features = false } gdbstub = { version = "0.6.6", default-features = false } gdbstub_arch = { version = "0.2.4" } -igvm = { version = "0.3.2", default-features = false } -igvm_defs = { version = "0.3.2", default-features = false } +igvm = { version = "0.3.4", default-features = false } +igvm_defs = { version = "0.3.4", default-features = false } intrusive-collections = "0.9.6" libfuzzer-sys = "0.4" log = "0.4.17" diff --git a/bootlib/src/igvm_params.rs b/bootlib/src/igvm_params.rs index b5243bd78..4de05add1 100644 --- a/bootlib/src/igvm_params.rs +++ b/bootlib/src/igvm_params.rs @@ -136,12 +136,19 @@ pub struct IgvmParamBlock { /// memory region (e.g. for VMSA contents). pub kernel_reserved_size: u32, - /// The number of bytes in the kernel memory region. - pub kernel_size: u32, - /// The guest physical address of the base of the kernel memory region. pub kernel_base: u64, + /// The minimum size to allocate for the kernel in bytes. If the hypervisor supplies a memory + /// region in the memory map that starts at kernel_base and is larger, that size will be used + /// instead. + pub kernel_min_size: u32, + + /// The maximum size to allocate for the kernel in bytes. If the hypervisor supplies a memory + /// region in the memory map that starts at kernel_base and is larger, this maximum size will + /// be used instead. + pub kernel_max_size: u32, + /// The value of vTOM used by the guest, or zero if not used. pub vtom: u64, } diff --git a/igvmbuilder/src/gpa_map.rs b/igvmbuilder/src/gpa_map.rs index ac6fc038e..56f56aa69 100644 --- a/igvmbuilder/src/gpa_map.rs +++ b/igvmbuilder/src/gpa_map.rs @@ -64,6 +64,9 @@ pub struct GpaMap { pub general_params: GpaRange, pub memory_map: GpaRange, pub guest_context: GpaRange, + // The kernel region represents the maximum allowable size. The hypervisor may request that it + // be smaller to save memory on smaller machine shapes. However, the entire region should not + // overlap any other regions. pub kernel: GpaRange, pub vmsa: GpaRange, pub init_page_tables: GpaRange, @@ -116,19 +119,19 @@ impl GpaMap { let kernel_elf = GpaRange::new(kernel_address, kernel_elf_len as u64)?; let kernel_fs = GpaRange::new(kernel_elf.get_end(), kernel_fs_len as u64)?; - // Calculate the kernel size and base. + // Choose the kernel base and maximum size. let kernel = match options.hypervisor { Hypervisor::Qemu => { - // Place the kernel area at 512 GB with a size of 16 MB. + // Place the kernel area at 512 GB with a maximum size of 16 MB. GpaRange::new(0x0000008000000000, 0x01000000)? } Hypervisor::HyperV => { - // Place the kernel area at 64 MB with a size of 16 MB. + // Place the kernel area at 64 MB with a maximum size of 16 MB. GpaRange::new(0x04000000, 0x01000000)? } Hypervisor::Vanadium => { - // Place the kernel area at 8TiB-2GiB with a size of 16 MB. - GpaRange::new(0x7ff80000000, 0x01000000)? + // Place the kernel area at 8TiB-2GiB with a maximum size of 2 GiB. + GpaRange::new(0x7ff80000000, 0x80000000)? } }; diff --git a/igvmbuilder/src/igvm_builder.rs b/igvmbuilder/src/igvm_builder.rs index 367405078..9d7054dfc 100644 --- a/igvmbuilder/src/igvm_builder.rs +++ b/igvmbuilder/src/igvm_builder.rs @@ -192,6 +192,7 @@ impl IgvmBuilder { fn create_param_block(&self) -> Result> { let param_page_offset = PAGE_SIZE_4K as u32; let memory_map_offset = param_page_offset + PAGE_SIZE_4K as u32; + let kernel_min_size = 0x1000000; // 16 MiB let (guest_context_offset, param_area_size) = if self.gpa_map.guest_context.get_size() == 0 { (0, memory_map_offset + PAGE_SIZE_4K as u32) @@ -237,8 +238,9 @@ impl IgvmBuilder { stage1_size: self.gpa_map.stage1_image.get_size() as u32, stage1_base: self.gpa_map.stage1_image.get_start(), kernel_reserved_size: PAGE_SIZE_4K as u32, // Reserved for VMSA - kernel_size: self.gpa_map.kernel.get_size() as u32, kernel_base: self.gpa_map.kernel.get_start(), + kernel_min_size, + kernel_max_size: self.gpa_map.kernel.get_size() as u32, vtom, use_alternate_injection: u8::from(self.options.alt_injection), is_qemu, @@ -328,7 +330,7 @@ impl IgvmBuilder { self.directives.push(IgvmDirectiveHeader::RequiredMemory { gpa: param_block.kernel_base, compatibility_mask: COMPATIBILITY_MASK.get() & !VSM_COMPATIBILITY_MASK, - number_of_bytes: param_block.kernel_size, + number_of_bytes: param_block.kernel_min_size, vtl2_protectable: false, }); } @@ -337,7 +339,7 @@ impl IgvmBuilder { self.directives.push(IgvmDirectiveHeader::RequiredMemory { gpa: param_block.kernel_base, compatibility_mask: VSM_COMPATIBILITY_MASK, - number_of_bytes: param_block.kernel_size, + number_of_bytes: param_block.kernel_min_size, vtl2_protectable: true, }); } diff --git a/kernel/src/igvm_params.rs b/kernel/src/igvm_params.rs index cfbc13de1..a22ad4714 100644 --- a/kernel/src/igvm_params.rs +++ b/kernel/src/igvm_params.rs @@ -7,7 +7,7 @@ extern crate alloc; use crate::acpi::tables::ACPICPUInfo; -use crate::address::{PhysAddr, VirtAddr}; +use crate::address::{Address, PhysAddr, VirtAddr}; use crate::cpu::efer::EFERFlags; use crate::error::SvsmError; use crate::mm::{GuestPtr, PerCPUPageMappingGuard, PAGE_SIZE}; @@ -76,8 +76,34 @@ impl IgvmParams<'_> { pub fn find_kernel_region(&self) -> Result, SvsmError> { let kernel_base = PhysAddr::from(self.igvm_param_block.kernel_base); - let kernel_size: usize = self.igvm_param_block.kernel_size.try_into().unwrap(); - Ok(MemoryRegion::::new(kernel_base, kernel_size)) + let mut kernel_size = self.igvm_param_block.kernel_min_size; + + // Check the untrusted hypervisor-provided memory map to see if the size of the kernel + // should be adjusted. The base location and mimimum and maximum size specified by the + // measured igvm_param_block are still respected to ensure a malicious memory map cannot + // cause the SVSM kernel to overlap anything important or be so small it causes weird + // failures. But if the hypervisor gives a memory map entry of type HIDDEN that starts at + // kernel_start, use the size of that entry as a guide. This allows the hypervisor to + // adjust the size of the SVSM kernel to what it expects will be needed based on the + // machine shape. + if let Some(memory_map_region) = self.igvm_memory_map.memory_map.iter().find(|region| { + region.entry_type == MemoryMapEntryType::HIDDEN + && region.starting_gpa_page_number.try_into() == Ok(kernel_base.pfn()) + }) { + let region_size_bytes = memory_map_region + .number_of_pages + .try_into() + .unwrap_or(u32::MAX) + .saturating_mul(PAGE_SIZE as u32); + kernel_size = region_size_bytes.clamp( + self.igvm_param_block.kernel_min_size, + self.igvm_param_block.kernel_max_size, + ); + } + Ok(MemoryRegion::::new( + kernel_base, + kernel_size.try_into().unwrap(), + )) } pub fn reserved_kernel_area_size(&self) -> usize { diff --git a/kernel/src/stage2.rs b/kernel/src/stage2.rs index bc91a6c8d..996826398 100755 --- a/kernel/src/stage2.rs +++ b/kernel/src/stage2.rs @@ -363,6 +363,8 @@ pub extern "C" fn stage2_main(launch_info: &Stage2LaunchInfo) { .find_kernel_region() .expect("Failed to find memory region for SVSM kernel"); + log::info!("SVSM memory region: {kernel_region:?}"); + init_valid_bitmap_alloc(kernel_region).expect("Failed to allocate valid-bitmap"); // The physical memory region we've loaded so far