diff --git a/openhcl/virt_mshv_vtl/src/cvm_cpuid/snp.rs b/openhcl/virt_mshv_vtl/src/cvm_cpuid/snp.rs index 815b3a48d92..08d4fa99e6f 100644 --- a/openhcl/virt_mshv_vtl/src/cvm_cpuid/snp.rs +++ b/openhcl/virt_mshv_vtl/src/cvm_cpuid/snp.rs @@ -411,6 +411,7 @@ impl CpuidArchInitializer for SnpCpuidInitializer { .with_enable_extended_gva_ranges_flush_va_list(true) .with_access_guest_idle_msr(true) .with_access_vsm(self.access_vsm) + .with_access_vp_registers(true) .with_isolation(true) .with_fast_hypercall_output(true); diff --git a/openhcl/virt_mshv_vtl/src/cvm_cpuid/tdx.rs b/openhcl/virt_mshv_vtl/src/cvm_cpuid/tdx.rs index 1e6d5574465..a19fe8d3bf5 100644 --- a/openhcl/virt_mshv_vtl/src/cvm_cpuid/tdx.rs +++ b/openhcl/virt_mshv_vtl/src/cvm_cpuid/tdx.rs @@ -309,6 +309,7 @@ impl CpuidArchInitializer for TdxCpuidInitializer<'_> { .with_enable_extended_gva_ranges_flush_va_list(true) .with_access_guest_idle_msr(true) .with_access_vsm(self.access_vsm) + .with_access_vp_registers(true) .with_isolation(true) .with_fast_hypercall_output(true); diff --git a/openhcl/virt_mshv_vtl/src/lib.rs b/openhcl/virt_mshv_vtl/src/lib.rs index 919e14656b8..d3cd08190fd 100755 --- a/openhcl/virt_mshv_vtl/src/lib.rs +++ b/openhcl/virt_mshv_vtl/src/lib.rs @@ -373,6 +373,8 @@ struct GuestVsmVpState { #[inspect(with = "|x| x.as_ref().map(inspect::AsDebug)")] vtl0_exit_pending_event: Option, reg_intercept: SecureRegisterInterceptState, + /// Whether Mode-Based Execution Control is enabled on this VP. + vp_mbec_enabled: bool, } #[cfg(guest_arch = "x86_64")] @@ -381,6 +383,7 @@ impl GuestVsmVpState { GuestVsmVpState { vtl0_exit_pending_event: None, reg_intercept: Default::default(), + vp_mbec_enabled: false, } } } @@ -495,6 +498,8 @@ struct UhCvmPartitionState { hv: GlobalHv<2>, /// Guest VSM state. guest_vsm: RwLock>, + /// Whether the partition has the access vsm privilege. + access_vsm_privilege: bool, /// Dma client for shared visibility pages. shared_dma_client: Arc, /// Dma client for private visibility pages. @@ -520,6 +525,13 @@ impl UhCvmPartitionState { } ) } + + /// The access vsm privilege at the time of partition creation. Per VSM + /// spec, it is not updated if VTL 1 is later revoked. Used for validating + /// register access that depends only on the privilege availability. + fn access_vsm_privilege(&self) -> bool { + self.access_vsm_privilege + } } #[derive(Inspect)] @@ -539,13 +551,22 @@ struct UhCvmVpInner { proxy_redirect_interrupts: Mutex>, } +// TODO Guest VSM: cleanup these states for better clarity #[cfg_attr(guest_arch = "aarch64", expect(dead_code))] #[derive(Inspect)] #[inspect(tag = "guest_vsm_state")] /// Partition-wide state for guest vsm. enum GuestVsmState { + /// Whether VTL 1 is available. If the platform does not support VTL 1, or + /// VTL 1 was revoked, then the partition will be in this state. Note: some + /// vsm-related functionality may be available even if the state is + /// NotPlatformSupported. NotPlatformSupported, + /// OpenHCL has not yet handled the guest calling EnablePartitionVtl. NotGuestEnabled, + /// Note: this state is only used for CVMs. For non-CVMs, this is not an + /// accurate reflection of whether VTL 1 is enabled since the hypercall + /// goes to the hypervisor. Enabled { #[inspect(flatten)] vtl1: T, @@ -2288,6 +2309,7 @@ impl UhProtoPartition<'_> { lapic, hv, guest_vsm: RwLock::new(GuestVsmState::from_availability(guest_vsm_available)), + access_vsm_privilege: guest_vsm_available, shared_dma_client: late_params.shared_dma_client, private_dma_client: late_params.private_dma_client, hide_isolation: params.hide_isolation, diff --git a/openhcl/virt_mshv_vtl/src/processor/hardware_cvm/mod.rs b/openhcl/virt_mshv_vtl/src/processor/hardware_cvm/mod.rs index 762ff137e2e..1e807f92d39 100644 --- a/openhcl/virt_mshv_vtl/src/processor/hardware_cvm/mod.rs +++ b/openhcl/virt_mshv_vtl/src/processor/hardware_cvm/mod.rs @@ -26,6 +26,7 @@ use hv1_emulator::RequestInterrupt; use hv1_hypercall::HvRepResult; use hv1_structs::ProcessorSet; use hv1_structs::VtlArray; +use hv1_structs::VtlSet; use hvdef::HvCacheType; use hvdef::HvError; use hvdef::HvInterceptAccessType; @@ -368,6 +369,26 @@ impl UhHypercallHandler<'_, '_, B> { } Ok(()) } + HvX64RegisterName::VsmPartitionConfig => { + if target_vtl != GuestVtl::Vtl1 { + return Err(HvError::InvalidParameter); + } + if self.intercepted_vtl == GuestVtl::Vtl0 + && !self.vp.cvm_partition().access_vsm_privilege() + { + return Err(HvError::AccessDenied); + } + Ok(()) + } + HvX64RegisterName::VsmPartitionStatus | HvX64RegisterName::VsmCapabilities => { + if self.intercepted_vtl == GuestVtl::Vtl0 + && !self.vp.cvm_partition().access_vsm_privilege() + { + return Err(HvError::AccessDenied); + } + Ok(()) + } + _ => Ok(()), } } @@ -394,6 +415,99 @@ impl UhHypercallHandler<'_, '_, B> { // clean this up. match name.into() { + HvX64RegisterName::VsmPartitionConfig => { + let guest_vsm = self.vp.cvm_partition().guest_vsm.read(); + let GuestVsmState::Enabled { vtl1, .. } = &*guest_vsm else { + return Ok(HvRegisterValue::from(0u64)); + }; + + let protector = &self.vp.cvm_partition().isolated_memory_protector; + let default_protections = protector.default_vtl0_protections(); + Ok(u64::from( + HvRegisterVsmPartitionConfig::new() + .with_enable_vtl_protection(protector.vtl1_protections_enabled()) + .with_default_vtl_protection_mask(u32::from(default_protections) as u8) + .with_zero_memory_on_reset(vtl1.zero_memory_on_reset) + .with_deny_lower_vtl_startup(vtl1.deny_lower_vtl_startup), + ) + .into()) + } + HvX64RegisterName::VsmPartitionStatus => { + let guest_vsm = self.vp.cvm_partition().guest_vsm.read(); + let (enabled_vtl_set, maximum_vtl, mbec_enabled_vtl_set, sss_enabled_vtl_set) = + match &*guest_vsm { + GuestVsmState::Enabled { vtl1, .. } => { + let enabled_vtls = + VtlSet::new().with_vtl(Vtl::Vtl0).with_vtl(Vtl::Vtl1); + + let mbec_enabled_vtls = if vtl1.mbec_enabled { + enabled_vtls + } else { + VtlSet::new() + }; + + let sss_enabled_vtls = if vtl1.shadow_supervisor_stack_enabled { + enabled_vtls + } else { + VtlSet::new() + }; + + ( + u16::from(enabled_vtls), + 1u8, + u16::from(mbec_enabled_vtls), + u16::from(sss_enabled_vtls) as u8, + ) + } + GuestVsmState::NotGuestEnabled => { + let enabled_vtls = VtlSet::new().with_vtl(Vtl::Vtl0); + (u16::from(enabled_vtls), 1u8, 0u16, 0u8) + } + GuestVsmState::NotPlatformSupported => { + let enabled_vtls = VtlSet::new().with_vtl(Vtl::Vtl0); + (u16::from(enabled_vtls), 0u8, 0u16, 0u8) + } + }; + + Ok(u64::from( + hvdef::HvRegisterVsmPartitionStatus::new() + .with_enabled_vtl_set(enabled_vtl_set) + .with_maximum_vtl(maximum_vtl) + .with_mbec_enabled_vtl_set(mbec_enabled_vtl_set) + .with_supervisor_shadow_stack_enabled_vtl_set(sss_enabled_vtl_set), + ) + .into()) + } + HvX64RegisterName::VsmVpStatus => { + let active_vtl = self.intercepted_vtl; + let active_mbec_enabled = self + .vp + .backing + .cvm_state() + .vtl1 + .as_ref() + .is_some_and(|s| s.vp_mbec_enabled); + let mut enabled_vtls = VtlSet::new(); + enabled_vtls.set(Vtl::Vtl0); + if *self + .vp + .cvm_partition() + .vp_inner(self.vp.vp_index().index()) + .vtl1_enable_called + .lock() + { + enabled_vtls.set(Vtl::Vtl1); + } + let enabled_vtl_set = u16::from(enabled_vtls); + + Ok(u64::from( + hvdef::HvRegisterVsmVpStatus::new() + .with_active_vtl(active_vtl as u8) + .with_active_mbec_enabled(active_mbec_enabled) + .with_enabled_vtl_set(enabled_vtl_set), + ) + .into()) + } HvX64RegisterName::VsmCodePageOffsets => Ok(u64::from( self.vp.backing.cvm_state_mut().hv[vtl].vsm_code_page_offsets(true), ) @@ -401,7 +515,8 @@ impl UhHypercallHandler<'_, '_, B> { HvX64RegisterName::VsmCapabilities => Ok(u64::from( hvdef::HvRegisterVsmCapabilities::new() .with_deny_lower_vtl_startup(true) - .with_dr6_shared(self.vp.partition.hcl.dr6_shared()), + .with_dr6_shared(self.vp.partition.hcl.dr6_shared()) + .with_mbec_vtl_mask(VtlSet::new().with_vtl(Vtl::Vtl0).into()), ) .into()), HvX64RegisterName::VsmVpSecureConfigVtl0 => { @@ -1725,9 +1840,15 @@ impl hv1_hypercall::EnableVpVtl HvResult<()> { + let target_vp = if vp_index == hvdef::HV_VP_INDEX_SELF { + self.vp.vp_index().index() + } else { + vp_index + }; + tracing::debug!( vp_index = self.vp.vp_index().index(), - target_vp = vp_index, + target_vp, ?vtl, "HvEnableVpVtl" ); @@ -1735,7 +1856,7 @@ impl hv1_hypercall::EnableVpVtl= self.vp.partition.vps.len() { + if target_vp as usize >= self.vp.partition.vps.len() { return Err(HvError::InvalidVpIndex); } @@ -1766,7 +1887,7 @@ impl hv1_hypercall::EnableVpVtl hv1_hypercall::EnableVpVtl hv1_hypercall::EnableVpVtl { // For VTL 1, user mode needs to explicitly register the VMSA // with the hypervisor via the EnableVpVtl hypercall. - let target_cpu_index = self.vp.partition.vps[vp_index as usize].cpu_index; + let target_cpu_index = self.vp.partition.vps[target_vp as usize].cpu_index; let vmsa_pfn = self.vp.partition.hcl.vtl1_vmsa_pfn(target_cpu_index); let sev_control = hvdef::HvX64RegisterSevControl::new() .with_enable_encrypted_state(true) @@ -1817,7 +1938,7 @@ impl hv1_hypercall::EnableVpVtl hv1_hypercall::EnableVpVtl UhProcessor<'_, B> { .into_cpuid(); } } - CpuidFunction(hvdef::HV_CPUID_FUNCTION_MS_HV_FEATURES) => { - // Update the VSM access privilege if it's been revoked by UEFI. - if matches!( - *self.cvm_partition().guest_vsm.read(), - GuestVsmState::NotPlatformSupported - ) { - let mut features = hvdef::HvFeatures::from_cpuid([eax, ebx, ecx, edx]); - features.set_privileges(features.privileges().with_access_vsm(false)); - [eax, ebx, ecx, edx] = features.into_cpuid(); - } - } _ => {} } @@ -2670,6 +2780,14 @@ impl UhProcessor<'_, B> { _ => (), // Nothing to do }; + // Setting this on any VTL will enable it for all VTLs. This matches + // the hypervisor behavior, but for all intents and purposes only VTL 1 + // will be able to set this on VTL 0, so only a single VTL can have + // this configured anyway. + if let Some(vtl1_state) = self.backing.cvm_state_mut().vtl1.as_mut() { + vtl1_state.vp_mbec_enabled = config.mbec_enabled(); + } + Ok(()) } diff --git a/vm/hv1/hv1_structs/src/vtl_array.rs b/vm/hv1/hv1_structs/src/vtl_array.rs index 0c35cd7c2ec..164a2f8537c 100644 --- a/vm/hv1/hv1_structs/src/vtl_array.rs +++ b/vm/hv1/hv1_structs/src/vtl_array.rs @@ -201,6 +201,12 @@ impl VtlSet { .rev() .map(|i| Vtl::try_from(i as u8).unwrap()) } + + /// Sets the given [`Vtl`] in the set and returns self. + pub fn with_vtl(mut self, vtl: Vtl) -> Self { + self.set(vtl); + self + } } impl Inspect for VtlSet { @@ -217,6 +223,12 @@ impl From for VtlSet { } } +impl From for u16 { + fn from(set: VtlSet) -> Self { + set.bits.into_inner() + } +} + #[cfg(test)] mod tests { use super::VtlSet;