Skip to content

SNP: Secure AVIC support #1172

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 24 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
1 change: 1 addition & 0 deletions Cargo.lock
Original file line number Diff line number Diff line change
Expand Up @@ -9637,6 +9637,7 @@ dependencies = [
"arbitrary",
"bitfield-struct 0.10.1",
"open_enum",
"static_assertions",
"zerocopy 0.8.24",
]

Expand Down
14 changes: 12 additions & 2 deletions openhcl/hcl/src/ioctl.rs
Original file line number Diff line number Diff line change
Expand Up @@ -77,9 +77,10 @@ use std::sync::atomic::Ordering;
use thiserror::Error;
use user_driver::DmaClient;
use user_driver::memory::MemoryBlock;
use x86defs::snp::SevAvicPage;
use x86defs::snp::SevVmsa;
use x86defs::tdx::TdCallResultCode;
use x86defs::vmx::ApicPage;
use x86defs::vmx::VmxApicPage;
use zerocopy::FromBytes;
use zerocopy::FromZeros;
use zerocopy::Immutable;
Expand Down Expand Up @@ -1548,9 +1549,12 @@ enum BackingState {
},
Snp {
vmsa: VtlArray<MappedPage<SevVmsa>, 2>,
vtl0_apic_page: MappedPage<SevAvicPage>,
/// VTL1 runs with the alternate interrupt injection.
vtl1_apic_page: MemoryBlock,
},
Tdx {
vtl0_apic_page: MappedPage<ApicPage>,
vtl0_apic_page: MappedPage<VmxApicPage>,
vtl1_apic_page: MemoryBlock,
},
}
Expand Down Expand Up @@ -1602,6 +1606,12 @@ impl HclVp {
.map_err(|e| Error::MmapVp(e, Some(Vtl::Vtl1)))?;
BackingState::Snp {
vmsa: [vmsa_vtl0, vmsa_vtl1].into(),
vtl0_apic_page: MappedPage::new(fd, MSHV_APIC_PAGE_OFFSET | vp as i64)
.map_err(|e| Error::MmapVp(e, Some(Vtl::Vtl0)))?,
vtl1_apic_page: private_dma_client
.ok_or(Error::MissingPrivateMemory)?
.allocate_dma_buffer(HV_PAGE_SIZE as usize)
.map_err(Error::AllocVp)?,
}
}
IsolationType::Tdx => BackingState::Tdx {
Expand Down
48 changes: 47 additions & 1 deletion openhcl/hcl/src/ioctl/snp.rs
Original file line number Diff line number Diff line change
Expand Up @@ -25,12 +25,14 @@ use sidecar_client::SidecarVp;
use std::cell::UnsafeCell;
use std::os::fd::AsRawFd;
use thiserror::Error;
use x86defs::snp::SevAvicPage;
use x86defs::snp::SevRmpAdjust;
use x86defs::snp::SevVmsa;

/// Runner backing for SNP partitions.
pub struct Snp<'a> {
vmsa: VtlArray<&'a UnsafeCell<SevVmsa>, 2>,
avic_pages: VtlArray<&'a UnsafeCell<SevAvicPage>, 2>,
}

/// Error returned by failing SNP operations.
Expand Down Expand Up @@ -176,11 +178,28 @@ impl MshvVtl {
impl<'a> super::private::BackingPrivate<'a> for Snp<'a> {
fn new(vp: &'a HclVp, sidecar: Option<&SidecarVp<'_>>, _hcl: &Hcl) -> Result<Self, NoRunner> {
assert!(sidecar.is_none());
let super::BackingState::Snp { vmsa } = &vp.backing else {
let super::BackingState::Snp {
vtl0_apic_page,
vtl1_apic_page,
vmsa,
} = &vp.backing
else {
return Err(NoRunner::MismatchedIsolation);
};

// TODO: Register the VTL 1 AVIC page with the hypervisor.
// Specification: "SEV-ES Guest-Hypervisor Communication Block Standartization",
// 4.1.16.1 "Backing page support".
//
// The VTL 0 APIC page is registered by the kernel.
let vtl1_apic_page_addr = vtl1_apic_page.pfns()[0] * user_driver::memory::PAGE_SIZE64;

// SAFETY: The mapping is held for the appropriate lifetime, and the
// APIC page is never accessed as any other type, or by any other location.
let vtl1_apic_page = unsafe { &*vtl1_apic_page.base().cast() };

Ok(Self {
avic_pages: [vtl0_apic_page.as_ref(), vtl1_apic_page].into(),
vmsa: vmsa.each_ref().map(|mp| mp.as_ref()),
})
}
Expand Down Expand Up @@ -242,4 +261,31 @@ impl<'a> ProcessorRunner<'a, Snp<'a>> {
})
.into_inner()
}

