diff --git a/petri/src/vm/hyperv/mod.rs b/petri/src/vm/hyperv/mod.rs index 04e816f442..becb330f76 100644 --- a/petri/src/vm/hyperv/mod.rs +++ b/petri/src/vm/hyperv/mod.rs @@ -90,10 +90,18 @@ impl PetriVmConfig for PetriVmConfigHyperV { Ok((Box::new(vm), client)) } + fn with_secure_boot(self: Box) -> Box { + Box::new(Self::with_secure_boot(*self)) + } + fn with_windows_secure_boot_template(self: Box) -> Box { Box::new(Self::with_windows_secure_boot_template(*self)) } + fn with_uefi_ca_template(self: Box) -> Box { + Box::new(Self::with_uefi_ca_template(*self)) + } + fn with_processor_topology( self: Box, topology: ProcessorTopology, @@ -278,16 +286,7 @@ 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() { - OsFlavor::Windows => powershell::HyperVSecureBootTemplate::MicrosoftWindows, - OsFlavor::Linux => { - powershell::HyperVSecureBootTemplate::MicrosoftUEFICertificateAuthority - } - OsFlavor::FreeBsd | OsFlavor::Uefi => { - powershell::HyperVSecureBootTemplate::SecureBootDisabled - } - }), + secure_boot_template: None, openhcl_igvm, agent_image, openhcl_agent_image, @@ -535,6 +534,20 @@ impl PetriVmConfigHyperV { self } + /// Set the VM to enable secure boot and inject the templates. + pub fn with_secure_boot(self) -> Self { + if !matches!(self.generation, powershell::HyperVGeneration::Two) { + panic!("Secure boot is only supported for UEFI firmware."); + } + match self.os_flavor { + OsFlavor::Windows => self.with_windows_secure_boot_template(), + OsFlavor::Linux => self.with_uefi_ca_template(), + OsFlavor::FreeBsd | OsFlavor::Uefi => { + panic!("Secure boot is not supported for this OS flavor.") + } + } + } + /// 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) { @@ -544,6 +557,16 @@ impl PetriVmConfigHyperV { self } + /// Inject UEFI CA template into the VM's UEFI + pub fn with_uefi_ca_template(mut self) -> Self { + if !matches!(self.generation, powershell::HyperVGeneration::Two) { + panic!("Secure boot templates are only supported for UEFI firmware."); + } + self.secure_boot_template = + Some(powershell::HyperVSecureBootTemplate::MicrosoftUEFICertificateAuthority); + self + } + /// Sets a custom OpenHCL IGVM image to use. pub fn with_custom_openhcl(mut self, artifact: ResolvedArtifact) -> Self { self.openhcl_igvm = Some(artifact); diff --git a/petri/src/vm/hyperv/vm.rs b/petri/src/vm/hyperv/vm.rs index fc4e748dcb..b81a371608 100644 --- a/petri/src/vm/hyperv/vm.rs +++ b/petri/src/vm/hyperv/vm.rs @@ -119,6 +119,14 @@ impl HyperVVM { }, )?; + // Disable secure boot for generation 2 VMs + if generation == powershell::HyperVGeneration::Two { + powershell::run_set_vm_firmware(powershell::HyperVSetVMFirmwareArgs { + vmid: &vmid, + secure_boot_template: None, + })?; + } + Ok(this) } diff --git a/petri/src/vm/mod.rs b/petri/src/vm/mod.rs index c94c394d1a..6071754a39 100644 --- a/petri/src/vm/mod.rs +++ b/petri/src/vm/mod.rs @@ -35,8 +35,12 @@ pub trait PetriVmConfig: Send { /// Run the VM, launching pipette and returning a client to it. async fn run(self: Box) -> anyhow::Result<(Box, PipetteClient)>; + /// Set the VM to enable secure boot and inject the templates. + fn with_secure_boot(self: Box) -> Box; /// Inject Windows secure boot templates into the VM's UEFI. fn with_windows_secure_boot_template(self: Box) -> Box; + /// Inject UEFI CA template into the VM's UEFI. + fn with_uefi_ca_template(self: Box) -> Box; /// Set the VM to use the specified processor topology. fn with_processor_topology( self: Box, diff --git a/petri/src/vm/openvmm/mod.rs b/petri/src/vm/openvmm/mod.rs index 2d4ce21268..7f6f3d635f 100644 --- a/petri/src/vm/openvmm/mod.rs +++ b/petri/src/vm/openvmm/mod.rs @@ -152,10 +152,18 @@ impl PetriVmConfig for PetriVmConfigOpenVmm { Ok((Box::new(vm), client)) } + fn with_secure_boot(self: Box) -> Box { + Box::new(Self::with_secure_boot(*self)) + } + fn with_windows_secure_boot_template(self: Box) -> Box { Box::new(Self::with_windows_secure_boot_template(*self)) } + fn with_uefi_ca_template(self: Box) -> Box { + Box::new(Self::with_uefi_ca_template(*self)) + } + fn with_processor_topology( self: Box, topology: ProcessorTopology, diff --git a/petri/src/vm/openvmm/modify.rs b/petri/src/vm/openvmm/modify.rs index aeccc29ae3..0780c8cf3c 100644 --- a/petri/src/vm/openvmm/modify.rs +++ b/petri/src/vm/openvmm/modify.rs @@ -22,6 +22,7 @@ use hvlite_defs::config::VpciDeviceConfig; use hvlite_defs::config::Vtl2BaseAddressType; use petri_artifacts_common::tags::IsTestVmgs; use petri_artifacts_common::tags::MachineArch; +use petri_artifacts_common::tags::OsFlavor; use petri_artifacts_core::ResolvedArtifact; use tpm_resources::TpmDeviceHandle; use tpm_resources::TpmRegisterLayout; @@ -127,17 +128,28 @@ impl PetriVmConfigOpenVmm { self } - /// Enable secure boot for the VM. + /// Set the VM to enable secure boot and inject the templates. pub fn with_secure_boot(mut self) -> Self { + // Restrict to UEFI firmware if !self.firmware.is_uefi() { panic!("Secure boot is only supported for UEFI firmware."); } + + // Enable secure boot if self.firmware.is_openhcl() { self.ged.as_mut().unwrap().secure_boot_enabled = true; } else { self.config.secure_boot_enabled = true; } - self + + // Inject templates per os flavor + match self.os_flavor() { + OsFlavor::Windows => self.with_windows_secure_boot_template(), + OsFlavor::Linux => self.with_uefi_ca_template(), + OsFlavor::FreeBsd | OsFlavor::Uefi => { + panic!("Secure boot is not supported for this OS flavor.") + } + } } /// Inject Windows secure boot templates into the VM's UEFI. @@ -154,6 +166,20 @@ impl PetriVmConfigOpenVmm { self } + /// Inject UEFI CA template into the VM's UEFI + pub fn with_uefi_ca_template(mut self) -> Self { + if !self.firmware.is_uefi() { + panic!("Secure boot templates are only supported for UEFI firmware."); + } + if self.firmware.is_openhcl() { + self.ged.as_mut().unwrap().secure_boot_template = + get_resources::ged::GuestSecureBootTemplateType::MicrosoftUefiCertificateAuthoritiy; + } else { + self.config.custom_uefi_vars = hyperv_secure_boot_templates::x64::microsoft_uefi_ca(); + } + self + } + /// Enable the battery for the VM. pub fn with_battery(mut self) -> Self { if self.firmware.is_openhcl() { diff --git a/vmm_tests/vmm_tests/tests/tests/multiarch.rs b/vmm_tests/vmm_tests/tests/tests/multiarch.rs index 3c5a9a17ec..cd013f22d0 100644 --- a/vmm_tests/vmm_tests/tests/tests/multiarch.rs +++ b/vmm_tests/vmm_tests/tests/tests/multiarch.rs @@ -67,6 +67,74 @@ async fn boot(config: Box) -> anyhow::Result<()> { Ok(()) } +/// Basic boot test with secure boot enabled. +#[vmm_test( + openvmm_uefi_aarch64(vhd(ubuntu_2404_server_aarch64)), + openvmm_uefi_x64(vhd(windows_datacenter_core_2022_x64)), + openvmm_uefi_x64(vhd(ubuntu_2204_server_x64)), + openvmm_openhcl_uefi_x64(vhd(windows_datacenter_core_2022_x64)), + openvmm_openhcl_uefi_x64(vhd(ubuntu_2204_server_x64)), + hyperv_uefi_aarch64(vhd(windows_11_enterprise_aarch64)), + hyperv_uefi_aarch64(vhd(ubuntu_2404_server_aarch64)), + hyperv_uefi_x64(vhd(windows_datacenter_core_2022_x64)), + hyperv_uefi_x64(vhd(ubuntu_2204_server_x64)), + hyperv_openhcl_uefi_aarch64(vhd(windows_11_enterprise_aarch64)), + hyperv_openhcl_uefi_aarch64(vhd(ubuntu_2404_server_aarch64)), + hyperv_openhcl_uefi_x64(vhd(windows_datacenter_core_2022_x64)), + hyperv_openhcl_uefi_x64(vhd(ubuntu_2204_server_x64)) +)] +async fn secure_boot(config: Box) -> anyhow::Result<()> { + let (vm, agent) = config.with_secure_boot().run().await?; + agent.power_off().await?; + assert_eq!(vm.wait_for_teardown().await?, HaltReason::PowerOff); + Ok(()) +} + +/// Verify that Windows UEFI guests fail with a mismatched secure boot template. +#[vmm_test( + openvmm_uefi_x64(vhd(windows_datacenter_core_2022_x64)), + openvmm_openhcl_uefi_x64(vhd(windows_datacenter_core_2022_x64)), + hyperv_uefi_aarch64(vhd(windows_11_enterprise_aarch64)), + hyperv_uefi_x64(vhd(windows_datacenter_core_2022_x64)), + hyperv_openhcl_uefi_aarch64(vhd(windows_11_enterprise_aarch64)), + hyperv_openhcl_uefi_x64(vhd(windows_datacenter_core_2022_x64)) +)] +async fn mismatched_secure_boot_template_windows( + config: Box, +) -> anyhow::Result<()> { + let mut vm = config + .with_secure_boot() + .with_uefi_ca_template() + .run_without_agent() + .await?; + assert_eq!(vm.wait_for_boot_event().await?, FirmwareEvent::BootFailed); + assert_eq!(vm.wait_for_teardown().await?, HaltReason::PowerOff); + Ok(()) +} + +/// Verify that Linux UEFI guests fail with a mismatched secure boot template. +#[vmm_test( + openvmm_uefi_aarch64(vhd(ubuntu_2404_server_aarch64)), + openvmm_uefi_x64(vhd(ubuntu_2204_server_x64)), + openvmm_openhcl_uefi_x64(vhd(ubuntu_2204_server_x64)), + hyperv_uefi_aarch64(vhd(ubuntu_2404_server_aarch64)), + hyperv_uefi_x64(vhd(ubuntu_2204_server_x64)), + hyperv_openhcl_uefi_aarch64(vhd(ubuntu_2404_server_aarch64)), + hyperv_openhcl_uefi_x64(vhd(ubuntu_2204_server_x64)) +)] +async fn mismatched_secure_boot_template_linux( + config: Box, +) -> anyhow::Result<()> { + let mut vm = config + .with_secure_boot() + .with_windows_secure_boot_template() + .run_without_agent() + .await?; + assert_eq!(vm.wait_for_boot_event().await?, FirmwareEvent::BootFailed); + assert_eq!(vm.wait_for_teardown().await?, HaltReason::PowerOff); + Ok(()) +} + /// Basic boot test for guests that are expected to reboot // TODO: Remove this test and other enable Windows 11 ARM OpenVMM tests // once we figure out how to get the guest to not reboot via IMC or other