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