/// Gets a reference to the secure AVIC page for the given VTL.
pub fn secure_avic_page(&self, vtl: GuestVtl) -> &SevAvicPage {
// SAFETY: the APIC pages will not be concurrently accessed by the processor
// while this VP is in VTL2.
unsafe { &*self.state.avic_pages[vtl].get() }
}

/// Gets a mutable reference to the secure AVIC page for the given VTL.
pub fn secure_avic_page_mut(&mut self, vtl: GuestVtl) -> &mut SevAvicPage {
// SAFETY: the AVIC pages will not be concurrently accessed by the processor
// while this VP is in VTL2.
unsafe { &mut *self.state.avic_pages[vtl].get() }
}

/// Gets a mutable reference to the secure AVIC page for the given VTL.
pub fn secure_avic_page_vmsa_mut(
&mut self,
vtl: GuestVtl,
) -> (&mut SevAvicPage, VmsaWrapper<'_, &mut SevVmsa>) {
// SAFETY: the AVIC pages will not be concurrently accessed by the processor
// while this VP is in VTL2.
let avic_page = unsafe { &mut *self.state.avic_pages[vtl].get() };
let vmsa = self.vmsa_mut(vtl);

(avic_page, vmsa)
}
}
8 changes: 4 additions & 4 deletions openhcl/hcl/src/ioctl/tdx.rs
Original file line number Diff line number Diff line change
Expand Up @@ -39,12 +39,12 @@ use x86defs::tdx::TdxGp;
use x86defs::tdx::TdxL2Ctls;
use x86defs::tdx::TdxL2EnterGuestState;
use x86defs::tdx::TdxVmFlags;
use x86defs::vmx::ApicPage;
use x86defs::vmx::VmcsField;
use x86defs::vmx::VmxApicPage;

/// Runner backing for TDX partitions.
pub struct Tdx<'a> {
apic_pages: VtlArray<&'a UnsafeCell<ApicPage>, 2>,
apic_pages: VtlArray<&'a UnsafeCell<VmxApicPage>, 2>,
}

impl MshvVtl {
Expand Down Expand Up @@ -123,14 +123,14 @@ impl<'a> ProcessorRunner<'a, Tdx<'a>> {
}

