From ca4ff50f59457494fc1c8b3035fe09516ac387b0 Mon Sep 17 00:00:00 2001 From: Steven Malis Date: Tue, 6 May 2025 15:26:44 -0400 Subject: [PATCH 1/5] Wire up the IMC bit in UEFI config --- openvmm/openvmm_entry/src/lib.rs | 1 + petri/src/vm/openvmm/construct.rs | 1 + vm/devices/get/get_resources/src/lib.rs | 2 ++ vm/devices/get/guest_emulation_device/src/lib.rs | 8 +++++++- vm/devices/get/guest_emulation_device/src/resolver.rs | 2 ++ .../get/guest_emulation_device/src/test_utilities.rs | 1 + 6 files changed, 14 insertions(+), 1 deletion(-) diff --git a/openvmm/openvmm_entry/src/lib.rs b/openvmm/openvmm_entry/src/lib.rs index 2f95e1053d..5fdde45eaa 100644 --- a/openvmm/openvmm_entry/src/lib.rs +++ b/openvmm/openvmm_entry/src/lib.rs @@ -921,6 +921,7 @@ fn vm_config_from_command_line( UefiConsoleModeCli::None => UefiConsoleMode::None, }, default_boot_always_attempt: opt.default_boot_always_attempt, + enable_imc_when_isolated: opt.imc.is_some(), } }, com1: with_vmbus_com1_serial, diff --git a/petri/src/vm/openvmm/construct.rs b/petri/src/vm/openvmm/construct.rs index 06cb2c732d..556e166d27 100644 --- a/petri/src/vm/openvmm/construct.rs +++ b/petri/src/vm/openvmm/construct.rs @@ -840,6 +840,7 @@ impl PetriVmConfigSetupCore<'_> { enable_vpci_boot: false, console_mode: get_resources::ged::UefiConsoleMode::COM1, default_boot_always_attempt: false, + enable_imc_when_isolated: true, }, com1: true, com2: true, diff --git a/vm/devices/get/get_resources/src/lib.rs b/vm/devices/get/get_resources/src/lib.rs index 54e6af429e..59b12b83a3 100644 --- a/vm/devices/get/get_resources/src/lib.rs +++ b/vm/devices/get/get_resources/src/lib.rs @@ -107,6 +107,8 @@ pub mod ged { console_mode: UefiConsoleMode, /// Perform a default boot even if boot entries exist and fail default_boot_always_attempt: bool, + /// Enable the IMC device when the guest is isolated. + enable_imc_when_isolated: bool, }, /// Boot from PC/AT BIOS with Hyper-V generation 1 devices. Pcat { diff --git a/vm/devices/get/guest_emulation_device/src/lib.rs b/vm/devices/get/guest_emulation_device/src/lib.rs index acb4def880..397ab38e0c 100644 --- a/vm/devices/get/guest_emulation_device/src/lib.rs +++ b/vm/devices/get/guest_emulation_device/src/lib.rs @@ -166,6 +166,8 @@ pub enum GuestFirmwareConfig { console_mode: UefiConsoleMode, /// Perform a default boot even if boot entries exist and fail default_boot_always_attempt: bool, + /// Enable the IMC device when isolated + enable_imc_when_isolated: bool, }, Pcat { #[inspect(with = "|x| inspect::iter_by_index(x).map_value(inspect::AsDebug)")] @@ -1340,6 +1342,7 @@ impl GedChannel { let pcat_boot_device_order; let uefi_console_mode; let default_boot_always_attempt; + let enable_imc_when_isolated; match state.config.firmware { GuestFirmwareConfig::Uefi { enable_vpci_boot, @@ -1347,6 +1350,7 @@ impl GedChannel { disable_frontpage: v_disable_frontpage, console_mode, default_boot_always_attempt: v_default_boot_always_attempt, + enable_imc_when_isolated: v_enable_imc_when_isolated, } => { vpci_boot_enabled = enable_vpci_boot; enable_firmware_debugging = firmware_debug; @@ -1355,6 +1359,7 @@ impl GedChannel { pcat_boot_device_order = None; uefi_console_mode = Some(console_mode); default_boot_always_attempt = v_default_boot_always_attempt; + enable_imc_when_isolated = v_enable_imc_when_isolated; } GuestFirmwareConfig::Pcat { boot_order } => { vpci_boot_enabled = false; @@ -1364,6 +1369,7 @@ impl GedChannel { pcat_boot_device_order = Some(boot_order); uefi_console_mode = None; default_boot_always_attempt = false; + enable_imc_when_isolated = false; } } @@ -1418,7 +1424,7 @@ impl GedChannel { smbios: Default::default(), watchdog_enabled: false, always_relay_host_mmio: false, - imc_enabled: false, + imc_enabled: enable_imc_when_isolated, cxl_memory_enabled: false, }, dynamic: get_protocol::dps_json::HclDevicePlatformSettingsV2Dynamic { diff --git a/vm/devices/get/guest_emulation_device/src/resolver.rs b/vm/devices/get/guest_emulation_device/src/resolver.rs index 7573eb109c..eb0d903e04 100644 --- a/vm/devices/get/guest_emulation_device/src/resolver.rs +++ b/vm/devices/get/guest_emulation_device/src/resolver.rs @@ -97,6 +97,7 @@ impl AsyncResolveResource disable_frontpage, console_mode, default_boot_always_attempt, + enable_imc_when_isolated, } => crate::GuestFirmwareConfig::Uefi { enable_vpci_boot, firmware_debug, @@ -108,6 +109,7 @@ impl AsyncResolveResource UefiConsoleMode::None => get_protocol::UefiConsoleMode::NONE, }, default_boot_always_attempt, + enable_imc_when_isolated, }, GuestFirmwareConfig::Pcat { boot_order } => crate::GuestFirmwareConfig::Pcat { boot_order: boot_order.map(|x| match x { diff --git a/vm/devices/get/guest_emulation_device/src/test_utilities.rs b/vm/devices/get/guest_emulation_device/src/test_utilities.rs index 7dcc3a53ab..9978ba420b 100644 --- a/vm/devices/get/guest_emulation_device/src/test_utilities.rs +++ b/vm/devices/get/guest_emulation_device/src/test_utilities.rs @@ -243,6 +243,7 @@ pub fn create_host_channel( disable_frontpage: false, console_mode: UefiConsoleMode::DEFAULT, default_boot_always_attempt: false, + enable_imc_when_isolated: false, }, com1: true, com2: true, From 3aa1463c34b870b76d1cc80bbeae843d6b5ac581 Mon Sep 17 00:00:00 2001 From: Steven Malis Date: Tue, 6 May 2025 16:12:13 -0400 Subject: [PATCH 2/5] Add a second hive for VSM --- petri/guest-bootstrap/README.md | 12 +-- .../{imc.hiv => imc-pipette.hiv} | Bin 8192 -> 8192 bytes petri/guest-bootstrap/imc-vsm.hiv | Bin 0 -> 8192 bytes petri/make_imc_hive/src/windows/mod.rs | 70 +++++++++++++----- petri/src/vm/hyperv/mod.rs | 2 +- petri/src/vm/openvmm/start.rs | 2 +- 6 files changed, 62 insertions(+), 24 deletions(-) rename petri/guest-bootstrap/{imc.hiv => imc-pipette.hiv} (95%) create mode 100644 petri/guest-bootstrap/imc-vsm.hiv diff --git a/petri/guest-bootstrap/README.md b/petri/guest-bootstrap/README.md index 3e9160f91c..2827a59e0c 100644 --- a/petri/guest-bootstrap/README.md +++ b/petri/guest-bootstrap/README.md @@ -1,7 +1,9 @@ -This directory contains files needed to bootstrap the guest with the pipette -agent. +This directory contains files used to bootstrap the guest with the requested +configuration. -* `meta-data` and `user-data`: cloud-init files for Linux guests -* `imc.hiv`: an IMC hive for Windows guests +* `meta-data` and `user-data`: cloud-init files for enabling pipette for Linux guests +* `imc-pipette.hiv`: an IMC hive for enabling pipette for Windows guests +* `imc-vsm.hiv`: an IMC hive for enabling VSM for Windows guests -To update `imc.hiv`, on a Windows machine run `cargo run -p make_imc_hive PATH/TO/imc.hiv` +To update an IMC hive file, on a Windows machine run +`cargo run -p make_imc_hive PATH/TO/imc.hiv` diff --git a/petri/guest-bootstrap/imc.hiv b/petri/guest-bootstrap/imc-pipette.hiv similarity index 95% rename from petri/guest-bootstrap/imc.hiv rename to petri/guest-bootstrap/imc-pipette.hiv index dbec5425d717988296f11eb58fa2fe6e7c24d00a..e59b6dd4e6d36df3310e7ef6fb51e882d6e7036a 100644 GIT binary patch delta 88 zcmZp0XmD7-#BnD_TKCYt+l-q9ncnkGbl{vMpuw)qBncARtS?~52;)pn7fgh+&kGj9 O+3rGc@y+Xn0(k*j_a8X` delta 88 zcmZp0XmD7-#BuE4p@mc8Z!vBbWO~m#(SdW4fCf8MY_qRQK@b!hqY;!yQzb$Nueq1xNVuz)OR!FH5}YzR z7N-slii?v(C&w;=;!?Mc4h}kK{Qd6k+2%;+LVX|i?)&q;zrXL6^JJrL2qAbBQBFk) zVk8q(deR6U1AKqH_L&ek8@;ucld^)YrpD}5i0gfS({Uz`|4rv-EAr`_MiD_{~927=aJaw23iJ8!rpdwN^ zymFM`dkm40Zx}IdOR+~I78lolqR%{{*TH+OH2df?gtIVdMLz6GrzXGSzTGrL6~h=uJ>Ur_k7k*l z3c0FtAiN%XAA(lsdY*Lrg6sI6+pb7I@=$dzBHt71{oP6Pu_t0f8f;VdXm~yLo`Mz? z6dUNV-Ass8#9#XJ<90@Lnqu^vkrWU-%X3jq7c* z(Fyz~;YW;FmsCO^`=Cs&@ViVyjP^mV)TCp2wi{;OoV$uVnR6@82FMZL7co)98YI^L zh!|B*-Shk8EToe-rR62*_3V!86`i{4)udH$EqT-NWg|eJc&s5{9lY0?3h-cV<{%IE eXNfGAuSp}I5zq)|1T+E~0gZr0KqK& anyhow::Result<()> { - let path = std::env::args_os().nth(1).context("missing path")?; + let ty = std::env::args().nth(1).context("missing type")?; + let path = std::env::args_os().nth(2).context("missing path")?; let hive = Hive::create()?; - { - let mut key; - let mut parent = hive.as_ref(); - for subkey in ["SYSTEM", "CurrentControlSet", "Services", "pipette"] { - let new_key = parent.create_key(subkey)?; - key = new_key; - parent = key.as_ref(); - } - - parent.set_dword("Type", 0x10)?; // win32 service - parent.set_dword("Start", 2)?; // auto start - parent.set_dword("ErrorControl", 1)?; // normal - parent.set_sz("ImagePath", "D:\\pipette.exe --service")?; - parent.set_sz("DisplayName", "Petri pipette agent")?; - parent.set_sz("ObjectName", "LocalSystem")?; - parent.set_multi_sz("DependOnService", ["RpcSs"])?; + + match &*ty { + "pipette" => fill_hive_pipette(&hive)?, + // TODO: Once we have support for running pipette with VSM, also call + // fill_hive_pipette here. + "vsm" => fill_hive_vsm(&hive)?, + _ => anyhow::bail!("unknown type"), } // Windows defaults to 1, so we need to set it to 2 to cause Windows to @@ -35,3 +28,46 @@ pub(crate) fn main() -> anyhow::Result<()> { hive.save(path.as_ref())?; Ok(()) } + +fn subkey(hive: &Hive, path: &str) -> anyhow::Result { + let mut key = None; + let mut parent = hive.as_ref(); + for subkey in path.split('\\') { + let new_key = parent.create_key(subkey)?; + key = Some(new_key); + parent = key.as_ref().unwrap(); + } + Ok(key.unwrap()) +} + +/// Insert the pipette startup keys into the hive. +fn fill_hive_pipette(hive: &Hive) -> anyhow::Result<()> { + let svc_key = subkey(hive, r"SYSTEM\CurrentControlSet\Services\pipette")?; + svc_key.set_dword("Type", 0x10)?; // win32 service + svc_key.set_dword("Start", 2)?; // auto start + svc_key.set_dword("ErrorControl", 1)?; // normal + svc_key.set_sz("ImagePath", "D:\\pipette.exe --service")?; + svc_key.set_sz("DisplayName", "Petri pipette agent")?; + svc_key.set_sz("ObjectName", "LocalSystem")?; + svc_key.set_multi_sz("DependOnService", ["RpcSs"])?; + Ok(()) +} + +fn fill_hive_vsm(hive: &Hive) -> anyhow::Result<()> { + // Enable VBS + let vbs_key = subkey(hive, r"SYSTEM\CurrentControlSet\Control\DeviceGuard")?; + vbs_key.set_dword("EnableVirtualizationBasedSecurity", 1)?; + + // Enable Credential Guard - https://learn.microsoft.com/en-us/windows/security/identity-protection/credential-guard/configure?tabs=reg + let cg_key = subkey(hive, r"SYSTEM\CurrentControlSet\Control\Lsa")?; + cg_key.set_dword("LsaCfgFlags", 2)?; + + // Enable HVCI - https://learn.microsoft.com/en-us/windows/security/hardware-security/enable-virtualization-based-protection-of-code-integrity?tabs=reg + let hvci_key = subkey( + hive, + r"SYSTEM\CurrentControlSet\Control\DeviceGuard\Scenarios\HypervisorEnforcedCodeIntegrity", + )?; + hvci_key.set_dword("Enabled", 1)?; + + Ok(()) +} diff --git a/petri/src/vm/hyperv/mod.rs b/petri/src/vm/hyperv/mod.rs index 04e816f442..20396a60e2 100644 --- a/petri/src/vm/hyperv/mod.rs +++ b/petri/src/vm/hyperv/mod.rs @@ -413,7 +413,7 @@ impl PetriVmConfigHyperV { { let mut imc_hive_file = fs::File::create_new(&imc_hive)?; imc_hive_file - .write_all(include_bytes!("../../../guest-bootstrap/imc.hiv")) + .write_all(include_bytes!("../../../guest-bootstrap/imc-pipette.hiv")) .context("failed to write imc hive")?; } diff --git a/petri/src/vm/openvmm/start.rs b/petri/src/vm/openvmm/start.rs index d9f2a5a98f..a54f21ee66 100644 --- a/petri/src/vm/openvmm/start.rs +++ b/petri/src/vm/openvmm/start.rs @@ -213,7 +213,7 @@ impl PetriVmConfigOpenVmm { // location at runtime. let mut imc_hive_file = tempfile::tempfile().context("failed to create temp file")?; imc_hive_file - .write_all(include_bytes!("../../../guest-bootstrap/imc.hiv")) + .write_all(include_bytes!("../../../guest-bootstrap/imc-pipette.hiv")) .context("failed to write imc hive")?; // Add the IMC device. From 111e75062f22fefbd25a5c1d9f9e4f5c3ef65231 Mon Sep 17 00:00:00 2001 From: Steven Malis Date: Tue, 6 May 2025 16:40:03 -0400 Subject: [PATCH 3/5] Wire up hive switching and add the test --- petri/src/vm/hyperv/mod.rs | 103 +++++++++++++------ petri/src/vm/mod.rs | 2 + petri/src/vm/openvmm/construct.rs | 1 + petri/src/vm/openvmm/mod.rs | 5 + petri/src/vm/openvmm/modify.rs | 23 +++++ petri/src/vm/openvmm/start.rs | 41 +++++--- vmm_tests/vmm_tests/tests/tests/multiarch.rs | 13 +++ 7 files changed, 138 insertions(+), 50 deletions(-) diff --git a/petri/src/vm/hyperv/mod.rs b/petri/src/vm/hyperv/mod.rs index 20396a60e2..0d27361db8 100644 --- a/petri/src/vm/hyperv/mod.rs +++ b/petri/src/vm/hyperv/mod.rs @@ -33,6 +33,8 @@ use petri_artifacts_common::tags::OsFlavor; use petri_artifacts_core::ArtifactResolver; use petri_artifacts_core::ResolvedArtifact; use pipette_client::PipetteClient; +use powershell::HyperVGeneration; +use powershell::HyperVGuestStateIsolationType; use std::fs; use std::io::Write; use std::path::Path; @@ -48,9 +50,9 @@ pub struct PetriVmConfigHyperV { name: String, arch: MachineArch, // Specifies the generation for the virtual machine. - generation: powershell::HyperVGeneration, + generation: HyperVGeneration, // Specifies the Guest State Isolation Type - guest_state_isolation_type: powershell::HyperVGuestStateIsolationType, + guest_state_isolation_type: HyperVGuestStateIsolationType, // Specifies the amount of memory, in bytes, to assign to the virtual machine. memory: u64, proc_topology: ProcessorTopology, @@ -68,6 +70,7 @@ pub struct PetriVmConfigHyperV { os_flavor: OsFlavor, expected_boot_event: Option, + imc_hive: Option<&'static [u8]>, // Folder to store temporary data for this test temp_dir: tempfile::TempDir, @@ -128,6 +131,10 @@ impl PetriVmConfig for PetriVmConfigHyperV { fn with_uefi_frontpage(self: Box, enable: bool) -> Box { Box::new(Self::with_uefi_frontpage(*self, enable)) } + + fn with_guest_vsm(self: Box) -> Box { + Box::new(Self::with_guest_vsm(*self)) + } } /// A running VM that tests can interact with. @@ -235,14 +242,14 @@ impl PetriVmConfigHyperV { todo!("linux direct not supported on hyper-v") } Firmware::Pcat { guest, .. } => ( - powershell::HyperVGuestStateIsolationType::Disabled, - powershell::HyperVGeneration::One, + HyperVGuestStateIsolationType::Disabled, + HyperVGeneration::One, Some(guest.artifact()), None, ), Firmware::Uefi { guest, .. } => ( - powershell::HyperVGuestStateIsolationType::Disabled, - powershell::HyperVGeneration::Two, + HyperVGuestStateIsolationType::Disabled, + HyperVGeneration::Two, guest.artifact(), None, ), @@ -253,12 +260,12 @@ impl PetriVmConfigHyperV { vtl2_nvme_boot: _, // TODO } => ( match isolation { - Some(IsolationType::Vbs) => powershell::HyperVGuestStateIsolationType::Vbs, - Some(IsolationType::Snp) => powershell::HyperVGuestStateIsolationType::Snp, - Some(IsolationType::Tdx) => powershell::HyperVGuestStateIsolationType::Tdx, - None => powershell::HyperVGuestStateIsolationType::TrustedLaunch, + Some(IsolationType::Vbs) => HyperVGuestStateIsolationType::Vbs, + Some(IsolationType::Snp) => HyperVGuestStateIsolationType::Snp, + Some(IsolationType::Tdx) => HyperVGuestStateIsolationType::Tdx, + None => HyperVGuestStateIsolationType::TrustedLaunch, }, - powershell::HyperVGeneration::Two, + HyperVGeneration::Two, guest.artifact(), Some(igvm_path), ), @@ -278,8 +285,8 @@ impl PetriVmConfigHyperV { memory: 0x1_0000_0000, proc_topology: ProcessorTopology::default(), vhd_paths, - secure_boot_template: matches!(generation, powershell::HyperVGeneration::Two) - .then_some(match firmware.os_flavor() { + secure_boot_template: matches!(generation, HyperVGeneration::Two).then_some( + match firmware.os_flavor() { OsFlavor::Windows => powershell::HyperVSecureBootTemplate::MicrosoftWindows, OsFlavor::Linux => { powershell::HyperVSecureBootTemplate::MicrosoftUEFICertificateAuthority @@ -287,7 +294,8 @@ impl PetriVmConfigHyperV { OsFlavor::FreeBsd | OsFlavor::Uefi => { powershell::HyperVSecureBootTemplate::SecureBootDisabled } - }), + }, + ), openhcl_igvm, agent_image, openhcl_agent_image, @@ -298,6 +306,7 @@ impl PetriVmConfigHyperV { log_source: params.logger.clone(), disable_frontpage: true, openhcl_command_line: String::new(), + imc_hive: None, }) } @@ -348,12 +357,13 @@ impl PetriVmConfigHyperV { super::ApicMode::X2apicSupported => powershell::HyperVApicMode::X2Apic, super::ApicMode::X2apicEnabled => powershell::HyperVApicMode::X2Apic, }) - .or((self.arch == MachineArch::X86_64 - && self.generation == powershell::HyperVGeneration::Two) - .then_some({ - // This is necessary for some tests to pass. TODO: fix. - powershell::HyperVApicMode::X2Apic - })); + .or( + (self.arch == MachineArch::X86_64 && self.generation == HyperVGeneration::Two) + .then_some({ + // This is necessary for some tests to pass. TODO: fix. + powershell::HyperVApicMode::X2Apic + }), + ); vm.set_processor(&powershell::HyperVSetVMProcessorArgs { count: Some(vp_count), apic_mode, @@ -368,8 +378,8 @@ impl PetriVmConfigHyperV { for (i, vhds) in self.vhd_paths.iter().enumerate() { let (controller_type, controller_number) = match self.generation { - powershell::HyperVGeneration::One => (powershell::ControllerType::Ide, i as u32), - powershell::HyperVGeneration::Two => { + HyperVGeneration::One => (powershell::ControllerType::Ide, i as u32), + HyperVGeneration::Two => { (powershell::ControllerType::Scsi, vm.add_scsi_controller(0)?) } }; @@ -407,18 +417,21 @@ impl PetriVmConfigHyperV { } if matches!(self.os_flavor, OsFlavor::Windows) { + let imc_hive_contents = self + .imc_hive + .unwrap_or(include_bytes!("../../../guest-bootstrap/imc-pipette.hiv")); // Make a file for the IMC hive. It's not guaranteed to be at a fixed // location at runtime. - let imc_hive = self.temp_dir.path().join("imc.hiv"); - { - let mut imc_hive_file = fs::File::create_new(&imc_hive)?; - imc_hive_file - .write_all(include_bytes!("../../../guest-bootstrap/imc-pipette.hiv")) - .context("failed to write imc hive")?; - } + let imc_hive_file_path = self.temp_dir.path().join("imc.hiv"); + let mut imc_hive_file = fs::File::create_new(&imc_hive_file_path)?; + imc_hive_file + .write_all(imc_hive_contents) + .context("failed to write imc hive")?; // Set the IMC - vm.set_imc(&imc_hive)?; + vm.set_imc(&imc_hive_file_path)?; + } else { + assert!(self.imc_hive.is_none()); } let controller_number = vm.add_scsi_controller(0)?; @@ -444,9 +457,9 @@ impl PetriVmConfigHyperV { // don't increase VTL2 memory on CVMs !matches!( self.guest_state_isolation_type, - powershell::HyperVGuestStateIsolationType::Vbs - | powershell::HyperVGuestStateIsolationType::Snp - | powershell::HyperVGuestStateIsolationType::Tdx + HyperVGuestStateIsolationType::Vbs + | HyperVGuestStateIsolationType::Snp + | HyperVGuestStateIsolationType::Tdx ), )?; @@ -537,7 +550,7 @@ impl PetriVmConfigHyperV { /// Inject Windows secure boot templates into the VM's UEFI. pub fn with_windows_secure_boot_template(mut self) -> Self { - if !matches!(self.generation, powershell::HyperVGeneration::Two) { + if !matches!(self.generation, HyperVGeneration::Two) { panic!("Secure boot templates are only supported for UEFI firmware."); } self.secure_boot_template = Some(powershell::HyperVSecureBootTemplate::MicrosoftWindows); @@ -578,6 +591,28 @@ impl PetriVmConfigHyperV { self.disable_frontpage = !enable; self } + + /// Enables Guest VSM for the VM. + /// + /// Note: Currently this disables the ability to run with pipette. + /// This will be addressed in the future. + pub fn with_guest_vsm(mut self) -> Self { + if self.openhcl_igvm.is_none() + || !matches!( + self.guest_state_isolation_type, + HyperVGuestStateIsolationType::Vbs + | HyperVGuestStateIsolationType::Snp + | HyperVGuestStateIsolationType::Tdx + ) + { + panic!("Guest VSM is only supported for OpenHCL UEFI with isolation."); + } + if !matches!(self.os_flavor, OsFlavor::Windows) { + panic!("Guest VSM is only supported for Windows."); + } + self.imc_hive = Some(include_bytes!("../../../guest-bootstrap/imc-vsm.hiv")); + self + } } impl PetriVmHyperV { diff --git a/petri/src/vm/mod.rs b/petri/src/vm/mod.rs index b4b072e719..657061ab3a 100644 --- a/petri/src/vm/mod.rs +++ b/petri/src/vm/mod.rs @@ -60,6 +60,8 @@ pub trait PetriVmConfig: Send { ) -> Box; /// Sets whether UEFI frontpage is enabled. fn with_uefi_frontpage(self: Box, enable: bool) -> Box; + /// Enables Guest VSM. + fn with_guest_vsm(self: Box) -> Box; } /// Common processor topology information for the VM. diff --git a/petri/src/vm/openvmm/construct.rs b/petri/src/vm/openvmm/construct.rs index 556e166d27..1e14b970d2 100644 --- a/petri/src/vm/openvmm/construct.rs +++ b/petri/src/vm/openvmm/construct.rs @@ -406,6 +406,7 @@ impl PetriVmConfigOpenVmm { ged, vtl2_settings, framebuffer_access, + imc_hive: None, } .with_processor_topology(ProcessorTopology::default())) } diff --git a/petri/src/vm/openvmm/mod.rs b/petri/src/vm/openvmm/mod.rs index 51f7c13ca7..1fbdb4329a 100644 --- a/petri/src/vm/openvmm/mod.rs +++ b/petri/src/vm/openvmm/mod.rs @@ -127,6 +127,7 @@ pub struct PetriVmConfigOpenVmm { ged: Option, vtl2_settings: Option, framebuffer_access: Option, + imc_hive: Option<&'static [u8]>, } #[async_trait] @@ -182,6 +183,10 @@ impl PetriVmConfig for PetriVmConfigOpenVmm { fn with_uefi_frontpage(self: Box, enable: bool) -> Box { Box::new(Self::with_uefi_frontpage(*self, enable)) } + + fn with_guest_vsm(self: Box) -> Box { + Box::new(Self::with_guest_vsm(*self)) + } } /// Various channels and resources used to interact with the VM while it is running. diff --git a/petri/src/vm/openvmm/modify.rs b/petri/src/vm/openvmm/modify.rs index 2b14f920b1..cc6a035f57 100644 --- a/petri/src/vm/openvmm/modify.rs +++ b/petri/src/vm/openvmm/modify.rs @@ -6,6 +6,7 @@ use super::MANA_INSTANCE; use super::NIC_MAC_ADDRESS; use super::PetriVmConfigOpenVmm; +use crate::Firmware; use crate::ProcessorTopology; use chipset_resources::battery::BatteryDeviceHandleX64; use chipset_resources::battery::HostBatteryUpdate; @@ -23,6 +24,7 @@ use hvlite_defs::config::VpciDeviceConfig; use hvlite_defs::config::Vtl2BaseAddressType; use hvlite_helpers::disk::open_disk_type; use petri_artifacts_common::tags::MachineArch; +use petri_artifacts_common::tags::OsFlavor; use petri_artifacts_core::ResolvedArtifact; use std::path::Path; use tpm_resources::TpmDeviceHandle; @@ -434,4 +436,25 @@ impl PetriVmConfigOpenVmm { self } + + /// Enables Guest VSM for the VM. + /// + /// Note: Currently this disables the ability to run with pipette. + /// This will be addressed in the future. + pub fn with_guest_vsm(mut self) -> Self { + if !matches!( + self.firmware, + Firmware::OpenhclUefi { + isolation: Some(_), + .. + }, + ) { + panic!("Guest VSM is only supported for OpenHCL UEFI with isolation."); + } + if !matches!(self.os_flavor(), OsFlavor::Windows) { + panic!("Guest VSM is only supported for Windows."); + } + self.imc_hive = Some(include_bytes!("../../../guest-bootstrap/imc-vsm.hiv")); + self + } } diff --git a/petri/src/vm/openvmm/start.rs b/petri/src/vm/openvmm/start.rs index a54f21ee66..05078df54b 100644 --- a/petri/src/vm/openvmm/start.rs +++ b/petri/src/vm/openvmm/start.rs @@ -52,8 +52,28 @@ impl PetriVmConfigOpenVmm { ged, vtl2_settings, framebuffer_access, + + imc_hive, } = self; + if let Some(imc_hive_contents) = imc_hive { + assert!(matches!(firmware.os_flavor(), OsFlavor::Windows)); + // Make a file for the IMC hive. + let mut imc_hive_file = tempfile::tempfile().context("failed to create temp file")?; + imc_hive_file + .write_all(imc_hive_contents) + .context("failed to write imc hive")?; + + // Add the IMC device. + config.vmbus_devices.push(( + DeviceVtl::Vtl0, + vmbfs_resources::VmbfsImcDeviceHandle { + file: imc_hive_file, + } + .into_resource(), + )); + } + if firmware.is_openhcl() { // Add a pipette disk for VTL 2 const UH_CIDATA_SCSI_INSTANCE: Guid = @@ -158,6 +178,8 @@ impl PetriVmConfigOpenVmm { /// Build and boot the requested VM. Does not configure and start pipette. /// Should only be used for testing platforms that pipette does not support. pub async fn run_without_agent(self) -> anyhow::Result { + // TODO: Once we run pipette with VSM, uncomment this. + //assert!(self.imc_hive.is_none()); self.run_core().await } @@ -208,22 +230,9 @@ impl PetriVmConfigOpenVmm { .into_resource(), )); - if matches!(self.firmware.os_flavor(), OsFlavor::Windows) { - // Make a file for the IMC hive. It's not guaranteed to be at a fixed - // location at runtime. - let mut imc_hive_file = tempfile::tempfile().context("failed to create temp file")?; - imc_hive_file - .write_all(include_bytes!("../../../guest-bootstrap/imc-pipette.hiv")) - .context("failed to write imc hive")?; - - // Add the IMC device. - self.config.vmbus_devices.push(( - DeviceVtl::Vtl0, - vmbfs_resources::VmbfsImcDeviceHandle { - file: imc_hive_file, - } - .into_resource(), - )); + // If a pipette IMC hive hasn't been added, add the default one. + if self.imc_hive.is_none() && matches!(self.firmware.os_flavor(), OsFlavor::Windows) { + self.imc_hive = Some(include_bytes!("../../../guest-bootstrap/imc-pipette.hiv")); } let is_linux_direct = self.firmware.is_linux_direct(); diff --git a/vmm_tests/vmm_tests/tests/tests/multiarch.rs b/vmm_tests/vmm_tests/tests/tests/multiarch.rs index e5c5ee30ab..2a1bc4ab23 100644 --- a/vmm_tests/vmm_tests/tests/tests/multiarch.rs +++ b/vmm_tests/vmm_tests/tests/tests/multiarch.rs @@ -293,6 +293,19 @@ async fn boot_no_agent_single_proc(config: Box) -> anyhow::Re Ok(()) } +/// Basic boot test without agent but with VSM enabled. +#[cfg(windows)] +#[vmm_test_macros::hyperv_test( + hyperv_openhcl_uefi_x64[tdx](vhd(windows_datacenter_core_2025_x64)) +)] +async fn boot_no_agent_vsm(config: petri::hyperv::PetriVmConfigHyperV) -> anyhow::Result<()> { + let mut vm = config.with_guest_vsm().run_without_agent().await?; + vm.wait_for_successful_boot_event().await?; + vm.send_enlightened_shutdown(ShutdownKind::Shutdown).await?; + assert_eq!(vm.wait_for_teardown().await?, HaltReason::PowerOff); + Ok(()) +} + /// Basic reboot test without agent // TODO: Reenable guests that use the framebuffer once #74 is fixed. #[openvmm_test( From 1d5d55f9f2d35a783351da8dd59f0d091d0e8e8a Mon Sep 17 00:00:00 2001 From: Steven Malis Date: Tue, 6 May 2025 16:42:53 -0400 Subject: [PATCH 4/5] Add todo --- vmm_tests/vmm_tests/tests/tests/multiarch.rs | 1 + 1 file changed, 1 insertion(+) diff --git a/vmm_tests/vmm_tests/tests/tests/multiarch.rs b/vmm_tests/vmm_tests/tests/tests/multiarch.rs index 2a1bc4ab23..a15230017f 100644 --- a/vmm_tests/vmm_tests/tests/tests/multiarch.rs +++ b/vmm_tests/vmm_tests/tests/tests/multiarch.rs @@ -301,6 +301,7 @@ async fn boot_no_agent_single_proc(config: Box) -> anyhow::Re async fn boot_no_agent_vsm(config: petri::hyperv::PetriVmConfigHyperV) -> anyhow::Result<()> { let mut vm = config.with_guest_vsm().run_without_agent().await?; vm.wait_for_successful_boot_event().await?; + // TODO: Check that VSM is enabled in the guest. vm.send_enlightened_shutdown(ShutdownKind::Shutdown).await?; assert_eq!(vm.wait_for_teardown().await?, HaltReason::PowerOff); Ok(()) From 39438e0ce6098614c3d59ca8030771320723cb39 Mon Sep 17 00:00:00 2001 From: Steven Malis Date: Wed, 7 May 2025 12:08:52 -0400 Subject: [PATCH 5/5] Purposefully break VSM to see if the test fails --- openhcl/virt_mshv_vtl/src/processor/tdx/mod.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/openhcl/virt_mshv_vtl/src/processor/tdx/mod.rs b/openhcl/virt_mshv_vtl/src/processor/tdx/mod.rs index 774165fa90..d753dc4e18 100644 --- a/openhcl/virt_mshv_vtl/src/processor/tdx/mod.rs +++ b/openhcl/virt_mshv_vtl/src/processor/tdx/mod.rs @@ -2552,7 +2552,7 @@ impl UhProcessor<'_, TdxBacked> { // zero/write ignore. Ok(0) } - x86defs::X86X_MSR_CSTAR => Ok(self.backing.vtls[vtl].msr_cstar), + x86defs::X86X_MSR_CSTAR => Ok(0), x86defs::X86X_MSR_MCG_CAP => Ok(0), x86defs::X86X_MSR_MCG_STATUS => Ok(0), x86defs::X86X_MSR_MC_UPDATE_PATCH_LEVEL => Ok(0xFFFFFFFF),