1use crate::vp_context_builder::VpContextBuilder;
7use crate::vp_context_builder::VpContextPageState;
8use crate::vp_context_builder::VpContextState;
9use crate::vp_context_builder::snp::InjectionType;
10use crate::vp_context_builder::snp::SecureAvic;
11use crate::vp_context_builder::snp::SnpHardwareContext;
12use crate::vp_context_builder::tdx::TdxHardwareContext;
13use crate::vp_context_builder::vbs::VbsRegister;
14use crate::vp_context_builder::vbs::VbsVpContext;
15use anyhow::Context;
16use crypto::sha_384::Sha384;
17use hvdef::Vtl;
18use igvm::IgvmDirectiveHeader;
19use igvm::IgvmFile;
20use igvm::IgvmInitializationHeader;
21use igvm::IgvmPlatformHeader;
22use igvm::IgvmRelocatableRegion;
23use igvm::IgvmRevision;
24use igvm::snp_defs::SevVmsa;
25use igvm_defs::IGVM_VHS_PARAMETER;
26use igvm_defs::IGVM_VHS_PARAMETER_INSERT;
27use igvm_defs::IGVM_VHS_SUPPORTED_PLATFORM;
28use igvm_defs::IgvmPageDataFlags;
29use igvm_defs::IgvmPageDataType;
30use igvm_defs::IgvmPlatformType;
31use igvm_defs::PAGE_SIZE_4K;
32use igvm_defs::SnpPolicy;
33use igvm_defs::TdxPolicy;
34use loader::importer::Aarch64Register;
35use loader::importer::BootPageAcceptance;
36use loader::importer::GuestArch;
37use loader::importer::GuestArchKind;
38use loader::importer::IgvmParameterType;
39use loader::importer::ImageLoad;
40use loader::importer::IsolationConfig;
41use loader::importer::IsolationType;
42use loader::importer::ParameterAreaIndex;
43use loader::importer::X86Register;
44use memory_range::MemoryRange;
45use range_map_vec::Entry;
46use range_map_vec::RangeMap;
47use std::collections::BTreeMap;
48use std::collections::BTreeSet;
49use std::fmt::Debug;
50use std::fmt::Display;
51use zerocopy::FromBytes;
52use zerocopy::IntoBytes;
53
54pub const DEFAULT_COMPATIBILITY_MASK: u32 = 0x1;
55
56const TDX_SHARED_GPA_BOUNDARY_BITS: u8 = 47;
57
58fn to_igvm_vtl(vtl: Vtl) -> igvm::hv_defs::Vtl {
59 match vtl {
60 Vtl::Vtl0 => igvm::hv_defs::Vtl::Vtl0,
61 Vtl::Vtl1 => igvm::hv_defs::Vtl::Vtl1,
62 Vtl::Vtl2 => igvm::hv_defs::Vtl::Vtl2,
63 }
64}
65
66#[expect(dead_code)]
69#[derive(Debug, Clone)]
70struct PageTableRegion {
71 gpa: u64,
72 size_pages: u64,
73 used_size_pages: u64,
74}
75
76#[derive(Debug, Clone)]
77enum RelocationType {
78 PageTable(PageTableRegion),
79 Normal(IgvmRelocatableRegion),
80}
81
82#[derive(Debug, Clone, PartialEq, Eq)]
83struct RangeInfo {
84 tag: String,
85 acceptance: BootPageAcceptance,
86}
87
88#[derive(Debug, Copy, Clone)]
90pub struct SnpLinuxDirectConfig {
91 pub policy: SnpPolicy,
92 pub c_bit_mask: u64,
93 pub ram_page_count: u64,
94 pub vmsa_page: Option<u64>,
95 pub injection_type: InjectionType,
96}
97
98pub struct IgvmLoader<R: VbsRegister + GuestArch> {
99 accepted_ranges: RangeMap<u64, RangeInfo>,
100 relocatable_regions: RangeMap<u64, RelocationType>,
101 required_memory: Vec<RequiredMemory>,
102 page_table_region: Option<PageTableRegion>,
103 platform_header: IgvmPlatformHeader,
104 initialization_headers: Vec<IgvmInitializationHeader>,
105 directives: Vec<IgvmDirectiveHeader>,
106 page_data_directives: Vec<IgvmDirectiveHeader>,
107 vp_context: Option<Box<dyn VpContextBuilder<Register = R>>>,
108 max_vtl: Vtl,
109 parameter_areas: BTreeMap<(u64, u32), u32>,
110 isolation_type: LoaderIsolationType,
111 paravisor_present: bool,
112 imported_regions_config_page: Option<u64>,
113 expected_page_hashes_config_page: Option<u64>,
114 snp_linux_direct: Option<SnpLinuxDirectConfig>,
115}
116
117pub struct IgvmVtlLoader<'a, R: VbsRegister + GuestArch> {
118 loader: &'a mut IgvmLoader<R>,
119 vtl: Vtl,
120 vp_context: Option<VbsVpContext<R>>,
121}
122
123impl<R: VbsRegister + GuestArch> IgvmVtlLoader<'_, R> {
124 pub fn loader(&self) -> &IgvmLoader<R> {
125 self.loader
126 }
127
128 pub fn nested_loader(&mut self) -> IgvmVtlLoader<'_, R> {
134 IgvmVtlLoader {
135 loader: &mut *self.loader,
136 vtl: Vtl::Vtl0,
137 vp_context: Some(VbsVpContext::new(self.vtl)),
138 }
139 }
140
141 pub fn take_vp_context(&mut self) -> Vec<u8> {
142 self.vp_context
143 .take()
144 .map_or_else(Vec::new, |vp| vp.as_page())
145 }
146}
147
148#[derive(Copy, Clone, Debug, PartialEq, Eq)]
149pub enum LoaderIsolationType {
150 None,
151 Vbs {
152 enable_debug: bool,
153 },
154 Snp {
155 shared_gpa_boundary_bits: Option<u8>,
156 policy: SnpPolicy,
157 injection_type: InjectionType,
158 secure_avic: SecureAvic,
159 },
161 Tdx {
162 policy: TdxPolicy,
163 },
164}
165
166pub trait IgvmLoaderRegister: VbsRegister {
169 fn init(
171 with_paravisor: bool,
172 max_vtl: Vtl,
173 isolation: LoaderIsolationType,
174 ) -> (
175 IgvmPlatformHeader,
176 Vec<IgvmInitializationHeader>,
177 Box<dyn VpContextBuilder<Register = Self>>,
178 );
179
180 fn igvm_revision() -> IgvmRevision;
182}
183
184impl IgvmLoaderRegister for X86Register {
185 fn init(
186 with_paravisor: bool,
187 max_vtl: Vtl,
188 isolation: LoaderIsolationType,
189 ) -> (
190 IgvmPlatformHeader,
191 Vec<IgvmInitializationHeader>,
192 Box<dyn VpContextBuilder<Register = Self>>,
193 ) {
194 match isolation {
195 LoaderIsolationType::None | LoaderIsolationType::Vbs { .. } => {
196 unreachable!("should be handled by common code")
197 }
198 LoaderIsolationType::Snp {
199 shared_gpa_boundary_bits,
200 policy,
201 injection_type,
202 secure_avic,
203 } => {
204 let shared_gpa_boundary =
206 1 << shared_gpa_boundary_bits.expect("shared gpa boundary must be set");
207
208 let info = IGVM_VHS_SUPPORTED_PLATFORM {
210 compatibility_mask: DEFAULT_COMPATIBILITY_MASK,
211 highest_vtl: max_vtl as u8,
212 platform_type: IgvmPlatformType::SEV_SNP,
213 platform_version: igvm_defs::IGVM_SEV_SNP_PLATFORM_VERSION,
214 shared_gpa_boundary,
215 };
216
217 let platform_header = IgvmPlatformHeader::SupportedPlatform(info);
218
219 let init_header = IgvmInitializationHeader::GuestPolicy {
220 policy: policy.into(),
221 compatibility_mask: DEFAULT_COMPATIBILITY_MASK,
222 };
223
224 let vp_context_builder = Box::new(SnpHardwareContext::new(
225 max_vtl,
226 !with_paravisor,
227 shared_gpa_boundary,
228 injection_type,
229 secure_avic,
230 ));
231
232 (platform_header, vec![init_header], vp_context_builder)
233 }
234 LoaderIsolationType::Tdx { policy } => {
235 let info = IGVM_VHS_SUPPORTED_PLATFORM {
237 compatibility_mask: DEFAULT_COMPATIBILITY_MASK,
238 highest_vtl: max_vtl as u8,
239 platform_type: IgvmPlatformType::TDX,
240 platform_version: igvm_defs::IGVM_TDX_PLATFORM_VERSION,
241 shared_gpa_boundary: 1 << TDX_SHARED_GPA_BOUNDARY_BITS,
242 };
243
244 let platform_header = IgvmPlatformHeader::SupportedPlatform(info);
245
246 let mut init_headers = Vec::new();
247 if u64::from(policy) != 0 {
248 init_headers.push(IgvmInitializationHeader::GuestPolicy {
249 policy: policy.into(),
250 compatibility_mask: DEFAULT_COMPATIBILITY_MASK,
251 });
252 }
253
254 let vp_context_builder = Box::new(TdxHardwareContext::new(!with_paravisor));
255
256 (platform_header, init_headers, vp_context_builder)
257 }
258 }
259 }
260
261 fn igvm_revision() -> IgvmRevision {
262 IgvmRevision::V1
266 }
267}
268
269impl IgvmLoaderRegister for Aarch64Register {
270 fn init(
271 _with_paravisor: bool,
272 _max_vtl: Vtl,
273 _isolation: LoaderIsolationType,
274 ) -> (
275 IgvmPlatformHeader,
276 Vec<IgvmInitializationHeader>,
277 Box<dyn VpContextBuilder<Register = Self>>,
278 ) {
279 unreachable!("should never be called")
280 }
281
282 fn igvm_revision() -> IgvmRevision {
283 IgvmRevision::V2 {
285 arch: igvm::Arch::AArch64,
286 page_size: 4096,
287 }
288 }
289}
290
291#[derive(Debug, Clone)]
292struct RequiredMemory {
293 range: MemoryRange,
294 vtl2_protectable: bool,
295}
296
297#[derive(Debug)]
303pub struct MapFile {
304 isolation: LoaderIsolationType,
305 required_memory: Vec<RequiredMemory>,
306 accepted_ranges: Vec<(MemoryRange, RangeInfo)>,
307 relocatable_regions: Vec<(MemoryRange, RelocationType)>,
308 reported_ranges: Vec<(MemoryRange, String)>,
309}
310
311impl MapFile {
312 pub fn report_range(&mut self, range: MemoryRange, tag: impl Into<String>) {
318 self.reported_ranges.push((range, tag.into()));
319 }
320
321 pub fn emit_tracing(&self) {
323 tracing::info!(isolation = ?self.isolation, "IGVM file isolation");
324 tracing::info!("IGVM file layout:");
325 for (range, info) in self.accepted_ranges.iter() {
326 tracing::info!(
327 tag = info.tag,
328 size_bytes = range.len(),
329 "{:#x} - {:#x}",
330 range.start(),
331 range.end(),
332 );
333 }
334
335 if !self.required_memory.is_empty() {
336 tracing::info!("IGVM file required memory:");
337 for region in &self.required_memory {
338 tracing::info!(
339 size_bytes = region.range.len(),
340 vtl2_protectable = region.vtl2_protectable,
341 "{:#x} - {:#x}",
342 region.range.start(),
343 region.range.end(),
344 );
345 }
346 }
347
348 if !self.relocatable_regions.is_empty() {
349 tracing::info!("IGVM file relocatable regions:");
350 for (range, info) in self.relocatable_regions.iter().rev() {
351 match info {
352 RelocationType::PageTable(region) => {
353 tracing::info!(
354 size_bytes = region.size_pages * PAGE_SIZE_4K,
355 "{:#x} - {:#x} pagetable relocation region",
356 region.gpa,
357 range.end(),
358 );
359 }
360 RelocationType::Normal(region) => {
361 tracing::info!(
362 base_gpa = format_args!("{:#x}", region.base_gpa),
363 size_bytes = region.size,
364 minimum_relocation_gpa =
365 format_args!("{:#x}", region.minimum_relocation_gpa),
366 maximum_relocation_gpa =
367 format_args!("{:#x}", region.maximum_relocation_gpa),
368 relocation_alignment = region.relocation_alignment,
369 "{:#x} - {:#x} relocation region",
370 region.base_gpa,
371 range.end(),
372 );
373 }
374 }
375 }
376 }
377
378 if !self.reported_ranges.is_empty() {
379 tracing::info!("IGVM file reported ranges:");
380 for (range, tag) in &self.reported_ranges {
381 tracing::info!(
382 size_bytes = range.len(),
383 "{:#x} - {:#x} {}",
384 range.start(),
385 range.end(),
386 tag,
387 );
388 }
389 }
390 }
391}
392
393impl Display for MapFile {
394 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
395 writeln!(f, "IGVM file isolation: {:?}", self.isolation)?;
396
397 writeln!(f, "IGVM file layout:")?;
398 for (range, info) in &self.accepted_ranges {
399 writeln!(
400 f,
401 " {:016x} - {:016x} ({:#x} bytes) {}",
402 range.start(),
403 range.end(),
404 range.len(),
405 info.tag
406 )?;
407 }
408
409 if !self.required_memory.is_empty() {
410 writeln!(f, "IGVM file required memory:")?;
411 for region in &self.required_memory {
412 writeln!(
413 f,
414 " {:016x} - {:016x} ({:#x} bytes) {}",
415 region.range.start(),
416 region.range.end(),
417 region.range.len(),
418 if region.vtl2_protectable {
419 "VTL2 protectable"
420 } else {
421 ""
422 }
423 )?;
424 }
425 }
426
427 if !self.relocatable_regions.is_empty() {
428 writeln!(f, "IGVM file relocatable regions:")?;
429 for (range, info) in &self.relocatable_regions {
430 match info {
431 RelocationType::PageTable(region) => {
432 writeln!(
433 f,
434 " {:016x} - {:016x} ({:#x} bytes) pagetable relocation region",
435 region.gpa,
436 range.end(),
437 region.size_pages * PAGE_SIZE_4K,
438 )?;
439 }
440 RelocationType::Normal(region) => {
441 writeln!(
442 f,
443 " {:016x} - {:016x} ({:#x} bytes) relocation region",
444 region.base_gpa,
445 range.end(),
446 region.size
447 )?;
448 }
449 }
450 }
451 }
452
453 if !self.reported_ranges.is_empty() {
454 writeln!(f, "IGVM file reported ranges:")?;
455 for (range, tag) in &self.reported_ranges {
456 writeln!(
457 f,
458 " {:016x} - {:016x} ({:#x} bytes) {}",
459 range.start(),
460 range.end(),
461 range.len(),
462 tag
463 )?;
464 }
465 }
466
467 Ok(())
468 }
469}
470
471#[derive(Debug)]
473pub struct IgvmOutput {
474 pub guest: IgvmFile,
475 pub map: MapFile,
476}
477
478impl IgvmLoader<X86Register> {
479 pub fn new_snp_linux_direct(config: SnpLinuxDirectConfig) -> Self {
481 let isolation_type = LoaderIsolationType::Snp {
482 shared_gpa_boundary_bits: None,
483 policy: config.policy,
484 injection_type: config.injection_type,
485 secure_avic: SecureAvic::Disabled,
486 };
487 let platform_header = IgvmPlatformHeader::SupportedPlatform(IGVM_VHS_SUPPORTED_PLATFORM {
488 compatibility_mask: DEFAULT_COMPATIBILITY_MASK,
489 highest_vtl: Vtl::Vtl0 as u8,
490 platform_type: IgvmPlatformType::SEV_SNP,
491 platform_version: igvm_defs::IGVM_SEV_SNP_PLATFORM_VERSION,
492 shared_gpa_boundary: 0,
493 });
494 let initialization_headers = vec![IgvmInitializationHeader::GuestPolicy {
495 policy: config.policy.into(),
496 compatibility_mask: DEFAULT_COMPATIBILITY_MASK,
497 }];
498 let mut vp_context =
499 SnpHardwareContext::new_linux_direct(config.c_bit_mask, config.injection_type);
500 if let Some(page) = config.vmsa_page {
501 vp_context.set_vp_context_memory(page);
502 }
503
504 Self {
505 accepted_ranges: RangeMap::new(),
506 relocatable_regions: RangeMap::new(),
507 required_memory: Vec::new(),
508 page_table_region: None,
509 platform_header,
510 initialization_headers,
511 directives: Vec::new(),
512 page_data_directives: Vec::new(),
513 vp_context: Some(Box::new(vp_context)),
514 max_vtl: Vtl::Vtl0,
515 parameter_areas: BTreeMap::new(),
516 isolation_type,
517 paravisor_present: false,
518 imported_regions_config_page: None,
519 expected_page_hashes_config_page: None,
520 snp_linux_direct: Some(config),
521 }
522 }
523}
524
525impl<R: IgvmLoaderRegister + GuestArch + 'static> IgvmLoader<R> {
526 pub fn new(with_paravisor: bool, isolation_type: LoaderIsolationType) -> Self {
527 let vp_context_builder: Option<Box<dyn VpContextBuilder<Register = R>>>;
528 let platform_header;
529 let max_vtl = if with_paravisor { Vtl::Vtl2 } else { Vtl::Vtl0 };
530 let initialization_headers;
531
532 match isolation_type {
533 LoaderIsolationType::None | LoaderIsolationType::Vbs { .. } => {
534 vp_context_builder = Some(Box::new(VbsVpContext::<R>::new(max_vtl)));
535
536 let info = IGVM_VHS_SUPPORTED_PLATFORM {
538 compatibility_mask: DEFAULT_COMPATIBILITY_MASK,
539 highest_vtl: max_vtl as u8,
540 platform_type: IgvmPlatformType::VSM_ISOLATION,
541 platform_version: igvm_defs::IGVM_VSM_ISOLATION_PLATFORM_VERSION,
542 shared_gpa_boundary: 0,
543 };
544
545 platform_header = IgvmPlatformHeader::SupportedPlatform(info);
546 initialization_headers = Vec::new();
547 }
548 _ => {
549 let (header, init_headers, vp_builder) =
550 R::init(with_paravisor, max_vtl, isolation_type);
551 platform_header = header;
552 initialization_headers = init_headers;
553 vp_context_builder = Some(vp_builder);
554 }
555 }
556
557 IgvmLoader {
558 accepted_ranges: RangeMap::new(),
559 relocatable_regions: RangeMap::new(),
560 required_memory: Vec::new(),
561 page_table_region: None,
562 platform_header,
563 initialization_headers,
564 directives: Vec::new(),
565 page_data_directives: Vec::new(),
566 vp_context: vp_context_builder,
567 max_vtl,
568 parameter_areas: BTreeMap::new(),
569 isolation_type,
570 paravisor_present: with_paravisor,
571 imported_regions_config_page: None,
572 expected_page_hashes_config_page: None,
573 snp_linux_direct: None,
574 }
575 }
576
577 fn finalize_snp_linux_direct(&mut self, config: SnpLinuxDirectConfig) -> anyhow::Result<()> {
587 anyhow::ensure!(
588 self.required_memory.is_empty(),
589 "SNP Linux-direct image already contains a required-memory directive"
590 );
591 let ram_size = config
592 .ram_page_count
593 .checked_mul(PAGE_SIZE_4K)
594 .context("RAM size overflow")?;
595 let number_of_bytes = ram_size
596 .try_into()
597 .context("RAM size does not fit in an IGVM required-memory directive")?;
598 self.directives.insert(
599 0,
600 IgvmDirectiveHeader::RequiredMemory {
601 gpa: 0,
602 compatibility_mask: DEFAULT_COMPATIBILITY_MASK,
603 number_of_bytes,
604 vtl2_protectable: false,
605 },
606 );
607 self.required_memory.push(RequiredMemory {
608 range: MemoryRange::new(0..ram_size),
609 vtl2_protectable: false,
610 });
611
612 let mut page_data_pages = BTreeSet::new();
613 for directive in &self.page_data_directives {
614 let IgvmDirectiveHeader::PageData { gpa, .. } = directive else {
615 unreachable!("page_data_directives contains only PageData")
616 };
617 anyhow::ensure!(
618 gpa.is_multiple_of(PAGE_SIZE_4K),
619 "unaligned page-data GPA {gpa:#x}"
620 );
621 let page = gpa / PAGE_SIZE_4K;
622 anyhow::ensure!(
623 page < config.ram_page_count,
624 "page-data GPA {gpa:#x} lies outside configured RAM"
625 );
626 anyhow::ensure!(
627 page_data_pages.insert(page),
628 "duplicate page-data GPA {gpa:#x}"
629 );
630 }
631
632 self.page_data_directives
633 .sort_unstable_by_key(|directive| match directive {
634 IgvmDirectiveHeader::PageData { gpa, .. } => *gpa,
635 _ => unreachable!("page_data_directives contains only PageData"),
636 });
637
638 let vmsa_count = self
639 .directives
640 .iter()
641 .filter(|directive| matches!(directive, IgvmDirectiveHeader::SnpVpContext { .. }))
642 .count();
643 anyhow::ensure!(
644 vmsa_count == 1,
645 "expected one SNP BSP VMSA context, found {vmsa_count}"
646 );
647 Ok(())
648 }
649
650 fn generate_cryptographic_hashes_of_shared_pages(
659 &mut self,
660 ) -> (Vec<u8>, Vec<loader_defs::paravisor::ExpectedPageHash>) {
661 self.page_data_directives
664 .sort_unstable_by_key(|directive| match directive {
665 IgvmDirectiveHeader::PageData { gpa, .. } => *gpa,
666 _ => unreachable!("all directives should be IgvmDirectiveHeader::PageData"),
667 });
668
669 let mut combined = Sha384::new();
670 let mut per_page = Vec::new();
671 self.page_data_directives.iter().for_each(|directive| {
672 if let IgvmDirectiveHeader::PageData {
673 gpa: _,
674 compatibility_mask: _,
675 flags,
676 data_type,
677 data,
678 } = directive
679 {
680 if *data_type == IgvmPageDataType::NORMAL && flags.shared() {
681 let mut zero_data;
684 let data_to_hash = if data.len() < PAGE_SIZE_4K as usize {
685 zero_data = vec![0; PAGE_SIZE_4K as usize];
686 zero_data[..data.len()].copy_from_slice(data);
687 &zero_data
688 } else {
689 data
690 };
691
692 combined.update(data_to_hash);
693
694 let mut per = Sha384::new();
697 per.update(data_to_hash);
698 let hash: [u8; 48] = per
699 .finish()
700 .as_bytes()
701 .try_into()
702 .expect("sha384 output should be 48 bytes");
703 per_page.push(loader_defs::paravisor::ExpectedPageHash { sha384_hash: hash });
704 }
705 }
706 });
707 (combined.finish().to_vec(), per_page)
708 }
709
710 pub fn finalize(mut self) -> anyhow::Result<IgvmOutput> {
712 let mut state = Vec::new();
714 self.vp_context.take().unwrap().finalize(&mut state);
715
716 for context in state {
717 match context {
718 VpContextState::Page(VpContextPageState {
719 page_base,
720 page_count,
721 acceptance,
722 data,
723 }) => {
724 self.import_pages(page_base, page_count, "vp-context-page", acceptance, &data)
725 .context("failed to import vp context page")?;
726 }
727 VpContextState::Directive(directive) => {
728 self.directives.push(directive);
729 }
730 }
731 }
732
733 self.accepted_ranges
736 .merge_adjacent(range_map_vec::u64_is_adjacent);
737
738 if let Some(page_base) = self.imported_regions_config_page {
740 let (combined_hash, per_page_hashes) =
745 self.generate_cryptographic_hashes_of_shared_pages();
746
747 if let Some(hashes_page_base) = self.expected_page_hashes_config_page {
760 use loader_defs::paravisor::{
761 EXPECTED_PAGE_HASH_MAX_COUNT, EXPECTED_PAGE_HASHES_MAGIC,
762 EXPECTED_PAGE_HASHES_VERSION, ExpectedPageHashesHeader,
763 PARAVISOR_MEASURED_VTL2_CONFIG_PAGE_HASHES_SIZE_PAGES,
764 };
765
766 let count = per_page_hashes.len();
767 if count > EXPECTED_PAGE_HASH_MAX_COUNT {
768 anyhow::bail!(
769 "expected-page-hashes region overflow: {} pages > {} max \
770 (increase PARAVISOR_MEASURED_VTL2_CONFIG_PAGE_HASHES_SIZE_PAGES)",
771 count,
772 EXPECTED_PAGE_HASH_MAX_COUNT,
773 );
774 }
775
776 let hashes_header = ExpectedPageHashesHeader {
777 magic: EXPECTED_PAGE_HASHES_MAGIC,
778 version: EXPECTED_PAGE_HASHES_VERSION,
779 page_hash_count: count as u32,
780 reserved: 0,
781 };
782
783 let region_bytes_capacity = PARAVISOR_MEASURED_VTL2_CONFIG_PAGE_HASHES_SIZE_PAGES
784 as usize
785 * PAGE_SIZE_4K as usize;
786 let mut region = Vec::with_capacity(region_bytes_capacity);
787 region.extend_from_slice(hashes_header.as_bytes());
788 region.extend_from_slice(per_page_hashes.as_bytes());
789 region.resize(region_bytes_capacity, 0);
793
794 self.import_pages(
795 hashes_page_base,
796 PARAVISOR_MEASURED_VTL2_CONFIG_PAGE_HASHES_SIZE_PAGES,
797 "loader-expected-page-hashes",
798 BootPageAcceptance::Exclusive,
799 ®ion,
800 )
801 .context("failed to import expected-page-hashes region")?;
802 }
803
804 let mut imported_regions_data: Vec<_> = self.imported_regions();
807
808 imported_regions_data.push(loader_defs::paravisor::ImportedRegionDescriptor::new(
811 page_base, 1, true,
812 ));
813
814 imported_regions_data.sort_by_key(|region| region.base_page_number);
817
818 let page_header = loader_defs::paravisor::ImportedRegionsPageHeader {
819 sha384_hash: combined_hash
820 .as_bytes()
821 .try_into()
822 .expect("hash should be correct size"),
823 };
824
825 let mut imported_regions_page = page_header.as_bytes().to_vec();
826
827 imported_regions_page.extend_from_slice(imported_regions_data.as_bytes());
829
830 self.import_pages(
832 page_base,
833 1,
834 "loader-imported-regions",
835 BootPageAcceptance::Exclusive,
836 imported_regions_page.as_bytes(),
837 )
838 .context("failed to import config regions")?;
839 }
840
841 for ((page_base, _page_count), index) in self.parameter_areas.iter() {
843 self.directives.push(IgvmDirectiveHeader::ParameterInsert(
844 IGVM_VHS_PARAMETER_INSERT {
845 gpa: page_base * PAGE_SIZE_4K,
846 compatibility_mask: DEFAULT_COMPATIBILITY_MASK,
847 parameter_area_index: *index,
848 },
849 ));
850 }
851
852 if let Some(config) = self.snp_linux_direct {
853 self.finalize_snp_linux_direct(config)?;
854 }
855
856 self.directives.append(&mut self.page_data_directives);
861
862 let map_file = MapFile {
864 isolation: self.isolation_type,
865 required_memory: self.required_memory,
866 accepted_ranges: self
867 .accepted_ranges
868 .iter()
869 .rev()
870 .map(|(range, info)| {
871 (
872 MemoryRange::from_4k_gpn_range(*range.start()..(range.end() + 1)),
873 info.clone(),
874 )
875 })
876 .collect(),
877 relocatable_regions: self
878 .relocatable_regions
879 .iter()
880 .rev()
881 .map(|(range, info)| {
882 (
883 MemoryRange::new(*range.start()..(range.end() + 1)),
884 info.clone(),
885 )
886 })
887 .collect(),
888 reported_ranges: Vec::new(),
889 };
890
891 let igvm_file = IgvmFile::new(
893 R::igvm_revision(),
894 vec![self.platform_header],
895 self.initialization_headers,
896 self.directives,
897 )
898 .context("unable to create igvm file")?;
899
900 let output = IgvmOutput {
901 guest: igvm_file,
902 map: map_file,
903 };
904 Ok(output)
905 }
906
907 fn accept_new_range(
910 &mut self,
911 page_base: u64,
912 page_count: u64,
913 tag: &str,
914 acceptance: BootPageAcceptance,
915 ) -> anyhow::Result<()> {
916 let page_end = page_base + page_count - 1;
917 match self.accepted_ranges.entry(page_base..=page_end) {
918 Entry::Overlapping(entry) => {
919 let (overlap_start, overlap_end, ref overlap_info) = *entry.get();
920 Err(anyhow::anyhow!(
921 "{} at {} ({:?}) overlaps {} at {}",
922 tag,
923 MemoryRange::from_4k_gpn_range(page_base..page_end + 1),
924 acceptance,
925 overlap_info.tag,
926 MemoryRange::from_4k_gpn_range(overlap_start..overlap_end + 1),
927 ))
928 }
929 Entry::Vacant(entry) => {
930 entry.insert(RangeInfo {
931 tag: tag.to_string(),
932 acceptance,
933 });
934 Ok(())
935 }
936 }
937 }
938
939 fn imported_regions(&self) -> Vec<loader_defs::paravisor::ImportedRegionDescriptor> {
940 self.accepted_ranges
944 .iter()
945 .map(|(r, info)| {
946 loader_defs::paravisor::ImportedRegionDescriptor::new(
947 *r.start(),
948 r.end() - r.start() + 1,
949 info.acceptance != BootPageAcceptance::Shared,
950 )
951 })
952 .collect()
953 }
954
955 pub fn arch(&self) -> GuestArchKind {
957 R::arch()
958 }
959
960 pub fn next_available_gpa(&self) -> anyhow::Result<u64> {
962 self.page_data_directives
963 .iter()
964 .filter_map(|directive| match directive {
965 IgvmDirectiveHeader::PageData { gpa, .. } => Some(*gpa),
966 _ => None,
967 })
968 .max()
969 .map_or(Ok(0), |gpa| {
970 gpa.checked_add(PAGE_SIZE_4K)
971 .context("next imported address overflow")
972 })
973 }
974
975 pub fn unimported_ram_ranges(
977 &self,
978 additional_imported_pages: impl IntoIterator<Item = u64>,
979 ) -> anyhow::Result<Vec<std::ops::Range<u64>>> {
980 let config = self
981 .snp_linux_direct
982 .context("unimported RAM ranges require an SNP Linux-direct loader")?;
983 let mut imported_pages = BTreeSet::new();
984 for (range, _) in self.accepted_ranges.iter() {
985 for page in range.clone() {
986 if page < config.ram_page_count {
987 imported_pages.insert(page);
988 }
989 }
990 }
991 imported_pages.extend(additional_imported_pages);
992 anyhow::ensure!(
993 imported_pages
994 .last()
995 .is_none_or(|page| *page < config.ram_page_count),
996 "an imported page lies outside RAM"
997 );
998
999 let mut ranges = Vec::new();
1000 let mut cursor = 0;
1001 for page in imported_pages {
1002 if cursor < page {
1003 ranges.push(cursor..page);
1004 }
1005 cursor = page + 1;
1006 }
1007 if cursor < config.ram_page_count {
1008 ranges.push(cursor..config.ram_page_count);
1009 }
1010 Ok(ranges)
1011 }
1012
1013 pub fn loader(&mut self) -> IgvmVtlLoader<'_, R> {
1014 IgvmVtlLoader {
1015 vtl: self.max_vtl,
1016 loader: self,
1017 vp_context: None,
1018 }
1019 }
1020
1021 fn import_pages(
1022 &mut self,
1023 page_base: u64,
1024 page_count: u64,
1025 debug_tag: &'static str,
1026 acceptance: BootPageAcceptance,
1027 mut data: &[u8],
1028 ) -> Result<(), anyhow::Error> {
1029 tracing::debug!(
1030 page_base,
1031 ?acceptance,
1032 page_count,
1033 data_size = data.len(),
1034 "Importing page",
1035 );
1036
1037 anyhow::ensure!(page_count != 0, "cannot import an empty page range");
1038 page_base
1039 .checked_add(page_count)
1040 .context("imported page range overflow")?;
1041
1042 self.accept_new_range(page_base, page_count, debug_tag, acceptance)?;
1044
1045 if page_count * PAGE_SIZE_4K < data.len() as u64 {
1047 anyhow::bail!(
1048 "data len {:x} is larger than page_count {page_count:x}",
1049 data.len()
1050 );
1051 }
1052
1053 if acceptance == BootPageAcceptance::VpContext {
1056 match self.isolation_type {
1058 LoaderIsolationType::Snp { .. } => {}
1059 _ => {
1060 anyhow::bail!("vpcontext acceptance only supported on SNP");
1061 }
1062 }
1063
1064 if data.len() < size_of::<x86defs::snp::SevVmsa>() {
1072 anyhow::bail!(
1073 "data len {:x} is smaller than the architectural VMSA size {:x}",
1074 data.len(),
1075 size_of::<x86defs::snp::SevVmsa>()
1076 );
1077 }
1078 if data.len() > size_of::<SevVmsa>() {
1079 anyhow::bail!(
1080 "data len {:x} exceeds VMSA size {:x}",
1081 data.len(),
1082 size_of::<SevVmsa>()
1083 );
1084 }
1085
1086 if page_count != 1 {
1088 anyhow::bail!("page count {page_count:x} for snp vmsa is not 1");
1089 }
1090
1091 let mut padded = vec![0u8; size_of::<SevVmsa>()];
1092 padded[..data.len()].copy_from_slice(data);
1093
1094 let vmsa = SevVmsa::read_from_bytes(padded.as_slice()).expect("should be correct size");
1095 if let Some(config) = self.snp_linux_direct {
1096 anyhow::ensure!(vmsa.rip != 0, "Linux loader did not provide an entry point");
1097 anyhow::ensure!(
1098 vmsa.cr3 & config.c_bit_mask != 0,
1099 "initial CR3 does not contain the configured SNP C-bit"
1100 );
1101 anyhow::ensure!(
1102 !vmsa.sev_features.vtom() && vmsa.virtual_tom == 0,
1103 "C-bit image must not enable vTOM"
1104 );
1105 }
1106 self.directives.push(IgvmDirectiveHeader::SnpVpContext {
1107 gpa: page_base * PAGE_SIZE_4K,
1108 compatibility_mask: DEFAULT_COMPATIBILITY_MASK,
1109 vp_index: 0,
1110 vmsa: Box::new(vmsa),
1111 });
1112 } else {
1113 for page in page_base..page_base + page_count {
1114 let (data_type, flags) = match acceptance {
1115 BootPageAcceptance::Exclusive => {
1116 (IgvmPageDataType::NORMAL, IgvmPageDataFlags::new())
1117 }
1118 BootPageAcceptance::ExclusiveUnmeasured => (
1119 IgvmPageDataType::NORMAL,
1120 IgvmPageDataFlags::new().with_unmeasured(true),
1121 ),
1122 BootPageAcceptance::SecretsPage => {
1123 (IgvmPageDataType::SECRETS, IgvmPageDataFlags::new())
1124 }
1125 BootPageAcceptance::CpuidPage => {
1126 (IgvmPageDataType::CPUID_DATA, IgvmPageDataFlags::new())
1127 }
1128 BootPageAcceptance::CpuidExtendedStatePage => {
1129 (IgvmPageDataType::CPUID_XF, IgvmPageDataFlags::new())
1130 }
1131 BootPageAcceptance::VpContext => unreachable!(),
1132 BootPageAcceptance::Shared => (
1133 IgvmPageDataType::NORMAL,
1134 IgvmPageDataFlags::new().with_shared(true),
1135 ),
1136 };
1137
1138 let import_data_len = std::cmp::min(PAGE_SIZE_4K as usize, data.len());
1140 let (import_data, data_remaining) = data.split_at(import_data_len);
1141 data = data_remaining;
1142
1143 self.page_data_directives
1144 .push(IgvmDirectiveHeader::PageData {
1145 gpa: page * PAGE_SIZE_4K,
1146 compatibility_mask: DEFAULT_COMPATIBILITY_MASK,
1147 flags,
1148 data_type,
1149 data: import_data.to_vec(),
1150 });
1151 }
1152 }
1153
1154 Ok(())
1155 }
1156}
1157
1158impl<R: IgvmLoaderRegister + GuestArch + 'static> ImageLoad<R> for IgvmVtlLoader<'_, R> {
1159 fn isolation_config(&self) -> IsolationConfig {
1160 match self.loader.isolation_type {
1161 LoaderIsolationType::None => IsolationConfig {
1162 paravisor_present: self.loader.paravisor_present,
1163 isolation_type: IsolationType::None,
1164 shared_gpa_boundary_bits: None,
1165 },
1166 LoaderIsolationType::Vbs { .. } => IsolationConfig {
1167 paravisor_present: self.loader.paravisor_present,
1168 isolation_type: IsolationType::Vbs,
1169 shared_gpa_boundary_bits: None,
1170 },
1171 LoaderIsolationType::Snp {
1172 shared_gpa_boundary_bits,
1173 policy: _,
1174 injection_type: _,
1175 secure_avic: _,
1176 } => IsolationConfig {
1177 paravisor_present: self.loader.paravisor_present,
1178 isolation_type: IsolationType::Snp,
1179 shared_gpa_boundary_bits,
1180 },
1181 LoaderIsolationType::Tdx { .. } => IsolationConfig {
1182 paravisor_present: self.loader.paravisor_present,
1183 isolation_type: IsolationType::Tdx,
1184 shared_gpa_boundary_bits: Some(TDX_SHARED_GPA_BOUNDARY_BITS),
1185 },
1186 }
1187 }
1188
1189 fn create_parameter_area(
1190 &mut self,
1191 page_base: u64,
1192 page_count: u32,
1193 debug_tag: &str,
1194 ) -> anyhow::Result<ParameterAreaIndex> {
1195 self.create_parameter_area_with_data(page_base, page_count, debug_tag, &[])
1196 }
1197
1198 fn create_parameter_area_with_data(
1199 &mut self,
1200 page_base: u64,
1201 page_count: u32,
1202 debug_tag: &str,
1203 initial_data: &[u8],
1204 ) -> anyhow::Result<ParameterAreaIndex> {
1205 let area_id = (page_base, page_count);
1206
1207 self.loader.accept_new_range(
1209 page_base,
1210 page_count as u64,
1211 debug_tag,
1212 BootPageAcceptance::ExclusiveUnmeasured,
1213 )?;
1214
1215 let index: u32 = self
1216 .loader
1217 .parameter_areas
1218 .len()
1219 .try_into()
1220 .expect("parameter area greater than u32");
1221 self.loader.parameter_areas.insert(area_id, index);
1222
1223 self.loader
1225 .directives
1226 .push(IgvmDirectiveHeader::ParameterArea {
1227 number_of_bytes: page_count as u64 * PAGE_SIZE_4K,
1228 parameter_area_index: index,
1229 initial_data: initial_data.to_vec(),
1230 });
1231
1232 tracing::debug!(
1233 index,
1234 page_base,
1235 page_count,
1236 initial_data_len = initial_data.len(),
1237 "Creating new parameter area",
1238 );
1239
1240 Ok(ParameterAreaIndex(index))
1241 }
1242
1243 fn import_parameter(
1244 &mut self,
1245 parameter_area: ParameterAreaIndex,
1246 byte_offset: u32,
1247 parameter_type: IgvmParameterType,
1248 ) -> anyhow::Result<()> {
1249 let index = parameter_area.0;
1250
1251 if index >= self.loader.parameter_areas.len() as u32 {
1252 anyhow::bail!("invalid parameter area index: {:x}", index);
1253 }
1254
1255 tracing::debug!(
1256 ?parameter_type,
1257 parameter_area_index = parameter_area.0,
1258 byte_offset,
1259 "Importing parameter",
1260 );
1261
1262 let info = IGVM_VHS_PARAMETER {
1263 parameter_area_index: index,
1264 byte_offset,
1265 };
1266
1267 let header = match parameter_type {
1268 IgvmParameterType::VpCount => IgvmDirectiveHeader::VpCount(info),
1269 IgvmParameterType::Srat => IgvmDirectiveHeader::Srat(info),
1270 IgvmParameterType::Madt => IgvmDirectiveHeader::Madt(info),
1271 IgvmParameterType::Slit => IgvmDirectiveHeader::Slit(info),
1272 IgvmParameterType::Pptt => IgvmDirectiveHeader::Pptt(info),
1273 IgvmParameterType::MmioRanges => IgvmDirectiveHeader::MmioRanges(info),
1274 IgvmParameterType::MemoryMap => IgvmDirectiveHeader::MemoryMap(info),
1275 IgvmParameterType::CommandLine => IgvmDirectiveHeader::CommandLine(info),
1276 IgvmParameterType::DeviceTree => IgvmDirectiveHeader::DeviceTree(info),
1277 };
1278
1279 self.loader.directives.push(header);
1280
1281 Ok(())
1282 }
1283
1284 fn import_pages(
1285 &mut self,
1286 page_base: u64,
1287 page_count: u64,
1288 debug_tag: &'static str,
1289 acceptance: BootPageAcceptance,
1290 data: &[u8],
1291 ) -> anyhow::Result<()> {
1292 self.loader
1293 .import_pages(page_base, page_count, debug_tag, acceptance, data)
1294 }
1295
1296 fn import_vp_register(&mut self, register: R) -> anyhow::Result<()> {
1297 if let Some(vp_context) = &mut self.vp_context {
1298 vp_context.import_vp_register(register)
1299 } else {
1300 self.loader
1301 .vp_context
1302 .as_mut()
1303 .unwrap()
1304 .import_vp_register(register);
1305 }
1306
1307 Ok(())
1308 }
1309
1310 fn verify_startup_memory_available(
1311 &mut self,
1312 page_base: u64,
1313 page_count: u64,
1314 memory_type: loader::importer::StartupMemoryType,
1315 ) -> anyhow::Result<()> {
1316 let gpa = page_base * PAGE_SIZE_4K;
1317 let compatibility_mask = DEFAULT_COMPATIBILITY_MASK;
1318 let number_of_bytes = (page_count * PAGE_SIZE_4K)
1319 .try_into()
1320 .expect("startup memory request overflowed u32");
1321
1322 tracing::trace!(
1323 page_base,
1324 page_count,
1325 ?memory_type,
1326 number_of_bytes,
1327 "verify memory"
1328 );
1329
1330 let vtl2_protectable =
1334 memory_type == loader::importer::StartupMemoryType::Vtl2ProtectableRam;
1335
1336 self.loader
1337 .directives
1338 .push(IgvmDirectiveHeader::RequiredMemory {
1339 gpa,
1340 compatibility_mask,
1341 number_of_bytes,
1342 vtl2_protectable,
1343 });
1344
1345 self.loader.required_memory.push(RequiredMemory {
1346 range: MemoryRange::new(gpa..gpa + number_of_bytes as u64),
1347 vtl2_protectable,
1348 });
1349
1350 Ok(())
1351 }
1352
1353 fn set_vp_context_page(&mut self, page_base: u64) -> anyhow::Result<()> {
1354 if let Some(config) = &mut self.loader.snp_linux_direct {
1355 anyhow::ensure!(
1356 page_base < config.ram_page_count,
1357 "Linux-selected VP context page lies outside RAM"
1358 );
1359 if config.vmsa_page.is_some() {
1360 return Ok(());
1361 }
1362 config.vmsa_page = Some(page_base);
1363 }
1364 self.loader
1365 .vp_context
1366 .as_mut()
1367 .unwrap()
1368 .set_vp_context_memory(page_base);
1369
1370 Ok(())
1371 }
1372
1373 fn relocation_region(
1374 &mut self,
1375 gpa: u64,
1376 size_bytes: u64,
1377 relocation_alignment: u64,
1378 minimum_relocation_gpa: u64,
1379 maximum_relocation_gpa: u64,
1380 apply_rip_offset: bool,
1381 apply_gdtr_offset: bool,
1382 vp_index: u16,
1383 ) -> anyhow::Result<()> {
1384 if let Some(overlap) = self
1385 .loader
1386 .relocatable_regions
1387 .get_range(gpa..=(gpa + size_bytes - 1))
1388 {
1389 anyhow::bail!(
1390 "new relocation region overlaps existing region {:?}",
1391 overlap
1392 );
1393 }
1394
1395 if !size_bytes.is_multiple_of(PAGE_SIZE_4K) {
1396 anyhow::bail!("relocation size {size_bytes:#x} must be a multiple of 4K");
1397 }
1398
1399 if !relocation_alignment.is_multiple_of(PAGE_SIZE_4K) {
1400 anyhow::bail!(
1401 "relocation alignment {relocation_alignment:#x} must be a multiple of 4K"
1402 );
1403 }
1404
1405 if !gpa.is_multiple_of(relocation_alignment) {
1406 anyhow::bail!(
1407 "relocation base {gpa:#x} must be aligned to relocation alignment {relocation_alignment:#x}"
1408 );
1409 }
1410
1411 if !minimum_relocation_gpa.is_multiple_of(relocation_alignment) {
1412 anyhow::bail!(
1413 "relocation minimum GPA {minimum_relocation_gpa:#x} must be aligned to relocation alignment {relocation_alignment:#x}"
1414 );
1415 }
1416
1417 if !maximum_relocation_gpa.is_multiple_of(relocation_alignment) {
1418 anyhow::bail!(
1419 "relocation maximum GPA {maximum_relocation_gpa:#x} must be aligned to relocation alignment {relocation_alignment:#x}"
1420 );
1421 }
1422
1423 self.loader
1424 .initialization_headers
1425 .push(IgvmInitializationHeader::RelocatableRegion {
1426 compatibility_mask: DEFAULT_COMPATIBILITY_MASK,
1427 relocation_alignment,
1428 relocation_region_gpa: gpa,
1429 relocation_region_size: size_bytes,
1430 minimum_relocation_gpa,
1431 maximum_relocation_gpa,
1432 is_vtl2: self.vtl == Vtl::Vtl2,
1433 apply_rip_offset,
1434 apply_gdtr_offset,
1435 vp_index,
1436 vtl: to_igvm_vtl(self.vtl),
1437 });
1438
1439 self.loader.relocatable_regions.insert(
1440 gpa..=gpa + size_bytes - 1,
1441 RelocationType::Normal(IgvmRelocatableRegion {
1442 base_gpa: gpa,
1443 size: size_bytes,
1444 minimum_relocation_gpa,
1445 maximum_relocation_gpa,
1446 relocation_alignment,
1447 is_vtl2: self.vtl == Vtl::Vtl2,
1448 apply_rip_offset,
1449 apply_gdtr_offset,
1450 vp_index,
1451 vtl: to_igvm_vtl(self.vtl),
1452 }),
1453 );
1454
1455 Ok(())
1456 }
1457
1458 fn page_table_relocation(
1459 &mut self,
1460 page_table_gpa: u64,
1461 size_pages: u64,
1462 used_size_pages: u64,
1463 vp_index: u16,
1464 ) -> anyhow::Result<()> {
1465 if let Some(region) = &self.loader.page_table_region {
1467 anyhow::bail!("page table relocation already set {:?}", region)
1468 }
1469
1470 if used_size_pages > size_pages {
1471 anyhow::bail!(
1472 "used size pages {used_size_pages:#x} cannot be greater than size pages {size_pages:#x}"
1473 );
1474 }
1475
1476 let end_gpa = page_table_gpa + size_pages * PAGE_SIZE_4K - 1;
1477
1478 if let Some(overlap) = self
1480 .loader
1481 .relocatable_regions
1482 .get_range(page_table_gpa..=end_gpa)
1483 {
1484 anyhow::bail!(
1485 "new page table relocation region overlaps existing region {:?}",
1486 overlap
1487 );
1488 }
1489
1490 self.loader.initialization_headers.push(
1491 IgvmInitializationHeader::PageTableRelocationRegion {
1492 compatibility_mask: DEFAULT_COMPATIBILITY_MASK,
1493 gpa: page_table_gpa,
1494 size: size_pages * PAGE_SIZE_4K,
1495 used_size: used_size_pages * PAGE_SIZE_4K,
1496 vp_index,
1497 vtl: to_igvm_vtl(self.vtl),
1498 },
1499 );
1500
1501 let region = PageTableRegion {
1502 gpa: page_table_gpa,
1503 size_pages,
1504 used_size_pages,
1505 };
1506
1507 self.loader.relocatable_regions.insert(
1508 page_table_gpa..=end_gpa,
1509 RelocationType::PageTable(region.clone()),
1510 );
1511
1512 self.loader.page_table_region = Some(region);
1513
1514 Ok(())
1515 }
1516
1517 fn set_imported_regions_config_page(&mut self, page_base: u64) {
1518 self.loader.imported_regions_config_page = Some(page_base);
1519 }
1520
1521 fn set_expected_page_hashes_config_page(&mut self, page_base: u64) {
1522 self.loader.expected_page_hashes_config_page = Some(page_base);
1523 }
1524}
1525
1526#[cfg(test)]
1527mod tests {
1528 use super::IgvmLoader;
1529 use super::*;
1530 use igvm::IgvmSerializer;
1531 use loader::importer::BootPageAcceptance;
1532 use loader::importer::ImageLoad;
1533 use loader_defs::paravisor::ImportedRegionDescriptor;
1534
1535 #[test]
1536 fn reported_ranges_appear_in_map_output() {
1537 let mut map = MapFile {
1538 isolation: LoaderIsolationType::None,
1539 required_memory: Vec::new(),
1540 accepted_ranges: Vec::new(),
1541 relocatable_regions: Vec::new(),
1542 reported_ranges: Vec::new(),
1543 };
1544 map.report_range(
1545 MemoryRange::new(0x1000..0x3000),
1546 "snp-bootshim-accepted-ram [PVALIDATE]",
1547 );
1548
1549 assert!(map.to_string().contains(concat!(
1550 "IGVM file reported ranges:\n",
1551 " 0000000000001000 - 0000000000003000 (0x2000 bytes) ",
1552 "snp-bootshim-accepted-ram [PVALIDATE]\n",
1553 )));
1554 }
1555
1556 #[test]
1557 fn test_snp_measurement() {
1558 use igvm_defs::SnpPolicy;
1559 let ref_ld: [u8; 48] = [
1560 136, 154, 25, 56, 108, 130, 226, 33, 155, 222, 211, 233, 42, 118, 78, 140, 0, 194, 155,
1561 150, 109, 4, 166, 98, 188, 166, 207, 223, 236, 100, 123, 144, 81, 153, 86, 83, 57, 7,
1562 131, 132, 101, 87, 145, 50, 99, 215, 28, 79,
1563 ];
1564
1565 let mut loader = IgvmLoader::<X86Register>::new(
1566 true,
1567 LoaderIsolationType::Snp {
1568 shared_gpa_boundary_bits: Some(39),
1569 policy: SnpPolicy::from((0x1 << 17) | (0x1 << 16) | (0x1f)),
1570 injection_type: InjectionType::Restricted,
1571 secure_avic: SecureAvic::Enabled,
1572 },
1573 );
1574 let data = vec![0, 5];
1575 loader
1576 .import_pages(0, 5, "data", BootPageAcceptance::Exclusive, &data)
1577 .unwrap();
1578 loader
1579 .import_pages(5, 5, "data", BootPageAcceptance::ExclusiveUnmeasured, &data)
1580 .unwrap();
1581 loader
1582 .import_pages(10, 1, "data", BootPageAcceptance::Exclusive, &data)
1583 .unwrap();
1584 loader
1585 .import_pages(20, 1, "data", BootPageAcceptance::Shared, &data)
1586 .unwrap();
1587
1588 let igvm_output = loader.finalize().unwrap();
1589 let serializer = IgvmSerializer::new(&igvm_output.guest).unwrap();
1590 let measurement = serializer
1591 .measurement_for(IgvmPlatformType::SEV_SNP)
1592 .expect("snp measurement");
1593 assert_eq!(ref_ld.as_slice(), measurement.digest.as_slice());
1594 }
1595
1596 #[test]
1597 fn test_tdx_measurement() {
1598 let ref_mrtd: [u8; 48] = [
1599 200, 137, 46, 40, 88, 218, 231, 7, 90, 231, 125, 247, 18, 243, 41, 158, 32, 81, 49, 30,
1600 168, 163, 220, 29, 216, 52, 151, 164, 255, 25, 88, 0, 246, 62, 147, 140, 34, 201, 70,
1601 89, 34, 32, 239, 182, 77, 169, 96, 235,
1602 ];
1603
1604 let mut loader = IgvmLoader::<X86Register>::new(
1605 true,
1606 LoaderIsolationType::Tdx {
1607 policy: TdxPolicy::new()
1608 .with_debug_allowed(0u8)
1609 .with_sept_ve_disable(0u8),
1610 },
1611 );
1612 let data = vec![0, 5];
1613 loader
1614 .import_pages(0, 5, "data", BootPageAcceptance::Exclusive, &data)
1615 .unwrap();
1616 loader
1617 .import_pages(5, 5, "data", BootPageAcceptance::ExclusiveUnmeasured, &data)
1618 .unwrap();
1619 loader
1620 .import_pages(10, 1, "data", BootPageAcceptance::Exclusive, &data)
1621 .unwrap();
1622 loader
1623 .import_pages(20, 1, "data", BootPageAcceptance::Shared, &data)
1624 .unwrap();
1625
1626 let igvm_output = loader.finalize().unwrap();
1627 let serializer = IgvmSerializer::new(&igvm_output.guest).unwrap();
1628 let measurement = serializer
1629 .measurement_for(IgvmPlatformType::TDX)
1630 .expect("tdx measurement");
1631 assert_eq!(ref_mrtd.as_slice(), measurement.digest.as_slice());
1632 }
1633
1634 #[test]
1635 fn test_vbs_digest() {
1636 let ref_digest: [u8; 32] = [
1637 0x30, 0x13, 0x4C, 0x9B, 0xB8, 0x9C, 0xD7, 0x2D, 0x8A, 0x41, 0x8D, 0x1E, 0x7A, 0xFB,
1638 0x75, 0x92, 0x7F, 0x45, 0xE8, 0x57, 0x1D, 0xDA, 0x7A, 0xC7, 0xBE, 0x87, 0xD4, 0xB6,
1639 0xC7, 0x2C, 0xA6, 0x4C,
1640 ];
1641 let mut loader = IgvmLoader::<X86Register>::new(
1642 true,
1643 LoaderIsolationType::Vbs {
1644 enable_debug: false,
1645 },
1646 );
1647 {
1648 let mut loader = loader.loader();
1649
1650 let data = vec![0, 5];
1651 loader
1652 .import_pages(0, 5, "data", BootPageAcceptance::Exclusive, &data)
1653 .unwrap();
1654 loader
1655 .import_pages(5, 5, "data", BootPageAcceptance::ExclusiveUnmeasured, &data)
1656 .unwrap();
1657 loader
1658 .import_pages(10, 1, "data", BootPageAcceptance::Exclusive, &data)
1659 .unwrap();
1660 loader
1661 .import_pages(20, 1, "data", BootPageAcceptance::Shared, &data)
1662 .unwrap();
1663 }
1664
1665 let igvm_output = loader.finalize().unwrap();
1666 let serializer = IgvmSerializer::new(&igvm_output.guest).unwrap();
1667 let measurement = serializer
1668 .measurement_for(IgvmPlatformType::VSM_ISOLATION)
1669 .expect("vbs measurement");
1670 assert_eq!(ref_digest.as_slice(), measurement.digest.as_slice());
1671 }
1672
1673 #[test]
1674 fn test_accepted_regions() {
1675 let mut loader = IgvmLoader::<X86Register>::new(true, LoaderIsolationType::None);
1676
1677 let data = vec![0, 5];
1678 loader
1679 .import_pages(0, 5, "test1", BootPageAcceptance::Exclusive, &data)
1680 .unwrap();
1681
1682 loader
1683 .import_pages(15, 5, "test2", BootPageAcceptance::Exclusive, &data)
1684 .unwrap();
1685
1686 loader
1687 .import_pages(10, 5, "test3", BootPageAcceptance::Exclusive, &data)
1688 .unwrap();
1689
1690 assert_eq!(
1691 loader.imported_regions(),
1692 vec![
1693 ImportedRegionDescriptor::new(15, 5, true),
1694 ImportedRegionDescriptor::new(10, 5, true),
1695 ImportedRegionDescriptor::new(0, 5, true),
1696 ]
1697 );
1698
1699 loader
1700 .import_pages(20, 10, "test1", BootPageAcceptance::Exclusive, &data)
1701 .unwrap();
1702
1703 loader
1704 .import_pages(30, 1, "test2", BootPageAcceptance::Exclusive, &data)
1705 .unwrap();
1706
1707 assert_eq!(
1708 loader.imported_regions(),
1709 vec![
1710 ImportedRegionDescriptor::new(30, 1, true),
1711 ImportedRegionDescriptor::new(20, 10, true),
1712 ImportedRegionDescriptor::new(15, 5, true),
1713 ImportedRegionDescriptor::new(10, 5, true),
1714 ImportedRegionDescriptor::new(0, 5, true),
1715 ]
1716 );
1717 }
1718}