1use crate::identity_mapping::Measurement;
7use crate::identity_mapping::SnpMeasurement;
8use crate::identity_mapping::TdxMeasurement;
9use crate::identity_mapping::VbsMeasurement;
10use crate::signed_measurement::generate_snp_measurement;
11use crate::signed_measurement::generate_tdx_measurement;
12use crate::signed_measurement::generate_vbs_measurement;
13use crate::vp_context_builder::VpContextBuilder;
14use crate::vp_context_builder::VpContextPageState;
15use crate::vp_context_builder::VpContextState;
16use crate::vp_context_builder::snp::InjectionType;
17use crate::vp_context_builder::snp::SnpHardwareContext;
18use crate::vp_context_builder::tdx::TdxHardwareContext;
19use crate::vp_context_builder::vbs::VbsRegister;
20use crate::vp_context_builder::vbs::VbsVpContext;
21use anyhow::Context;
22use hvdef::Vtl;
23use igvm::IgvmDirectiveHeader;
24use igvm::IgvmFile;
25use igvm::IgvmInitializationHeader;
26use igvm::IgvmPlatformHeader;
27use igvm::IgvmRelocatableRegion;
28use igvm::IgvmRevision;
29use igvm::snp_defs::SevVmsa;
30use igvm_defs::IGVM_VHS_PARAMETER;
31use igvm_defs::IGVM_VHS_PARAMETER_INSERT;
32use igvm_defs::IGVM_VHS_SUPPORTED_PLATFORM;
33use igvm_defs::IgvmPageDataFlags;
34use igvm_defs::IgvmPageDataType;
35use igvm_defs::IgvmPlatformType;
36use igvm_defs::PAGE_SIZE_4K;
37use igvm_defs::SnpPolicy;
38use igvm_defs::TdxPolicy;
39use loader::importer::Aarch64Register;
40use loader::importer::BootPageAcceptance;
41use loader::importer::GuestArch;
42use loader::importer::GuestArchKind;
43use loader::importer::IgvmParameterType;
44use loader::importer::ImageLoad;
45use loader::importer::IsolationConfig;
46use loader::importer::IsolationType;
47use loader::importer::ParameterAreaIndex;
48use loader::importer::X86Register;
49use memory_range::MemoryRange;
50use range_map_vec::Entry;
51use range_map_vec::RangeMap;
52use sha2::Digest;
53use sha2::Sha384;
54use std::collections::BTreeMap;
55use std::fmt::Debug;
56use std::fmt::Display;
57use zerocopy::FromBytes;
58use zerocopy::IntoBytes;
59
60pub const DEFAULT_COMPATIBILITY_MASK: u32 = 0x1;
61
62const TDX_SHARED_GPA_BOUNDARY_BITS: u8 = 47;
63
64fn to_igvm_vtl(vtl: Vtl) -> igvm::hv_defs::Vtl {
65 match vtl {
66 Vtl::Vtl0 => igvm::hv_defs::Vtl::Vtl0,
67 Vtl::Vtl1 => igvm::hv_defs::Vtl::Vtl1,
68 Vtl::Vtl2 => igvm::hv_defs::Vtl::Vtl2,
69 }
70}
71
72#[expect(dead_code)]
75#[derive(Debug, Clone)]
76struct PageTableRegion {
77 gpa: u64,
78 size_pages: u64,
79 used_size_pages: u64,
80}
81
82#[derive(Debug, Clone)]
83enum RelocationType {
84 PageTable(PageTableRegion),
85 Normal(IgvmRelocatableRegion),
86}
87
88#[derive(Debug, Clone, PartialEq, Eq)]
89struct RangeInfo {
90 tag: String,
91 acceptance: BootPageAcceptance,
92}
93
94pub struct IgvmLoader<R: VbsRegister + GuestArch> {
95 accepted_ranges: RangeMap<u64, RangeInfo>,
96 relocatable_regions: RangeMap<u64, RelocationType>,
97 required_memory: Vec<RequiredMemory>,
98 page_table_region: Option<PageTableRegion>,
99 platform_header: IgvmPlatformHeader,
100 initialization_headers: Vec<IgvmInitializationHeader>,
101 directives: Vec<IgvmDirectiveHeader>,
102 page_data_directives: Vec<IgvmDirectiveHeader>,
103 vp_context: Option<Box<dyn VpContextBuilder<Register = R>>>,
104 max_vtl: Vtl,
105 parameter_areas: BTreeMap<(u64, u32), u32>,
106 isolation_type: LoaderIsolationType,
107 paravisor_present: bool,
108 imported_regions_config_page: Option<u64>,
109}
110
111pub struct IgvmVtlLoader<'a, R: VbsRegister + GuestArch> {
112 loader: &'a mut IgvmLoader<R>,
113 vtl: Vtl,
114 vp_context: Option<VbsVpContext<R>>,
115}
116
117impl<R: VbsRegister + GuestArch> IgvmVtlLoader<'_, R> {
118 pub fn loader(&self) -> &IgvmLoader<R> {
119 self.loader
120 }
121
122 pub fn nested_loader(&mut self) -> IgvmVtlLoader<'_, R> {
128 IgvmVtlLoader {
129 loader: &mut *self.loader,
130 vtl: Vtl::Vtl0,
131 vp_context: Some(VbsVpContext::new(self.vtl)),
132 }
133 }
134
135 pub fn take_vp_context(&mut self) -> Vec<u8> {
136 self.vp_context
137 .take()
138 .map_or_else(Vec::new, |vp| vp.as_page())
139 }
140}
141
142#[derive(Copy, Clone, Debug, PartialEq, Eq)]
143pub enum LoaderIsolationType {
144 None,
145 Vbs {
146 enable_debug: bool,
147 },
148 Snp {
149 shared_gpa_boundary_bits: Option<u8>,
150 policy: SnpPolicy,
151 injection_type: InjectionType,
152 },
154 Tdx {
155 policy: TdxPolicy,
156 },
157}
158
159pub trait IgvmLoaderRegister: VbsRegister {
162 fn init(
164 with_paravisor: bool,
165 max_vtl: Vtl,
166 isolation: LoaderIsolationType,
167 ) -> (
168 IgvmPlatformHeader,
169 Vec<IgvmInitializationHeader>,
170 Box<dyn VpContextBuilder<Register = Self>>,
171 );
172
173 fn generate_measurement(
175 isolation: LoaderIsolationType,
176 initialization_headers: &[IgvmInitializationHeader],
177 directive_headers: &[IgvmDirectiveHeader],
178 svn: u32,
179 debug_enabled: bool,
180 ) -> anyhow::Result<Option<Measurement>>;
181
182 fn igvm_revision() -> IgvmRevision;
184}
185
186impl IgvmLoaderRegister for X86Register {
187 fn init(
188 with_paravisor: bool,
189 max_vtl: Vtl,
190 isolation: LoaderIsolationType,
191 ) -> (
192 IgvmPlatformHeader,
193 Vec<IgvmInitializationHeader>,
194 Box<dyn VpContextBuilder<Register = Self>>,
195 ) {
196 match isolation {
197 LoaderIsolationType::None | LoaderIsolationType::Vbs { .. } => {
198 unreachable!("should be handled by common code")
199 }
200 LoaderIsolationType::Snp {
201 shared_gpa_boundary_bits,
202 policy,
203 injection_type,
204 } => {
205 let shared_gpa_boundary =
207 1 << shared_gpa_boundary_bits.expect("shared gpa boundary must be set");
208
209 let info = IGVM_VHS_SUPPORTED_PLATFORM {
211 compatibility_mask: DEFAULT_COMPATIBILITY_MASK,
212 highest_vtl: max_vtl as u8,
213 platform_type: IgvmPlatformType::SEV_SNP,
214 platform_version: igvm_defs::IGVM_SEV_SNP_PLATFORM_VERSION,
215 shared_gpa_boundary,
216 };
217
218 let platform_header = IgvmPlatformHeader::SupportedPlatform(info);
219
220 let init_header = IgvmInitializationHeader::GuestPolicy {
221 policy: policy.into(),
222 compatibility_mask: DEFAULT_COMPATIBILITY_MASK,
223 };
224
225 let vp_context_builder = Box::new(SnpHardwareContext::new(
226 max_vtl,
227 !with_paravisor,
228 shared_gpa_boundary,
229 injection_type,
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 generate_measurement(
262 isolation: LoaderIsolationType,
263 initialization_headers: &[IgvmInitializationHeader],
264 directive_headers: &[IgvmDirectiveHeader],
265 svn: u32,
266 debug_enabled: bool,
267 ) -> anyhow::Result<Option<Measurement>> {
268 let measurement = match isolation {
269 LoaderIsolationType::Snp { .. } => {
270 let ld = generate_snp_measurement(initialization_headers, directive_headers, svn)
271 .context("generating snp measurement failed")?;
272 Some(Measurement::Snp(SnpMeasurement::new(
273 ld,
274 svn,
275 debug_enabled,
276 )))
277 }
278 LoaderIsolationType::Tdx { .. } => {
279 let mrtd = generate_tdx_measurement(directive_headers)
280 .context("generating tdx measurement failed")?;
281 Some(Measurement::Tdx(TdxMeasurement::new(
282 mrtd,
283 svn,
284 debug_enabled,
285 )))
286 }
287 LoaderIsolationType::Vbs { enable_debug } => {
288 let boot_digest = generate_vbs_measurement(directive_headers, enable_debug, svn)
289 .context("generating vbs measurement failed")?;
290 Some(Measurement::Vbs(VbsMeasurement::new(
291 boot_digest,
292 svn,
293 debug_enabled,
294 )))
295 }
296 _ => None,
297 };
298 Ok(measurement)
299 }
300
301 fn igvm_revision() -> IgvmRevision {
302 IgvmRevision::V1
306 }
307}
308
309impl IgvmLoaderRegister for Aarch64Register {
310 fn init(
311 _with_paravisor: bool,
312 _max_vtl: Vtl,
313 _isolation: LoaderIsolationType,
314 ) -> (
315 IgvmPlatformHeader,
316 Vec<IgvmInitializationHeader>,
317 Box<dyn VpContextBuilder<Register = Self>>,
318 ) {
319 unreachable!("should never be called")
320 }
321
322 fn generate_measurement(
323 _isolation: LoaderIsolationType,
324 _initialization_headers: &[IgvmInitializationHeader],
325 _directive_headers: &[IgvmDirectiveHeader],
326 _svn: u32,
327 _debug_enabled: bool,
328 ) -> anyhow::Result<Option<Measurement>> {
329 Ok(None)
330 }
331
332 fn igvm_revision() -> IgvmRevision {
333 IgvmRevision::V2 {
335 arch: igvm::Arch::AArch64,
336 page_size: 4096,
337 }
338 }
339}
340
341#[derive(Debug, Clone)]
342struct RequiredMemory {
343 range: MemoryRange,
344 vtl2_protectable: bool,
345}
346
347#[derive(Debug)]
353pub struct MapFile {
354 isolation: LoaderIsolationType,
355 required_memory: Vec<RequiredMemory>,
356 accepted_ranges: Vec<(MemoryRange, RangeInfo)>,
357 relocatable_regions: Vec<(MemoryRange, RelocationType)>,
358}
359
360impl MapFile {
361 pub fn emit_tracing(&self) {
363 tracing::info!(isolation = ?self.isolation, "IGVM file isolation");
364 tracing::info!("IGVM file layout:");
365 for (range, info) in self.accepted_ranges.iter() {
366 tracing::info!(
367 tag = info.tag,
368 size_bytes = range.len(),
369 "{:#x} - {:#x}",
370 range.start(),
371 range.end(),
372 );
373 }
374
375 if !self.required_memory.is_empty() {
376 tracing::info!("IGVM file required memory:");
377 for region in &self.required_memory {
378 tracing::info!(
379 size_bytes = region.range.len(),
380 vtl2_protectable = region.vtl2_protectable,
381 "{:#x} - {:#x}",
382 region.range.start(),
383 region.range.end(),
384 );
385 }
386 }
387
388 if !self.relocatable_regions.is_empty() {
389 tracing::info!("IGVM file relocatable regions:");
390 for (range, info) in self.relocatable_regions.iter().rev() {
391 match info {
392 RelocationType::PageTable(region) => {
393 tracing::info!(
394 size_bytes = region.size_pages * PAGE_SIZE_4K,
395 "{:#x} - {:#x} pagetable relocation region",
396 region.gpa,
397 range.end(),
398 );
399 }
400 RelocationType::Normal(region) => {
401 tracing::info!(
402 base_gpa = format_args!("{:#x}", region.base_gpa),
403 size_bytes = region.size,
404 minimum_relocation_gpa =
405 format_args!("{:#x}", region.minimum_relocation_gpa),
406 maximum_relocation_gpa =
407 format_args!("{:#x}", region.maximum_relocation_gpa),
408 relocation_alignment = region.relocation_alignment,
409 "{:#x} - {:#x} relocation region",
410 region.base_gpa,
411 range.end(),
412 );
413 }
414 }
415 }
416 }
417 }
418}
419
420impl Display for MapFile {
421 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
422 writeln!(f, "IGVM file isolation: {:?}", self.isolation)?;
423
424 writeln!(f, "IGVM file layout:")?;
425 for (range, info) in &self.accepted_ranges {
426 writeln!(
427 f,
428 " {:016x} - {:016x} ({:#x} bytes) {}",
429 range.start(),
430 range.end(),
431 range.len(),
432 info.tag
433 )?;
434 }
435
436 if !self.required_memory.is_empty() {
437 writeln!(f, "IGVM file required memory:")?;
438 for region in &self.required_memory {
439 writeln!(
440 f,
441 " {:016x} - {:016x} ({:#x} bytes) {}",
442 region.range.start(),
443 region.range.end(),
444 region.range.len(),
445 if region.vtl2_protectable {
446 "VTL2 protectable"
447 } else {
448 ""
449 }
450 )?;
451 }
452 }
453
454 if !self.relocatable_regions.is_empty() {
455 writeln!(f, "IGVM file relocatable regions:")?;
456 for (range, info) in &self.relocatable_regions {
457 match info {
458 RelocationType::PageTable(region) => {
459 writeln!(
460 f,
461 " {:016x} - {:016x} ({:#x} bytes) pagetable relocation region",
462 region.gpa,
463 range.end(),
464 region.size_pages * PAGE_SIZE_4K,
465 )?;
466 }
467 RelocationType::Normal(region) => {
468 writeln!(
469 f,
470 " {:016x} - {:016x} ({:#x} bytes) relocation region",
471 region.base_gpa,
472 range.end(),
473 region.size
474 )?;
475 }
476 }
477 }
478 }
479
480 Ok(())
481 }
482}
483
484#[derive(Debug)]
486pub struct IgvmOutput {
487 pub guest: IgvmFile,
488 pub map: MapFile,
489 pub doc: Option<Measurement>,
490}
491
492impl<R: IgvmLoaderRegister + GuestArch + 'static> IgvmLoader<R> {
493 pub fn new(with_paravisor: bool, isolation_type: LoaderIsolationType) -> Self {
494 let vp_context_builder: Option<Box<dyn VpContextBuilder<Register = R>>>;
495 let platform_header;
496 let max_vtl = if with_paravisor { Vtl::Vtl2 } else { Vtl::Vtl0 };
497 let initialization_headers;
498
499 match isolation_type {
500 LoaderIsolationType::None | LoaderIsolationType::Vbs { .. } => {
501 vp_context_builder = Some(Box::new(VbsVpContext::<R>::new(max_vtl)));
502
503 let info = IGVM_VHS_SUPPORTED_PLATFORM {
505 compatibility_mask: DEFAULT_COMPATIBILITY_MASK,
506 highest_vtl: max_vtl as u8,
507 platform_type: IgvmPlatformType::VSM_ISOLATION,
508 platform_version: igvm_defs::IGVM_VSM_ISOLATION_PLATFORM_VERSION,
509 shared_gpa_boundary: 0,
510 };
511
512 platform_header = IgvmPlatformHeader::SupportedPlatform(info);
513 initialization_headers = Vec::new();
514 }
515 _ => {
516 let (header, init_headers, vp_builder) =
517 R::init(with_paravisor, max_vtl, isolation_type);
518 platform_header = header;
519 initialization_headers = init_headers;
520 vp_context_builder = Some(vp_builder);
521 }
522 }
523
524 IgvmLoader {
525 accepted_ranges: RangeMap::new(),
526 relocatable_regions: RangeMap::new(),
527 required_memory: Vec::new(),
528 page_table_region: None,
529 platform_header,
530 initialization_headers,
531 directives: Vec::new(),
532 page_data_directives: Vec::new(),
533 vp_context: vp_context_builder,
534 max_vtl,
535 parameter_areas: BTreeMap::new(),
536 isolation_type,
537 paravisor_present: with_paravisor,
538 imported_regions_config_page: None,
539 }
540 }
541
542 fn generate_cryptographic_hash_of_shared_pages(&mut self) -> Vec<u8> {
543 self.page_data_directives
545 .sort_unstable_by_key(|directive| match directive {
546 IgvmDirectiveHeader::PageData { gpa, .. } => *gpa,
547 _ => unreachable!("all directives should be IgvmDirectiveHeader::PageData"),
548 });
549
550 let mut hasher = Sha384::new();
552 self.page_data_directives.iter().for_each(|directive| {
553 if let IgvmDirectiveHeader::PageData {
554 gpa: _,
555 compatibility_mask: _,
556 flags,
557 data_type,
558 data,
559 } = directive
560 {
561 if *data_type == IgvmPageDataType::NORMAL && flags.shared() {
562 let mut zero_data;
565 let data_to_hash = if data.len() < PAGE_SIZE_4K as usize {
566 zero_data = vec![0; PAGE_SIZE_4K as usize];
567 zero_data[..data.len()].copy_from_slice(data);
568 &zero_data
569 } else {
570 data
571 };
572
573 hasher.update(data_to_hash);
574 }
575 }
576 });
577 hasher.finalize().to_vec()
578 }
579
580 pub fn finalize(mut self, guest_svn: u32) -> anyhow::Result<IgvmOutput> {
582 let mut state = Vec::new();
584 self.vp_context.take().unwrap().finalize(&mut state);
585
586 for context in state {
587 match context {
588 VpContextState::Page(VpContextPageState {
589 page_base,
590 page_count,
591 acceptance,
592 data,
593 }) => {
594 self.import_pages(page_base, page_count, "vp-context-page", acceptance, &data)
595 .context("failed to import vp context page")?;
596 }
597 VpContextState::Directive(directive) => {
598 self.directives.push(directive);
599 }
600 }
601 }
602
603 self.accepted_ranges
606 .merge_adjacent(range_map_vec::u64_is_adjacent);
607
608 if let Some(page_base) = self.imported_regions_config_page {
610 let mut imported_regions_data: Vec<_> = self.imported_regions();
611
612 imported_regions_data.push(loader_defs::paravisor::ImportedRegionDescriptor::new(
614 page_base, 1, true,
615 ));
616
617 imported_regions_data.sort_by_key(|region| region.base_page_number);
620
621 let hash = self.generate_cryptographic_hash_of_shared_pages();
624 let page_header = loader_defs::paravisor::ImportedRegionsPageHeader {
625 sha384_hash: hash
626 .as_bytes()
627 .try_into()
628 .expect("hash should be correct size"),
629 };
630
631 let mut imported_regions_page = page_header.as_bytes().to_vec();
632
633 imported_regions_page.extend_from_slice(imported_regions_data.as_bytes());
635
636 self.import_pages(
638 page_base,
639 1,
640 "loader-imported-regions",
641 BootPageAcceptance::Exclusive,
642 imported_regions_page.as_bytes(),
643 )
644 .context("failed to import config regions")?;
645 }
646
647 for ((page_base, _page_count), index) in self.parameter_areas.iter() {
649 self.directives.push(IgvmDirectiveHeader::ParameterInsert(
650 IGVM_VHS_PARAMETER_INSERT {
651 gpa: page_base * PAGE_SIZE_4K,
652 compatibility_mask: DEFAULT_COMPATIBILITY_MASK,
653 parameter_area_index: *index,
654 },
655 ));
656 }
657
658 self.directives.append(&mut self.page_data_directives);
661
662 let doc = R::generate_measurement(
665 self.isolation_type,
666 &self.initialization_headers,
667 &self.directives,
668 guest_svn,
669 self.confidential_debug(),
670 )?;
671
672 let map_file = MapFile {
674 isolation: self.isolation_type,
675 required_memory: self.required_memory,
676 accepted_ranges: self
677 .accepted_ranges
678 .iter()
679 .rev()
680 .map(|(range, info)| {
681 (
682 MemoryRange::from_4k_gpn_range(*range.start()..(range.end() + 1)),
683 info.clone(),
684 )
685 })
686 .collect(),
687 relocatable_regions: self
688 .relocatable_regions
689 .iter()
690 .rev()
691 .map(|(range, info)| {
692 (
693 MemoryRange::new(*range.start()..(range.end() + 1)),
694 info.clone(),
695 )
696 })
697 .collect(),
698 };
699
700 map_file.emit_tracing();
701
702 let igvm_file = IgvmFile::new(
704 R::igvm_revision(),
705 vec![self.platform_header],
706 self.initialization_headers,
707 self.directives,
708 )
709 .context("unable to create igvm file")?;
710
711 let output = IgvmOutput {
712 guest: igvm_file,
713 map: map_file,
714 doc,
715 };
716 Ok(output)
717 }
718
719 fn accept_new_range(
722 &mut self,
723 page_base: u64,
724 page_count: u64,
725 tag: &str,
726 acceptance: BootPageAcceptance,
727 ) -> anyhow::Result<()> {
728 let page_end = page_base + page_count - 1;
729 match self.accepted_ranges.entry(page_base..=page_end) {
730 Entry::Overlapping(entry) => {
731 let (overlap_start, overlap_end, ref overlap_info) = *entry.get();
732 Err(anyhow::anyhow!(
733 "{} at {} ({:?}) overlaps {} at {}",
734 tag,
735 MemoryRange::from_4k_gpn_range(page_base..page_end + 1),
736 acceptance,
737 overlap_info.tag,
738 MemoryRange::from_4k_gpn_range(overlap_start..overlap_end + 1),
739 ))
740 }
741 Entry::Vacant(entry) => {
742 entry.insert(RangeInfo {
743 tag: tag.to_string(),
744 acceptance,
745 });
746 Ok(())
747 }
748 }
749 }
750
751 fn imported_regions(&self) -> Vec<loader_defs::paravisor::ImportedRegionDescriptor> {
752 self.accepted_ranges
756 .iter()
757 .map(|(r, info)| {
758 loader_defs::paravisor::ImportedRegionDescriptor::new(
759 *r.start(),
760 r.end() - r.start() + 1,
761 info.acceptance != BootPageAcceptance::Shared,
762 )
763 })
764 .collect()
765 }
766
767 pub fn arch(&self) -> GuestArchKind {
769 R::arch()
770 }
771
772 pub fn confidential_debug(&self) -> bool {
775 match self.isolation_type {
776 LoaderIsolationType::Vbs { enable_debug } => enable_debug,
777 LoaderIsolationType::Snp { policy, .. } => policy.debug() == 1,
778 LoaderIsolationType::Tdx { policy } => policy.debug_allowed() == 1,
779 _ => false,
780 }
781 }
782
783 pub fn loader(&mut self) -> IgvmVtlLoader<'_, R> {
784 IgvmVtlLoader {
785 vtl: self.max_vtl,
786 loader: self,
787 vp_context: None,
788 }
789 }
790
791 fn import_pages(
792 &mut self,
793 page_base: u64,
794 page_count: u64,
795 debug_tag: &str,
796 acceptance: BootPageAcceptance,
797 mut data: &[u8],
798 ) -> Result<(), anyhow::Error> {
799 tracing::debug!(
800 page_base,
801 ?acceptance,
802 page_count,
803 data_size = data.len(),
804 "Importing page",
805 );
806
807 self.accept_new_range(page_base, page_count, debug_tag, acceptance)?;
809
810 if page_count * PAGE_SIZE_4K < data.len() as u64 {
812 anyhow::bail!(
813 "data len {:x} is larger than page_count {page_count:x}",
814 data.len()
815 );
816 }
817
818 if acceptance == BootPageAcceptance::VpContext {
821 match self.isolation_type {
823 LoaderIsolationType::Snp { .. } => {}
824 _ => {
825 anyhow::bail!("vpcontext acceptance only supported on SNP");
826 }
827 }
828
829 if data.len() != size_of::<SevVmsa>() {
831 anyhow::bail!("data len {:x} does not match VMSA size", data.len());
832 }
833
834 if page_count != 1 {
836 anyhow::bail!("page count {page_count:x} for snp vmsa is not 1");
837 }
838
839 self.directives.push(IgvmDirectiveHeader::SnpVpContext {
840 gpa: page_base * PAGE_SIZE_4K,
841 compatibility_mask: DEFAULT_COMPATIBILITY_MASK,
842 vp_index: 0,
843 vmsa: Box::new(SevVmsa::read_from_bytes(data).expect("should be correct size")), });
845 } else {
846 for page in page_base..page_base + page_count {
847 let (data_type, flags) = match acceptance {
848 BootPageAcceptance::Exclusive => {
849 (IgvmPageDataType::NORMAL, IgvmPageDataFlags::new())
850 }
851 BootPageAcceptance::ExclusiveUnmeasured => (
852 IgvmPageDataType::NORMAL,
853 IgvmPageDataFlags::new().with_unmeasured(true),
854 ),
855 BootPageAcceptance::ErrorPage => todo!(),
856 BootPageAcceptance::SecretsPage => {
857 (IgvmPageDataType::SECRETS, IgvmPageDataFlags::new())
858 }
859 BootPageAcceptance::CpuidPage => {
860 (IgvmPageDataType::CPUID_DATA, IgvmPageDataFlags::new())
861 }
862 BootPageAcceptance::CpuidExtendedStatePage => {
863 (IgvmPageDataType::CPUID_XF, IgvmPageDataFlags::new())
864 }
865 BootPageAcceptance::VpContext => unreachable!(),
866 BootPageAcceptance::Shared => (
867 IgvmPageDataType::NORMAL,
868 IgvmPageDataFlags::new().with_shared(true),
869 ),
870 };
871
872 let import_data_len = std::cmp::min(PAGE_SIZE_4K as usize, data.len());
874 let (import_data, data_remaining) = data.split_at(import_data_len);
875 data = data_remaining;
876
877 self.page_data_directives
878 .push(IgvmDirectiveHeader::PageData {
879 gpa: page * PAGE_SIZE_4K,
880 compatibility_mask: DEFAULT_COMPATIBILITY_MASK,
881 flags,
882 data_type,
883 data: import_data.to_vec(),
884 });
885 }
886 }
887
888 Ok(())
889 }
890}
891
892impl<R: IgvmLoaderRegister + GuestArch + 'static> ImageLoad<R> for IgvmVtlLoader<'_, R> {
893 fn isolation_config(&self) -> IsolationConfig {
894 match self.loader.isolation_type {
895 LoaderIsolationType::None => IsolationConfig {
896 paravisor_present: self.loader.paravisor_present,
897 isolation_type: IsolationType::None,
898 shared_gpa_boundary_bits: None,
899 },
900 LoaderIsolationType::Vbs { .. } => IsolationConfig {
901 paravisor_present: self.loader.paravisor_present,
902 isolation_type: IsolationType::Vbs,
903 shared_gpa_boundary_bits: None,
904 },
905 LoaderIsolationType::Snp {
906 shared_gpa_boundary_bits,
907 policy: _,
908 injection_type: _,
909 } => IsolationConfig {
910 paravisor_present: self.loader.paravisor_present,
911 isolation_type: IsolationType::Snp,
912 shared_gpa_boundary_bits,
913 },
914 LoaderIsolationType::Tdx { .. } => IsolationConfig {
915 paravisor_present: self.loader.paravisor_present,
916 isolation_type: IsolationType::Tdx,
917 shared_gpa_boundary_bits: Some(TDX_SHARED_GPA_BOUNDARY_BITS),
918 },
919 }
920 }
921
922 fn create_parameter_area(
923 &mut self,
924 page_base: u64,
925 page_count: u32,
926 debug_tag: &str,
927 ) -> anyhow::Result<ParameterAreaIndex> {
928 self.create_parameter_area_with_data(page_base, page_count, debug_tag, &[])
929 }
930
931 fn create_parameter_area_with_data(
932 &mut self,
933 page_base: u64,
934 page_count: u32,
935 debug_tag: &str,
936 initial_data: &[u8],
937 ) -> anyhow::Result<ParameterAreaIndex> {
938 let area_id = (page_base, page_count);
939
940 self.loader.accept_new_range(
942 page_base,
943 page_count as u64,
944 debug_tag,
945 BootPageAcceptance::ExclusiveUnmeasured,
946 )?;
947
948 let index: u32 = self
949 .loader
950 .parameter_areas
951 .len()
952 .try_into()
953 .expect("parameter area greater than u32");
954 self.loader.parameter_areas.insert(area_id, index);
955
956 self.loader
958 .directives
959 .push(IgvmDirectiveHeader::ParameterArea {
960 number_of_bytes: page_count as u64 * PAGE_SIZE_4K,
961 parameter_area_index: index,
962 initial_data: initial_data.to_vec(),
963 });
964
965 tracing::debug!(
966 index,
967 page_base,
968 page_count,
969 initial_data_len = initial_data.len(),
970 "Creating new parameter area",
971 );
972
973 Ok(ParameterAreaIndex(index))
974 }
975
976 fn import_parameter(
977 &mut self,
978 parameter_area: ParameterAreaIndex,
979 byte_offset: u32,
980 parameter_type: IgvmParameterType,
981 ) -> anyhow::Result<()> {
982 let index = parameter_area.0;
983
984 if index >= self.loader.parameter_areas.len() as u32 {
985 anyhow::bail!("invalid parameter area index: {:x}", index);
986 }
987
988 tracing::debug!(
989 ?parameter_type,
990 parameter_area_index = parameter_area.0,
991 byte_offset,
992 "Importing parameter",
993 );
994
995 let info = IGVM_VHS_PARAMETER {
996 parameter_area_index: index,
997 byte_offset,
998 };
999
1000 let header = match parameter_type {
1001 IgvmParameterType::VpCount => IgvmDirectiveHeader::VpCount(info),
1002 IgvmParameterType::Srat => IgvmDirectiveHeader::Srat(info),
1003 IgvmParameterType::Madt => IgvmDirectiveHeader::Madt(info),
1004 IgvmParameterType::Slit => IgvmDirectiveHeader::Slit(info),
1005 IgvmParameterType::Pptt => IgvmDirectiveHeader::Pptt(info),
1006 IgvmParameterType::MmioRanges => IgvmDirectiveHeader::MmioRanges(info),
1007 IgvmParameterType::MemoryMap => IgvmDirectiveHeader::MemoryMap(info),
1008 IgvmParameterType::CommandLine => IgvmDirectiveHeader::CommandLine(info),
1009 IgvmParameterType::DeviceTree => IgvmDirectiveHeader::DeviceTree(info),
1010 };
1011
1012 self.loader.directives.push(header);
1013
1014 Ok(())
1015 }
1016
1017 fn import_pages(
1018 &mut self,
1019 page_base: u64,
1020 page_count: u64,
1021 debug_tag: &str,
1022 acceptance: BootPageAcceptance,
1023 data: &[u8],
1024 ) -> anyhow::Result<()> {
1025 self.loader
1026 .import_pages(page_base, page_count, debug_tag, acceptance, data)
1027 }
1028
1029 fn import_vp_register(&mut self, register: R) -> anyhow::Result<()> {
1030 if let Some(vp_context) = &mut self.vp_context {
1031 vp_context.import_vp_register(register)
1032 } else {
1033 self.loader
1034 .vp_context
1035 .as_mut()
1036 .unwrap()
1037 .import_vp_register(register);
1038 }
1039
1040 Ok(())
1041 }
1042
1043 fn verify_startup_memory_available(
1044 &mut self,
1045 page_base: u64,
1046 page_count: u64,
1047 memory_type: loader::importer::StartupMemoryType,
1048 ) -> anyhow::Result<()> {
1049 let gpa = page_base * PAGE_SIZE_4K;
1050 let compatibility_mask = DEFAULT_COMPATIBILITY_MASK;
1051 let number_of_bytes = (page_count * PAGE_SIZE_4K)
1052 .try_into()
1053 .expect("startup memory request overflowed u32");
1054
1055 tracing::trace!(
1056 page_base,
1057 page_count,
1058 ?memory_type,
1059 number_of_bytes,
1060 "verify memory"
1061 );
1062
1063 let vtl2_protectable =
1067 memory_type == loader::importer::StartupMemoryType::Vtl2ProtectableRam;
1068
1069 self.loader
1070 .directives
1071 .push(IgvmDirectiveHeader::RequiredMemory {
1072 gpa,
1073 compatibility_mask,
1074 number_of_bytes,
1075 vtl2_protectable,
1076 });
1077
1078 self.loader.required_memory.push(RequiredMemory {
1079 range: MemoryRange::new(gpa..gpa + number_of_bytes as u64),
1080 vtl2_protectable,
1081 });
1082
1083 Ok(())
1084 }
1085
1086 fn set_vp_context_page(&mut self, page_base: u64) -> anyhow::Result<()> {
1087 self.loader
1088 .vp_context
1089 .as_mut()
1090 .unwrap()
1091 .set_vp_context_memory(page_base);
1092
1093 Ok(())
1094 }
1095
1096 fn relocation_region(
1097 &mut self,
1098 gpa: u64,
1099 size_bytes: u64,
1100 relocation_alignment: u64,
1101 minimum_relocation_gpa: u64,
1102 maximum_relocation_gpa: u64,
1103 apply_rip_offset: bool,
1104 apply_gdtr_offset: bool,
1105 vp_index: u16,
1106 ) -> anyhow::Result<()> {
1107 if let Some(overlap) = self
1108 .loader
1109 .relocatable_regions
1110 .get_range(gpa..=(gpa + size_bytes - 1))
1111 {
1112 anyhow::bail!(
1113 "new relocation region overlaps existing region {:?}",
1114 overlap
1115 );
1116 }
1117
1118 if !size_bytes.is_multiple_of(PAGE_SIZE_4K) {
1119 anyhow::bail!("relocation size {size_bytes:#x} must be a multiple of 4K");
1120 }
1121
1122 if !relocation_alignment.is_multiple_of(PAGE_SIZE_4K) {
1123 anyhow::bail!(
1124 "relocation alignment {relocation_alignment:#x} must be a multiple of 4K"
1125 );
1126 }
1127
1128 if !gpa.is_multiple_of(relocation_alignment) {
1129 anyhow::bail!(
1130 "relocation base {gpa:#x} must be aligned to relocation alignment {relocation_alignment:#x}"
1131 );
1132 }
1133
1134 if !minimum_relocation_gpa.is_multiple_of(relocation_alignment) {
1135 anyhow::bail!(
1136 "relocation minimum GPA {minimum_relocation_gpa:#x} must be aligned to relocation alignment {relocation_alignment:#x}"
1137 );
1138 }
1139
1140 if !maximum_relocation_gpa.is_multiple_of(relocation_alignment) {
1141 anyhow::bail!(
1142 "relocation maximum GPA {maximum_relocation_gpa:#x} must be aligned to relocation alignment {relocation_alignment:#x}"
1143 );
1144 }
1145
1146 self.loader
1147 .initialization_headers
1148 .push(IgvmInitializationHeader::RelocatableRegion {
1149 compatibility_mask: DEFAULT_COMPATIBILITY_MASK,
1150 relocation_alignment,
1151 relocation_region_gpa: gpa,
1152 relocation_region_size: size_bytes,
1153 minimum_relocation_gpa,
1154 maximum_relocation_gpa,
1155 is_vtl2: self.vtl == Vtl::Vtl2,
1156 apply_rip_offset,
1157 apply_gdtr_offset,
1158 vp_index,
1159 vtl: to_igvm_vtl(self.vtl),
1160 });
1161
1162 self.loader.relocatable_regions.insert(
1163 gpa..=gpa + size_bytes - 1,
1164 RelocationType::Normal(IgvmRelocatableRegion {
1165 base_gpa: gpa,
1166 size: size_bytes,
1167 minimum_relocation_gpa,
1168 maximum_relocation_gpa,
1169 relocation_alignment,
1170 is_vtl2: self.vtl == Vtl::Vtl2,
1171 apply_rip_offset,
1172 apply_gdtr_offset,
1173 vp_index,
1174 vtl: to_igvm_vtl(self.vtl),
1175 }),
1176 );
1177
1178 Ok(())
1179 }
1180
1181 fn page_table_relocation(
1182 &mut self,
1183 page_table_gpa: u64,
1184 size_pages: u64,
1185 used_size_pages: u64,
1186 vp_index: u16,
1187 ) -> anyhow::Result<()> {
1188 if let Some(region) = &self.loader.page_table_region {
1190 anyhow::bail!("page table relocation already set {:?}", region)
1191 }
1192
1193 if used_size_pages > size_pages {
1194 anyhow::bail!(
1195 "used size pages {used_size_pages:#x} cannot be greater than size pages {size_pages:#x}"
1196 );
1197 }
1198
1199 let end_gpa = page_table_gpa + size_pages * PAGE_SIZE_4K - 1;
1200
1201 if let Some(overlap) = self
1203 .loader
1204 .relocatable_regions
1205 .get_range(page_table_gpa..=end_gpa)
1206 {
1207 anyhow::bail!(
1208 "new page table relocation region overlaps existing region {:?}",
1209 overlap
1210 );
1211 }
1212
1213 self.loader.initialization_headers.push(
1214 IgvmInitializationHeader::PageTableRelocationRegion {
1215 compatibility_mask: DEFAULT_COMPATIBILITY_MASK,
1216 gpa: page_table_gpa,
1217 size: size_pages * PAGE_SIZE_4K,
1218 used_size: used_size_pages * PAGE_SIZE_4K,
1219 vp_index,
1220 vtl: to_igvm_vtl(self.vtl),
1221 },
1222 );
1223
1224 let region = PageTableRegion {
1225 gpa: page_table_gpa,
1226 size_pages,
1227 used_size_pages,
1228 };
1229
1230 self.loader.relocatable_regions.insert(
1231 page_table_gpa..=end_gpa,
1232 RelocationType::PageTable(region.clone()),
1233 );
1234
1235 self.loader.page_table_region = Some(region);
1236
1237 Ok(())
1238 }
1239
1240 fn set_imported_regions_config_page(&mut self, page_base: u64) {
1241 self.loader.imported_regions_config_page = Some(page_base);
1242 }
1243}
1244
1245#[cfg(test)]
1246mod tests {
1247 use super::IgvmLoader;
1248 use super::*;
1249 use crate::identity_mapping::Measurement;
1250 use loader::importer::BootPageAcceptance;
1251 use loader::importer::ImageLoad;
1252 use loader_defs::paravisor::ImportedRegionDescriptor;
1253
1254 #[test]
1255 fn test_snp_measurement() {
1256 use igvm_defs::SnpPolicy;
1257 let ref_ld: [u8; 48] = [
1258 136, 154, 25, 56, 108, 130, 226, 33, 155, 222, 211, 233, 42, 118, 78, 140, 0, 194, 155,
1259 150, 109, 4, 166, 98, 188, 166, 207, 223, 236, 100, 123, 144, 81, 153, 86, 83, 57, 7,
1260 131, 132, 101, 87, 145, 50, 99, 215, 28, 79,
1261 ];
1262
1263 let mut loader = IgvmLoader::<X86Register>::new(
1264 true,
1265 LoaderIsolationType::Snp {
1266 shared_gpa_boundary_bits: Some(39),
1267 policy: SnpPolicy::from((0x1 << 17) | (0x1 << 16) | (0x1f)),
1268 injection_type: InjectionType::Restricted,
1269 },
1270 );
1271 let data = vec![0, 5];
1272 loader
1273 .import_pages(0, 5, "data", BootPageAcceptance::Exclusive, &data)
1274 .unwrap();
1275 loader
1276 .import_pages(5, 5, "data", BootPageAcceptance::ExclusiveUnmeasured, &data)
1277 .unwrap();
1278 loader
1279 .import_pages(10, 1, "data", BootPageAcceptance::Exclusive, &data)
1280 .unwrap();
1281 loader
1282 .import_pages(20, 1, "data", BootPageAcceptance::Shared, &data)
1283 .unwrap();
1284
1285 let igvm_output = loader.finalize(1).unwrap();
1286 let doc = igvm_output.doc.expect("doc");
1287 let Measurement::Snp(snp_measurement) = doc else {
1288 panic!("known to be snp")
1289 };
1290 assert_eq!(ref_ld, snp_measurement.series[0].reference.snp_ld);
1291 }
1292
1293 #[test]
1294 fn test_tdx_measurement() {
1295 let ref_mrtd: [u8; 48] = [
1296 214, 88, 54, 138, 48, 41, 223, 124, 152, 113, 236, 159, 157, 24, 134, 87, 36, 12, 163,
1297 162, 115, 128, 222, 247, 130, 13, 114, 103, 87, 67, 73, 89, 166, 251, 86, 245, 63, 209,
1298 246, 246, 164, 240, 96, 164, 22, 183, 142, 219,
1299 ];
1300
1301 let mut loader = IgvmLoader::<X86Register>::new(
1302 true,
1303 LoaderIsolationType::Tdx {
1304 policy: TdxPolicy::new()
1305 .with_debug_allowed(0u8)
1306 .with_sept_ve_disable(0u8),
1307 },
1308 );
1309 let data = vec![0, 5];
1310 loader
1311 .import_pages(0, 5, "data", BootPageAcceptance::Exclusive, &data)
1312 .unwrap();
1313 loader
1314 .import_pages(5, 5, "data", BootPageAcceptance::ExclusiveUnmeasured, &data)
1315 .unwrap();
1316 loader
1317 .import_pages(10, 1, "data", BootPageAcceptance::Exclusive, &data)
1318 .unwrap();
1319 loader
1320 .import_pages(20, 1, "data", BootPageAcceptance::Shared, &data)
1321 .unwrap();
1322
1323 let igvm_output = loader.finalize(1).unwrap();
1324 let doc = igvm_output.doc.expect("doc");
1325 let Measurement::Tdx(tdx_measurement) = doc else {
1326 panic!("known to be tdx")
1327 };
1328 assert_eq!(ref_mrtd, tdx_measurement.series[0].reference.tdx_mrtd);
1329 }
1330
1331 #[test]
1332 fn test_vbs_digest() {
1333 let ref_digest: [u8; 32] = [
1334 0x30, 0x13, 0x4C, 0x9B, 0xB8, 0x9C, 0xD7, 0x2D, 0x8A, 0x41, 0x8D, 0x1E, 0x7A, 0xFB,
1335 0x75, 0x92, 0x7F, 0x45, 0xE8, 0x57, 0x1D, 0xDA, 0x7A, 0xC7, 0xBE, 0x87, 0xD4, 0xB6,
1336 0xC7, 0x2C, 0xA6, 0x4C,
1337 ];
1338 let mut loader = IgvmLoader::<X86Register>::new(
1339 true,
1340 LoaderIsolationType::Vbs {
1341 enable_debug: false,
1342 },
1343 );
1344 {
1345 let mut loader = loader.loader();
1346
1347 let data = vec![0, 5];
1348 loader
1349 .import_pages(0, 5, "data", BootPageAcceptance::Exclusive, &data)
1350 .unwrap();
1351 loader
1352 .import_pages(5, 5, "data", BootPageAcceptance::ExclusiveUnmeasured, &data)
1353 .unwrap();
1354 loader
1355 .import_pages(10, 1, "data", BootPageAcceptance::Exclusive, &data)
1356 .unwrap();
1357 loader
1358 .import_pages(20, 1, "data", BootPageAcceptance::Shared, &data)
1359 .unwrap();
1360 }
1361
1362 let igvm_output = loader.finalize(1).unwrap();
1363 let doc = igvm_output.doc.expect("doc");
1364 let Measurement::Vbs(vbs_measurement) = doc else {
1365 panic!("known to be vbs")
1366 };
1367 assert_eq!(
1368 ref_digest,
1369 vbs_measurement.series[0].reference.vbs_boot_digest
1370 );
1371 }
1372
1373 #[test]
1374 fn test_accepted_regions() {
1375 let mut loader = IgvmLoader::<X86Register>::new(true, LoaderIsolationType::None);
1376
1377 let data = vec![0, 5];
1378 loader
1379 .import_pages(0, 5, "test1", BootPageAcceptance::Exclusive, &data)
1380 .unwrap();
1381
1382 loader
1383 .import_pages(15, 5, "test2", BootPageAcceptance::Exclusive, &data)
1384 .unwrap();
1385
1386 loader
1387 .import_pages(10, 5, "test3", BootPageAcceptance::Exclusive, &data)
1388 .unwrap();
1389
1390 assert_eq!(
1391 loader.imported_regions(),
1392 vec![
1393 ImportedRegionDescriptor::new(15, 5, true),
1394 ImportedRegionDescriptor::new(10, 5, true),
1395 ImportedRegionDescriptor::new(0, 5, true),
1396 ]
1397 );
1398
1399 loader
1400 .import_pages(20, 10, "test1", BootPageAcceptance::Exclusive, &data)
1401 .unwrap();
1402
1403 loader
1404 .import_pages(30, 1, "test2", BootPageAcceptance::Exclusive, &data)
1405 .unwrap();
1406
1407 assert_eq!(
1408 loader.imported_regions(),
1409 vec![
1410 ImportedRegionDescriptor::new(30, 1, true),
1411 ImportedRegionDescriptor::new(20, 10, true),
1412 ImportedRegionDescriptor::new(15, 5, true),
1413 ImportedRegionDescriptor::new(10, 5, true),
1414 ImportedRegionDescriptor::new(0, 5, true),
1415 ]
1416 );
1417 }
1418}