Skip to content

Add a vmm test for TDX with Guest VSM, Credential Guard, and HVCI #1305

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Draft
wants to merge 5 commits into
base: main
Choose a base branch
from
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion openhcl/virt_mshv_vtl/src/processor/tdx/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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),
Expand Down
1 change: 1 addition & 0 deletions openvmm/openvmm_entry/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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,
Expand Down
12 changes: 7 additions & 5 deletions petri/guest-bootstrap/README.md
Original file line number Diff line number Diff line change
@@ -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 <type> PATH/TO/imc.hiv`
Binary file not shown.
Binary file added petri/guest-bootstrap/imc-vsm.hiv
Binary file not shown.
70 changes: 53 additions & 17 deletions petri/make_imc_hive/src/windows/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -5,26 +5,19 @@ mod offreg;

use self::offreg::Hive;
use anyhow::Context;
use offreg::OwnedKey;

pub(crate) fn main() -> 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
Expand All @@ -35,3 +28,46 @@ pub(crate) fn main() -> anyhow::Result<()> {
hive.save(path.as_ref())?;
Ok(())
}

fn subkey(hive: &Hive, path: &str) -> anyhow::Result<OwnedKey> {
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(())
}
103 changes: 69 additions & 34 deletions petri/src/vm/hyperv/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand All @@ -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,
Expand All @@ -68,6 +70,7 @@ pub struct PetriVmConfigHyperV {

os_flavor: OsFlavor,
expected_boot_event: Option<FirmwareEvent>,
imc_hive: Option<&'static [u8]>,

// Folder to store temporary data for this test
temp_dir: tempfile::TempDir,
Expand Down Expand Up @@ -128,6 +131,10 @@ impl PetriVmConfig for PetriVmConfigHyperV {
fn with_uefi_frontpage(self: Box<Self>, enable: bool) -> Box<dyn PetriVmConfig> {
Box::new(Self::with_uefi_frontpage(*self, enable))
}

fn with_guest_vsm(self: Box<Self>) -> Box<dyn PetriVmConfig> {
Box::new(Self::with_guest_vsm(*self))
}
}

/// A running VM that tests can interact with.
Expand Down Expand Up @@ -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,
),
Expand All @@ -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),
),
Expand All @@ -278,16 +285,17 @@ 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
}
OsFlavor::FreeBsd | OsFlavor::Uefi => {
powershell::HyperVSecureBootTemplate::SecureBootDisabled
}
}),
},
),
openhcl_igvm,
agent_image,
openhcl_agent_image,
Expand All @@ -298,6 +306,7 @@ impl PetriVmConfigHyperV {
log_source: params.logger.clone(),
disable_frontpage: true,
openhcl_command_line: String::new(),
imc_hive: None,
})
}

Expand Down Expand Up @@ -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,
Expand All @@ -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)?)
}
};
Expand Down Expand Up @@ -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.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)?;
Expand All @@ -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
),
)?;

Expand Down Expand Up @@ -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);
Expand Down Expand Up @@ -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 {
Expand Down
2 changes: 2 additions & 0 deletions petri/src/vm/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -60,6 +60,8 @@ pub trait PetriVmConfig: Send {
) -> Box<dyn PetriVmConfig>;
/// Sets whether UEFI frontpage is enabled.
fn with_uefi_frontpage(self: Box<Self>, enable: bool) -> Box<dyn PetriVmConfig>;
/// Enables Guest VSM.
fn with_guest_vsm(self: Box<Self>) -> Box<dyn PetriVmConfig>;
}

/// Common processor topology information for the VM.
Expand Down
2 changes: 2 additions & 0 deletions petri/src/vm/openvmm/construct.rs
Original file line number Diff line number Diff line change
Expand Up @@ -406,6 +406,7 @@ impl PetriVmConfigOpenVmm {
ged,
vtl2_settings,
framebuffer_access,
imc_hive: None,
}
.with_processor_topology(ProcessorTopology::default()))
}
Expand Down Expand Up @@ -840,6 +841,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,
Expand Down
5 changes: 5 additions & 0 deletions petri/src/vm/openvmm/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -127,6 +127,7 @@ pub struct PetriVmConfigOpenVmm {
ged: Option<get_resources::ged::GuestEmulationDeviceHandle>,
vtl2_settings: Option<Vtl2Settings>,
framebuffer_access: Option<FramebufferAccess>,
imc_hive: Option<&'static [u8]>,
}

#[async_trait]
Expand Down Expand Up @@ -182,6 +183,10 @@ impl PetriVmConfig for PetriVmConfigOpenVmm {
fn with_uefi_frontpage(self: Box<Self>, enable: bool) -> Box<dyn PetriVmConfig> {
Box::new(Self::with_uefi_frontpage(*self, enable))
}

fn with_guest_vsm(self: Box<Self>) -> Box<dyn PetriVmConfig> {
Box::new(Self::with_guest_vsm(*self))
}
}

/// Various channels and resources used to interact with the VM while it is running.
Expand Down
Loading