diff --git a/openhcl/underhill_core/src/loader/mod.rs b/openhcl/underhill_core/src/loader/mod.rs index fdbd708507..7584f125cb 100644 --- a/openhcl/underhill_core/src/loader/mod.rs +++ b/openhcl/underhill_core/src/loader/mod.rs @@ -36,12 +36,13 @@ pub enum LoadKind { Uefi, Pcat, Linux, + StaticElf, } impl From for FirmwareType { fn from(value: LoadKind) -> Self { match value { - LoadKind::None | LoadKind::Linux => FirmwareType::None, + LoadKind::None | LoadKind::Linux | LoadKind::StaticElf => FirmwareType::None, LoadKind::Uefi => FirmwareType::Uefi, LoadKind::Pcat => FirmwareType::Pcat, } @@ -117,6 +118,16 @@ pub fn load( tracing::info!("loading nothing into VTL0"); VpContext::Vbs(Vec::new()) } + LoadKind::StaticElf => { + tracing::info!("loading static ELF into VTL0"); + + // Static ELF image is already loaded into guest memory. + let static_elf_info = vtl0_info + .supports_static_elf + .as_ref() + .ok_or(Error::LinuxSupport)?; + static_elf_info.vp_context.clone() + } LoadKind::Uefi => { tracing::info!("loading UEFI into VTL0"); // UEFI image is already loaded into guest memory, so only the diff --git a/openhcl/underhill_core/src/loader/vtl0_config.rs b/openhcl/underhill_core/src/loader/vtl0_config.rs index fff60acb39..92a9848487 100644 --- a/openhcl/underhill_core/src/loader/vtl0_config.rs +++ b/openhcl/underhill_core/src/loader/vtl0_config.rs @@ -59,11 +59,20 @@ pub struct LinuxInfo { pub command_line: Option, } +#[derive(Debug)] +pub struct StaticElfInfo { + /// The region of memory used by the static ELF. + pub elf_range: MemoryRange, + /// The VP context for the static ELF. + pub vp_context: VpContext, +} + #[derive(Debug)] pub struct MeasuredVtl0Info { pub supports_pcat: bool, pub supports_uefi: Option, pub supports_linux: Option, + pub supports_static_elf: Option, } impl MeasuredVtl0Info { @@ -155,6 +164,35 @@ impl MeasuredVtl0Info { None }; + let supports_static_elf = if measured_config.supported_vtl0.static_elf_supported() { + let static_elf = &measured_config.static_elf; + let vp_context = match static_elf.vp_context.pages() { + Some((vtl0_vp_context_page_base, vtl0_vp_context_page_count)) => { + assert!(vtl0_vp_context_page_base != 0); + assert_eq!(vtl0_vp_context_page_count, 1); + + let mut vtl0_vp_context_raw: Vec = vec![0; HV_PAGE_SIZE as usize]; + gm.read_at( + vtl0_vp_context_page_base * HV_PAGE_SIZE, + vtl0_vp_context_raw.as_mut_slice(), + ) + .map_err(Error::GuestMemoryAccess)?; + config_pages.push(vtl0_vp_context_page_base); + + parse_vtl0_vp_context(vtl0_vp_context_raw)? + } + None => VpContext::Vbs(Vec::new()), + }; + + Some(StaticElfInfo { + elf_range: memory_range_from_page_region(&static_elf.region) + .ok_or(Error::UefiFirmwareRegion)?, + vp_context, + }) + } else { + None + }; + // Clear measured info from VTL0 memory. gm.zero_range( &PagedRange::new(0, config_pages.len() * HV_PAGE_SIZE as usize, &config_pages) @@ -166,6 +204,7 @@ impl MeasuredVtl0Info { supports_pcat, supports_uefi, supports_linux, + supports_static_elf, }) } @@ -200,6 +239,18 @@ impl MeasuredVtl0Info { } } + if let Some(static_elf) = &self.supports_static_elf { + if load_kind != LoadKind::StaticElf { + // Clear out the memory used by the static ELF. + gm.fill_at( + static_elf.elf_range.start(), + 0, + static_elf.elf_range.len() as usize, + ) + .map_err(Error::GuestMemoryAccess)?; + } + } + Ok(()) } } diff --git a/openhcl/underhill_core/src/worker.rs b/openhcl/underhill_core/src/worker.rs index 742544efc1..b0229e4bc6 100644 --- a/openhcl/underhill_core/src/worker.rs +++ b/openhcl/underhill_core/src/worker.rs @@ -260,7 +260,7 @@ pub struct UnderhillEnvCfg { /// Force load the specified image in VTL0. The image must support the /// option specified. /// - /// Valid options are "pcat, uefi, linux". + /// Valid options are "pcat, uefi, linux, static_elf". pub force_load_vtl0_image: Option, /// Use the user-mode NVMe driver. pub nvme_vfio: bool, @@ -1674,6 +1674,7 @@ async fn new_underhill_vm( "pcat" => LoadKind::Pcat, "uefi" => LoadKind::Uefi, "linux" => LoadKind::Linux, + "static_elf" => LoadKind::StaticElf, _ => anyhow::bail!("unexpected force load vtl0 type {kind}"), } } else { diff --git a/vm/loader/igvmfilegen/src/main.rs b/vm/loader/igvmfilegen/src/main.rs index 9115bdad78..4f31e215d3 100644 --- a/vm/loader/igvmfilegen/src/main.rs +++ b/vm/loader/igvmfilegen/src/main.rs @@ -26,6 +26,7 @@ use igvmfilegen_config::LinuxImage; use igvmfilegen_config::ResourceType; use igvmfilegen_config::Resources; use igvmfilegen_config::SnpInjectionType; +use igvmfilegen_config::StaticElfImage; use igvmfilegen_config::UefiConfigType; use loader::importer::Aarch64Register; use loader::importer::GuestArch; @@ -33,6 +34,7 @@ use loader::importer::GuestArchKind; use loader::importer::ImageLoad; use loader::importer::X86Register; use loader::linux::InitrdConfig; +use loader::linux::StaticElfLoadInfo; use loader::paravisor::CommandLineType; use loader::paravisor::Vtl0Config; use loader::paravisor::Vtl0Linux; @@ -411,6 +413,17 @@ trait IgvmfilegenRegister: IgvmLoaderRegister + 'static { F: std::io::Read + std::io::Seek, Self: GuestArch; + fn load_static_elf( + importer: &mut impl ImageLoad, + image: &mut F, + minimum_start_address: u64, + load_offset: u64, + assume_pic: bool, + ) -> Result + where + F: std::io::Read + std::io::Seek, + Self: GuestArch; + fn load_openhcl( importer: &mut dyn ImageLoad, kernel_image: &mut F, @@ -453,6 +466,26 @@ impl IgvmfilegenRegister for X86Register { ) } + fn load_static_elf( + importer: &mut impl ImageLoad, + image: &mut F, + minimum_start_address: u64, + load_offset: u64, + assume_pic: bool, + ) -> Result + where + F: std::io::Read + std::io::Seek, + Self: GuestArch, + { + loader::linux::load_static_elf_x64( + importer, + image, + minimum_start_address, + load_offset, + assume_pic, + ) + } + fn load_openhcl( importer: &mut dyn ImageLoad, kernel_image: &mut F, @@ -509,6 +542,26 @@ impl IgvmfilegenRegister for Aarch64Register { ) } + fn load_static_elf( + importer: &mut impl ImageLoad, + image: &mut F, + minimum_start_address: u64, + load_offset: u64, + assume_pic: bool, + ) -> Result + where + F: std::io::Read + std::io::Seek, + Self: GuestArch, + { + loader::linux::load_static_elf_arm64( + importer, + image, + minimum_start_address, + load_offset, + assume_pic, + ) + } + fn load_openhcl( importer: &mut dyn ImageLoad, kernel_image: &mut F, @@ -561,6 +614,7 @@ fn load_image<'a, R: IgvmfilegenRegister + GuestArch + 'static>( memory_page_count, uefi, ref linux, + ref static_elf, } => { if uefi && linux.is_some() { anyhow::bail!("cannot include both UEFI and Linux images in OpenHCL image"); @@ -614,6 +668,7 @@ fn load_image<'a, R: IgvmfilegenRegister + GuestArch + 'static>( supports_pcat: loader.loader().arch() == GuestArchKind::X86_64, supports_uefi: Some((load_info, vp_context)), supports_linux: None, + supports_static_elf: None, } } else if let Some(linux) = linux { let load_info = load_linux(&mut loader.nested_loader(), linux, resources)?; @@ -624,12 +679,29 @@ fn load_image<'a, R: IgvmfilegenRegister + GuestArch + 'static>( command_line: &linux.command_line, load_info, }), + supports_static_elf: None, + } + } else if let Some(static_elf) = static_elf { + let mut inner_loader = loader.nested_loader(); + let StaticElfLoadInfo { gpa, size } = + load_static_elf(&mut inner_loader, static_elf, resources)?; + let vp_context = inner_loader.take_vp_context(); + Vtl0Config { + supports_pcat: false, + supports_uefi: None, + supports_linux: None, + supports_static_elf: Some(loader::paravisor::Vtl0StaticElf { + gpa, + size, + vp_context, + }), } } else { Vtl0Config { supports_pcat: false, supports_uefi: None, supports_linux: None, + supports_static_elf: None, } }; @@ -722,3 +794,29 @@ fn load_linux( .context("loading linux kernel and initrd")?; Ok(load_info) } + +fn load_static_elf( + loader: &mut IgvmVtlLoader<'_, R>, + static_elf: &StaticElfImage, + resources: &Resources, +) -> Result { + let path = resources + .get(ResourceType::StaticElf) + .expect("validated present"); + let mut image = fs_err::File::open(path) + .context(format!("reading vtl0 kernel image at {}", path.display()))?; + let StaticElfImage { + start_address: minimum_start_address, + load_offset, + assume_pic, + } = *static_elf; + let load_info = R::load_static_elf( + loader, + &mut image, + minimum_start_address as u64, + load_offset as u64, + assume_pic, + ) + .context("loading static elf")?; + Ok(load_info) +} diff --git a/vm/loader/igvmfilegen_config/src/lib.rs b/vm/loader/igvmfilegen_config/src/lib.rs index f1fa7d1179..e4c3aa7961 100644 --- a/vm/loader/igvmfilegen_config/src/lib.rs +++ b/vm/loader/igvmfilegen_config/src/lib.rs @@ -95,6 +95,7 @@ pub enum Image { /// Include the Linux kernel for loading into the guest. #[serde(skip_serializing_if = "Option::is_none")] linux: Option, + static_elf: Option, }, /// Load the Linux kernel. /// TODO: Currently, this only works with underhill. @@ -110,6 +111,17 @@ pub struct LinuxImage { pub command_line: CString, } +#[derive(Serialize, Deserialize, Debug)] +#[serde(rename_all = "snake_case")] +pub struct StaticElfImage { + /// Load address. + pub start_address: usize, + /// Load offset. + pub load_offset: usize, + /// Whether the code is position independent or not. + pub assume_pic: bool, +} + impl Image { /// Get the required resources for this image config. pub fn required_resources(&self) -> Vec { @@ -194,6 +206,7 @@ impl Config { #[derive(Serialize, Deserialize, Debug, PartialEq, Eq, Clone, Copy, Hash, PartialOrd, Ord)] #[serde(rename_all = "snake_case")] pub enum ResourceType { + StaticElf, Uefi, UnderhillKernel, OpenhclBoot, diff --git a/vm/loader/loader_defs/src/paravisor.rs b/vm/loader/loader_defs/src/paravisor.rs index f0502a6a6b..5bae532e36 100644 --- a/vm/loader/loader_defs/src/paravisor.rs +++ b/vm/loader/loader_defs/src/paravisor.rs @@ -290,6 +290,17 @@ pub struct LinuxInfo { pub command_line: PageRegionDescriptor, } +/// Measured config about ELF loaded into VTL0. +#[repr(C)] +#[derive(Copy, Clone, Debug, IntoBytes, Immutable, KnownLayout, FromBytes)] +#[cfg_attr(feature = "inspect", derive(Inspect))] +pub struct ElfInfo { + /// The memory the image was loaded into. + pub region: PageRegionDescriptor, + /// The VP context for the image. + pub vp_context: PageRegionDescriptor, +} + /// Measured config about UEFI loaded into VTL0. #[repr(C)] #[derive(Copy, Clone, Debug, IntoBytes, Immutable, KnownLayout, FromBytes)] @@ -315,8 +326,11 @@ pub struct SupportedVtl0LoadInfo { /// This image supports Linux Direct. #[bits(1)] pub linux_direct_supported: bool, + /// This image supports a static ELF. + #[bits(1)] + pub static_elf_supported: bool, /// Currently reserved. - #[bits(61)] + #[bits(60)] pub reserved: u64, } @@ -336,6 +350,8 @@ pub struct ParavisorMeasuredVtl0Config { pub uefi_info: UefiInfo, /// If Linux is supported, information about Linux for VTL0. pub linux_info: LinuxInfo, + /// If a static ELF is supported, information about it for VTL0. + pub static_elf: ElfInfo, } impl ParavisorMeasuredVtl0Config { diff --git a/vm/loader/src/linux.rs b/vm/loader/src/linux.rs index 43b0879f43..a54b198c66 100644 --- a/vm/loader/src/linux.rs +++ b/vm/loader/src/linux.rs @@ -3,8 +3,8 @@ //! Linux specific loader definitions and implementation. +use crate::common::DEFAULT_GDT_SIZE; use crate::common::import_default_gdt; -use crate::elf::load_static_elf; use crate::importer::Aarch64Register; use crate::importer::BootPageAcceptance; use crate::importer::GuestArch; @@ -200,6 +200,15 @@ pub struct LoadInfo { pub dtb: Option>, } +/// Information returned about the kernel loaded. +#[derive(Debug, Default)] +pub struct StaticElfLoadInfo { + /// The base gpa the image was loaded at. + pub gpa: u64, + /// The size in bytes of the region the image was loaded at. + pub size: u64, +} + /// Check if an address is aligned to a page. fn check_address_alignment(address: u64) -> Result<(), Error> { if address % HV_PAGE_SIZE != 0 { @@ -269,7 +278,7 @@ where minimum_address_used: min_addr, next_available_address: next_addr, entrypoint, - } = load_static_elf( + } = crate::elf::load_static_elf( importer, kernel_image, kernel_minimum_start_address, @@ -294,6 +303,122 @@ where }) } +/// Load only a Static ELF to VTL0 on x86_64. +/// This does not setup register state or any other config information. +/// +/// # Arguments +/// +/// * `importer` - The importer to use. +/// * `image` - Static ELF image. +/// * `minimum_start_address` - The minimum address the kernel can load at. +/// It cannot contain an entrypoint or program headers that refer to memory below this address. +pub fn load_static_elf_x64( + importer: &mut dyn ImageLoad, + image: &mut F, + minimum_start_address: u64, + load_offset: u64, + assume_pic: bool, +) -> Result +where + F: std::io::Read + std::io::Seek, +{ + tracing::trace!(minimum_start_address, "loading x86_64 static elf"); + let crate::elf::LoadInfo { + minimum_address_used: min_addr, + next_available_address: next_addr, + entrypoint, + } = crate::elf::load_static_elf( + importer, + image, + minimum_start_address, + load_offset, + assume_pic, + BootPageAcceptance::Exclusive, + "static-elf", + ) + .map_err(Error::ElfLoader)?; + tracing::trace!(min_addr, next_addr, entrypoint, "loaded static elf"); + + let isolation_config = importer.isolation_config(); + let image_gpa_base: u64 = min_addr; + let page_table_gpa_base: u64 = next_addr; + let image_page_count = align_up_to_page_size(next_addr - min_addr) / HV_PAGE_SIZE; + let page_table_size: u64 = HV_PAGE_SIZE * 6; + let gdt_gpa_base: u64 = page_table_gpa_base + page_table_size; + + let page_tables = build_page_tables_64(page_table_gpa_base, 0, IdentityMapSize::Size4Gb, None); + + // Size must match expected compiled constant + assert_eq!(page_tables.len(), page_table_size as usize); + + let mut total_page_count = image_gpa_base / HV_PAGE_SIZE + image_page_count; + + importer + .import_pages( + page_table_gpa_base / HV_PAGE_SIZE, + page_table_size / HV_PAGE_SIZE, + "static-elf-page-tables", + BootPageAcceptance::Exclusive, + &page_tables, + ) + .map_err(Error::Importer)?; + + total_page_count += page_table_size / HV_PAGE_SIZE; + + // The default GDT is used with a page count of one. + assert_eq!(DEFAULT_GDT_SIZE, HV_PAGE_SIZE); + import_default_gdt(importer, gdt_gpa_base / HV_PAGE_SIZE).map_err(Error::Importer)?; + total_page_count += DEFAULT_GDT_SIZE / HV_PAGE_SIZE; + + let mut import_reg = |register| { + importer + .import_vp_register(register) + .map_err(Error::Importer) + }; + + // Set CR0 + import_reg(X86Register::Cr0( + x86defs::X64_CR0_PG | x86defs::X64_CR0_NE | x86defs::X64_CR0_MP | x86defs::X64_CR0_PE, + ))?; + + // Set CR3 to point to page table which starts right after the image. + import_reg(X86Register::Cr3(page_table_gpa_base))?; + + // Set CR4 + import_reg(X86Register::Cr4( + x86defs::X64_CR4_PAE + | x86defs::X64_CR4_MCE + | x86defs::X64_CR4_FXSR + | x86defs::X64_CR4_XMMEXCPT, + ))?; + + // Set EFER to LME, LMA, and NXE for 64 bit mode. + import_reg(X86Register::Efer( + x86defs::X64_EFER_LMA | x86defs::X64_EFER_LME | x86defs::X64_EFER_NXE, + ))?; + + // Set PAT + import_reg(X86Register::Pat(x86defs::X86X_MSR_DEFAULT_PAT))?; + + // Set RIP to the entry point. + // Whether the code is PIC or not, is checked when loading the image. + import_reg(X86Register::Rip(entrypoint))?; + tracing::info!("static elf entrypoint: {entrypoint:x?}"); + + // Set R8-R11 to the hypervisor isolation CPUID leaf values. + let isolation_cpuid = isolation_config.get_cpuid(); + + import_reg(X86Register::R8(isolation_cpuid.eax as u64))?; + import_reg(X86Register::R9(isolation_cpuid.ebx as u64))?; + import_reg(X86Register::R10(isolation_cpuid.ecx as u64))?; + import_reg(X86Register::R11(isolation_cpuid.edx as u64))?; + + Ok(StaticElfLoadInfo { + gpa: min_addr, + size: total_page_count * HV_PAGE_SIZE, + }) +} + /// Load the configuration info and registers for the Linux kernel based on the provided LoadInfo. /// /// # Arguments @@ -665,6 +790,48 @@ where }) } +/// Load only a Static ELF to VTL0 on arm64. +/// This does not setup register state or any other config information. +/// +/// # Arguments +/// +/// * `importer` - The importer to use. +/// * `image` - Static ELF image. +/// * `minimum_start_address` - The minimum address the kernel can load at. +/// It cannot contain an entrypoint or program headers that refer to memory below this address. +pub fn load_static_elf_arm64( + importer: &mut dyn ImageLoad, + image: &mut F, + minimum_start_address: u64, + load_offset: u64, + assume_pic: bool, +) -> Result +where + F: std::io::Read + std::io::Seek, +{ + tracing::trace!(minimum_start_address, "loading aarch64 static elf"); + let crate::elf::LoadInfo { + minimum_address_used: min_addr, + next_available_address: next_addr, + entrypoint, + } = crate::elf::load_static_elf( + importer, + image, + minimum_start_address, + load_offset, + assume_pic, + BootPageAcceptance::Exclusive, + "static-elf", + ) + .map_err(Error::ElfLoader)?; + tracing::trace!(min_addr, next_addr, entrypoint, "loaded static elf"); + + Ok(StaticElfLoadInfo { + gpa: min_addr, + size: next_addr - min_addr, + }) +} + /// Load the configuration info and registers for the Linux kernel based on the provided LoadInfo. /// Parameters: /// * `importer` - The importer to use. diff --git a/vm/loader/src/paravisor.rs b/vm/loader/src/paravisor.rs index 9be883353f..24132f76d3 100644 --- a/vm/loader/src/paravisor.rs +++ b/vm/loader/src/paravisor.rs @@ -50,6 +50,16 @@ use x86defs::cpuid::CpuidFunction; use zerocopy::FromZeros; use zerocopy::IntoBytes; +#[derive(Debug)] +pub struct Vtl0StaticElf { + /// The base gpa the image was loaded at. + pub gpa: u64, + /// The size in bytes of the region the image was loaded at. + pub size: u64, + /// The gpa of the entrypoint of the image. + pub vp_context: Vec, +} + #[derive(Debug)] pub struct Vtl0Linux<'a> { pub command_line: &'a std::ffi::CString, @@ -62,6 +72,7 @@ pub struct Vtl0Config<'a> { /// The load info and the VP context page. pub supports_uefi: Option<(crate::uefi::LoadInfo, Vec)>, pub supports_linux: Option>, + pub supports_static_elf: Option, } // See HclDefs.h @@ -695,6 +706,7 @@ where supports_pcat, supports_uefi, supports_linux, + supports_static_elf, } = vtl0_config; if supports_pcat { @@ -754,7 +766,7 @@ where }; let command_line_page = free_page; - // free_page += 1; + free_page += 1; // Import the command line as a C string. importer @@ -777,6 +789,38 @@ where }; } + if let Some(Vtl0StaticElf { + gpa, + size, + vp_context, + }) = &supports_static_elf + { + measured_config + .supported_vtl0 + .set_static_elf_supported(true); + let vp_context_page = free_page; + // free_page += 1; + measured_config.static_elf = ElfInfo { + region: PageRegionDescriptor { + base_page_number: gpa / HV_PAGE_SIZE, + page_count: size / HV_PAGE_SIZE, + }, + vp_context: PageRegionDescriptor { + base_page_number: vp_context_page, + page_count: 1, + }, + }; + + // Deposit the vp context. + importer.import_pages( + vp_context_page, + 1, + "openhcl-static-elf-vp-context", + BootPageAcceptance::Exclusive, + vp_context, + )?; + } + importer .import_pages( PARAVISOR_VTL0_MEASURED_CONFIG_BASE_PAGE_X64, @@ -858,6 +902,7 @@ where supports_pcat, supports_uefi, supports_linux, + supports_static_elf: _, } = vtl0_config; assert!(!supports_pcat);