/// Gets a reference to the tdx APIC page for the given VTL.
pub fn tdx_apic_page(&self, vtl: GuestVtl) -> &ApicPage {
pub fn tdx_apic_page(&self, vtl: GuestVtl) -> &VmxApicPage {
// SAFETY: the APIC pages will not be concurrently accessed by the processor
// while this VP is in VTL2.
unsafe { &*self.state.apic_pages[vtl].get() }
}

/// Gets a mutable reference to the tdx APIC page for the given VTL.
pub fn tdx_apic_page_mut(&mut self, vtl: GuestVtl) -> &mut ApicPage {
pub fn tdx_apic_page_mut(&mut self, vtl: GuestVtl) -> &mut VmxApicPage {
// SAFETY: the APIC pages will not be concurrently accessed by the processor
// while this VP is in VTL2.
unsafe { &mut *self.state.apic_pages[vtl].get() }
Expand Down
79 changes: 70 additions & 9 deletions openhcl/virt_mshv_vtl/src/processor/snp/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -66,6 +66,7 @@ use virt_support_x86emu::translate::TranslationRegisters;
use vmcore::vmtime::VmTimeAccess;
use x86defs::RFlags;
use x86defs::cpuid::CpuidFunction;
use x86defs::snp::SevAvicPage;
use x86defs::snp::SevEventInjectInfo;
use x86defs::snp::SevExitCode;
use x86defs::snp::SevInvlpgbEcx;
Expand Down Expand Up @@ -314,6 +315,7 @@ pub struct SnpBackedShared {
pub(crate) cvm: UhCvmPartitionState,
invlpgb_count_max: u16,
tsc_aux_virtualized: bool,
secure_avic: bool,
}

impl SnpBackedShared {
Expand All @@ -328,16 +330,32 @@ impl SnpBackedShared {
.result(CpuidFunction::ExtendedAddressSpaceSizes.0, 0, &[0; 4])[3],
)
.invlpgb_count_max();
let tsc_aux_virtualized = x86defs::cpuid::ExtendedSevFeaturesEax::from(
let extended_sev_features = x86defs::cpuid::ExtendedSevFeaturesEax::from(
params
.cpuid
.result(CpuidFunction::ExtendedSevFeatures.0, 0, &[0; 4])[0],
)
.tsc_aux_virtualization();
);
let tsc_aux_virtualized = extended_sev_features.tsc_aux_virtualization();

let msr = devmsr::MsrDevice::new(0).expect("open msr");
let secure_avic =
SevStatusMsr::from(msr.read_msr(x86defs::X86X_AMD_MSR_SEV).expect("read msr"))
.secure_avic();

// Sanity check through the CPUID leaf.
if secure_avic {
assert!(
extended_sev_features.secure_avic()
&& extended_sev_features.guest_intercept_control()
);

tracing::info!("Secure AVIC is available");
}

Ok(Self {
invlpgb_count_max,
tsc_aux_virtualized,
secure_avic,
cvm,
})
}
Expand Down Expand Up @@ -432,6 +450,12 @@ impl BackingPrivate for SnpBacked {
this.runner
.set_vp_registers_hvcall(Vtl::Vtl0, values)
.expect("set_vp_registers hypercall for direct overlays should succeed");

// Enable APIC offload if supported for VTL 0.
this.set_apic_offload(GuestVtl::Vtl0, this.shared.secure_avic);
this.backing.cvm.lapics[GuestVtl::Vtl0]
.lapic
.enable_offload();
}

type StateAccess<'p, 'a>
Expand Down Expand Up @@ -493,7 +517,7 @@ impl BackingPrivate for SnpBacked {
dev: &impl CpuIo,
) -> Result<bool, UhRunVpError> {
this.cvm_handle_cross_vtl_interrupts(|this, vtl, check_rflags| {
let vmsa = this.runner.vmsa_mut(vtl);
let (avic_page, vmsa) = this.runner.secure_avic_page_vmsa_mut(vtl);
if vmsa.event_inject().valid()
&& vmsa.event_inject().interruption_type() == x86defs::snp::SEV_INTR_TYPE_NMI
{
Expand All @@ -506,6 +530,7 @@ impl BackingPrivate for SnpBacked {
.access(&mut SnpApicClient {
partition: this.partition,
vmsa,
avic_page,
dev,
vmtime: &this.vmtime,
vtl,
Expand Down Expand Up @@ -590,6 +615,18 @@ impl BackingPrivate for SnpBacked {
}
}

impl UhProcessor<'_, SnpBacked> {
fn set_apic_offload(&mut self, vtl: GuestVtl, offload: bool) {
assert!(vtl == GuestVtl::Vtl0);
// Share the common architectural parts with TDX.
if offload {
// TODO: Load the emulated state into the hardware
} else {
// TODO: Load the hardware state into the emulator
}
}
}

fn virt_seg_to_snp(val: SegmentRegister) -> SevSelector {
SevSelector {
selector: val.selector,
Expand Down Expand Up @@ -669,6 +706,7 @@ fn init_vmsa(vmsa: &mut VmsaWrapper<'_, &mut SevVmsa>, vtl: GuestVtl, vtom: Opti
struct SnpApicClient<'a, T> {
partition: &'a UhPartitionInner,
vmsa: VmsaWrapper<'a, &'a mut SevVmsa>,
avic_page: &'a mut SevAvicPage,
dev: &'a T,
vmtime: &'a VmTimeAccess,
vtl: GuestVtl,
Expand Down Expand Up @@ -701,10 +739,26 @@ impl<T: CpuIo> ApicClient for SnpApicClient<'_, T> {
}

fn pull_offload(&mut self) -> ([u32; 8], [u32; 8]) {
unreachable!()
assert_eq!(self.vtl, GuestVtl::Vtl0);
pull_apic_offload(self.avic_page)
}
}

fn pull_apic_offload(page: &mut SevAvicPage) -> ([u32; 8], [u32; 8]) {
let mut irr = [0; 8];
let mut isr = [0; 8];
for (((irr, page_irr), isr), page_isr) in irr
.iter_mut()
.zip(page.irr.iter_mut())
.zip(isr.iter_mut())
.zip(page.isr.iter_mut())
{
*irr = std::mem::take(&mut page_irr.value);
*isr = std::mem::take(&mut page_isr.value);
}
(irr, isr)
}

impl<T: CpuIo> UhHypercallHandler<'_, '_, T, SnpBacked> {
// Trusted hypercalls from the guest.
const TRUSTED_DISPATCHER: hv1_hypercall::Dispatcher<Self> = hv1_hypercall::dispatcher!(
Expand Down Expand Up @@ -986,7 +1040,7 @@ impl UhProcessor<'_, SnpBacked> {
return;
}

let vmsa = self.runner.vmsa_mut(entered_from_vtl);
let (avic_page, vmsa) = self.runner.secure_avic_page_vmsa_mut(entered_from_vtl);
let gp = if is_write {
let value = (vmsa.rax() as u32 as u64) | ((vmsa.rdx() as u32 as u64) << 32);

Expand All @@ -995,6 +1049,7 @@ impl UhProcessor<'_, SnpBacked> {
.access(&mut SnpApicClient {
partition: self.partition,
vmsa,
avic_page,
dev,
vmtime: &self.vmtime,
vtl: entered_from_vtl,
Expand All @@ -1018,6 +1073,7 @@ impl UhProcessor<'_, SnpBacked> {
.access(&mut SnpApicClient {
partition: self.partition,
vmsa,
avic_page,
dev,
vmtime: &self.vmtime,
vtl: entered_from_vtl,
Expand Down Expand Up @@ -1178,7 +1234,7 @@ impl UhProcessor<'_, SnpBacked> {
.map_err(|err| VpHaltReason::Hypervisor(UhRunVpError::Run(err)))?;

let entered_from_vtl = next_vtl;
let mut vmsa = self.runner.vmsa_mut(entered_from_vtl);
let (avic_page, mut vmsa) = self.runner.secure_avic_page_vmsa_mut(entered_from_vtl);

// TODO SNP: The guest busy bit needs to be tested and set atomically.
if vmsa.v_intr_cntrl().guest_busy() {
Expand Down Expand Up @@ -1228,6 +1284,7 @@ impl UhProcessor<'_, SnpBacked> {
.access(&mut SnpApicClient {
partition: self.partition,
vmsa,
avic_page,
dev,
vmtime: &self.vmtime,
vtl: entered_from_vtl,
Expand Down Expand Up @@ -1738,11 +1795,13 @@ impl<T: CpuIo> X86EmulatorSupport for UhEmulationState<'_, '_, T, SnpBacked> {

fn lapic_read(&mut self, address: u64, data: &mut [u8]) {
let vtl = self.vtl;
let (avic_page, vmsa) = self.vp.runner.secure_avic_page_vmsa_mut(vtl);
self.vp.backing.cvm.lapics[vtl]
.lapic
.access(&mut SnpApicClient {
partition: self.vp.partition,
vmsa: self.vp.runner.vmsa_mut(vtl),
vmsa,
avic_page,
dev: self.devices,
vmtime: &self.vp.vmtime,
vtl,
Expand All @@ -1752,11 +1811,13 @@ impl<T: CpuIo> X86EmulatorSupport for UhEmulationState<'_, '_, T, SnpBacked> {

fn lapic_write(&mut self, address: u64, data: &[u8]) {
let vtl = self.vtl;
let (avic_page, vmsa) = self.vp.runner.secure_avic_page_vmsa_mut(vtl);
self.vp.backing.cvm.lapics[vtl]
.lapic
.access(&mut SnpApicClient {
partition: self.vp.partition,
vmsa: self.vp.runner.vmsa_mut(vtl),
vmsa,
avic_page,
dev: self.devices,
vmtime: &self.vp.vmtime,
vtl,
Expand Down
Loading