Skip to main content

virt_mshv/x86_64/
snp.rs

1// Copyright (c) Microsoft Corporation.
2// Licensed under the MIT License.
3
4//! AMD SEV-SNP support for the x86_64 MSHV backend.
5
6use super::*;
7use crate::KernelError;
8use std::io;
9
10pub(super) const SNP_IMPORT_CHUNK_PAGES: usize = 256;
11
12#[derive(Debug, Error)]
13pub(crate) enum SnpError {
14    #[error("failed to map the SNP GHCB page")]
15    MapGhcbPage(#[source] io::Error),
16    #[error("SNP launch is already in progress")]
17    LaunchInProgress,
18    #[error("SNP launch previously failed")]
19    LaunchFailed,
20    #[error("unsupported SNP initial page import type: {0:?}")]
21    UnsupportedPageImportType(virt::InitialPageImportType),
22    #[error("invalid SNP initial page range")]
23    InvalidPageRange,
24    #[error("missing SNP VMSA import")]
25    MissingVmsa,
26    #[error("multiple SNP VMSA imports")]
27    MultipleVmsa,
28    #[error("missing SNP CPUID import")]
29    MissingCpuid,
30    #[error("multiple SNP CPUID imports")]
31    MultipleCpuid,
32    #[error("too many SNP CPUID entries: {0}")]
33    TooManyCpuidEntries(usize),
34    #[error("failed to query SNP CPUID")]
35    Cpuid(#[source] KernelError),
36    #[error("failed to write SNP CPUID page")]
37    GuestMemory(#[source] guestmem::GuestMemoryError),
38    #[error("failed to map SNP guest memory")]
39    MapGuestMemory(#[source] KernelError),
40    #[error("failed to import SNP pages")]
41    ImportIsolatedPages(#[source] KernelError),
42    #[error("failed to complete SNP isolated import")]
43    CompleteIsolatedImport(#[source] KernelError),
44}
45
46pub(super) const SNP_HYPERV_CPUID_FUNCTIONS: [u32; 10] = [
47    hvdef::HV_CPUID_FUNCTION_HV_VENDOR_AND_MAX_FUNCTION,
48    hvdef::HV_CPUID_FUNCTION_HV_INTERFACE,
49    hvdef::HV_CPUID_FUNCTION_MS_HV_VERSION,
50    hvdef::HV_CPUID_FUNCTION_MS_HV_FEATURES,
51    hvdef::HV_CPUID_FUNCTION_MS_HV_ENLIGHTENMENT_INFORMATION,
52    hvdef::HV_CPUID_FUNCTION_MS_HV_IMPLEMENTATION_LIMITS,
53    hvdef::HV_CPUID_FUNCTION_MS_HV_HARDWARE_FEATURES,
54    hvdef::HV_CPUID_FUNCTION_MS_HV_NESTED_FEATURES,
55    hvdef::HV_CPUID_FUNCTION_MS_HV_ISOLATION_CONFIGURATION,
56    hvdef::VIRTUALIZATION_STACK_CPUID_PROPERTIES,
57];
58
59#[derive(Debug, Copy, Clone, Eq, PartialEq, inspect::Inspect)]
60pub(crate) enum SnpLaunchState {
61    NotStarted,
62    Started,
63    Finished,
64    Failed,
65}
66
67#[derive(Debug, inspect::Inspect)]
68pub(crate) struct MshvSnpConfig {
69    #[inspect(hex)]
70    snp_policy: u64,
71    #[inspect(rename = "id_block_enabled", with = "Option::is_some")]
72    id_block: Option<virt::SnpIdBlock>,
73    #[inspect(hex)]
74    pub(super) vmsa_gpa: u64,
75    /// Stable userspace backing for the VMSA mapping registered with MSHV.
76    #[inspect(skip)]
77    vmsa_memory: GuestMemory,
78    #[inspect(hex)]
79    sev_features: u64,
80    restricted_injection: bool,
81}
82
83pub(super) fn prepare_snp_config(
84    config: &virt::SnpConfig,
85    physical_address_width: u8,
86) -> Result<MshvSnpConfig, Error> {
87    if config.highest_vtl != 0 {
88        return Err(ErrorInner::UnsupportedSnpVtl(config.highest_vtl).into());
89    }
90    if config.shared_gpa_boundary != 0 {
91        return Err(ErrorInner::UnsupportedSnpSharedGpaBoundary(config.shared_gpa_boundary).into());
92    }
93    if config.has_relocation {
94        return Err(ErrorInner::SnpIgvmRelocationUnsupported.into());
95    }
96    if config.vp_contexts.len() != 1 || config.vp_contexts[0].vp_index != VpIndex::BSP {
97        return Err(ErrorInner::InvalidSnpIgvmTopology.into());
98    }
99
100    let vmsa = &config.vp_contexts[0];
101    let vmsa_end = vmsa
102        .gpa
103        .checked_add(hvdef::HV_PAGE_SIZE)
104        .ok_or(ErrorInner::InvalidSnpVmsaGpa(vmsa.gpa))?;
105    if !vmsa.gpa.is_multiple_of(hvdef::HV_PAGE_SIZE)
106        || (physical_address_width < u64::BITS as u8 && vmsa_end > (1u64 << physical_address_width))
107    {
108        return Err(ErrorInner::InvalidSnpVmsaGpa(vmsa.gpa).into());
109    }
110
111    let (parsed_vmsa, _) = x86defs::snp::SevVmsa::read_from_prefix(vmsa.page.as_ref())
112        .map_err(|_| ErrorInner::InvalidSnpIgvmVmsa)?;
113    let allowed_features = x86defs::snp::SevFeatures::new()
114        .with_snp(true)
115        .with_restrict_injection(true);
116    if !parsed_vmsa.sev_features.snp()
117        || parsed_vmsa.sev_features.vtom()
118        || parsed_vmsa.virtual_tom != 0
119        || u64::from(parsed_vmsa.sev_features) & !u64::from(allowed_features) != 0
120    {
121        return Err(ErrorInner::UnsupportedSnpIgvmVmsa {
122            sev_features: parsed_vmsa.sev_features.into_bits(),
123            virtual_tom: parsed_vmsa.virtual_tom,
124        }
125        .into());
126    }
127
128    let mut vmsa_memory = GuestMemory::allocate(hvdef::HV_PAGE_SIZE as usize);
129    let Some(vmsa_bytes) = vmsa_memory.inner_buf_mut() else {
130        return Err(ErrorInner::InvalidSnpVmsaBacking.into());
131    };
132    vmsa_bytes.copy_from_slice(vmsa.page.as_ref());
133    let vmsa_gpa = vmsa.gpa;
134    let sev_features = parsed_vmsa.sev_features.into_bits();
135    let restricted_injection = parsed_vmsa.sev_features.restrict_injection();
136
137    Ok(MshvSnpConfig {
138        snp_policy: config.policy,
139        id_block: config.id_block.clone(),
140        vmsa_gpa,
141        vmsa_memory,
142        sev_features,
143        restricted_injection,
144    })
145}
146
147#[derive(inspect::Inspect)]
148pub(crate) struct SnpPartitionState {
149    /// Launch progress is synchronized because loading and memory mapping use
150    /// shared partition references.
151    pub(crate) launch_state: Mutex<SnpLaunchState>,
152    /// BSP SEV features used to validate GHCB AP-creation requests.
153    ///
154    /// AMD GHCB specification 56421, "SNP AP Creation", supplies the AP VMSA's
155    /// SEV_FEATURES in RAX and requires them to match the requesting vCPU. Each
156    /// created AP is validated against this BSP value, making it the canonical
157    /// feature set for subsequent requests.
158    pub(super) sev_features: Mutex<Option<u64>>,
159    pub(super) cpuid_offloads_enabled: bool,
160    config: Option<Box<MshvSnpConfig>>,
161}
162
163impl SnpPartitionState {
164    pub(super) fn new(disable_cpuid_offload: bool) -> Self {
165        Self {
166            launch_state: Mutex::new(SnpLaunchState::NotStarted),
167            sev_features: Mutex::new(None),
168            cpuid_offloads_enabled: !disable_cpuid_offload,
169            config: None,
170        }
171    }
172
173    pub(super) fn with_config(
174        disable_cpuid_offload: bool,
175        config: Option<Box<MshvSnpConfig>>,
176    ) -> Self {
177        let mut state = Self::new(disable_cpuid_offload);
178        state.config = config;
179        state
180    }
181}
182
183pub(crate) struct SnpVpState {
184    ghcb_page: MshvGhcbPage,
185}
186
187impl SnpVpState {
188    pub(super) fn new(vcpufd: &VcpuFd) -> Result<Self, Error> {
189        // SAFETY: The VP fd owns a kernel GHCB state page at this documented
190        // mmap offset for encrypted VPs when the target kernel advertises support.
191        let page = unsafe {
192            libc::mmap(
193                std::ptr::null_mut(),
194                hvdef::HV_PAGE_SIZE as usize,
195                libc::PROT_READ | libc::PROT_WRITE,
196                libc::MAP_SHARED,
197                vcpufd.as_raw_fd(),
198                i64::from(mshv_bindings::MSHV_VP_MMAP_OFFSET_GHCB)
199                    * libc::sysconf(libc::_SC_PAGE_SIZE),
200            )
201        };
202        if page == libc::MAP_FAILED {
203            return Err(SnpError::MapGhcbPage(io::Error::last_os_error()).into());
204        }
205        Ok(Self {
206            ghcb_page: MshvGhcbPage(page.cast()),
207        })
208    }
209
210    pub(super) fn page_ptr(&mut self) -> *mut x86defs::snp::GhcbPage {
211        self.ghcb_page.0
212    }
213}
214
215struct MshvGhcbPage(*mut x86defs::snp::GhcbPage);
216
217// SAFETY: The mapping is uniquely owned by the processor binder and is only
218// accessed while the binder is mutably borrowed by a bound processor.
219unsafe impl Send for MshvGhcbPage {}
220
221impl Drop for MshvGhcbPage {
222    fn drop(&mut self) {
223        // SAFETY: The pointer was returned by mmap for exactly one page and is
224        // unmapped exactly once here.
225        unsafe {
226            libc::munmap(self.0.cast(), hvdef::HV_PAGE_SIZE as usize);
227        }
228    }
229}
230
231#[repr(C)]
232#[derive(Debug, Copy, Clone)]
233pub(super) struct ImportIsolatedPagesHeader {
234    pub(super) page_type: u8,
235    pub(super) rsvd: [u8; 7],
236    pub(super) page_count: u64,
237}
238
239#[repr(C)]
240#[derive(Debug, Copy, Clone)]
241pub(super) struct ModifyGpaHostAccessHeader {
242    pub(super) flags: u8,
243    pub(super) rsvd: [u8; 7],
244    pub(super) page_count: u64,
245}
246
247pub(super) const GHCB_RAX_VALID_BIT: u64 =
248    1 << (std::mem::offset_of!(x86defs::snp::GhcbSaveArea, rax) / size_of::<u64>());
249pub(super) const GHCB_RBX_VALID_BIT: u64 =
250    1 << (std::mem::offset_of!(x86defs::snp::GhcbSaveArea, rbx) / size_of::<u64>() - 64);
251pub(super) const GHCB_RCX_VALID_BIT: u64 =
252    1 << (std::mem::offset_of!(x86defs::snp::GhcbSaveArea, rcx) / size_of::<u64>() - 64);
253pub(super) const GHCB_RDX_VALID_BIT: u64 =
254    1 << (std::mem::offset_of!(x86defs::snp::GhcbSaveArea, rdx) / size_of::<u64>() - 64);
255pub(super) const GHCB_R8_VALID_BIT: u64 =
256    1 << (std::mem::offset_of!(x86defs::snp::GhcbSaveArea, r8) / size_of::<u64>() - 64);
257pub(super) const GHCB_SW_EXIT_CODE_VALID_BIT: u64 =
258    1 << (std::mem::offset_of!(x86defs::snp::GhcbSaveArea, sw_exit_code) / size_of::<u64>() - 64);
259pub(super) const GHCB_SW_EXIT_INFO1_VALID_BIT: u64 =
260    1 << (std::mem::offset_of!(x86defs::snp::GhcbSaveArea, sw_exit_info1) / size_of::<u64>() - 64);
261pub(super) const GHCB_SW_EXIT_INFO2_VALID_BIT: u64 =
262    1 << (std::mem::offset_of!(x86defs::snp::GhcbSaveArea, sw_exit_info2) / size_of::<u64>() - 64);
263pub(super) const GHCB_SW_SCRATCH_VALID_BIT: u64 =
264    1 << (std::mem::offset_of!(x86defs::snp::GhcbSaveArea, sw_scratch) / size_of::<u64>() - 64);
265pub(super) const GHCB_XCR0_VALID_BIT: u64 =
266    1 << (std::mem::offset_of!(x86defs::snp::GhcbSaveArea, xcr0) / size_of::<u64>() - 64);
267pub(super) const GHCB_XSS_VALID_BIT: u64 =
268    1 << (std::mem::offset_of!(x86defs::snp::GhcbSaveArea, xss) / size_of::<u64>());
269pub(super) const SVM_EXITCODE_CPUID: u64 = 0x72;
270pub(super) const GHCB_SHARED_BUFFER_OFFSET: u64 =
271    std::mem::offset_of!(x86defs::snp::GhcbPage, shared_buffer) as u64;
272pub(super) const SVM_NAE_SNP_AP_CREATE: u32 = 1;
273pub(super) const GHCB_ERROR_RESPONSE: u64 = 2;
274pub(super) const GHCB_ERROR_INVALID_INPUT: u64 = 5;
275pub(super) const SNP_UNSAFE_VMSA_ALIGNMENT: u64 = 2 * 1024 * 1024;
276
277#[derive(Debug, Copy, Clone, Eq, PartialEq)]
278pub(super) struct SnpApCreateRequest {
279    pub(super) apic_id: u32,
280    pub(super) vmsa_gpa: u64,
281    pub(super) sev_features: u64,
282}
283
284#[derive(Debug, Copy, Clone, Eq, PartialEq)]
285pub(super) enum SnpApCreateRequestError {
286    MissingInput,
287    UnsupportedOperation(u16),
288    UnsupportedVmpl(u16),
289    InvalidSevFeatures(u64),
290    InvalidVmsaGpa(u64),
291}
292
293pub(super) fn set_ghcb_rax(ghcb: &mut x86defs::snp::GhcbPage, rax: u64) {
294    ghcb.save.rax = rax;
295    ghcb.save.valid_bitmap0 |= GHCB_RAX_VALID_BIT;
296}
297
298pub(super) fn set_ghcb_gp(ghcb: &mut x86defs::snp::GhcbPage, index: usize, value: u64) -> bool {
299    match index {
300        index if index == x86emu::Gp::RAX as usize => set_ghcb_rax(ghcb, value),
301        index if index == x86emu::Gp::RCX as usize => {
302            ghcb.save.rcx = value;
303            ghcb.save.valid_bitmap1 |= GHCB_RCX_VALID_BIT;
304        }
305        index if index == x86emu::Gp::RDX as usize => {
306            ghcb.save.rdx = value;
307            ghcb.save.valid_bitmap1 |= GHCB_RDX_VALID_BIT;
308        }
309        index if index == x86emu::Gp::R8 as usize => {
310            ghcb.save.r8 = value;
311            ghcb.save.valid_bitmap1 |= GHCB_R8_VALID_BIT;
312        }
313        _ => return false,
314    }
315    true
316}
317
318pub(super) fn read_snp_start_vp_input(
319    vcpufd: &VcpuFd,
320    gpa: u64,
321) -> Result<hvdef::hypercall::StartVirtualProcessorX64, mshv_ioctls::MshvError> {
322    let mut data = [0; size_of::<hvdef::hypercall::StartVirtualProcessorX64>()];
323    for (offset, chunk) in data.chunks_mut(16).enumerate() {
324        let mut request = mshv_bindings::mshv_read_write_gpa {
325            base_gpa: gpa + (offset * 16) as u64,
326            byte_count: chunk.len() as u32,
327            ..Default::default()
328        };
329        let result = vcpufd.gpa_read(&mut request)?;
330        chunk.copy_from_slice(&result.data[..chunk.len()]);
331    }
332    Ok(
333        hvdef::hypercall::StartVirtualProcessorX64::read_from_bytes(&data)
334            .expect("buffer is exactly the StartVirtualProcessor input size"),
335    )
336}
337
338pub(super) fn ghcb_rax_is_valid(ghcb: &x86defs::snp::GhcbPage) -> bool {
339    ghcb.save.valid_bitmap0 & GHCB_RAX_VALID_BIT != 0
340}
341
342/// Checks registers duplicated between the kernel-produced MSHV intercept
343/// message and the guest-populated GHCB.
344///
345/// RAX and RCX identify the Hyper-V hypercall and must be valid in both
346/// sources. MSHV does not always mark RDX and R8 valid in the GHCB, so compare
347/// those fields only when the guest included them there. The intercept message
348/// remains the register source used to dispatch the hypercall.
349fn snp_hypercall_registers_are_consistent(
350    ghcb: &x86defs::snp::GhcbPage,
351    info: &hvdef::HvX64HypercallInterceptMessage,
352) -> bool {
353    ghcb_rax_is_valid(ghcb)
354        && ghcb.save.valid_bitmap1 & GHCB_RCX_VALID_BIT != 0
355        && ghcb.save.rax == info.rax
356        && ghcb.save.rcx == info.rcx
357        && (ghcb.save.valid_bitmap1 & GHCB_RDX_VALID_BIT == 0 || ghcb.save.rdx == info.rdx)
358        && (ghcb.save.valid_bitmap1 & GHCB_R8_VALID_BIT == 0 || ghcb.save.r8 == info.r8)
359}
360
361pub(super) fn ghcb_exit_fields_are_valid(ghcb: &x86defs::snp::GhcbPage) -> bool {
362    ghcb.save.valid_bitmap1 & (GHCB_SW_EXIT_CODE_VALID_BIT | GHCB_SW_EXIT_INFO1_VALID_BIT)
363        == GHCB_SW_EXIT_CODE_VALID_BIT | GHCB_SW_EXIT_INFO1_VALID_BIT
364}
365
366pub(super) fn ghcb_mmio_fields_are_valid(ghcb: &x86defs::snp::GhcbPage) -> bool {
367    ghcb.save.valid_bitmap1 & (GHCB_SW_EXIT_INFO2_VALID_BIT | GHCB_SW_SCRATCH_VALID_BIT)
368        == GHCB_SW_EXIT_INFO2_VALID_BIT | GHCB_SW_SCRATCH_VALID_BIT
369}
370
371pub(super) fn ghcb_exit_info2_is_valid(ghcb: &x86defs::snp::GhcbPage) -> bool {
372    ghcb.save.valid_bitmap1 & GHCB_SW_EXIT_INFO2_VALID_BIT != 0
373}
374
375pub(super) fn set_ghcb_error(ghcb: &mut x86defs::snp::GhcbPage, error: u64) {
376    ghcb.save.sw_exit_info1 = GHCB_ERROR_RESPONSE;
377    ghcb.save.sw_exit_info2 = error;
378    ghcb.save.valid_bitmap1 |= GHCB_SW_EXIT_INFO1_VALID_BIT | GHCB_SW_EXIT_INFO2_VALID_BIT;
379}
380
381pub(super) fn parse_snp_ap_create_request(
382    ghcb: &x86defs::snp::GhcbPage,
383) -> Result<SnpApCreateRequest, SnpApCreateRequestError> {
384    let operation = ghcb.save.sw_exit_info1 as u16;
385    if operation != SVM_NAE_SNP_AP_CREATE as u16 {
386        // TODO: Implement CREATE_ON_INIT and DESTROY once the MSHV kernel ABI
387        // provides the target-VP lifecycle operations needed for them.
388        return Err(SnpApCreateRequestError::UnsupportedOperation(operation));
389    }
390
391    let vmpl = (ghcb.save.sw_exit_info1 >> 16) as u16;
392    if vmpl != 0 {
393        return Err(SnpApCreateRequestError::UnsupportedVmpl(vmpl));
394    }
395
396    if !ghcb_rax_is_valid(ghcb) || !ghcb_exit_info2_is_valid(ghcb) {
397        return Err(SnpApCreateRequestError::MissingInput);
398    }
399
400    let sev_features = ghcb.save.rax;
401    if sev_features & 1 == 0 {
402        return Err(SnpApCreateRequestError::InvalidSevFeatures(sev_features));
403    }
404
405    let vmsa_gpa = ghcb.save.sw_exit_info2;
406    // Conservatively mirror KVM's workaround for the SNP erratum where a
407    // hugepage can collide with a 2 MiB-aligned VMSA RMP entry.
408    if !vmsa_gpa.is_multiple_of(hvdef::HV_PAGE_SIZE)
409        || vmsa_gpa.is_multiple_of(SNP_UNSAFE_VMSA_ALIGNMENT)
410    {
411        return Err(SnpApCreateRequestError::InvalidVmsaGpa(vmsa_gpa));
412    }
413
414    Ok(SnpApCreateRequest {
415        apic_id: (ghcb.save.sw_exit_info1 >> 32) as u32,
416        vmsa_gpa,
417        sev_features,
418    })
419}
420
421pub(super) fn vp_index_for_apic_id(
422    apic_id: u32,
423    vps: impl IntoIterator<Item = (VpIndex, u32)>,
424) -> Option<VpIndex> {
425    vps.into_iter()
426        .find_map(|(vp_index, candidate)| (candidate == apic_id).then_some(vp_index))
427}
428
429pub(super) fn snp_host_access_flags(visibility: u32) -> Option<u8> {
430    let acquire = 1 << mshv_bindings::MSHV_GPA_HOST_ACCESS_BIT_ACQUIRE;
431    let readable = 1 << mshv_bindings::MSHV_GPA_HOST_ACCESS_BIT_READABLE;
432    let writable = 1 << mshv_bindings::MSHV_GPA_HOST_ACCESS_BIT_WRITABLE;
433    match visibility {
434        0 => Some(0),
435        // The current MSHV kernel tests the readable flag when setting
436        // writable access, so a read-only request would become read-write.
437        1 => None,
438        3 => Some(acquire | readable | writable),
439        _ => None,
440    }
441}
442
443pub(crate) fn acquire_snp_host_access(
444    partition: &MshvPartitionInner,
445    addr: u64,
446    size: u64,
447) -> anyhow::Result<()> {
448    // TODO: The current prototype implementation does not coordinate
449    // acquisition with guest visibility changes. In particular, there is no
450    // per-page state preventing a fault on another thread from acquiring
451    // access while a GPA attribute intercept is revoking it. A complete
452    // implementation must serialize acquisition with revocation and block or
453    // fail this request if the guest is making the page private.
454    anyhow::ensure!(
455        addr.is_multiple_of(hvdef::HV_PAGE_SIZE)
456            && size.is_multiple_of(hvdef::HV_PAGE_SIZE)
457            && size != 0,
458        "host-access range must be page aligned and nonempty"
459    );
460    let page_count = size / hvdef::HV_PAGE_SIZE;
461    let flags = (1 << mshv_bindings::MSHV_GPA_HOST_ACCESS_BIT_ACQUIRE)
462        | (1 << mshv_bindings::MSHV_GPA_HOST_ACCESS_BIT_READABLE)
463        | (1 << mshv_bindings::MSHV_GPA_HOST_ACCESS_BIT_WRITABLE);
464    let mut buf = HeaderVec::<ModifyGpaHostAccessHeader, u64, 0>::new(ModifyGpaHostAccessHeader {
465        flags,
466        rsvd: [0; 7],
467        page_count,
468    });
469    let gpas = (0..page_count)
470        .map(|page| addr + page * hvdef::HV_PAGE_SIZE)
471        .collect::<Vec<_>>();
472    buf.extend_tail_from_slice(&gpas);
473    // SAFETY: The custom header matches `mshv_modify_gpa_host_access`,
474    // followed by `page_count` contiguous GPA values.
475    let args = unsafe {
476        &*buf
477            .as_ptr()
478            .cast::<mshv_bindings::mshv_modify_gpa_host_access>()
479    };
480    partition.vmfd.modify_gpa_host_access(args)?;
481    Ok(())
482}
483
484pub(super) fn parse_snp_gpa_range(
485    range: hvdef::hypercall::HvGpaRange,
486) -> Result<(u64, u64), VpHaltReason> {
487    const PAGES_PER_2MB: u64 = 512;
488    const PAGES_PER_1GB: u64 = 512 * PAGES_PER_2MB;
489
490    let page = range.as_extended();
491    let unit_count = page
492        .additional_pages()
493        .checked_add(1)
494        .ok_or(VpHaltReason::TripleFault { vtl: Vtl::Vtl0 })?;
495    if !page.large_page() {
496        return Ok((page.gpa_page_number(), unit_count));
497    }
498
499    let page = range.as_extended_large_page();
500    let pages_per_unit = if page.page_size() {
501        PAGES_PER_1GB
502    } else {
503        PAGES_PER_2MB
504    };
505    if page.page_size() && !page.gpa_large_page_number().is_multiple_of(PAGES_PER_2MB) {
506        return Err(VpHaltReason::TripleFault { vtl: Vtl::Vtl0 });
507    }
508    let start_pfn = page
509        .gpa_large_page_number()
510        .checked_mul(PAGES_PER_2MB)
511        .ok_or(VpHaltReason::TripleFault { vtl: Vtl::Vtl0 })?;
512    let page_count = unit_count
513        .checked_mul(pages_per_unit)
514        .ok_or(VpHaltReason::TripleFault { vtl: Vtl::Vtl0 })?;
515    Ok((start_pfn, page_count))
516}
517
518pub(super) fn sanitize_snp_cpuid(
519    function: u32,
520    index: u32,
521    expose_hypervisor: bool,
522    values: &mut [u32; 4],
523) {
524    if function == x86defs::cpuid::CpuidFunction::VersionAndFeatures.0 && !expose_hypervisor {
525        values[2] &= !(1 << 31);
526    }
527    if function == x86defs::cpuid::CpuidFunction::ExtendedStateEnumeration.0 && index == 1 {
528        // TODO: Import a CPUID extended-state page and preserve supported XSS
529        // components. The normal SNP CPUID page does not contain the required
530        // component subleaves, so exposing this bitmap makes Linux consume
531        // missing entries as zero-offset user state.
532        values[2] = 0;
533        values[3] = 0;
534    }
535}
536
537pub(super) fn get_snp_cpuid_values(
538    vcpufd: &VcpuFd,
539    function: u32,
540    index: u32,
541    xfem: u64,
542    xss: u64,
543    expose_hypervisor: bool,
544) -> Result<[u32; 4], mshv_ioctls::MshvError> {
545    let mut values = vcpufd.get_cpuid_values(function, index, xfem, xss)?;
546    sanitize_snp_cpuid(function, index, expose_hypervisor, &mut values);
547    Ok(values)
548}
549
550pub(super) fn snp_cpuid_overrides(expose_hypervisor: bool) -> [virt::CpuidLeaf; 2] {
551    [
552        // Make the hypervisor-present bit match the guest contract.
553        virt::CpuidLeaf::new(
554            x86defs::cpuid::CpuidFunction::VersionAndFeatures.0,
555            [0, 0, u32::from(expose_hypervisor) << 31, 0],
556        )
557        .masked([0, 0, 1 << 31, 0]),
558        // Do not expose supervisor state without an SNP extended-state page.
559        virt::CpuidLeaf::new(
560            x86defs::cpuid::CpuidFunction::ExtendedStateEnumeration.0,
561            [0; 4],
562        )
563        .indexed(1)
564        .masked([0, 0, u32::MAX, u32::MAX]),
565    ]
566}
567
568pub(super) fn add_snp_hyperv_cpuid_leaves(
569    page: &mut x86defs::snp::HvPspCpuidPage,
570) -> Result<(), usize> {
571    let mut count = page.count as usize;
572    if count > x86defs::snp::HV_PSP_CPUID_LEAF_COUNT_MAX {
573        return Err(count);
574    }
575
576    for function in SNP_HYPERV_CPUID_FUNCTIONS {
577        if page.cpuid_leaf_info[..count]
578            .iter()
579            .any(|leaf| leaf.eax_in == function && leaf.ecx_in == 0)
580        {
581            continue;
582        }
583        if count == x86defs::snp::HV_PSP_CPUID_LEAF_COUNT_MAX {
584            return Err(count + 1);
585        }
586
587        page.cpuid_leaf_info[count].eax_in = function;
588        count += 1;
589    }
590    page.count = count as u32;
591    Ok(())
592}
593
594pub(super) fn snp_hv_cpuid_overrides(native_max_leaf: u32) -> [virt::CpuidLeaf; 3] {
595    const HV_ISOLATION_TYPE_SNP: u32 = 2;
596    let privileges = hvdef::HvPartitionPrivilege::new()
597        .with_start_virtual_processor(true)
598        .with_isolation(true)
599        .into_bits();
600    let privilege_high = (privileges >> 32) as u32;
601
602    // TODO: Investigate why this MSHV environment does not derive the Hyper-V
603    // isolation CPUID contract from MSHV_PT_ISOLATION_SNP. Cloud Hypervisor
604    // does not install an equivalent override, but we have not confirmed
605    // whether its environment receives the isolation leaves correctly from
606    // MSHV. Without these overrides, ACI Linux does not recognize a
607    // non-paravisor Hyper-V SNP guest and uses the hypercall-page overlay
608    // instead of direct VMMCALL hypercalls. Correctly describing isolation
609    // also keeps ACI's restricted-injection doorbell EOI path active, making
610    // the previous APIC-access recommendation mask unnecessary.
611    [
612        // Make the isolation configuration leaf discoverable.
613        virt::CpuidLeaf::new(
614            hvdef::HV_CPUID_FUNCTION_HV_VENDOR_AND_MAX_FUNCTION,
615            [
616                native_max_leaf.max(hvdef::HV_CPUID_FUNCTION_MS_HV_ISOLATION_CONFIGURATION),
617                0,
618                0,
619                0,
620            ],
621        )
622        .masked([u32::MAX, 0, 0, 0]),
623        // Tell the guest that this is an isolated Hyper-V partition.
624        virt::CpuidLeaf::new(
625            hvdef::HV_CPUID_FUNCTION_MS_HV_FEATURES,
626            [0, privilege_high, 0, 0],
627        )
628        .masked([0, privilege_high, 0, 0]),
629        // Describe physical SNP without a paravisor or vTOM boundary.
630        virt::CpuidLeaf::new(
631            hvdef::HV_CPUID_FUNCTION_MS_HV_ISOLATION_CONFIGURATION,
632            [0, HV_ISOLATION_TYPE_SNP, 0, 0],
633        )
634        .masked([u32::MAX; 4]),
635    ]
636}
637
638pub(super) fn snp_start_vp_vmsa_gpa(
639    context: &hvdef::hypercall::InitialVpContextX64,
640) -> Option<u64> {
641    let encoded = context.rip;
642    // TODO: Confirm this ACI-specific overload is the intended Microsoft
643    // Hypervisor SNP contract. ACI zeroes the nominal register context and
644    // stores `vmsa_gpa | 1` in its first eight bytes for HvCallStartVP.
645    if encoded & 1 == 0
646        || context.as_bytes()[size_of::<u64>()..]
647            .iter()
648            .any(|&x| x != 0)
649    {
650        return None;
651    }
652    let vmsa_gpa = encoded & !1;
653    (vmsa_gpa.is_multiple_of(hvdef::HV_PAGE_SIZE)
654        && !vmsa_gpa.is_multiple_of(SNP_UNSAFE_VMSA_ALIGNMENT))
655    .then_some(vmsa_gpa)
656}
657
658impl virt::AcceptInitialPages for MshvPartition {
659    type Error = Error;
660
661    fn accept_initial_pages(&self, pages: &[virt::InitialPageImport]) -> Result<(), Self::Error> {
662        self.inner.snp_launch_initial_pages(pages)
663    }
664}
665
666impl MshvPartitionInner {
667    pub(super) fn add_snp_vmsa_mapping(&self) -> Result<(), Error> {
668        let Some(config) = self.isolation.snp().and_then(|snp| snp.config.as_deref()) else {
669            return Ok(());
670        };
671        let Some(vmsa_bytes) = config.vmsa_memory.inner_buf() else {
672            return Err(ErrorInner::InvalidSnpVmsaBacking.into());
673        };
674        let flags = (1 << mshv_bindings::MSHV_SET_MEM_BIT_WRITABLE)
675            | (1 << mshv_bindings::MSHV_SET_MEM_BIT_EXECUTABLE);
676        let region = mshv_bindings::mshv_user_mem_region {
677            size: hvdef::HV_PAGE_SIZE,
678            guest_pfn: config.vmsa_gpa / hvdef::HV_PAGE_SIZE,
679            userspace_addr: vmsa_bytes.as_ptr() as u64,
680            flags,
681            rsvd: [0; 7],
682        };
683        self.memory.lock().ranges.push(Some(crate::MshvMemoryRange {
684            region,
685            mapped: false,
686        }));
687        Ok(())
688    }
689
690    fn snp_launch_initial_pages(&self, pages: &[virt::InitialPageImport]) -> Result<(), Error> {
691        let Some(snp) = self.isolation.snp() else {
692            return Err(ErrorInner::IsolationNotSupported.into());
693        };
694        {
695            let mut state = snp.launch_state.lock();
696            match *state {
697                SnpLaunchState::NotStarted => *state = SnpLaunchState::Started,
698                SnpLaunchState::Started => return Err(SnpError::LaunchInProgress.into()),
699                SnpLaunchState::Finished => return Ok(()),
700                SnpLaunchState::Failed => return Err(SnpError::LaunchFailed.into()),
701            }
702        }
703
704        match self.snp_launch_initial_pages_inner(snp, pages) {
705            Ok(()) => {
706                *snp.launch_state.lock() = SnpLaunchState::Finished;
707                Ok(())
708            }
709            Err(err) => {
710                *snp.launch_state.lock() = SnpLaunchState::Failed;
711                Err(err)
712            }
713        }
714    }
715
716    fn snp_launch_initial_pages_inner(
717        &self,
718        snp: &SnpPartitionState,
719        pages: &[virt::InitialPageImport],
720    ) -> Result<(), Error> {
721        let (vmsa_gpa, cpuid_gpa) = snp_launch_pages(pages)?;
722        let sev_features = if let Some(config) = &snp.config {
723            if vmsa_gpa != config.vmsa_gpa {
724                return Err(ErrorInner::InvalidSnpVmsaImport.into());
725            }
726            config.sev_features
727        } else {
728            let vmsa = self
729                .gm
730                .read_plain::<x86defs::snp::SevVmsa>(vmsa_gpa)
731                .map_err(SnpError::GuestMemory)?;
732            vmsa.sev_features.into_bits()
733        };
734        // GHCB SNP AP-creation requests supply a new VMSA. Save the BSP launch
735        // features so handle_snp_ap_create can require each AP VMSA to use the
736        // same guest-visible SEV feature set.
737        *snp.sev_features.lock() = Some(sev_features);
738        self.write_snp_cpuid_page(cpuid_gpa)?;
739
740        self.vmfd
741            .set_partition_property(
742                HvPartitionPropertyCode::IsolationState.0,
743                mshv_bindings::hv_partition_isolation_state_HV_PARTITION_ISOLATION_SECURE as u64,
744            )
745            .map_err(|e| ErrorInner::SetPartitionProperty(e.into()))?;
746
747        {
748            let mut memory = self.memory.lock();
749            for range in memory.ranges.iter_mut().flatten() {
750                if !range.mapped {
751                    self.vmfd
752                        .map_user_memory(range.region)
753                        .map_err(|e| SnpError::MapGuestMemory(e.into()))?;
754                    range.mapped = true;
755                }
756            }
757        }
758
759        let mut batch_type = None;
760        let mut batch = Vec::with_capacity(SNP_IMPORT_CHUNK_PAGES);
761        for page in ordered_snp_import_pages(pages, snp.config.is_some()) {
762            let Some(page_type) = snp_isolated_page_type(page.import_type)? else {
763                continue;
764            };
765            validate_snp_page_range(page.range)?;
766            for pfn in
767                page.range.start() / hvdef::HV_PAGE_SIZE..page.range.end() / hvdef::HV_PAGE_SIZE
768            {
769                if batch_type != Some(page_type) || batch.len() == SNP_IMPORT_CHUNK_PAGES {
770                    if let Some(current_type) = batch_type {
771                        self.import_isolated_pages(current_type, &batch)?;
772                        batch.clear();
773                    }
774                    batch_type = Some(page_type);
775                }
776                batch.push(pfn);
777            }
778        }
779        if let Some(page_type) = batch_type {
780            self.import_isolated_pages(page_type, &batch)?;
781        }
782
783        self.complete_isolated_import(snp.config.as_deref())?;
784
785        let sev_control =
786            mshv_bindings::snp::get_sev_control_register(vmsa_gpa / hvdef::HV_PAGE_SIZE);
787        self.bsp_vcpufd
788            .set_hvdef_regs(&[HvRegisterAssoc::from((
789                HvX64RegisterName::SevControl,
790                sev_control,
791            ))])
792            .map_err(ErrorInner::Register)?;
793        Ok(())
794    }
795
796    fn write_snp_cpuid_page(&self, cpuid_gpa: u64) -> Result<(), Error> {
797        let mut page = self
798            .gm
799            .read_plain::<x86defs::snp::HvPspCpuidPage>(cpuid_gpa)
800            .map_err(SnpError::GuestMemory)?;
801        let count = page.count as usize;
802        if count > x86defs::snp::HV_PSP_CPUID_LEAF_COUNT_MAX {
803            return Err(SnpError::TooManyCpuidEntries(count).into());
804        }
805        if self.caps.hv1 {
806            // TODO: Determine the correct long-term strategy for exposing
807            // synthetic Hyper-V CPUID leaves to direct-boot SNP guests: include
808            // them in the measured CPUID page, rely on GHCB CPUID fallback, or
809            // support both based on the guest contract.
810            //
811            // The loader creates this page with the architectural x86 and AMD
812            // leaves needed by an SNP guest. Add the synthetic Hyper-V leaf
813            // requests here because their values depend on the MSHV partition
814            // configuration. The loop below queries MSHV for every requested
815            // leaf and writes the results into this page before it is imported
816            // through the SNP launch API. The PSP consequently measures these
817            // synthetic values along with the rest of the CPUID page.
818            //
819            // This is not inherently required by the SNP or GHCB protocols. A
820            // guest can treat a synthetic leaf absent from the measured table
821            // as unsupported by that table and retry it through the GHCB CPUID
822            // protocol. With CPUID VMGEXIT offloading enabled, the hypervisor
823            // can answer that request using the CPUID intercept results
824            // registered above. With offloading disabled, OpenVMM answers the
825            // forwarded MSR or page-protocol request.
826            //
827            // Some Linux versions instead treat an absent, out-of-range
828            // synthetic leaf as a successful all-zero result. In particular,
829            // returning zeros for 0x40000000 prevents Hyper-V vendor detection
830            // and therefore disables the entire Hyper-V guest interface. Keep
831            // appending the leaves for compatibility with those guests. A
832            // guest that returns -EOPNOTSUPP for missing synthetic leaves does
833            // not require this augmentation.
834            add_snp_hyperv_cpuid_leaves(&mut page).map_err(SnpError::TooManyCpuidEntries)?;
835        }
836        let count = page.count as usize;
837
838        for leaf in &mut page.cpuid_leaf_info[..count] {
839            let values = get_snp_cpuid_values(
840                &self.bsp_vcpufd,
841                leaf.eax_in,
842                leaf.ecx_in,
843                leaf.xfem_in,
844                leaf.xss_in,
845                self.caps.hv1,
846            )
847            .map_err(|e| SnpError::Cpuid(e.into()))?;
848            leaf.eax_out = values[0];
849            leaf.ebx_out = values[1];
850            leaf.ecx_out = values[2];
851            leaf.edx_out = values[3];
852            leaf.reserved_z = 0;
853        }
854        self.gm
855            .write_plain(cpuid_gpa, &page)
856            .map_err(SnpError::GuestMemory)?;
857        Ok(())
858    }
859
860    fn import_isolated_pages(&self, page_type: u8, pfns: &[u64]) -> Result<(), Error> {
861        if pfns.is_empty() {
862            return Ok(());
863        }
864
865        let mut buf =
866            HeaderVec::<ImportIsolatedPagesHeader, u64, 0>::new(ImportIsolatedPagesHeader {
867                page_type,
868                rsvd: [0; 7],
869                page_count: pfns.len() as u64,
870            });
871        buf.extend_tail_from_slice(pfns);
872        // SAFETY: The custom header has the same C layout as
873        // `mshv_import_isolated_pages`, followed by `page_count` u64 PFNs.
874        let args = unsafe {
875            &*buf
876                .as_ptr()
877                .cast::<mshv_bindings::mshv_import_isolated_pages>()
878        };
879        self.vmfd
880            .import_isolated_pages(args)
881            .map_err(|e| SnpError::ImportIsolatedPages(e.into()))?;
882        Ok(())
883    }
884
885    fn complete_isolated_import(&self, config: Option<&MshvSnpConfig>) -> Result<(), Error> {
886        let mut data = mshv_bindings::mshv_complete_isolated_import::default();
887        let (parameters, snp_policy) = snp_launch_finish_data(config);
888        tracing::info!(
889            snp_policy,
890            id_block_enabled = parameters.id_block_enabled != 0,
891            vmsa_gpa = config.map(|config| config.vmsa_gpa),
892            restricted_injection = config.map(|config| config.restricted_injection),
893            "completing MSHV SNP launch"
894        );
895        data.import_data.psp_parameters = parameters;
896        self.vmfd
897            .complete_isolated_import(&data)
898            .map_err(|e| SnpError::CompleteIsolatedImport(e.into()))?;
899        Ok(())
900    }
901}
902
903/// Builds the PSP launch-finish data from the prepared MSHV SNP configuration.
904///
905/// The returned `u64` is the effective SNP policy, which the caller records in
906/// launch diagnostics.
907fn snp_launch_finish_data(
908    config: Option<&MshvSnpConfig>,
909) -> (mshv_bindings::hv_psp_launch_finish_data, u64) {
910    let mut parameters = mshv_bindings::hv_psp_launch_finish_data::default();
911    let policy = config.map_or_else(
912        || {
913            let policy = mshv_bindings::snp::get_default_snp_guest_policy();
914            // SAFETY: This generated C union contains a valid u64 view.
915            unsafe { policy.as_uint64 }
916        },
917        |config| config.snp_policy,
918    );
919    parameters.id_block.policy = mshv_bindings::hv_snp_guest_policy { as_uint64: policy };
920
921    if let Some(source) = config.and_then(|config| config.id_block.as_ref()) {
922        let id_block = virt::x86::snp::snp_id_block(source, policy);
923        parameters.id_block.launch_digest = id_block.ld;
924        parameters.id_block.family_id = id_block.family_id;
925        parameters.id_block.image_id = id_block.image_id;
926        parameters.id_block.version = id_block.version;
927        parameters.id_block.guest_svn = id_block.guest_svn;
928
929        let id_auth = virt::x86::snp::snp_id_auth(source);
930        parameters
931            .id_auth_info
932            .id_block_signature
933            .copy_from_slice(id_auth.id_block_signature.as_bytes());
934        parameters
935            .id_auth_info
936            .id_key
937            .copy_from_slice(id_auth.id_key.as_bytes());
938        parameters
939            .id_auth_info
940            .id_key_signature
941            .copy_from_slice(id_auth.id_key_signature.as_bytes());
942        parameters
943            .id_auth_info
944            .author_key
945            .copy_from_slice(id_auth.author_key.as_bytes());
946        parameters.id_auth_info.id_key_algorithm = id_auth.id_key_algorithm;
947        parameters.id_auth_info.auth_key_algorithm = id_auth.author_key_algorithm;
948        parameters.id_block_enabled = 1;
949        parameters.author_key_enabled = u8::from(source.author_key_enabled != 0);
950    }
951
952    (parameters, policy)
953}
954
955/// Returns launch pages in the order required by the active launch source.
956///
957/// IGVM page directives must retain file order so MSHV reproduces the launch
958/// digest signed by the ID block. Loader-generated launches have no external
959/// measurement order, so they retain the existing deterministic GPA sort.
960fn ordered_snp_import_pages(
961    pages: &[virt::InitialPageImport],
962    preserve_measurement_order: bool,
963) -> Vec<virt::InitialPageImport> {
964    let mut ordered_pages = pages.to_vec();
965    if !preserve_measurement_order {
966        ordered_pages.sort_by_key(|page| page.range.start());
967    }
968    ordered_pages
969}
970
971/// Finds and validates the unique VMSA and CPUID pages needed for SNP launch.
972fn snp_launch_pages(pages: &[virt::InitialPageImport]) -> Result<(u64, u64), Error> {
973    let mut vmsa = None;
974    let mut cpuid = None;
975    for page in pages {
976        let slot = match page.import_type {
977            virt::InitialPageImportType::VpContext => &mut vmsa,
978            virt::InitialPageImportType::Cpuid => &mut cpuid,
979            _ => continue,
980        };
981        if slot.is_some() {
982            return Err(match page.import_type {
983                virt::InitialPageImportType::VpContext => SnpError::MultipleVmsa,
984                virt::InitialPageImportType::Cpuid => SnpError::MultipleCpuid,
985                _ => unreachable!(),
986            }
987            .into());
988        }
989        validate_snp_page_range(page.range)?;
990        if page.range.len() != hvdef::HV_PAGE_SIZE {
991            return Err(SnpError::InvalidPageRange.into());
992        }
993        *slot = Some(page.range.start());
994    }
995    Ok((
996        vmsa.ok_or(SnpError::MissingVmsa)?,
997        cpuid.ok_or(SnpError::MissingCpuid)?,
998    ))
999}
1000
1001fn validate_snp_page_range(range: MemoryRange) -> Result<(), Error> {
1002    if range.is_empty()
1003        || !range.start().is_multiple_of(hvdef::HV_PAGE_SIZE)
1004        || !range.end().is_multiple_of(hvdef::HV_PAGE_SIZE)
1005    {
1006        return Err(SnpError::InvalidPageRange.into());
1007    }
1008    Ok(())
1009}
1010
1011fn snp_isolated_page_type(import_type: virt::InitialPageImportType) -> Result<Option<u8>, Error> {
1012    Ok(Some(match import_type {
1013        virt::InitialPageImportType::Normal => mshv_bindings::MSHV_ISOLATED_PAGE_NORMAL as u8,
1014        virt::InitialPageImportType::NormalUnmeasured => {
1015            mshv_bindings::MSHV_ISOLATED_PAGE_UNMEASURED as u8
1016        }
1017        virt::InitialPageImportType::VpContext => mshv_bindings::MSHV_ISOLATED_PAGE_VMSA as u8,
1018        virt::InitialPageImportType::Secrets => mshv_bindings::MSHV_ISOLATED_PAGE_SECRETS as u8,
1019        virt::InitialPageImportType::Cpuid => mshv_bindings::MSHV_ISOLATED_PAGE_CPUID as u8,
1020        virt::InitialPageImportType::Shared => return Ok(None),
1021        virt::InitialPageImportType::CpuidExtendedState => {
1022            return Err(SnpError::UnsupportedPageImportType(import_type).into());
1023        }
1024    }))
1025}
1026
1027impl MshvProcessor<'_> {
1028    fn snp_hypercall_registers(
1029        &mut self,
1030        info: &hvdef::HvX64HypercallInterceptMessage,
1031    ) -> Result<HvX64RegisterPage, VpHaltReason> {
1032        let ghcb = self
1033            .runner
1034            .ghcb_page()
1035            .ok_or(VpHaltReason::TripleFault { vtl: Vtl::Vtl0 })?;
1036        if !snp_hypercall_registers_are_consistent(ghcb, info) {
1037            tracelimit::warn_ratelimited!("inconsistent SNP hypercall registers");
1038            return Err(VpHaltReason::TripleFault { vtl: Vtl::Vtl0 });
1039        }
1040
1041        let mut regs = HvX64RegisterPage::new_zeroed();
1042        regs.gp_registers[x86emu::Gp::RAX as usize] = info.rax;
1043        regs.gp_registers[x86emu::Gp::RBX as usize] = info.rbx;
1044        regs.gp_registers[x86emu::Gp::RCX as usize] = info.rcx;
1045        regs.gp_registers[x86emu::Gp::RDX as usize] = info.rdx;
1046        regs.gp_registers[x86emu::Gp::RSI as usize] = info.rsi;
1047        regs.gp_registers[x86emu::Gp::RDI as usize] = info.rdi;
1048        regs.gp_registers[x86emu::Gp::R8 as usize] = info.r8;
1049        for (dst, src) in regs.xmm.iter_mut().zip(&info.xmm_registers) {
1050            *dst = u128::from(*src);
1051        }
1052        Ok(regs)
1053    }
1054
1055    fn dispatch_snp_hypercall(
1056        &mut self,
1057        info: &hvdef::HvX64HypercallInterceptMessage,
1058        regs: &mut HvX64RegisterPage,
1059    ) -> (u16, u8) {
1060        let vcpufd = self.runner.vcpufd;
1061        let mut handler = MshvHypercallHandler {
1062            partition: self.partition,
1063            reg_page: regs,
1064            caller_vp: self.vpindex,
1065            isolated: true,
1066            modified_gp: 0,
1067            modified_xmm: 0,
1068        };
1069
1070        if info.rcx as u16 == hvdef::HypercallCode::HvCallStartVirtualProcessor.0 {
1071            let input_end = info
1072                .rdx
1073                .checked_add(size_of::<hvdef::hypercall::StartVirtualProcessorX64>() as u64);
1074            let result = input_end
1075                .ok_or(hvdef::HvError::InvalidParameter)
1076                .and_then(|_| {
1077                    read_snp_start_vp_input(vcpufd, info.rdx).map_err(|err| {
1078                        tracelimit::warn_ratelimited!(
1079                            error = &err as &dyn std::error::Error,
1080                            input_gpa = info.rdx,
1081                            "failed to read SNP StartVirtualProcessor input"
1082                        );
1083                        hvdef::HvError::InvalidParameter
1084                    })
1085                })
1086                .and_then(|input| {
1087                    if input.rsvd0 != 0 || input.rsvd1 != 0 {
1088                        return Err(hvdef::HvError::InvalidParameter);
1089                    }
1090                    hv1_hypercall::StartVirtualProcessor::start_virtual_processor(
1091                        &mut handler,
1092                        input.partition_id,
1093                        input.vp_index,
1094                        Vtl::try_from(input.target_vtl)?,
1095                        &input.vp_context,
1096                    )
1097                });
1098            let output = match result {
1099                Ok(()) => hvdef::hypercall::HypercallOutput::SUCCESS,
1100                Err(err) => err.into(),
1101            };
1102            hv1_hypercall::X64RegisterState::set_gp(
1103                &mut handler,
1104                hv1_hypercall::X64HypercallRegister::Rax,
1105                output.into(),
1106            );
1107        } else {
1108            MshvHypercallHandler::DISPATCHER.dispatch(
1109                &self.partition.gm,
1110                X64RegisterIo::new(&mut handler, true, false),
1111            );
1112        }
1113        (handler.modified_gp, handler.modified_xmm)
1114    }
1115
1116    fn write_snp_hypercall_output(
1117        &mut self,
1118        regs: &HvX64RegisterPage,
1119        modified_gp: u16,
1120        modified_xmm: u8,
1121    ) -> Result<(), VpHaltReason> {
1122        if modified_xmm != 0 {
1123            tracelimit::warn_ratelimited!(modified_xmm, "unsupported SNP hypercall XMM output");
1124            return Err(VpHaltReason::TripleFault { vtl: Vtl::Vtl0 });
1125        }
1126        let ghcb = self
1127            .runner
1128            .ghcb_page()
1129            .ok_or(VpHaltReason::TripleFault { vtl: Vtl::Vtl0 })?;
1130        for (index, value) in regs.gp_registers.iter().copied().enumerate() {
1131            if modified_gp & (1 << index) != 0 && !set_ghcb_gp(ghcb, index, value) {
1132                tracelimit::warn_ratelimited!(
1133                    index,
1134                    "unsupported SNP hypercall GP-register output"
1135                );
1136                return Err(VpHaltReason::TripleFault { vtl: Vtl::Vtl0 });
1137            }
1138        }
1139        Ok(())
1140    }
1141
1142    fn handle_snp_hypercall_intercept(&mut self, message: &HvMessage) -> Result<(), VpHaltReason> {
1143        let info = message.as_message::<hvdef::HvX64HypercallInterceptMessage>();
1144        let mut regs = self.snp_hypercall_registers(info)?;
1145        let (modified_gp, modified_xmm) = self.dispatch_snp_hypercall(info, &mut regs);
1146        self.write_snp_hypercall_output(&regs, modified_gp, modified_xmm)
1147    }
1148
1149    /// Dispatches SNP exits that can be handled without a VP register page.
1150    pub(super) async fn handle_snp_exit(
1151        &mut self,
1152        exit: &HvMessage,
1153        dev: &impl CpuIo,
1154    ) -> Result<(), VpHaltReason> {
1155        match exit.header.typ {
1156            HvMessageType::HvMessageTypeUnrecoverableException => {
1157                let info = exit.as_message::<hvdef::HvX64UnrecoverableExceptionMessage>();
1158                tracelimit::warn_ratelimited!(
1159                    rip = info.header.rip,
1160                    "SNP VP reported an unrecoverable exception"
1161                );
1162                Err(VpHaltReason::TripleFault { vtl: Vtl::Vtl0 })
1163            }
1164            HvMessageType::HvMessageTypeGpaAttributeIntercept => {
1165                self.handle_snp_gpa_attribute_intercept(exit)
1166            }
1167            HvMessageType::HvMessageTypeHypercallIntercept => {
1168                tracing::trace!("HYPERCALL_INTERCEPT");
1169                self.handle_snp_hypercall_intercept(exit)
1170            }
1171            HvMessageType::HvMessageTypeSynicSintDeliverable => {
1172                let info = exit.as_message::<hvdef::HvX64SynicSintDeliverableMessage>();
1173                self.handle_sint_deliverable(info.deliverable_sints);
1174                Ok(())
1175            }
1176            HvMessageType::HvMessageTypeX64ApicEoi => {
1177                let info = exit.as_message::<hvdef::HvX64ApicEoiMessage>();
1178                dev.handle_eoi(info.interrupt_vector);
1179                Ok(())
1180            }
1181            HvMessageType::HvMessageTypeX64SevVmgexitIntercept => {
1182                self.handle_sev_vmgexit_intercept(exit, dev).await
1183            }
1184            HvMessageType::HvMessageTypeUnacceptedGpa
1185            | HvMessageType::HvMessageTypeUnmappedGpa
1186            | HvMessageType::HvMessageTypeGpaIntercept => {
1187                let info = exit.as_message::<hvdef::HvX64MemoryInterceptMessage>();
1188                let instruction = info
1189                    .instruction_bytes
1190                    .get(..info.instruction_byte_count as usize);
1191                tracelimit::warn_ratelimited!(
1192                    gpa = info.guest_physical_address,
1193                    gva = info.guest_virtual_address,
1194                    rip = info.header.rip,
1195                    access = ?info.header.intercept_access_type,
1196                    instruction_count = info.instruction_byte_count,
1197                    ?instruction,
1198                    exit_type = ?exit.header.typ,
1199                    "unexpected memory intercept for SNP VP"
1200                );
1201                Err(VpHaltReason::TripleFault { vtl: Vtl::Vtl0 })
1202            }
1203            exit_type => {
1204                tracelimit::warn_ratelimited!(
1205                    ?exit_type,
1206                    "unexpected non-VMGEXIT message for SNP VP"
1207                );
1208                Err(VpHaltReason::TripleFault { vtl: Vtl::Vtl0 })
1209            }
1210        }
1211    }
1212
1213    fn ensure_cpuid_intercept_expected(
1214        cpuid_offloads_enabled: bool,
1215        protocol: &'static str,
1216        function: u32,
1217        index: u32,
1218    ) -> Result<(), VpHaltReason> {
1219        if cpuid_offloads_enabled {
1220            tracelimit::warn_ratelimited!(
1221                protocol,
1222                function,
1223                index,
1224                "unexpected SNP CPUID intercept while CPUID offloads are enabled"
1225            );
1226            return Err(VpHaltReason::TripleFault { vtl: Vtl::Vtl0 });
1227        }
1228        Ok(())
1229    }
1230
1231    pub(super) fn modify_gpa_host_access(
1232        &self,
1233        gpas: &[u64],
1234        flags: u8,
1235    ) -> Result<(), VpHaltReason> {
1236        if gpas.is_empty() {
1237            return Ok(());
1238        }
1239
1240        let mut buf =
1241            HeaderVec::<ModifyGpaHostAccessHeader, u64, 0>::new(ModifyGpaHostAccessHeader {
1242                flags,
1243                rsvd: [0; 7],
1244                page_count: gpas.len() as u64,
1245            });
1246        buf.extend_tail_from_slice(gpas);
1247        // SAFETY: The custom header matches `mshv_modify_gpa_host_access`
1248        // followed by `page_count` contiguous GPA values. Despite the UAPI
1249        // field name `guest_pfns`, the kernel converts each entry with
1250        // `HVPFN_DOWN`, so the variable array contains byte GPAs.
1251        let args = unsafe {
1252            &*buf
1253                .as_ptr()
1254                .cast::<mshv_bindings::mshv_modify_gpa_host_access>()
1255        };
1256        self.partition
1257            .vmfd
1258            .modify_gpa_host_access(args)
1259            .map_err(|err| {
1260                tracelimit::error_ratelimited!(
1261                    error = &err as &dyn std::error::Error,
1262                    first_gpa = gpas[0],
1263                    page_count = gpas.len(),
1264                    flags,
1265                    "failed to modify SNP GPA host access"
1266                );
1267                VpHaltReason::TripleFault { vtl: Vtl::Vtl0 }
1268            })
1269    }
1270
1271    pub(super) fn handle_snp_gpa_attribute_intercept(
1272        &self,
1273        message: &HvMessage,
1274    ) -> Result<(), VpHaltReason> {
1275        const BATCH_PAGES: usize = 256;
1276        let info = message.as_message::<hvdef::HvX64GpaAttributeInterceptMessage>();
1277        let range_count = info.flags.range_count() as usize;
1278        let ranges = &info.ranges;
1279        if range_count == 0 || range_count > ranges.len() {
1280            return Err(VpHaltReason::TripleFault { vtl: Vtl::Vtl0 });
1281        }
1282
1283        let flags = snp_host_access_flags(info.flags.host_visibility())
1284            .ok_or(VpHaltReason::TripleFault { vtl: Vtl::Vtl0 })?;
1285        if info.flags.adjust() || info.flags.memory_type() != 0 {
1286            return Err(VpHaltReason::TripleFault { vtl: Vtl::Vtl0 });
1287        }
1288
1289        // TODO: The current prototype implementation assumes that no
1290        // virtstack component is using these pages. Before revoking host
1291        // access, mark the ranges as revoking so that new GuestMemory faults
1292        // cannot reacquire them, then drain active GuestMemory accesses,
1293        // acquisitions already in progress, locked ranges, and device/DMA
1294        // users. Only after the release ioctl succeeds should the ranges be
1295        // marked private and the VP resumed, allowing the pending guest
1296        // visibility hypercall to be re-executed. If the accesses cannot be
1297        // drained, deny the intercept instead of reporting success.
1298        let mut gpas = Vec::with_capacity(BATCH_PAGES);
1299        for range in &ranges[..range_count] {
1300            let (start_pfn, page_count) = parse_snp_gpa_range(*range)?;
1301            let end_pfn = start_pfn
1302                .checked_add(page_count)
1303                .ok_or(VpHaltReason::TripleFault { vtl: Vtl::Vtl0 })?;
1304
1305            for pfn in start_pfn..end_pfn {
1306                gpas.push(
1307                    pfn.checked_mul(hvdef::HV_PAGE_SIZE)
1308                        .ok_or(VpHaltReason::TripleFault { vtl: Vtl::Vtl0 })?,
1309                );
1310                if gpas.len() == BATCH_PAGES {
1311                    self.modify_gpa_host_access(&gpas, flags)?;
1312                    gpas.clear();
1313                }
1314            }
1315        }
1316        self.modify_gpa_host_access(&gpas, flags)
1317    }
1318
1319    pub(super) fn sev_set_reg(
1320        &self,
1321        name: HvX64RegisterName,
1322        value: u64,
1323    ) -> Result<(), VpHaltReason> {
1324        self.runner
1325            .vcpufd
1326            .set_hvdef_regs(&[HvRegisterAssoc::from((name, value))])
1327            .map_err(|err| {
1328                tracelimit::error_ratelimited!(
1329                    error = &err as &dyn std::error::Error,
1330                    ?name,
1331                    "failed to set SNP VP register"
1332                );
1333                VpHaltReason::TripleFault { vtl: Vtl::Vtl0 }
1334            })
1335    }
1336
1337    pub(super) fn sev_get_reg(&self, name: HvX64RegisterName) -> Result<u64, VpHaltReason> {
1338        let mut assoc = [HvRegisterAssoc::from((name, 0u64))];
1339        self.runner
1340            .vcpufd
1341            .get_hvdef_regs(&mut assoc)
1342            .map_err(|err| {
1343                tracelimit::error_ratelimited!(
1344                    error = &err as &dyn std::error::Error,
1345                    ?name,
1346                    "failed to get SNP VP register"
1347                );
1348                VpHaltReason::TripleFault { vtl: Vtl::Vtl0 }
1349            })?;
1350        Ok(assoc[0].value.as_u64())
1351    }
1352
1353    pub(super) async fn handle_sev_vmgexit_intercept(
1354        &mut self,
1355        message: &HvMessage,
1356        dev: &impl CpuIo,
1357    ) -> Result<(), VpHaltReason> {
1358        use mshv_bindings::snp::*;
1359        use x86defs::snp::GhcbInfo;
1360
1361        let info = message.as_message::<hvdef::HvX64VmgexitInterceptMessage>();
1362        let ghcb_op = (info.ghcb_msr & GHCB_INFO_MASK as u64) as u32;
1363        let ghcb_data = info.ghcb_msr >> GHCB_INFO_BIT_WIDTH;
1364        tracing::trace!(
1365            ghcb_op,
1366            ghcb_data,
1367            ghcb_page_valid = info.flags.ghcb_page_valid(),
1368            "SNP VMGEXIT"
1369        );
1370
1371        // CPUID requests reach userspace only when the corresponding VMGEXIT
1372        // offloads are disabled. The negotiation, registration, unregistration,
1373        // shutdown, and normal page-protocol operations are always handled here.
1374        match ghcb_op {
1375            GHCB_INFO_SPECIAL_DBGPRINT => {}
1376            GHCB_INFO_HYP_FEATURE_REQUEST if ghcb_data == 0 => {
1377                let features =
1378                    u64::from(GHCB_HYP_FEATURE_SEV_SNP | GHCB_HYP_FEATURE_SEV_SNP_AP_CREATION)
1379                        | x86defs::snp::GHCB_HYP_FEATURE_GHCB_UNREGISTER;
1380                let response =
1381                    GHCB_INFO_HYP_FEATURE_RESPONSE as u64 | features << GHCB_INFO_BIT_WIDTH;
1382                self.sev_set_reg(HvX64RegisterName::Ghcb, response)?;
1383            }
1384            GHCB_INFO_CPUID_REQUEST => {
1385                let function = (info.ghcb_msr >> 32) as u32;
1386                let register = ((info.ghcb_msr >> 30) & 3) as usize;
1387                Self::ensure_cpuid_intercept_expected(
1388                    self.partition
1389                        .isolation
1390                        .snp()
1391                        .is_some_and(|snp| snp.cpuid_offloads_enabled),
1392                    "MSR",
1393                    function,
1394                    0,
1395                )?;
1396                let values = get_snp_cpuid_values(
1397                    self.runner.vcpufd,
1398                    function,
1399                    0,
1400                    0,
1401                    0,
1402                    self.partition.caps.hv1,
1403                )
1404                .map_err(|err| {
1405                    tracelimit::error_ratelimited!(
1406                        error = &err as &dyn std::error::Error,
1407                        function,
1408                        "failed to service SNP MSR CPUID request"
1409                    );
1410                    VpHaltReason::TripleFault { vtl: Vtl::Vtl0 }
1411                })?;
1412                let response = GHCB_INFO_CPUID_RESPONSE as u64 | u64::from(values[register]) << 32;
1413                self.sev_set_reg(HvX64RegisterName::Ghcb, response)?;
1414            }
1415            GHCB_INFO_SEV_INFO_REQUEST => {
1416                let values = self
1417                    .runner
1418                    .vcpufd
1419                    .get_cpuid_values(0x8000_001f, 0, 0, 0)
1420                    .map_err(|err| {
1421                        tracelimit::error_ratelimited!(
1422                            error = &err as &dyn std::error::Error,
1423                            "failed to query SNP CPUID leaf"
1424                        );
1425                        VpHaltReason::TripleFault { vtl: Vtl::Vtl0 }
1426                    })?;
1427                let c_bit = u64::from(values[1] & 0x3f);
1428                let response = GHCB_INFO_SEV_INFO_RESPONSE as u64
1429                    | u64::from(GHCB_PROTOCOL_VERSION_MAX) << 48
1430                    | u64::from(GHCB_PROTOCOL_VERSION_MIN) << 32
1431                    | c_bit << 24;
1432                self.sev_set_reg(HvX64RegisterName::Ghcb, response)?;
1433            }
1434            GHCB_INFO_REGISTER_REQUEST => {
1435                let previous = self.sev_get_reg(HvX64RegisterName::SevGhcbGpa)?;
1436                let page_number = ghcb_data;
1437                let ghcb_gpa = page_number << GHCB_INFO_BIT_WIDTH;
1438                self.sev_set_reg(HvX64RegisterName::SevGhcbGpa, previous & !1)?;
1439                self.sev_set_reg(HvX64RegisterName::SevGhcbGpa, ghcb_gpa | 1)?;
1440                self.sev_set_reg(
1441                    HvX64RegisterName::Ghcb,
1442                    GHCB_INFO_REGISTER_RESPONSE as u64 | page_number << GHCB_INFO_BIT_WIDTH,
1443                )?;
1444            }
1445            op if u64::from(op) == GhcbInfo::UNREGISTER_REQUEST.0 => {
1446                let failed = u64::MAX >> GHCB_INFO_BIT_WIDTH;
1447                let gfn = if ghcb_data != 0 {
1448                    tracelimit::warn_ratelimited!(
1449                        ghcb_data,
1450                        "nonzero reserved data in SNP GHCB unregister request"
1451                    );
1452                    failed
1453                } else {
1454                    // Release the hypervisor's GHCB mapping before acknowledging
1455                    // the request, so the guest can make the page private.
1456                    let result =
1457                        self.sev_get_reg(HvX64RegisterName::SevGhcbGpa)
1458                            .and_then(|registered| {
1459                                if registered & 1 == 0 {
1460                                    return Ok(0);
1461                                }
1462                                self.sev_set_reg(HvX64RegisterName::SevGhcbGpa, registered & !1)?;
1463                                Ok(registered >> GHCB_INFO_BIT_WIDTH)
1464                            });
1465                    // Register access failures are logged by sev_get/set_reg.
1466                    result.unwrap_or(failed)
1467                };
1468                self.sev_set_reg(
1469                    HvX64RegisterName::Ghcb,
1470                    (gfn << GHCB_INFO_BIT_WIDTH) | GhcbInfo::UNREGISTER_RESPONSE.0,
1471                )?;
1472            }
1473            GHCB_INFO_SHUTDOWN_REQUEST => {
1474                tracing::error!(ghcb_data, "SNP guest requested shutdown");
1475                return Err(VpHaltReason::PowerOff);
1476            }
1477            GHCB_INFO_NORMAL => {
1478                self.handle_sev_nae(info, ghcb_data, dev).await?;
1479            }
1480            _ => {
1481                tracelimit::warn_ratelimited!(
1482                    ghcb_op,
1483                    ghcb_data,
1484                    "unsupported SNP GHCB MSR operation"
1485                );
1486                return Err(VpHaltReason::TripleFault { vtl: Vtl::Vtl0 });
1487            }
1488        }
1489
1490        Ok(())
1491    }
1492
1493    /// Handles GHCB page-protocol non-automatic exits forwarded by MSHV.
1494    ///
1495    /// The GHCB is guest-writable shared memory, so validate its fields against
1496    /// the hypervisor-supplied intercept snapshot before using them.
1497    pub(super) async fn handle_sev_nae(
1498        &mut self,
1499        info: &hvdef::HvX64VmgexitInterceptMessage,
1500        ghcb_pfn: u64,
1501        dev: &impl CpuIo,
1502    ) -> Result<(), VpHaltReason> {
1503        use mshv_bindings::snp::*;
1504
1505        if !info.flags.ghcb_page_valid()
1506            || x86defs::snp::GhcbUsage(info.ghcb_page.ghcb_usage) != x86defs::snp::GhcbUsage::BASE
1507            || !(GHCB_PROTOCOL_VERSION_MIN..=GHCB_PROTOCOL_VERSION_MAX)
1508                .contains(&u32::from(info.ghcb_page.standard.ghcb_protocol_version))
1509        {
1510            tracelimit::warn_ratelimited!(
1511                ghcb_page_valid = info.flags.ghcb_page_valid(),
1512                ghcb_usage = info.ghcb_page.ghcb_usage,
1513                "invalid SNP GHCB page"
1514            );
1515            return Err(VpHaltReason::TripleFault { vtl: Vtl::Vtl0 });
1516        }
1517
1518        let ghcb = self
1519            .runner
1520            .ghcb_page()
1521            .ok_or(VpHaltReason::TripleFault { vtl: Vtl::Vtl0 })?;
1522        if !ghcb_exit_fields_are_valid(ghcb)
1523            || ghcb.save.sw_exit_code != info.ghcb_page.standard.sw_exit_code
1524            || ghcb.save.sw_exit_info1 != info.ghcb_page.standard.sw_exit_info1
1525        {
1526            return Err(VpHaltReason::TripleFault { vtl: Vtl::Vtl0 });
1527        }
1528
1529        let ghcb_gpa = ghcb_pfn
1530            .checked_mul(hvdef::HV_PAGE_SIZE)
1531            .ok_or(VpHaltReason::TripleFault { vtl: Vtl::Vtl0 })?;
1532        let registered = self.sev_get_reg(HvX64RegisterName::SevGhcbGpa)?;
1533        if registered & 1 == 0 || registered & !0xfff != ghcb_gpa {
1534            tracelimit::warn_ratelimited!(
1535                ghcb_gpa,
1536                registered,
1537                "SNP VMGEXIT used an unregistered GHCB page"
1538            );
1539            return Err(VpHaltReason::TripleFault { vtl: Vtl::Vtl0 });
1540        }
1541
1542        match info.ghcb_page.standard.sw_exit_code {
1543            SVM_EXITCODE_CPUID => {
1544                let cpuid_offloads_enabled = self
1545                    .partition
1546                    .isolation
1547                    .snp()
1548                    .is_some_and(|snp| snp.cpuid_offloads_enabled);
1549                let ghcb = self
1550                    .runner
1551                    .ghcb_page()
1552                    .ok_or(VpHaltReason::TripleFault { vtl: Vtl::Vtl0 })?;
1553                if !ghcb_rax_is_valid(ghcb) || ghcb.save.valid_bitmap1 & GHCB_RCX_VALID_BIT == 0 {
1554                    return Err(VpHaltReason::TripleFault { vtl: Vtl::Vtl0 });
1555                }
1556
1557                let function = ghcb.save.rax as u32;
1558                let index = ghcb.save.rcx as u32;
1559                Self::ensure_cpuid_intercept_expected(
1560                    cpuid_offloads_enabled,
1561                    "NAE",
1562                    function,
1563                    index,
1564                )?;
1565                let xfem = if ghcb.save.valid_bitmap1 & GHCB_XCR0_VALID_BIT != 0 {
1566                    ghcb.save.xcr0
1567                } else {
1568                    1
1569                };
1570                let xss = if ghcb.save.valid_bitmap0 & GHCB_XSS_VALID_BIT != 0 {
1571                    ghcb.save.xss
1572                } else {
1573                    0
1574                };
1575                let values = get_snp_cpuid_values(
1576                    self.runner.vcpufd,
1577                    function,
1578                    index,
1579                    xfem,
1580                    xss,
1581                    self.partition.caps.hv1,
1582                )
1583                .map_err(|err| {
1584                    tracelimit::error_ratelimited!(
1585                        error = &err as &dyn std::error::Error,
1586                        function,
1587                        index,
1588                        "failed to service SNP GHCB CPUID request"
1589                    );
1590                    VpHaltReason::TripleFault { vtl: Vtl::Vtl0 }
1591                })?;
1592
1593                let ghcb = self
1594                    .runner
1595                    .ghcb_page()
1596                    .ok_or(VpHaltReason::TripleFault { vtl: Vtl::Vtl0 })?;
1597                set_ghcb_rax(ghcb, u64::from(values[0]));
1598                ghcb.save.rbx = u64::from(values[1]);
1599                ghcb.save.rcx = u64::from(values[2]);
1600                ghcb.save.rdx = u64::from(values[3]);
1601                ghcb.save.valid_bitmap1 |=
1602                    GHCB_RBX_VALID_BIT | GHCB_RCX_VALID_BIT | GHCB_RDX_VALID_BIT;
1603                ghcb.save.sw_exit_info1 = 0;
1604            }
1605            exit_code if exit_code == u64::from(SVM_EXITCODE_IOIO_PROT) => {
1606                let exit_info = u32::try_from(info.ghcb_page.standard.sw_exit_info1)
1607                    .map_err(|_| VpHaltReason::TripleFault { vtl: Vtl::Vtl0 })?;
1608                if exit_info & ((1 << 1) | (1 << 2) | (1 << 3) | (0x7 << 13)) != 0 {
1609                    return Err(VpHaltReason::TripleFault { vtl: Vtl::Vtl0 });
1610                }
1611                let len = match exit_info & 0x70 {
1612                    0x10 => 1,
1613                    0x20 => 2,
1614                    0x40 => 4,
1615                    _ => return Err(VpHaltReason::TripleFault { vtl: Vtl::Vtl0 }),
1616                };
1617                let port = (exit_info >> 16) as u16;
1618                let is_write = exit_info & 1 == 0;
1619                let ghcb = self
1620                    .runner
1621                    .ghcb_page()
1622                    .ok_or(VpHaltReason::TripleFault { vtl: Vtl::Vtl0 })?;
1623                if is_write && !ghcb_rax_is_valid(ghcb) {
1624                    return Err(VpHaltReason::TripleFault { vtl: Vtl::Vtl0 });
1625                }
1626                let mut rax = ghcb.save.rax;
1627                virt_support_x86emu::emulate::emulate_io(
1628                    self.vpindex,
1629                    is_write,
1630                    port,
1631                    &mut rax,
1632                    len,
1633                    dev,
1634                )
1635                .await;
1636                let ghcb = self
1637                    .runner
1638                    .ghcb_page()
1639                    .ok_or(VpHaltReason::TripleFault { vtl: Vtl::Vtl0 })?;
1640                if !is_write {
1641                    set_ghcb_rax(ghcb, rax);
1642                }
1643                ghcb.save.sw_exit_info1 = 0;
1644            }
1645            exit_code
1646                if exit_code == u64::from(SVM_EXITCODE_MMIO_READ)
1647                    || exit_code == u64::from(SVM_EXITCODE_MMIO_WRITE) =>
1648            {
1649                let ghcb = self
1650                    .runner
1651                    .ghcb_page()
1652                    .ok_or(VpHaltReason::TripleFault { vtl: Vtl::Vtl0 })?;
1653                let len = usize::try_from(info.ghcb_page.standard.sw_exit_info2)
1654                    .ok()
1655                    .filter(|len| matches!(len, 1 | 2 | 4 | 8))
1656                    .ok_or(VpHaltReason::TripleFault { vtl: Vtl::Vtl0 })?;
1657                let expected_scratch = ghcb_gpa
1658                    .checked_add(GHCB_SHARED_BUFFER_OFFSET)
1659                    .ok_or(VpHaltReason::TripleFault { vtl: Vtl::Vtl0 })?;
1660                if !ghcb_mmio_fields_are_valid(ghcb)
1661                    || ghcb.save.sw_exit_info2 != info.ghcb_page.standard.sw_exit_info2
1662                    || ghcb.save.sw_scratch != info.ghcb_page.standard.sw_scratch
1663                    || info.ghcb_page.standard.sw_scratch != expected_scratch
1664                {
1665                    return Err(VpHaltReason::TripleFault { vtl: Vtl::Vtl0 });
1666                }
1667
1668                let address = info.ghcb_page.standard.sw_exit_info1;
1669                address
1670                    .checked_add((len - 1) as u64)
1671                    .ok_or(VpHaltReason::TripleFault { vtl: Vtl::Vtl0 })?;
1672                if exit_code == u64::from(SVM_EXITCODE_MMIO_READ) {
1673                    let mut data = [0; 8];
1674                    dev.read_mmio(self.vpindex, address, &mut data[..len]).await;
1675                    let ghcb = self
1676                        .runner
1677                        .ghcb_page()
1678                        .ok_or(VpHaltReason::TripleFault { vtl: Vtl::Vtl0 })?;
1679                    ghcb.shared_buffer[..len].copy_from_slice(&data[..len]);
1680                    ghcb.save.sw_exit_info1 = 0;
1681                } else {
1682                    let mut data = [0; 8];
1683                    data[..len].copy_from_slice(&ghcb.shared_buffer[..len]);
1684                    dev.write_mmio(self.vpindex, address, &data[..len]).await;
1685                    let ghcb = self
1686                        .runner
1687                        .ghcb_page()
1688                        .ok_or(VpHaltReason::TripleFault { vtl: Vtl::Vtl0 })?;
1689                    ghcb.save.sw_exit_info1 = 0;
1690                }
1691            }
1692            exit_code if exit_code == u64::from(SVM_EXITCODE_HV_DOORBELL_PAGE) => {
1693                if info.ghcb_page.standard.sw_exit_info1 != u64::from(SVM_NAE_HV_DOORBELL_PAGE_SET)
1694                {
1695                    return Err(VpHaltReason::TripleFault { vtl: Vtl::Vtl0 });
1696                }
1697
1698                let ghcb = self
1699                    .runner
1700                    .ghcb_page()
1701                    .ok_or(VpHaltReason::TripleFault { vtl: Vtl::Vtl0 })?;
1702                let doorbell_gpa = info.ghcb_page.standard.sw_exit_info2;
1703                let doorbell_end = doorbell_gpa
1704                    .checked_add(hvdef::HV_PAGE_SIZE)
1705                    .ok_or(VpHaltReason::TripleFault { vtl: Vtl::Vtl0 })?;
1706                if !ghcb_exit_info2_is_valid(ghcb)
1707                    || ghcb.save.sw_exit_info2 != doorbell_gpa
1708                    || !doorbell_gpa.is_multiple_of(hvdef::HV_PAGE_SIZE)
1709                    || !self.partition.mem_layout.ram().iter().any(|range| {
1710                        range
1711                            .range
1712                            .contains(&MemoryRange::new(doorbell_gpa..doorbell_end))
1713                    })
1714                {
1715                    return Err(VpHaltReason::TripleFault { vtl: Vtl::Vtl0 });
1716                }
1717
1718                // Userspace does not maintain SNP page-visibility state yet.
1719                // Hyper-V validates that the GPA is suitable for use as a
1720                // doorbell page; propagate a rejected register write as a
1721                // fatal guest error.
1722                self.sev_set_reg(HvX64RegisterName::SevDoorbellGpa, doorbell_gpa | 1)?;
1723                let ghcb = self
1724                    .runner
1725                    .ghcb_page()
1726                    .ok_or(VpHaltReason::TripleFault { vtl: Vtl::Vtl0 })?;
1727                ghcb.save.sw_exit_info1 = 0;
1728            }
1729            exit_code if exit_code == u64::from(SVM_EXITCODE_SNP_AP_CREATION) => {
1730                self.handle_snp_ap_create(info, ghcb_gpa)?;
1731            }
1732            exit_code => {
1733                tracelimit::warn_ratelimited!(
1734                    exit_code,
1735                    sw_exit_info1 = info.ghcb_page.standard.sw_exit_info1,
1736                    sw_exit_info2 = info.ghcb_page.standard.sw_exit_info2,
1737                    sw_scratch = info.ghcb_page.standard.sw_scratch,
1738                    "unhandled SNP GHCB NAE"
1739                );
1740                return Err(VpHaltReason::TripleFault { vtl: Vtl::Vtl0 });
1741            }
1742        }
1743
1744        Ok(())
1745    }
1746
1747    pub(super) fn handle_snp_ap_create(
1748        &mut self,
1749        info: &hvdef::HvX64VmgexitInterceptMessage,
1750        ghcb_gpa: u64,
1751    ) -> Result<(), VpHaltReason> {
1752        let request = {
1753            let ghcb = self
1754                .runner
1755                .ghcb_page()
1756                .ok_or(VpHaltReason::TripleFault { vtl: Vtl::Vtl0 })?;
1757            if ghcb.save.sw_exit_info2 != info.ghcb_page.standard.sw_exit_info2 {
1758                return Err(VpHaltReason::TripleFault { vtl: Vtl::Vtl0 });
1759            }
1760            match parse_snp_ap_create_request(ghcb) {
1761                Ok(request) => request,
1762                Err(error) => {
1763                    tracelimit::warn_ratelimited!(
1764                        ?error,
1765                        "rejected invalid SNP AP creation request"
1766                    );
1767                    set_ghcb_error(ghcb, GHCB_ERROR_INVALID_INPUT);
1768                    return Ok(());
1769                }
1770            }
1771        };
1772        let Some(snp) = self.partition.isolation.snp() else {
1773            return Err(VpHaltReason::TripleFault { vtl: Vtl::Vtl0 });
1774        };
1775        let launch_sev_features = *snp.sev_features.lock();
1776        if launch_sev_features != Some(request.sev_features) {
1777            tracelimit::warn_ratelimited!(
1778                sev_features = request.sev_features,
1779                ?launch_sev_features,
1780                "rejected SNP AP creation with mismatched SEV features"
1781            );
1782            let ghcb = self
1783                .runner
1784                .ghcb_page()
1785                .ok_or(VpHaltReason::TripleFault { vtl: Vtl::Vtl0 })?;
1786            set_ghcb_error(ghcb, GHCB_ERROR_INVALID_INPUT);
1787            return Ok(());
1788        }
1789
1790        let target_vp = vp_index_for_apic_id(
1791            request.apic_id,
1792            self.partition
1793                .vps
1794                .iter()
1795                .map(|vp| (vp.vp_info.base.vp_index, vp.vp_info.apic_id)),
1796        );
1797        let vmsa_end = request.vmsa_gpa.checked_add(hvdef::HV_PAGE_SIZE);
1798        let valid_vmsa = vmsa_end.is_some_and(|end| {
1799            request.vmsa_gpa != ghcb_gpa
1800                && self.partition.mem_layout.ram().iter().any(|range| {
1801                    range
1802                        .range
1803                        .contains(&MemoryRange::new(request.vmsa_gpa..end))
1804                })
1805        });
1806        let target_vp = match target_vp {
1807            Some(target_vp) if valid_vmsa && !target_vp.is_bsp() && target_vp != self.vpindex => {
1808                target_vp
1809            }
1810            _ => {
1811                tracelimit::warn_ratelimited!(
1812                    apic_id = request.apic_id,
1813                    vmsa_gpa = request.vmsa_gpa,
1814                    sev_features = request.sev_features,
1815                    ?target_vp,
1816                    "rejected invalid SNP AP creation target"
1817                );
1818                let ghcb = self
1819                    .runner
1820                    .ghcb_page()
1821                    .ok_or(VpHaltReason::TripleFault { vtl: Vtl::Vtl0 })?;
1822                set_ghcb_error(ghcb, GHCB_ERROR_INVALID_INPUT);
1823                return Ok(());
1824            }
1825        };
1826        tracing::trace!(
1827            target_vp = target_vp.index(),
1828            apic_id = request.apic_id,
1829            vmsa_gpa = request.vmsa_gpa,
1830            sev_features = request.sev_features,
1831            "creating SNP AP"
1832        );
1833        let request = mshv_bindings::mshv_sev_snp_ap_create {
1834            vp_id: u64::from(target_vp.index()),
1835            vmsa_gpa: request.vmsa_gpa,
1836        };
1837        if let Err(error) = self.partition.vmfd.sev_snp_ap_create(&request) {
1838            tracelimit::error_ratelimited!(
1839                error = &error as &dyn std::error::Error,
1840                target_vp = target_vp.index(),
1841                vmsa_gpa = request.vmsa_gpa,
1842                "failed to create SNP AP"
1843            );
1844            let ghcb = self
1845                .runner
1846                .ghcb_page()
1847                .ok_or(VpHaltReason::TripleFault { vtl: Vtl::Vtl0 })?;
1848            set_ghcb_error(ghcb, GHCB_ERROR_INVALID_INPUT);
1849            return Ok(());
1850        }
1851
1852        let ghcb = self
1853            .runner
1854            .ghcb_page()
1855            .ok_or(VpHaltReason::TripleFault { vtl: Vtl::Vtl0 })?;
1856        ghcb.save.sw_exit_info1 = 0;
1857        ghcb.save.valid_bitmap1 |= GHCB_SW_EXIT_INFO1_VALID_BIT;
1858        Ok(())
1859    }
1860}
1861
1862#[cfg(test)]
1863mod tests {
1864    use super::*;
1865    use test_with_tracing::test;
1866
1867    #[test]
1868    fn snp_hypercall_requires_valid_consistent_registers() {
1869        let mut ghcb = x86defs::snp::GhcbPage::new_zeroed();
1870        let mut info = hvdef::HvX64HypercallInterceptMessage::new_zeroed();
1871        for (index, value) in [
1872            (x86emu::Gp::RAX as usize, 1),
1873            (x86emu::Gp::RCX as usize, 2),
1874            (x86emu::Gp::RDX as usize, 3),
1875            (x86emu::Gp::R8 as usize, 4),
1876        ] {
1877            assert!(set_ghcb_gp(&mut ghcb, index, value));
1878        }
1879        info.rax = ghcb.save.rax;
1880        info.rcx = ghcb.save.rcx;
1881        info.rdx = ghcb.save.rdx;
1882        info.r8 = ghcb.save.r8;
1883
1884        assert!(snp_hypercall_registers_are_consistent(&ghcb, &info));
1885
1886        ghcb.save.rdx ^= 1;
1887        assert!(!snp_hypercall_registers_are_consistent(&ghcb, &info));
1888        ghcb.save.valid_bitmap1 &= !GHCB_RDX_VALID_BIT;
1889        assert!(snp_hypercall_registers_are_consistent(&ghcb, &info));
1890
1891        ghcb.save.r8 ^= 1;
1892        assert!(!snp_hypercall_registers_are_consistent(&ghcb, &info));
1893        ghcb.save.valid_bitmap1 &= !GHCB_R8_VALID_BIT;
1894        assert!(snp_hypercall_registers_are_consistent(&ghcb, &info));
1895    }
1896
1897    #[test]
1898    fn snp_hv_cpuid_exposes_isolation() {
1899        let leaves = snp_hv_cpuid_overrides(0x40000010);
1900        assert_eq!(leaves[0].result[0], 0x40000010);
1901        let privileges = hvdef::HvPartitionPrivilege::from(
1902            u64::from(leaves[1].result[0]) | (u64::from(leaves[1].result[1]) << 32),
1903        );
1904        assert!(privileges.start_virtual_processor());
1905        assert!(privileges.isolation());
1906        assert_eq!(leaves[2].result, [0, 2, 0, 0]);
1907    }
1908
1909    #[test]
1910    fn snp_cpuid_overrides_match_sanitization() {
1911        let hidden = snp_cpuid_overrides(false);
1912        assert_eq!(hidden[0].result[2], 0);
1913        assert_eq!(hidden[0].mask[2], 1 << 31);
1914
1915        let exposed = snp_cpuid_overrides(true);
1916        assert_eq!(exposed[0].result[2], 1 << 31);
1917        assert_eq!(exposed[0].mask[2], 1 << 31);
1918
1919        assert_eq!(
1920            exposed[1].function,
1921            x86defs::cpuid::CpuidFunction::ExtendedStateEnumeration.0
1922        );
1923        assert_eq!(exposed[1].index, Some(1));
1924        assert_eq!(exposed[1].result[2..], [0, 0]);
1925        assert_eq!(exposed[1].mask[2..], [u32::MAX, u32::MAX]);
1926    }
1927
1928    #[test]
1929    fn adds_hyperv_leaves_to_snp_cpuid_page() {
1930        let mut page = x86defs::snp::HvPspCpuidPage::new_zeroed();
1931        page.count = 1;
1932        page.cpuid_leaf_info[0].eax_in = hvdef::HV_CPUID_FUNCTION_HV_VENDOR_AND_MAX_FUNCTION;
1933
1934        add_snp_hyperv_cpuid_leaves(&mut page).unwrap();
1935
1936        assert_eq!(page.count as usize, SNP_HYPERV_CPUID_FUNCTIONS.len());
1937        for function in SNP_HYPERV_CPUID_FUNCTIONS {
1938            assert_eq!(
1939                page.cpuid_leaf_info[..page.count as usize]
1940                    .iter()
1941                    .filter(|leaf| leaf.eax_in == function && leaf.ecx_in == 0)
1942                    .count(),
1943                1
1944            );
1945        }
1946    }
1947
1948    #[test]
1949    fn parses_aci_snp_start_vp_context() {
1950        let mut context = hvdef::hypercall::InitialVpContextX64::new_zeroed();
1951        context.rip = 0x517001;
1952        assert_eq!(snp_start_vp_vmsa_gpa(&context), Some(0x517000));
1953
1954        context.rflags = 2;
1955        assert_eq!(snp_start_vp_vmsa_gpa(&context), None);
1956        context.rflags = 0;
1957        context.rip = 0x517000;
1958        assert_eq!(snp_start_vp_vmsa_gpa(&context), None);
1959        context.rip = SNP_UNSAFE_VMSA_ALIGNMENT | 1;
1960        assert_eq!(snp_start_vp_vmsa_gpa(&context), None);
1961    }
1962
1963    #[test]
1964    fn maps_supported_snp_import_types() {
1965        assert_eq!(
1966            snp_isolated_page_type(virt::InitialPageImportType::Normal).unwrap(),
1967            Some(mshv_bindings::MSHV_ISOLATED_PAGE_NORMAL as u8)
1968        );
1969        assert_eq!(
1970            snp_isolated_page_type(virt::InitialPageImportType::NormalUnmeasured).unwrap(),
1971            Some(mshv_bindings::MSHV_ISOLATED_PAGE_UNMEASURED as u8)
1972        );
1973        assert_eq!(
1974            snp_isolated_page_type(virt::InitialPageImportType::VpContext).unwrap(),
1975            Some(mshv_bindings::MSHV_ISOLATED_PAGE_VMSA as u8)
1976        );
1977        assert_eq!(
1978            snp_isolated_page_type(virt::InitialPageImportType::Secrets).unwrap(),
1979            Some(mshv_bindings::MSHV_ISOLATED_PAGE_SECRETS as u8)
1980        );
1981        assert_eq!(
1982            snp_isolated_page_type(virt::InitialPageImportType::Cpuid).unwrap(),
1983            Some(mshv_bindings::MSHV_ISOLATED_PAGE_CPUID as u8)
1984        );
1985        assert_eq!(
1986            snp_isolated_page_type(virt::InitialPageImportType::Shared).unwrap(),
1987            None
1988        );
1989        assert!(matches!(
1990            snp_isolated_page_type(virt::InitialPageImportType::CpuidExtendedState),
1991            Err(Error(ErrorInner::Snp(SnpError::UnsupportedPageImportType(
1992                virt::InitialPageImportType::CpuidExtendedState
1993            ))))
1994        ));
1995    }
1996
1997    #[test]
1998    fn orders_snp_import_pages_for_launch_source() {
1999        let pages = [
2000            virt::InitialPageImport {
2001                range: MemoryRange::new(0x3000..0x4000),
2002                import_type: virt::InitialPageImportType::Normal,
2003                tag: "third",
2004            },
2005            virt::InitialPageImport {
2006                range: MemoryRange::new(0x1000..0x2000),
2007                import_type: virt::InitialPageImportType::Normal,
2008                tag: "first",
2009            },
2010        ];
2011
2012        assert_eq!(ordered_snp_import_pages(&pages, true), pages);
2013        assert_eq!(
2014            ordered_snp_import_pages(&pages, false),
2015            [pages[1].clone(), pages[0].clone()]
2016        );
2017    }
2018
2019    #[test]
2020    fn validates_unique_snp_pages() {
2021        let pages = [
2022            virt::InitialPageImport {
2023                range: MemoryRange::new(0x1000..0x2000),
2024                import_type: virt::InitialPageImportType::VpContext,
2025                tag: "vmsa",
2026            },
2027            virt::InitialPageImport {
2028                range: MemoryRange::new(0x2000..0x3000),
2029                import_type: virt::InitialPageImportType::Cpuid,
2030                tag: "cpuid",
2031            },
2032        ];
2033
2034        assert_eq!(snp_launch_pages(&pages).unwrap(), (0x1000, 0x2000));
2035
2036        let duplicate = [pages[0].clone(), pages[0].clone(), pages[1].clone()];
2037        assert!(matches!(
2038            snp_launch_pages(&duplicate),
2039            Err(Error(ErrorInner::Snp(SnpError::MultipleVmsa)))
2040        ));
2041    }
2042
2043    #[test]
2044    fn rejects_invalid_snp_page_ranges() {
2045        assert!(matches!(
2046            validate_snp_page_range(MemoryRange::EMPTY),
2047            Err(Error(ErrorInner::Snp(SnpError::InvalidPageRange)))
2048        ));
2049
2050        let pages = [
2051            virt::InitialPageImport {
2052                range: MemoryRange::new(0x1000..0x3000),
2053                import_type: virt::InitialPageImportType::VpContext,
2054                tag: "vmsa",
2055            },
2056            virt::InitialPageImport {
2057                range: MemoryRange::new(0x3000..0x4000),
2058                import_type: virt::InitialPageImportType::Cpuid,
2059                tag: "cpuid",
2060            },
2061        ];
2062        assert!(matches!(
2063            snp_launch_pages(&pages),
2064            Err(Error(ErrorInner::Snp(SnpError::InvalidPageRange)))
2065        ));
2066    }
2067
2068    #[test]
2069    fn marks_ghcb_rax_valid() {
2070        let mut ghcb = x86defs::snp::GhcbPage::new_zeroed();
2071
2072        assert!(!ghcb_rax_is_valid(&ghcb));
2073        set_ghcb_rax(&mut ghcb, 0x1234_5678);
2074
2075        assert_eq!(ghcb.save.rax, 0x1234_5678);
2076        assert!(ghcb_rax_is_valid(&ghcb));
2077    }
2078
2079    #[test]
2080    fn validates_ghcb_exit_fields() {
2081        let mut ghcb = x86defs::snp::GhcbPage::new_zeroed();
2082
2083        assert!(!ghcb_exit_fields_are_valid(&ghcb));
2084        ghcb.save.valid_bitmap1 |= GHCB_SW_EXIT_CODE_VALID_BIT;
2085        assert!(!ghcb_exit_fields_are_valid(&ghcb));
2086        ghcb.save.valid_bitmap1 |= GHCB_SW_EXIT_INFO1_VALID_BIT;
2087        assert!(ghcb_exit_fields_are_valid(&ghcb));
2088    }
2089
2090    #[test]
2091    fn validates_ghcb_mmio_fields() {
2092        let mut ghcb = x86defs::snp::GhcbPage::new_zeroed();
2093
2094        assert!(!ghcb_mmio_fields_are_valid(&ghcb));
2095        ghcb.save.valid_bitmap1 |= GHCB_SW_EXIT_INFO2_VALID_BIT;
2096        assert!(!ghcb_mmio_fields_are_valid(&ghcb));
2097        ghcb.save.valid_bitmap1 |= GHCB_SW_SCRATCH_VALID_BIT;
2098        assert!(ghcb_mmio_fields_are_valid(&ghcb));
2099    }
2100
2101    #[test]
2102    fn validates_ghcb_exit_info2() {
2103        let mut ghcb = x86defs::snp::GhcbPage::new_zeroed();
2104
2105        assert!(!ghcb_exit_info2_is_valid(&ghcb));
2106        ghcb.save.valid_bitmap1 |= GHCB_SW_EXIT_INFO2_VALID_BIT;
2107        assert!(ghcb_exit_info2_is_valid(&ghcb));
2108    }
2109
2110    #[test]
2111    fn parses_snp_ap_create_requests() {
2112        let mut ghcb = x86defs::snp::GhcbPage::new_zeroed();
2113        ghcb.save.sw_exit_info1 = (7u64 << 32) | u64::from(SVM_NAE_SNP_AP_CREATE);
2114        ghcb.save.sw_exit_info2 = 0x20_000;
2115        set_ghcb_rax(&mut ghcb, 9);
2116        ghcb.save.valid_bitmap1 |= GHCB_SW_EXIT_INFO1_VALID_BIT | GHCB_SW_EXIT_INFO2_VALID_BIT;
2117
2118        assert_eq!(
2119            parse_snp_ap_create_request(&ghcb),
2120            Ok(SnpApCreateRequest {
2121                apic_id: 7,
2122                vmsa_gpa: 0x20_000,
2123                sev_features: 9,
2124            })
2125        );
2126
2127        ghcb.save.sw_exit_info1 = 2;
2128        assert_eq!(
2129            parse_snp_ap_create_request(&ghcb),
2130            Err(SnpApCreateRequestError::UnsupportedOperation(2))
2131        );
2132
2133        ghcb.save.sw_exit_info1 = 0;
2134        assert_eq!(
2135            parse_snp_ap_create_request(&ghcb),
2136            Err(SnpApCreateRequestError::UnsupportedOperation(0))
2137        );
2138
2139        ghcb.save.sw_exit_info1 = (7u64 << 32) | (1 << 16) | u64::from(SVM_NAE_SNP_AP_CREATE);
2140        assert_eq!(
2141            parse_snp_ap_create_request(&ghcb),
2142            Err(SnpApCreateRequestError::UnsupportedVmpl(1))
2143        );
2144
2145        ghcb.save.sw_exit_info1 = (7u64 << 32) | u64::from(SVM_NAE_SNP_AP_CREATE);
2146        ghcb.save.sw_exit_info2 = 0x20_001;
2147        assert_eq!(
2148            parse_snp_ap_create_request(&ghcb),
2149            Err(SnpApCreateRequestError::InvalidVmsaGpa(0x20_001))
2150        );
2151
2152        ghcb.save.sw_exit_info2 = SNP_UNSAFE_VMSA_ALIGNMENT;
2153        assert_eq!(
2154            parse_snp_ap_create_request(&ghcb),
2155            Err(SnpApCreateRequestError::InvalidVmsaGpa(
2156                SNP_UNSAFE_VMSA_ALIGNMENT
2157            ))
2158        );
2159
2160        ghcb.save.sw_exit_info2 = 0x20_000;
2161        ghcb.save.rax = 0;
2162        assert_eq!(
2163            parse_snp_ap_create_request(&ghcb),
2164            Err(SnpApCreateRequestError::InvalidSevFeatures(0))
2165        );
2166
2167        set_ghcb_rax(&mut ghcb, 9);
2168        ghcb.save.valid_bitmap1 &= !GHCB_SW_EXIT_INFO2_VALID_BIT;
2169        assert_eq!(
2170            parse_snp_ap_create_request(&ghcb),
2171            Err(SnpApCreateRequestError::MissingInput)
2172        );
2173    }
2174
2175    #[test]
2176    fn maps_snp_apic_ids_to_vp_indices() {
2177        let vps = [(VpIndex::new(0), 0), (VpIndex::new(1), 4)];
2178        assert_eq!(vp_index_for_apic_id(4, vps), Some(VpIndex::new(1)));
2179        assert_eq!(vp_index_for_apic_id(3, vps), None);
2180    }
2181
2182    #[test]
2183    fn encodes_ghcb_errors() {
2184        let mut ghcb = x86defs::snp::GhcbPage::new_zeroed();
2185        ghcb.save.valid_bitmap1 |= GHCB_SW_EXIT_CODE_VALID_BIT;
2186        set_ghcb_error(&mut ghcb, GHCB_ERROR_INVALID_INPUT);
2187
2188        assert_eq!(ghcb.save.sw_exit_info1, GHCB_ERROR_RESPONSE);
2189        assert_eq!(ghcb.save.sw_exit_info2, GHCB_ERROR_INVALID_INPUT);
2190        assert!(ghcb_exit_fields_are_valid(&ghcb));
2191        assert!(ghcb_exit_info2_is_valid(&ghcb));
2192    }
2193
2194    #[test]
2195    fn builds_snp_host_access_flags() {
2196        assert_eq!(snp_host_access_flags(0), Some(0));
2197        assert_eq!(snp_host_access_flags(1), None);
2198        assert_eq!(
2199            snp_host_access_flags(3),
2200            Some(
2201                1 << mshv_bindings::MSHV_GPA_HOST_ACCESS_BIT_ACQUIRE
2202                    | 1 << mshv_bindings::MSHV_GPA_HOST_ACCESS_BIT_READABLE
2203                    | 1 << mshv_bindings::MSHV_GPA_HOST_ACCESS_BIT_WRITABLE
2204            )
2205        );
2206        assert_eq!(snp_host_access_flags(2), None);
2207    }
2208
2209    #[test]
2210    fn parses_snp_gpa_ranges() {
2211        let mut range = hvdef::hypercall::HvGpaRange(
2212            hvdef::hypercall::HvGpaRangeExtended::new()
2213                .with_additional_pages(2)
2214                .with_gpa_page_number(0x1234)
2215                .into_bits(),
2216        );
2217        assert_eq!(parse_snp_gpa_range(range).unwrap(), (0x1234, 3));
2218
2219        range = hvdef::hypercall::HvGpaRange(
2220            hvdef::hypercall::HvGpaRangeExtendedLargePage::new()
2221                .with_additional_pages(1)
2222                .with_large_page(true)
2223                .with_gpa_large_page_number(1)
2224                .into_bits(),
2225        );
2226        assert_eq!(parse_snp_gpa_range(range).unwrap(), (0x200, 1024));
2227
2228        range = hvdef::hypercall::HvGpaRange(
2229            hvdef::hypercall::HvGpaRangeExtendedLargePage::new()
2230                .with_large_page(true)
2231                .with_page_size(true)
2232                .with_gpa_large_page_number(512)
2233                .into_bits(),
2234        );
2235        assert_eq!(parse_snp_gpa_range(range).unwrap(), (512 * 512, 512 * 512));
2236
2237        range = hvdef::hypercall::HvGpaRange(
2238            hvdef::hypercall::HvGpaRangeExtendedLargePage::new()
2239                .with_large_page(true)
2240                .with_page_size(true)
2241                .with_gpa_large_page_number(1)
2242                .into_bits(),
2243        );
2244        assert!(parse_snp_gpa_range(range).is_err());
2245    }
2246
2247    #[test]
2248    fn sanitizes_snp_cpuid() {
2249        let mut values = [0, 0, 1 << 31, 0];
2250        sanitize_snp_cpuid(
2251            x86defs::cpuid::CpuidFunction::VersionAndFeatures.0,
2252            0,
2253            false,
2254            &mut values,
2255        );
2256        assert_eq!(values[2], 0);
2257
2258        let mut values = [0, 0, 1 << 31, 0];
2259        sanitize_snp_cpuid(
2260            x86defs::cpuid::CpuidFunction::VersionAndFeatures.0,
2261            0,
2262            true,
2263            &mut values,
2264        );
2265        assert_eq!(values[2], 1 << 31);
2266
2267        let mut values = [0xb, 0x240, 0x1800, 1];
2268        sanitize_snp_cpuid(
2269            x86defs::cpuid::CpuidFunction::ExtendedStateEnumeration.0,
2270            1,
2271            false,
2272            &mut values,
2273        );
2274        assert_eq!(values, [0xb, 0x240, 0, 0]);
2275    }
2276}