Skip to content
Open
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
1 change: 1 addition & 0 deletions openhcl/virt_mshv_vtl/src/cvm_cpuid/snp.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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);

Expand Down
1 change: 1 addition & 0 deletions openhcl/virt_mshv_vtl/src/cvm_cpuid/tdx.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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);

Expand Down
22 changes: 22 additions & 0 deletions openhcl/virt_mshv_vtl/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -373,6 +373,8 @@ struct GuestVsmVpState {
#[inspect(with = "|x| x.as_ref().map(inspect::AsDebug)")]
vtl0_exit_pending_event: Option<hvdef::HvX64PendingExceptionEvent>,
reg_intercept: SecureRegisterInterceptState,
/// Whether Mode-Based Execution Control is enabled on this VP.
vp_mbec_enabled: bool,

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Is this different from CvmVtl1State's mbec_enabled?

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

CvmVtl1State is per-partition, no? This one is per-vp and controlled by a different register. Although, this is making me think I'm missing some validation later on in consuming this variable, so I should look into that.

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

It is per partition but I believe we're enforcing that every VP must match already, see my other comment.

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actually I think it can be different on initialization. Mbec enabled can be false for the vp and true for the partition. But when it's configured for the vp, the new mbec enabled value should be the same as on the partition.

}

#[cfg(guest_arch = "x86_64")]
Expand All @@ -381,6 +383,7 @@ impl GuestVsmVpState {
GuestVsmVpState {
vtl0_exit_pending_event: None,
reg_intercept: Default::default(),
vp_mbec_enabled: false,
}
}
}
Expand Down Expand Up @@ -495,6 +498,8 @@ struct UhCvmPartitionState {
hv: GlobalHv<2>,
/// Guest VSM state.
guest_vsm: RwLock<GuestVsmState<CvmVtl1State>>,
/// Whether the partition has the access vsm privilege.
access_vsm_privilege: bool,
/// Dma client for shared visibility pages.
shared_dma_client: Arc<dyn DmaClient>,
/// Dma client for private visibility pages.
Expand All @@ -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
}
Comment thread
smalis-msft marked this conversation as resolved.
}

#[derive(Inspect)]
Expand All @@ -539,13 +551,22 @@ struct UhCvmVpInner {
proxy_redirect_interrupts: Mutex<HashMap<u32, ProxyRedirectVectorInfo>>,
}

// 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<T: Inspect> {
/// 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,
Expand Down Expand Up @@ -2288,6 +2309,7 @@ impl UhProtoPartition<'_> {
lapic,
hv,
guest_vsm: RwLock::new(GuestVsmState::from_availability(guest_vsm_available)),
access_vsm_privilege: guest_vsm_available,
Comment thread
smalis-msft marked this conversation as resolved.
shared_dma_client: late_params.shared_dma_client,
private_dma_client: late_params.private_dma_client,
hide_isolation: params.hide_isolation,
Expand Down
160 changes: 139 additions & 21 deletions openhcl/virt_mshv_vtl/src/processor/hardware_cvm/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand Down Expand Up @@ -368,6 +369,26 @@ impl<B: HardwareIsolatedBacking> 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(()),
}
}
Expand All @@ -394,14 +415,108 @@ impl<B: HardwareIsolatedBacking> 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));
};
Comment thread
sluck-msft marked this conversation as resolved.

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),
)
.into()),
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 => {
Expand Down Expand Up @@ -1725,17 +1840,23 @@ impl<B: HardwareIsolatedBacking> hv1_hypercall::EnableVpVtl<hvdef::hypercall::In
vtl: Vtl,
vp_context: &hvdef::hypercall::InitialVpContextX64,
) -> 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"
);
if partition_id != hvdef::HV_PARTITION_ID_SELF {
return Err(HvError::InvalidPartitionId);
}

if vp_index as usize >= self.vp.partition.vps.len() {
if target_vp as usize >= self.vp.partition.vps.len() {
return Err(HvError::InvalidVpIndex);
}

Expand Down Expand Up @@ -1766,7 +1887,7 @@ impl<B: HardwareIsolatedBacking> hv1_hypercall::EnableVpVtl<hvdef::hypercall::In
// the higher VTL has not been enabled on any other VP because at that
// point, the higher VTL should be orchestrating its own enablement.
if self.intercepted_vtl < GuestVtl::Vtl1 {
if vtl1.enabled_on_any_vp || vp_index != current_vp_index {
if vtl1.enabled_on_any_vp || target_vp != current_vp_index {
return Err(HvError::AccessDenied);
}

Expand All @@ -1785,7 +1906,7 @@ impl<B: HardwareIsolatedBacking> hv1_hypercall::EnableVpVtl<hvdef::hypercall::In
let mut vtl1_enabled = self
.vp
.cvm_partition()
.vp_inner(vp_index)
.vp_inner(target_vp)
.vtl1_enable_called
.lock();

Expand All @@ -1798,7 +1919,7 @@ impl<B: HardwareIsolatedBacking> hv1_hypercall::EnableVpVtl<hvdef::hypercall::In
virt::IsolationType::Snp => {
// 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)
Expand All @@ -1817,7 +1938,7 @@ impl<B: HardwareIsolatedBacking> hv1_hypercall::EnableVpVtl<hvdef::hypercall::In
self.vp
.partition
.hcl
.enable_vp_vtl(vp_index, vtl, hv_vp_context)?;
.enable_vp_vtl(target_vp, vtl, hv_vp_context)?;

// Cannot fail from here
if let Some(mut vtl1) = gvsm_state {
Expand All @@ -1837,12 +1958,12 @@ impl<B: HardwareIsolatedBacking> hv1_hypercall::EnableVpVtl<hvdef::hypercall::In
*self
.vp
.cvm_partition()
.vp_inner(vp_index)
.vp_inner(target_vp)
.hv_start_enable_vtl_vp[vtl]
.lock() = Some(Box::new(enable_vp_vtl_state));
self.vp.partition.vps[vp_index as usize].wake(vtl, WakeReason::HV_START_ENABLE_VP_VTL);
self.vp.partition.vps[target_vp as usize].wake(vtl, WakeReason::HV_START_ENABLE_VP_VTL);

tracing::debug!(vp_index, "enabled vtl 1 on vp");
tracing::debug!(target_vp, "enabled vtl 1 on vp");

Ok(())
}
Expand Down Expand Up @@ -2078,17 +2199,6 @@ impl<B: HardwareIsolatedBacking> 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();
}
}

_ => {}
}
Expand Down Expand Up @@ -2670,6 +2780,14 @@ impl<B: HardwareIsolatedBacking> 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() {

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I don't think this is necessary, since we know that every vp must match the original vtl1 config, checked 10 lines above.

vtl1_state.vp_mbec_enabled = config.mbec_enabled();
}

Ok(())
}

Expand Down
12 changes: 12 additions & 0 deletions vm/hv1/hv1_structs/src/vtl_array.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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 {
Expand All @@ -217,6 +223,12 @@ impl From<u16> for VtlSet {
}
}

impl From<VtlSet> for u16 {
fn from(set: VtlSet) -> Self {
set.bits.into_inner()
}
}
Comment on lines +226 to +230

#[cfg(test)]
mod tests {
use super::VtlSet;
Expand Down
Loading