1use crate::file_loader::IgvmLoader;
7use crate::file_loader::IgvmOutput;
8use crate::file_loader::SnpLinuxDirectConfig;
9use crate::vp_context_builder::snp::InjectionType;
10use anyhow::Context;
11use anyhow::ensure;
12use chipset_resources::pm::DEFAULT_ACPI_IRQ;
13use chipset_resources::pm::DEFAULT_PM_PIO_BASE;
14use igvm_defs::SnpPolicy;
15use igvmfilegen_config::LinuxImage;
16use igvmfilegen_config::ResourceType;
17use igvmfilegen_config::Resources;
18use igvmfilegen_config::SnpInjectionType;
19use loader::importer::BootPageAcceptance;
20use loader::importer::ImageLoad;
21use loader::importer::X86Register;
22use loader::linux::InitrdAddressType;
23use loader::linux::InitrdConfig;
24use loader_defs::linux::SNP_BOOT_SHIM_MAX_RANGES;
25use loader_defs::linux::SNP_BOOT_SHIM_PARAMS_MAGIC;
26use loader_defs::linux::SNP_BOOT_SHIM_PARAMS_VERSION;
27use loader_defs::linux::SnpBootShimParams;
28use loader_defs::linux::SnpBootShimRange;
29use memory_range::MemoryRange;
30use serial_16550_resources::ComPort;
31use std::io::Seek;
32use vm_topology::memory::MemoryLayout;
33use vm_topology::pcie::PcieHostBridge;
34use vm_topology::processor::ProcessorTopology;
35use vm_topology::processor::TopologyBuilder;
36use vm_topology::processor::x86::X86Topology;
37use vmm_core::acpi_builder::AcpiArchConfig;
38use vmm_core::acpi_builder::AcpiTablesBuilder;
39use zerocopy::FromZeros;
40use zerocopy::IntoBytes;
41
42const PAGE_SIZE: u64 = igvm_defs::PAGE_SIZE_4K;
43const KVM_VMSA_GPA: u64 = 0xffff_ffff_f000;
50
51pub struct BuildParams<'a> {
53 pub linux: &'a LinuxImage,
55 pub processor_count: u32,
57 pub memory_page_count: u64,
59 pub c_bit_position: u8,
61 pub policy: SnpPolicy,
63 pub injection_type: &'a SnpInjectionType,
65 pub resources: &'a Resources,
67}
68
69struct FixedGuestLayout {
71 memory: MemoryLayout,
72 processors: ProcessorTopology<X86Topology>,
73 pcie_host_bridges: Vec<PcieHostBridge>,
74}
75
76impl FixedGuestLayout {
77 fn new(memory_page_count: u64, processor_count: u32) -> anyhow::Result<Self> {
78 let memory_size = memory_page_count
79 .checked_mul(PAGE_SIZE)
80 .context("RAM size overflow")?;
81 let memory = MemoryLayout::new(memory_size, &[], &[], &[], None)
82 .context("building memory layout")?;
83 let mut processors = TopologyBuilder::new_x86()
84 .build(processor_count)
85 .context("building processor topology")?;
86 processors.set_vnodes(&vec![0; processor_count as usize]);
88
89 Ok(Self {
90 memory,
91 processors,
92 pcie_host_bridges: Vec::new(),
93 })
94 }
95
96 fn acpi_builder(&self) -> AcpiTablesBuilder<'_, X86Topology> {
97 AcpiTablesBuilder {
104 processor_topology: &self.processors,
105 mem_layout: &self.memory,
106 cache_topology: None,
107 pcie_host_bridges: &self.pcie_host_bridges,
108 slit_info: None,
109 generic_initiators: &[],
110 arch: AcpiArchConfig::X86 {
111 with_ioapic: true,
112 with_pic: true,
113 with_pit: true,
114 with_psp: false,
115 pm_base: DEFAULT_PM_PIO_BASE,
116 acpi_irq: DEFAULT_ACPI_IRQ,
117 iommu: None,
118 },
119 }
120 }
121}
122
123fn open_linux_resources(
124 linux: &LinuxImage,
125 resources: &Resources,
126) -> anyhow::Result<(fs_err::File, Option<fs_err::File>)> {
127 let kernel_path = resources
128 .get(ResourceType::LinuxKernel)
129 .context("Linux kernel resource is missing")?;
130 let kernel = fs_err::File::open(kernel_path)
131 .with_context(|| format!("opening kernel {}", kernel_path.display()))?;
132
133 let initrd = if linux.use_initrd {
134 let initrd_path = resources
135 .get(ResourceType::LinuxInitrd)
136 .context("Linux initrd resource is missing")?;
137 Some(
138 fs_err::File::open(initrd_path)
139 .with_context(|| format!("opening initrd {}", initrd_path.display()))?,
140 )
141 } else {
142 None
143 };
144
145 Ok((kernel, initrd))
146}
147
148fn initrd_config(initrd: &mut Option<fs_err::File>) -> anyhow::Result<Option<InitrdConfig<'_>>> {
149 let Some(initrd) = initrd else {
150 return Ok(None);
151 };
152 let size = initrd
153 .seek(std::io::SeekFrom::End(0))
154 .context("measuring initrd")?;
155 initrd.rewind().context("rewinding initrd")?;
156 Ok(Some(InitrdConfig {
157 initrd_address: InitrdAddressType::AfterKernel,
158 initrd,
159 size,
160 }))
161}
162
163fn new_loader(
164 policy: SnpPolicy,
165 c_bit_position: u8,
166 memory_page_count: u64,
167 injection_type: &SnpInjectionType,
168) -> IgvmLoader<X86Register> {
169 IgvmLoader::new_snp_linux_direct(SnpLinuxDirectConfig {
170 policy,
171 c_bit_mask: 1u64 << c_bit_position,
172 ram_page_count: memory_page_count,
173 vmsa_page: Some(KVM_VMSA_GPA / PAGE_SIZE),
174 injection_type: match injection_type {
175 SnpInjectionType::Normal => InjectionType::Normal,
176 SnpInjectionType::Restricted => InjectionType::Restricted,
177 },
178 })
179}
180
181pub fn build(params: BuildParams<'_>) -> anyhow::Result<IgvmOutput> {
184 let BuildParams {
185 linux,
186 processor_count,
187 memory_page_count,
188 c_bit_position,
189 policy,
190 injection_type,
191 resources,
192 } = params;
193
194 let layout = FixedGuestLayout::new(memory_page_count, processor_count)?;
195 let acpi_builder = layout.acpi_builder();
196 let (mut kernel, mut initrd) = open_linux_resources(linux, resources)?;
197 let initrd_config = initrd_config(&mut initrd)?;
198 let mut loader = new_loader(policy, c_bit_position, memory_page_count, injection_type);
199 let com1 = ComPort::Com1;
200
201 let load_info = loader::linux::load_x86(
202 &mut loader.loader(),
203 &mut kernel,
204 initrd_config,
205 &linux.command_line,
206 &layout.memory,
207 |gpa| {
208 let tables = acpi_builder.build_acpi_tables(gpa, |dsdt| {
209 dsdt.add_apic();
210 dsdt.add_uart(b"\\_SB.UAR1", b"COM1", 1, com1.io_port(), com1.irq().into());
211 dsdt.add_rtc();
212 });
213 loader::linux::AcpiTables {
214 rsdp: tables.rsdp,
215 tables: tables.tables,
216 }
217 },
218 None,
219 Some(loader::linux::SnpBootConfig {
220 c_bit: c_bit_position,
221 }),
222 )
223 .context("loading direct-Linux image")?;
224
225 let kernel_runtime_end = kernel_runtime_end(
226 load_info.kernel.gpa,
227 load_info.kernel.size,
228 load_info
229 .bzimage_setup_header
230 .as_ref()
231 .map(|header| (u64::from(header.pref_address), u64::from(header.init_size))),
232 )?;
233 let bootshim_ranges = load_bootshim_and_handoff(
234 &mut loader,
235 resources,
236 load_info.kernel.entrypoint,
237 loader::linux::ZERO_PAGE_BASE,
238 memory_page_count,
239 kernel_runtime_end,
240 )?;
241
242 let mut output = loader.finalize().context("finalizing SNP IGVM")?;
243 for range in bootshim_ranges {
244 output.map.report_range(
245 MemoryRange::from_4k_gpn_range(range.start_gpn..range.start_gpn + range.page_count),
246 "snp-bootshim-accepted-ram [PVALIDATE]",
247 );
248 }
249 Ok(output)
250}
251
252fn load_bootshim_and_handoff(
264 loader: &mut IgvmLoader<X86Register>,
265 resources: &Resources,
266 linux_entry: u64,
267 linux_zero_page: u64,
268 memory_page_count: u64,
269 kernel_runtime_end: u64,
270) -> anyhow::Result<Vec<SnpBootShimRange>> {
271 let shim_base = align_up_to_page(loader.next_available_gpa()?.max(kernel_runtime_end));
272
273 let bootshim_path = resources
274 .get(ResourceType::SnpBootshim)
275 .context("SNP bootshim resource is missing")?;
276 let mut bootshim = fs_err::File::open(bootshim_path)
277 .with_context(|| format!("opening SNP bootshim {}", bootshim_path.display()))?;
278 let shim_load_info = loader::elf::load_static_elf(
279 &mut loader.loader(),
280 &mut bootshim,
281 0,
282 shim_base,
283 false,
284 BootPageAcceptance::Exclusive,
285 "snp-bootshim",
286 )
287 .context("loading SNP bootshim")?;
288
289 let params_gpa = align_up_to_page(shim_load_info.next_available_address);
290 let params_page = params_gpa / PAGE_SIZE;
291 ensure!(
292 params_page < memory_page_count,
293 "SNP bootshim parameter page lies outside configured RAM"
294 );
295
296 let bootshim_ranges = loader
297 .unimported_ram_ranges([params_page])?
298 .into_iter()
299 .map(|range| SnpBootShimRange {
300 start_gpn: range.start,
301 page_count: range.end - range.start,
302 })
303 .collect::<Vec<_>>();
304
305 let bootshim_params = build_bootshim_params(
306 linux_entry,
307 linux_zero_page,
308 memory_page_count * PAGE_SIZE,
309 &bootshim_ranges,
310 )?;
311 {
312 let mut importer = loader.loader();
313 importer
314 .import_pages(
315 params_page,
316 1,
317 "snp-bootshim-params",
318 BootPageAcceptance::Exclusive,
319 bootshim_params.as_bytes(),
320 )
321 .context("importing SNP bootshim parameters")?;
322 importer.import_vp_register(X86Register::Rip(shim_load_info.entrypoint))?;
323 importer.import_vp_register(X86Register::Rsi(params_gpa))?;
324 }
325
326 Ok(bootshim_ranges)
327}
328
329fn align_up_to_page(value: u64) -> u64 {
330 value
331 .checked_add(PAGE_SIZE - 1)
332 .expect("page alignment overflow")
333 & !(PAGE_SIZE - 1)
334}
335
336fn kernel_runtime_end(
337 load_gpa: u64,
338 image_size: u64,
339 bzimage_runtime: Option<(u64, u64)>,
340) -> anyhow::Result<u64> {
341 let (runtime_gpa, runtime_size) = bzimage_runtime
342 .map(|(preferred_gpa, init_size)| (load_gpa.max(preferred_gpa), init_size))
343 .unwrap_or((load_gpa, image_size));
344 runtime_gpa
345 .checked_add(runtime_size)
346 .context("Linux runtime image end overflow")
347}
348
349fn build_bootshim_params(
350 linux_entry: u64,
351 linux_zero_page: u64,
352 ram_end: u64,
353 ranges: &[SnpBootShimRange],
354) -> anyhow::Result<SnpBootShimParams> {
355 ensure!(
356 ranges.len() <= SNP_BOOT_SHIM_MAX_RANGES,
357 "sparse SNP layout requires {} bootshim ranges, but the parameter page supports at most {}",
358 ranges.len(),
359 SNP_BOOT_SHIM_MAX_RANGES
360 );
361 let mut params = SnpBootShimParams::new_zeroed();
362 params.magic = SNP_BOOT_SHIM_PARAMS_MAGIC;
363 params.version = SNP_BOOT_SHIM_PARAMS_VERSION;
364 params.range_count = ranges
365 .len()
366 .try_into()
367 .context("SNP bootshim range count does not fit in u32")?;
368 params.linux_entry = linux_entry;
369 params.linux_zero_page = linux_zero_page;
370 params.ram_end = ram_end;
371 params.ranges[..ranges.len()].copy_from_slice(ranges);
372 Ok(params)
373}
374
375#[cfg(test)]
376mod tests {
377 use super::*;
378 use crate::vp_context_builder::VpContextBuilder;
379 use crate::vp_context_builder::snp::SnpHardwareContext;
380 use acpi_spec::Header;
381 use acpi_spec::srat::SratApic;
382 use acpi_spec::srat::SratHeader;
383 use acpi_spec::srat::SratMemory;
384 use acpi_spec::srat::SratX2Apic;
385 use igvm::IgvmDirectiveHeader;
386 use igvm::IgvmFile;
387 use igvm::IgvmInitializationHeader;
388 use igvm::IgvmPlatformHeader;
389 use igvm::IgvmSerializer;
390 use igvm_defs::IgvmPageDataType;
391 use igvm_defs::IgvmPlatformType;
392 use loader::importer::BootPageAcceptance;
393 use loader::importer::ImageLoad;
394 use std::collections::BTreeSet;
395 use test_with_tracing::test;
396 use zerocopy::FromBytes;
397
398 const COMPATIBILITY_MASK: u32 = 1;
399 const TEST_POLICY: u64 = 0x30000;
400 const TEST_C_BIT_MASK: u64 = 1 << 51;
401 const TEST_SHIM_ENTRY: u64 = 0x100000;
402
403 fn test_loader_with_injection(
404 ram_page_count: u64,
405 injection_type: InjectionType,
406 ) -> IgvmLoader<X86Register> {
407 IgvmLoader::new_snp_linux_direct(SnpLinuxDirectConfig {
408 policy: SnpPolicy::from(TEST_POLICY),
409 c_bit_mask: TEST_C_BIT_MASK,
410 ram_page_count,
411 vmsa_page: Some(KVM_VMSA_GPA / PAGE_SIZE),
412 injection_type,
413 })
414 }
415
416 fn test_loader(ram_page_count: u64) -> IgvmLoader<X86Register> {
417 test_loader_with_injection(ram_page_count, InjectionType::Normal)
418 }
419
420 fn import_test_registers(importer: &mut dyn ImageLoad<X86Register>, params_gpa: u64) {
421 for register in [
422 X86Register::Rip(TEST_SHIM_ENTRY),
423 X86Register::Rsi(params_gpa),
424 X86Register::Cr3(0x4000 | TEST_C_BIT_MASK),
425 X86Register::Cr0(x86defs::X64_CR0_PE | x86defs::X64_CR0_PG),
426 X86Register::Cr4(x86defs::X64_CR4_PAE),
427 X86Register::Efer(x86defs::X64_EFER_LME | x86defs::X64_EFER_LMA),
428 ] {
429 importer.import_vp_register(register).unwrap();
430 }
431 }
432
433 fn assert_serialized_igvm_shape(
434 igvm: &IgvmFile,
435 ram_page_count: u64,
436 expected_pages: &[u64],
437 expected_handoff: (u64, u64),
438 ) {
439 let serializer = IgvmSerializer::new(igvm).unwrap();
440 let expected_launch_digest = serializer
441 .measurement_for(IgvmPlatformType::SEV_SNP)
442 .unwrap()
443 .digest
444 .clone();
445 let mut binary = Vec::new();
446 serializer.serialize(&mut binary).unwrap();
447 let reparsed = IgvmFile::new_from_binary(&binary, Some(igvm::IsolationType::Snp)).unwrap();
448
449 assert_eq!(reparsed.platforms().len(), 1);
450 let IgvmPlatformHeader::SupportedPlatform(platform) = &reparsed.platforms()[0];
451 assert_eq!(platform.platform_type, IgvmPlatformType::SEV_SNP);
452 assert_eq!(platform.highest_vtl, 0);
453 assert_eq!(platform.shared_gpa_boundary, 0);
454 assert!(reparsed.initializations().iter().any(|header| matches!(
455 header,
456 IgvmInitializationHeader::GuestPolicy {
457 policy: TEST_POLICY,
458 compatibility_mask: COMPATIBILITY_MASK,
459 }
460 )));
461
462 let mut covered_pages = BTreeSet::new();
463 let mut required_memory_count = 0;
464 let mut next_vmsa_index = 0;
465 let mut secrets_count = 0;
466 let mut cpuid_count = 0;
467
468 for directive in reparsed.directives() {
469 match directive {
470 IgvmDirectiveHeader::PageData {
471 gpa,
472 flags,
473 data_type,
474 data,
475 ..
476 } => {
477 assert!(gpa.is_multiple_of(PAGE_SIZE));
478 let page = gpa / PAGE_SIZE;
479 assert!(page < ram_page_count);
480 assert!(covered_pages.insert(page));
481 assert!(!flags.shared() && !flags.unmeasured());
482 assert!(data.len() <= PAGE_SIZE as usize);
483 match *data_type {
484 IgvmPageDataType::SECRETS => secrets_count += 1,
485 IgvmPageDataType::CPUID_DATA => cpuid_count += 1,
486 IgvmPageDataType::NORMAL => {}
487 unexpected => panic!("unexpected PageData type {unexpected:?}"),
488 }
489 }
490 IgvmDirectiveHeader::SnpVpContext {
491 gpa,
492 vp_index,
493 vmsa,
494 ..
495 } => {
496 assert_eq!(*gpa, KVM_VMSA_GPA);
497 assert_eq!(u32::from(*vp_index), next_vmsa_index);
498 assert_eq!(vmsa.rip, expected_handoff.0);
499 assert_eq!(vmsa.rsi, expected_handoff.1);
500 assert!(vmsa.sev_features.snp());
501 assert!(!vmsa.sev_features.vtom());
502 assert_eq!(vmsa.virtual_tom, 0);
503 assert_ne!(vmsa.cr3 & TEST_C_BIT_MASK, 0);
504 assert_ne!(vmsa.cr0 & x86defs::X64_CR0_ET, 0);
505 assert_ne!(vmsa.cr4 & x86defs::X64_CR4_MCE, 0);
506 assert_eq!(vmsa.rflags, u64::from(x86defs::RFlags::at_reset()));
507 assert_eq!(vmsa.dr6, 0xffff_0ff0);
508 assert_eq!(vmsa.dr7, 0x400);
509 assert_eq!(vmsa.x87_fcw, x86defs::xsave::INIT_FCW);
510 assert_eq!(vmsa.mxcsr, x86defs::xsave::DEFAULT_MXCSR);
511 next_vmsa_index += 1;
512 }
513 IgvmDirectiveHeader::RequiredMemory {
514 gpa,
515 number_of_bytes,
516 vtl2_protectable,
517 ..
518 } => {
519 assert_eq!(*gpa, 0);
520 assert_eq!(u64::from(*number_of_bytes), ram_page_count * PAGE_SIZE);
521 assert!(!vtl2_protectable);
522 required_memory_count += 1;
523 }
524 unexpected => panic!("unexpected directive in fixed SNP IGVM: {unexpected:?}"),
525 }
526 }
527
528 assert_eq!(
529 covered_pages,
530 expected_pages.iter().copied().collect::<BTreeSet<_>>()
531 );
532 assert_eq!(required_memory_count, 1);
533 assert_eq!(next_vmsa_index, 1);
534 assert_eq!(secrets_count, 1);
535 assert_eq!(cpuid_count, 1);
536
537 let measured_digest = IgvmSerializer::new(&reparsed)
538 .unwrap()
539 .measurement_for(IgvmPlatformType::SEV_SNP)
540 .unwrap()
541 .digest
542 .clone();
543 assert_eq!(measured_digest, expected_launch_digest);
544 }
545
546 #[test]
547 fn fixed_layout_srat_assigns_all_cpus_and_memory_to_node_zero() {
548 for processor_count in [1, 2, 4, 8, 256] {
549 let layout = FixedGuestLayout::new(64, processor_count).unwrap();
550 assert_eq!(layout.processors.vp_count(), processor_count);
551 assert_eq!(layout.memory.ram().len(), 1);
552 assert!(layout.processors.vps().all(|vp| vp.vnode == 0));
553
554 let srat = layout.acpi_builder().build_srat();
555 let (header, data) = Header::read_from_prefix(&srat).unwrap();
556 assert_eq!(header.signature, *b"SRAT");
557 assert_eq!(header.length.get() as usize, srat.len());
558 assert_eq!(
559 srat.iter().fold(0u8, |sum, &byte| sum.wrapping_add(byte)),
560 0
561 );
562 let (_, mut entries) = SratHeader::read_from_prefix(data).unwrap();
563
564 for apic_id in 0..processor_count {
565 if apic_id <= 0xfe {
566 let (entry, rest) = SratApic::read_from_prefix(entries).unwrap();
567 assert_eq!(entry.as_bytes(), SratApic::new(apic_id as u8, 0).as_bytes());
568 entries = rest;
569 } else {
570 let (entry, rest) = SratX2Apic::read_from_prefix(entries).unwrap();
571 assert_eq!(entry.as_bytes(), SratX2Apic::new(apic_id, 0).as_bytes());
572 entries = rest;
573 }
574 }
575 for range in layout.memory.ram() {
576 let (entry, rest) = SratMemory::read_from_prefix(entries).unwrap();
577 assert_eq!(
578 entry.as_bytes(),
579 SratMemory::new(range.range.start(), range.range.len(), 0).as_bytes(),
580 );
581 entries = rest;
582 }
583 assert!(entries.is_empty());
584 }
585 }
586
587 #[test]
588 fn bzimage_runtime_uses_preferred_address_above_load_address() {
589 assert_eq!(
590 kernel_runtime_end(0x100000, 0x200000, Some((0x400000, 0x300000))).unwrap(),
591 0x700000
592 );
593 }
594
595 #[test]
596 fn bzimage_runtime_uses_load_address_above_preferred_address() {
597 assert_eq!(
598 kernel_runtime_end(0x400000, 0x200000, Some((0x100000, 0x300000))).unwrap(),
599 0x700000
600 );
601 }
602
603 #[test]
604 fn raw_kernel_runtime_uses_image_size() {
605 assert_eq!(
606 kernel_runtime_end(0x100000, 0x200000, None).unwrap(),
607 0x300000
608 );
609 }
610
611 #[test]
612 fn rejects_kernel_runtime_end_overflow() {
613 assert!(kernel_runtime_end(u64::MAX, 1, None).is_err());
614 assert!(kernel_runtime_end(0, 0, Some((u64::MAX, 1))).is_err());
615 }
616
617 #[test]
618 fn vmsa_uses_c_bit_model() {
619 let params_gpa = 0x6000;
620 let mut context =
621 SnpHardwareContext::new_linux_direct(TEST_C_BIT_MASK, InjectionType::Normal);
622 for register in [
623 X86Register::Rip(TEST_SHIM_ENTRY),
624 X86Register::Rsi(params_gpa),
625 X86Register::Cr3(0x4000 | TEST_C_BIT_MASK),
626 X86Register::Cr0(x86defs::X64_CR0_PE | x86defs::X64_CR0_PG),
627 X86Register::Cr4(x86defs::X64_CR4_PAE),
628 X86Register::Efer(x86defs::X64_EFER_LME | x86defs::X64_EFER_LMA),
629 ] {
630 context.import_vp_register(register);
631 }
632
633 let vmsa = context.vmsa();
634 assert_eq!(vmsa.rip, TEST_SHIM_ENTRY);
635 assert_eq!(vmsa.rsi, params_gpa);
636 assert_ne!(vmsa.cr3 & TEST_C_BIT_MASK, 0);
637 assert_ne!(vmsa.cr0 & x86defs::X64_CR0_ET, 0);
638 assert_ne!(vmsa.cr4 & x86defs::X64_CR4_MCE, 0);
639 assert!(vmsa.sev_features.snp());
640 assert!(!vmsa.sev_features.vtom());
641 assert!(!vmsa.sev_features.debug_swap());
642 assert_eq!(vmsa.virtual_tom, 0);
643 assert_eq!(vmsa.rflags, u64::from(x86defs::RFlags::at_reset()));
644 assert_eq!(vmsa.dr6, 0xffff_0ff0);
645 assert_eq!(vmsa.dr7, 0x400);
646 assert_eq!(vmsa.tr.limit, 0xffff);
647 assert_eq!(vmsa.tr.attrib, 0x8b);
648 assert_eq!(vmsa.ldtr.limit, 0xffff);
649 assert_eq!(vmsa.ldtr.attrib, 0x82);
650 assert_eq!(vmsa.idtr.limit, 0xffff);
651 assert_eq!(vmsa.x87_fcw, x86defs::xsave::INIT_FCW);
652 assert_eq!(vmsa.mxcsr, x86defs::xsave::DEFAULT_MXCSR);
653 }
654
655 #[test]
656 fn normal_and_restricted_vmsas_use_the_selected_injection_mode() {
657 for (injection_type, restricted) in [
658 (InjectionType::Normal, false),
659 (InjectionType::Restricted, true),
660 ] {
661 let mut loader = test_loader_with_injection(1, injection_type);
662 import_test_registers(&mut loader.loader(), 0x6000);
663 let output = loader.finalize().unwrap();
664 let vmsa = output
665 .guest
666 .directives()
667 .iter()
668 .find_map(|directive| match directive {
669 IgvmDirectiveHeader::SnpVpContext { vmsa, .. } => Some(vmsa),
670 _ => None,
671 })
672 .unwrap();
673 assert!(vmsa.sev_features.snp());
674 assert_eq!(vmsa.sev_features.restrict_injection(), restricted);
675 assert!(!vmsa.sev_features.alternate_injection());
676 }
677 }
678
679 #[test]
680 fn sparse_igvm_serializes_and_preserves_measurement() {
681 const RAM_PAGE_COUNT: u64 = 8;
682 const PARAMS_PAGE: u64 = 6;
683 let params_gpa = PARAMS_PAGE * PAGE_SIZE;
684 let mut loader = test_loader(RAM_PAGE_COUNT);
685 {
686 let mut importer = loader.loader();
687 importer
688 .import_pages(1, 1, "secrets", BootPageAcceptance::SecretsPage, &[])
689 .unwrap();
690 importer
691 .import_pages(2, 1, "cpuid", BootPageAcceptance::CpuidPage, &[])
692 .unwrap();
693 }
694
695 let ranges = loader.unimported_ram_ranges([PARAMS_PAGE]).unwrap();
696 assert_eq!(ranges, [0..1, 3..6, 7..8]);
697 let ranges = ranges
698 .iter()
699 .map(|range| SnpBootShimRange {
700 start_gpn: range.start,
701 page_count: range.end - range.start,
702 })
703 .collect::<Vec<_>>();
704 let params = build_bootshim_params(
705 0x200000,
706 loader::linux::ZERO_PAGE_BASE,
707 RAM_PAGE_COUNT * PAGE_SIZE,
708 &ranges,
709 )
710 .unwrap();
711 {
712 let mut importer = loader.loader();
713 importer
714 .import_pages(
715 PARAMS_PAGE,
716 1,
717 "snp-bootshim-params",
718 BootPageAcceptance::Exclusive,
719 params.as_bytes(),
720 )
721 .unwrap();
722 import_test_registers(&mut importer, params_gpa);
723 }
724
725 let output = loader.finalize().unwrap();
726 let mut imported_pages = Vec::new();
727 let mut required_memory = None;
728 let mut handoff = None;
729 let mut serialized_params = None;
730 for directive in output.guest.directives() {
731 match directive {
732 IgvmDirectiveHeader::PageData {
733 gpa,
734 data_type,
735 data,
736 ..
737 } => {
738 imported_pages.push(*gpa / PAGE_SIZE);
739 if *gpa == params_gpa {
740 assert_eq!(*data_type, IgvmPageDataType::NORMAL);
741 let mut page = [0; PAGE_SIZE as usize];
742 page[..data.len()].copy_from_slice(data);
743 serialized_params =
744 Some(SnpBootShimParams::read_from_bytes(&page).unwrap());
745 }
746 }
747 IgvmDirectiveHeader::RequiredMemory {
748 gpa,
749 number_of_bytes,
750 ..
751 } => required_memory = Some((*gpa, *number_of_bytes)),
752 IgvmDirectiveHeader::SnpVpContext { vmsa, .. } => {
753 handoff = Some((vmsa.rip, vmsa.rsi));
754 }
755 _ => {}
756 }
757 }
758
759 imported_pages.sort_unstable();
760 assert_eq!(imported_pages, [1, 2, PARAMS_PAGE]);
761 assert_eq!(
762 required_memory,
763 Some((0, (RAM_PAGE_COUNT * PAGE_SIZE) as u32))
764 );
765 assert_eq!(handoff, Some((TEST_SHIM_ENTRY, params_gpa)));
766 assert_eq!(serialized_params, Some(params));
767 assert_serialized_igvm_shape(
768 &output.guest,
769 RAM_PAGE_COUNT,
770 &[1, 2, PARAMS_PAGE],
771 (TEST_SHIM_ENTRY, params_gpa),
772 );
773 }
774
775 #[test]
776 fn sparse_image_emits_only_the_bsp_vmsa() {
777 let mut loader = test_loader(1);
778 import_test_registers(&mut loader.loader(), 0x2000);
779
780 let output = loader.finalize().unwrap();
781 let vp_indexes = output
782 .guest
783 .directives()
784 .iter()
785 .filter_map(|directive| match directive {
786 IgvmDirectiveHeader::SnpVpContext { vp_index, .. } => Some(*vp_index),
787 _ => None,
788 })
789 .collect::<Vec<_>>();
790 assert_eq!(vp_indexes, [0]);
791 }
792
793 #[test]
794 fn overlapping_import_does_not_mutate_existing_pages() {
795 let mut loader = test_loader(4);
796 {
797 let mut importer = loader.loader();
798 importer
799 .import_pages(1, 1, "first", BootPageAcceptance::Exclusive, &[0xaa])
800 .unwrap();
801 let error = importer
802 .import_pages(
803 0,
804 2,
805 "overlap",
806 BootPageAcceptance::Exclusive,
807 &[0xbb; PAGE_SIZE as usize * 2],
808 )
809 .unwrap_err();
810 assert!(error.to_string().contains("overlaps"));
811 import_test_registers(&mut importer, 0x2000);
812 }
813
814 let output = loader.finalize().unwrap();
815 let page = output
816 .guest
817 .directives()
818 .iter()
819 .find_map(|directive| match directive {
820 IgvmDirectiveHeader::PageData { gpa, data, .. } if *gpa == PAGE_SIZE => Some(data),
821 _ => None,
822 })
823 .unwrap();
824 assert_eq!(page, &[0xaa]);
825 }
826
827 #[test]
828 fn rejects_too_many_bootshim_ranges() {
829 let ranges = vec![
830 SnpBootShimRange {
831 start_gpn: 0,
832 page_count: 1,
833 };
834 SNP_BOOT_SHIM_MAX_RANGES + 1
835 ];
836 assert!(
837 build_bootshim_params(0x100000, 0x2000, 0x200000, &ranges)
838 .unwrap_err()
839 .to_string()
840 .contains("supports at most")
841 );
842 }
843
844 #[test]
845 #[should_panic(expected = "page alignment overflow")]
846 fn bootshim_placement_overflow_panics() {
847 align_up_to_page(u64::MAX);
848 }
849}