1use 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 #[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 pub(crate) launch_state: Mutex<SnpLaunchState>,
152 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 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
217unsafe impl Send for MshvGhcbPage {}
220
221impl Drop for MshvGhcbPage {
222 fn drop(&mut self) {
223 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
342fn 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 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 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 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 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 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 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 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 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 [
612 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 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 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 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 *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 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 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
903fn 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 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
955fn 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
971fn 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(®s, modified_gp, modified_xmm)
1147 }
1148
1149 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 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 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 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 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 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 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 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}