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 if let Some(page_base) = self.imported_regions_config_page {
605 let mut imported_regions_data: Vec<_> = self.imported_regions();
606
607 imported_regions_data.push(loader_defs::paravisor::ImportedRegionDescriptor::new(
609 page_base, 1, true,
610 ));
611
612 imported_regions_data.sort_by_key(|region| region.base_page_number);
615
616 let hash = self.generate_cryptographic_hash_of_shared_pages();
619 let page_header = loader_defs::paravisor::ImportedRegionsPageHeader {
620 sha384_hash: hash
621 .as_bytes()
622 .try_into()
623 .expect("hash should be correct size"),
624 };
625
626 let mut imported_regions_page = page_header.as_bytes().to_vec();
627
628 imported_regions_page.extend_from_slice(imported_regions_data.as_bytes());
630
631 self.import_pages(
633 page_base,
634 1,
635 "loader-imported-regions",
636 BootPageAcceptance::Exclusive,
637 imported_regions_page.as_bytes(),
638 )
639 .context("failed to import config regions")?;
640 }
641
642 for ((page_base, _page_count), index) in self.parameter_areas.iter() {
644 self.directives.push(IgvmDirectiveHeader::ParameterInsert(
645 IGVM_VHS_PARAMETER_INSERT {
646 gpa: page_base * PAGE_SIZE_4K,
647 compatibility_mask: DEFAULT_COMPATIBILITY_MASK,
648 parameter_area_index: *index,
649 },
650 ));
651 }
652
653 self.directives.append(&mut self.page_data_directives);
656
657 let doc = R::generate_measurement(
660 self.isolation_type,
661 &self.initialization_headers,
662 &self.directives,
663 guest_svn,
664 self.confidential_debug(),
665 )?;
666
667 let map_file = MapFile {
669 isolation: self.isolation_type,
670 required_memory: self.required_memory,
671 accepted_ranges: self
672 .accepted_ranges
673 .iter()
674 .rev()
675 .map(|(range, info)| {
676 (
677 MemoryRange::from_4k_gpn_range(*range.start()..(range.end() + 1)),
678 info.clone(),
679 )
680 })
681 .collect(),
682 relocatable_regions: self
683 .relocatable_regions
684 .iter()
685 .rev()
686 .map(|(range, info)| {
687 (
688 MemoryRange::new(*range.start()..(range.end() + 1)),
689 info.clone(),
690 )
691 })
692 .collect(),
693 };
694
695 map_file.emit_tracing();
696
697 let igvm_file = IgvmFile::new(
699 R::igvm_revision(),
700 vec![self.platform_header],
701 self.initialization_headers,
702 self.directives,
703 )
704 .context("unable to create igvm file")?;
705
706 let output = IgvmOutput {
707 guest: igvm_file,
708 map: map_file,
709 doc,
710 };
711 Ok(output)
712 }
713
714 fn accept_new_range(
717 &mut self,
718 page_base: u64,
719 page_count: u64,
720 tag: &str,
721 acceptance: BootPageAcceptance,
722 ) -> anyhow::Result<()> {
723 let page_end = page_base + page_count - 1;
724 match self.accepted_ranges.entry(page_base..=page_end) {
725 Entry::Overlapping(entry) => {
726 let (overlap_start, overlap_end, ref overlap_info) = *entry.get();
727 Err(anyhow::anyhow!(
728 "{} at {} ({:?}) overlaps {} at {}",
729 tag,
730 MemoryRange::from_4k_gpn_range(page_base..page_end + 1),
731 acceptance,
732 overlap_info.tag,
733 MemoryRange::from_4k_gpn_range(overlap_start..overlap_end + 1),
734 ))
735 }
736 Entry::Vacant(entry) => {
737 entry.insert(RangeInfo {
738 tag: tag.to_string(),
739 acceptance,
740 });
741 Ok(())
742 }
743 }
744 }
745
746 fn imported_regions(&self) -> Vec<loader_defs::paravisor::ImportedRegionDescriptor> {
747 let regions: Vec<_> = self
748 .accepted_ranges
749 .iter()
750 .map(|(r, info)| {
751 loader_defs::paravisor::ImportedRegionDescriptor::new(
752 *r.start(),
753 r.end() - r.start() + 1,
754 info.acceptance != BootPageAcceptance::Shared,
755 )
756 })
757 .collect();
758 regions
759 }
760
761 pub fn arch(&self) -> GuestArchKind {
763 R::arch()
764 }
765
766 pub fn confidential_debug(&self) -> bool {
769 match self.isolation_type {
770 LoaderIsolationType::Vbs { enable_debug } => enable_debug,
771 LoaderIsolationType::Snp { policy, .. } => policy.debug() == 1,
772 LoaderIsolationType::Tdx { policy } => policy.debug_allowed() == 1,
773 _ => false,
774 }
775 }
776
777 pub fn loader(&mut self) -> IgvmVtlLoader<'_, R> {
778 IgvmVtlLoader {
779 vtl: self.max_vtl,
780 loader: self,
781 vp_context: None,
782 }
783 }
784
785 fn import_pages(
786 &mut self,
787 page_base: u64,
788 page_count: u64,
789 debug_tag: &str,
790 acceptance: BootPageAcceptance,
791 mut data: &[u8],
792 ) -> Result<(), anyhow::Error> {
793 tracing::debug!(
794 page_base,
795 ?acceptance,
796 page_count,
797 data_size = data.len(),
798 "Importing page",
799 );
800
801 self.accept_new_range(page_base, page_count, debug_tag, acceptance)?;
803
804 if page_count * PAGE_SIZE_4K < data.len() as u64 {
806 anyhow::bail!(
807 "data len {:x} is larger than page_count {page_count:x}",
808 data.len()
809 );
810 }
811
812 if acceptance == BootPageAcceptance::VpContext {
815 match self.isolation_type {
817 LoaderIsolationType::Snp { .. } => {}
818 _ => {
819 anyhow::bail!("vpcontext acceptance only supported on SNP");
820 }
821 }
822
823 if data.len() != size_of::<SevVmsa>() {
825 anyhow::bail!("data len {:x} does not match VMSA size", data.len());
826 }
827
828 if page_count != 1 {
830 anyhow::bail!("page count {page_count:x} for snp vmsa is not 1");
831 }
832
833 self.directives.push(IgvmDirectiveHeader::SnpVpContext {
834 gpa: page_base * PAGE_SIZE_4K,
835 compatibility_mask: DEFAULT_COMPATIBILITY_MASK,
836 vp_index: 0,
837 vmsa: Box::new(SevVmsa::read_from_bytes(data).expect("should be correct size")), });
839 } else {
840 for page in page_base..page_base + page_count {
841 let (data_type, flags) = match acceptance {
842 BootPageAcceptance::Exclusive => {
843 (IgvmPageDataType::NORMAL, IgvmPageDataFlags::new())
844 }
845 BootPageAcceptance::ExclusiveUnmeasured => (
846 IgvmPageDataType::NORMAL,
847 IgvmPageDataFlags::new().with_unmeasured(true),
848 ),
849 BootPageAcceptance::ErrorPage => todo!(),
850 BootPageAcceptance::SecretsPage => {
851 (IgvmPageDataType::SECRETS, IgvmPageDataFlags::new())
852 }
853 BootPageAcceptance::CpuidPage => {
854 (IgvmPageDataType::CPUID_DATA, IgvmPageDataFlags::new())
855 }
856 BootPageAcceptance::CpuidExtendedStatePage => {
857 (IgvmPageDataType::CPUID_XF, IgvmPageDataFlags::new())
858 }
859 BootPageAcceptance::VpContext => unreachable!(),
860 BootPageAcceptance::Shared => (
861 IgvmPageDataType::NORMAL,
862 IgvmPageDataFlags::new().with_shared(true),
863 ),
864 };
865
866 let import_data_len = std::cmp::min(PAGE_SIZE_4K as usize, data.len());
868 let (import_data, data_remaining) = data.split_at(import_data_len);
869 data = data_remaining;
870
871 self.page_data_directives
872 .push(IgvmDirectiveHeader::PageData {
873 gpa: page * PAGE_SIZE_4K,
874 compatibility_mask: DEFAULT_COMPATIBILITY_MASK,
875 flags,
876 data_type,
877 data: import_data.to_vec(),
878 });
879 }
880 }
881
882 Ok(())
883 }
884}
885
886impl<R: IgvmLoaderRegister + GuestArch + 'static> ImageLoad<R> for IgvmVtlLoader<'_, R> {
887 fn isolation_config(&self) -> IsolationConfig {
888 match self.loader.isolation_type {
889 LoaderIsolationType::None => IsolationConfig {
890 paravisor_present: self.loader.paravisor_present,
891 isolation_type: IsolationType::None,
892 shared_gpa_boundary_bits: None,
893 },
894 LoaderIsolationType::Vbs { .. } => IsolationConfig {
895 paravisor_present: self.loader.paravisor_present,
896 isolation_type: IsolationType::Vbs,
897 shared_gpa_boundary_bits: None,
898 },
899 LoaderIsolationType::Snp {
900 shared_gpa_boundary_bits,
901 policy: _,
902 injection_type: _,
903 } => IsolationConfig {
904 paravisor_present: self.loader.paravisor_present,
905 isolation_type: IsolationType::Snp,
906 shared_gpa_boundary_bits,
907 },
908 LoaderIsolationType::Tdx { .. } => IsolationConfig {
909 paravisor_present: self.loader.paravisor_present,
910 isolation_type: IsolationType::Tdx,
911 shared_gpa_boundary_bits: Some(TDX_SHARED_GPA_BOUNDARY_BITS),
912 },
913 }
914 }
915
916 fn create_parameter_area(
917 &mut self,
918 page_base: u64,
919 page_count: u32,
920 debug_tag: &str,
921 ) -> anyhow::Result<ParameterAreaIndex> {
922 self.create_parameter_area_with_data(page_base, page_count, debug_tag, &[])
923 }
924
925 fn create_parameter_area_with_data(
926 &mut self,
927 page_base: u64,
928 page_count: u32,
929 debug_tag: &str,
930 initial_data: &[u8],
931 ) -> anyhow::Result<ParameterAreaIndex> {
932 let area_id = (page_base, page_count);
933
934 self.loader.accept_new_range(
936 page_base,
937 page_count as u64,
938 debug_tag,
939 BootPageAcceptance::ExclusiveUnmeasured,
940 )?;
941
942 let index: u32 = self
943 .loader
944 .parameter_areas
945 .len()
946 .try_into()
947 .expect("parameter area greater than u32");
948 self.loader.parameter_areas.insert(area_id, index);
949
950 self.loader
952 .directives
953 .push(IgvmDirectiveHeader::ParameterArea {
954 number_of_bytes: page_count as u64 * PAGE_SIZE_4K,
955 parameter_area_index: index,
956 initial_data: initial_data.to_vec(),
957 });
958
959 tracing::debug!(
960 index,
961 page_base,
962 page_count,
963 initial_data_len = initial_data.len(),
964 "Creating new parameter area",
965 );
966
967 Ok(ParameterAreaIndex(index))
968 }
969
970 fn import_parameter(
971 &mut self,
972 parameter_area: ParameterAreaIndex,
973 byte_offset: u32,
974 parameter_type: IgvmParameterType,
975 ) -> anyhow::Result<()> {
976 let index = parameter_area.0;
977
978 if index >= self.loader.parameter_areas.len() as u32 {
979 anyhow::bail!("invalid parameter area index: {:x}", index);
980 }
981
982 tracing::debug!(
983 ?parameter_type,
984 parameter_area_index = parameter_area.0,
985 byte_offset,
986 "Importing parameter",
987 );
988
989 let info = IGVM_VHS_PARAMETER {
990 parameter_area_index: index,
991 byte_offset,
992 };
993
994 let header = match parameter_type {
995 IgvmParameterType::VpCount => IgvmDirectiveHeader::VpCount(info),
996 IgvmParameterType::Srat => IgvmDirectiveHeader::Srat(info),
997 IgvmParameterType::Madt => IgvmDirectiveHeader::Madt(info),
998 IgvmParameterType::Slit => IgvmDirectiveHeader::Slit(info),
999 IgvmParameterType::Pptt => IgvmDirectiveHeader::Pptt(info),
1000 IgvmParameterType::MmioRanges => IgvmDirectiveHeader::MmioRanges(info),
1001 IgvmParameterType::MemoryMap => IgvmDirectiveHeader::MemoryMap(info),
1002 IgvmParameterType::CommandLine => IgvmDirectiveHeader::CommandLine(info),
1003 IgvmParameterType::DeviceTree => IgvmDirectiveHeader::DeviceTree(info),
1004 };
1005
1006 self.loader.directives.push(header);
1007
1008 Ok(())
1009 }
1010
1011 fn import_pages(
1012 &mut self,
1013 page_base: u64,
1014 page_count: u64,
1015 debug_tag: &str,
1016 acceptance: BootPageAcceptance,
1017 data: &[u8],
1018 ) -> anyhow::Result<()> {
1019 self.loader
1020 .import_pages(page_base, page_count, debug_tag, acceptance, data)
1021 }
1022
1023 fn import_vp_register(&mut self, register: R) -> anyhow::Result<()> {
1024 if let Some(vp_context) = &mut self.vp_context {
1025 vp_context.import_vp_register(register)
1026 } else {
1027 self.loader
1028 .vp_context
1029 .as_mut()
1030 .unwrap()
1031 .import_vp_register(register);
1032 }
1033
1034 Ok(())
1035 }
1036
1037 fn verify_startup_memory_available(
1038 &mut self,
1039 page_base: u64,
1040 page_count: u64,
1041 memory_type: loader::importer::StartupMemoryType,
1042 ) -> anyhow::Result<()> {
1043 let gpa = page_base * PAGE_SIZE_4K;
1044 let compatibility_mask = DEFAULT_COMPATIBILITY_MASK;
1045 let number_of_bytes = (page_count * PAGE_SIZE_4K)
1046 .try_into()
1047 .expect("startup memory request overflowed u32");
1048
1049 tracing::trace!(
1050 page_base,
1051 page_count,
1052 ?memory_type,
1053 number_of_bytes,
1054 "verify memory"
1055 );
1056
1057 let vtl2_protectable =
1061 memory_type == loader::importer::StartupMemoryType::Vtl2ProtectableRam;
1062
1063 self.loader
1064 .directives
1065 .push(IgvmDirectiveHeader::RequiredMemory {
1066 gpa,
1067 compatibility_mask,
1068 number_of_bytes,
1069 vtl2_protectable,
1070 });
1071
1072 self.loader.required_memory.push(RequiredMemory {
1073 range: MemoryRange::new(gpa..gpa + number_of_bytes as u64),
1074 vtl2_protectable,
1075 });
1076
1077 Ok(())
1078 }
1079
1080 fn set_vp_context_page(&mut self, page_base: u64) -> anyhow::Result<()> {
1081 self.loader
1082 .vp_context
1083 .as_mut()
1084 .unwrap()
1085 .set_vp_context_memory(page_base);
1086
1087 Ok(())
1088 }
1089
1090 fn relocation_region(
1091 &mut self,
1092 gpa: u64,
1093 size_bytes: u64,
1094 relocation_alignment: u64,
1095 minimum_relocation_gpa: u64,
1096 maximum_relocation_gpa: u64,
1097 apply_rip_offset: bool,
1098 apply_gdtr_offset: bool,
1099 vp_index: u16,
1100 ) -> anyhow::Result<()> {
1101 if let Some(overlap) = self
1102 .loader
1103 .relocatable_regions
1104 .get_range(gpa..=(gpa + size_bytes - 1))
1105 {
1106 anyhow::bail!(
1107 "new relocation region overlaps existing region {:?}",
1108 overlap
1109 );
1110 }
1111
1112 if size_bytes % PAGE_SIZE_4K != 0 {
1113 anyhow::bail!("relocation size {size_bytes:#x} must be a multiple of 4K");
1114 }
1115
1116 if relocation_alignment % PAGE_SIZE_4K != 0 {
1117 anyhow::bail!(
1118 "relocation alignment {relocation_alignment:#x} must be a multiple of 4K"
1119 );
1120 }
1121
1122 if gpa % relocation_alignment != 0 {
1123 anyhow::bail!(
1124 "relocation base {gpa:#x} must be aligned to relocation alignment {relocation_alignment:#x}"
1125 );
1126 }
1127
1128 if minimum_relocation_gpa % relocation_alignment != 0 {
1129 anyhow::bail!(
1130 "relocation minimum GPA {minimum_relocation_gpa:#x} must be aligned to relocation alignment {relocation_alignment:#x}"
1131 );
1132 }
1133
1134 if maximum_relocation_gpa % relocation_alignment != 0 {
1135 anyhow::bail!(
1136 "relocation maximum GPA {maximum_relocation_gpa:#x} must be aligned to relocation alignment {relocation_alignment:#x}"
1137 );
1138 }
1139
1140 self.loader
1141 .initialization_headers
1142 .push(IgvmInitializationHeader::RelocatableRegion {
1143 compatibility_mask: DEFAULT_COMPATIBILITY_MASK,
1144 relocation_alignment,
1145 relocation_region_gpa: gpa,
1146 relocation_region_size: size_bytes,
1147 minimum_relocation_gpa,
1148 maximum_relocation_gpa,
1149 is_vtl2: self.vtl == Vtl::Vtl2,
1150 apply_rip_offset,
1151 apply_gdtr_offset,
1152 vp_index,
1153 vtl: to_igvm_vtl(self.vtl),
1154 });
1155
1156 self.loader.relocatable_regions.insert(
1157 gpa..=gpa + size_bytes - 1,
1158 RelocationType::Normal(IgvmRelocatableRegion {
1159 base_gpa: gpa,
1160 size: size_bytes,
1161 minimum_relocation_gpa,
1162 maximum_relocation_gpa,
1163 relocation_alignment,
1164 is_vtl2: self.vtl == Vtl::Vtl2,
1165 apply_rip_offset,
1166 apply_gdtr_offset,
1167 vp_index,
1168 vtl: to_igvm_vtl(self.vtl),
1169 }),
1170 );
1171
1172 Ok(())
1173 }
1174
1175 fn page_table_relocation(
1176 &mut self,
1177 page_table_gpa: u64,
1178 size_pages: u64,
1179 used_size_pages: u64,
1180 vp_index: u16,
1181 ) -> anyhow::Result<()> {
1182 if let Some(region) = &self.loader.page_table_region {
1184 anyhow::bail!("page table relocation already set {:?}", region)
1185 }
1186
1187 if used_size_pages > size_pages {
1188 anyhow::bail!(
1189 "used size pages {used_size_pages:#x} cannot be greater than size pages {size_pages:#x}"
1190 );
1191 }
1192
1193 let end_gpa = page_table_gpa + size_pages * PAGE_SIZE_4K - 1;
1194
1195 if let Some(overlap) = self
1197 .loader
1198 .relocatable_regions
1199 .get_range(page_table_gpa..=end_gpa)
1200 {
1201 anyhow::bail!(
1202 "new page table relocation region overlaps existing region {:?}",
1203 overlap
1204 );
1205 }
1206
1207 self.loader.initialization_headers.push(
1208 IgvmInitializationHeader::PageTableRelocationRegion {
1209 compatibility_mask: DEFAULT_COMPATIBILITY_MASK,
1210 gpa: page_table_gpa,
1211 size: size_pages * PAGE_SIZE_4K,
1212 used_size: used_size_pages * PAGE_SIZE_4K,
1213 vp_index,
1214 vtl: to_igvm_vtl(self.vtl),
1215 },
1216 );
1217
1218 let region = PageTableRegion {
1219 gpa: page_table_gpa,
1220 size_pages,
1221 used_size_pages,
1222 };
1223
1224 self.loader.relocatable_regions.insert(
1225 page_table_gpa..=end_gpa,
1226 RelocationType::PageTable(region.clone()),
1227 );
1228
1229 self.loader.page_table_region = Some(region);
1230
1231 Ok(())
1232 }
1233
1234 fn set_imported_regions_config_page(&mut self, page_base: u64) {
1235 self.loader.imported_regions_config_page = Some(page_base);
1236 }
1237}
1238
1239#[cfg(test)]
1240mod tests {
1241 use super::IgvmLoader;
1242 use super::*;
1243 use crate::identity_mapping::Measurement;
1244 use loader::importer::BootPageAcceptance;
1245 use loader::importer::ImageLoad;
1246 use loader_defs::paravisor::ImportedRegionDescriptor;
1247
1248 #[test]
1249 fn test_snp_measurement() {
1250 use igvm_defs::SnpPolicy;
1251 let ref_ld: [u8; 48] = [
1252 136, 154, 25, 56, 108, 130, 226, 33, 155, 222, 211, 233, 42, 118, 78, 140, 0, 194, 155,
1253 150, 109, 4, 166, 98, 188, 166, 207, 223, 236, 100, 123, 144, 81, 153, 86, 83, 57, 7,
1254 131, 132, 101, 87, 145, 50, 99, 215, 28, 79,
1255 ];
1256
1257 let mut loader = IgvmLoader::<X86Register>::new(
1258 true,
1259 LoaderIsolationType::Snp {
1260 shared_gpa_boundary_bits: Some(39),
1261 policy: SnpPolicy::from((0x1 << 17) | (0x1 << 16) | (0x1f)),
1262 injection_type: InjectionType::Restricted,
1263 },
1264 );
1265 let data = vec![0, 5];
1266 loader
1267 .import_pages(0, 5, "data", BootPageAcceptance::Exclusive, &data)
1268 .unwrap();
1269 loader
1270 .import_pages(5, 5, "data", BootPageAcceptance::ExclusiveUnmeasured, &data)
1271 .unwrap();
1272 loader
1273 .import_pages(10, 1, "data", BootPageAcceptance::Exclusive, &data)
1274 .unwrap();
1275 loader
1276 .import_pages(20, 1, "data", BootPageAcceptance::Shared, &data)
1277 .unwrap();
1278
1279 let igvm_output = loader.finalize(1).unwrap();
1280 let doc = igvm_output.doc.expect("doc");
1281 let Measurement::Snp(snp_measurement) = doc else {
1282 panic!("known to be snp")
1283 };
1284 assert_eq!(ref_ld, snp_measurement.series[0].reference.snp_ld);
1285 }
1286
1287 #[test]
1288 fn test_tdx_measurement() {
1289 let ref_mrtd: [u8; 48] = [
1290 206, 60, 73, 121, 202, 230, 0, 246, 193, 182, 64, 108, 252, 152, 1, 222, 218, 63, 165,
1291 202, 194, 205, 221, 12, 173, 76, 101, 161, 30, 223, 51, 124, 51, 125, 184, 32, 80, 57,
1292 85, 211, 87, 66, 249, 4, 184, 213, 34, 57,
1293 ];
1294
1295 let mut loader = IgvmLoader::<X86Register>::new(
1296 true,
1297 LoaderIsolationType::Tdx {
1298 policy: TdxPolicy::new()
1299 .with_debug_allowed(0u8)
1300 .with_sept_ve_disable(0u8),
1301 },
1302 );
1303 let data = vec![0, 5];
1304 loader
1305 .import_pages(0, 5, "data", BootPageAcceptance::Exclusive, &data)
1306 .unwrap();
1307 loader
1308 .import_pages(5, 5, "data", BootPageAcceptance::ExclusiveUnmeasured, &data)
1309 .unwrap();
1310 loader
1311 .import_pages(10, 1, "data", BootPageAcceptance::Exclusive, &data)
1312 .unwrap();
1313 loader
1314 .import_pages(20, 1, "data", BootPageAcceptance::Shared, &data)
1315 .unwrap();
1316
1317 let igvm_output = loader.finalize(1).unwrap();
1318 let doc = igvm_output.doc.expect("doc");
1319 let Measurement::Tdx(tdx_measurement) = doc else {
1320 panic!("known to be tdx")
1321 };
1322 assert_eq!(ref_mrtd, tdx_measurement.series[0].reference.tdx_mrtd);
1323 }
1324
1325 #[test]
1326 fn test_vbs_digest() {
1327 let ref_digest: [u8; 32] = [
1328 0x30, 0x13, 0x4C, 0x9B, 0xB8, 0x9C, 0xD7, 0x2D, 0x8A, 0x41, 0x8D, 0x1E, 0x7A, 0xFB,
1329 0x75, 0x92, 0x7F, 0x45, 0xE8, 0x57, 0x1D, 0xDA, 0x7A, 0xC7, 0xBE, 0x87, 0xD4, 0xB6,
1330 0xC7, 0x2C, 0xA6, 0x4C,
1331 ];
1332 let mut loader = IgvmLoader::<X86Register>::new(
1333 true,
1334 LoaderIsolationType::Vbs {
1335 enable_debug: false,
1336 },
1337 );
1338 {
1339 let mut loader = loader.loader();
1340
1341 let data = vec![0, 5];
1342 loader
1343 .import_pages(0, 5, "data", BootPageAcceptance::Exclusive, &data)
1344 .unwrap();
1345 loader
1346 .import_pages(5, 5, "data", BootPageAcceptance::ExclusiveUnmeasured, &data)
1347 .unwrap();
1348 loader
1349 .import_pages(10, 1, "data", BootPageAcceptance::Exclusive, &data)
1350 .unwrap();
1351 loader
1352 .import_pages(20, 1, "data", BootPageAcceptance::Shared, &data)
1353 .unwrap();
1354 }
1355
1356 let igvm_output = loader.finalize(1).unwrap();
1357 let doc = igvm_output.doc.expect("doc");
1358 let Measurement::Vbs(vbs_measurement) = doc else {
1359 panic!("known to be vbs")
1360 };
1361 assert_eq!(
1362 ref_digest,
1363 vbs_measurement.series[0].reference.vbs_boot_digest
1364 );
1365 }
1366
1367 #[test]
1368 fn test_accepted_regions() {
1369 let mut loader = IgvmLoader::<X86Register>::new(true, LoaderIsolationType::None);
1370
1371 let data = vec![0, 5];
1372 loader
1373 .import_pages(0, 5, "test1", BootPageAcceptance::Exclusive, &data)
1374 .unwrap();
1375
1376 loader
1377 .import_pages(15, 5, "test2", BootPageAcceptance::Exclusive, &data)
1378 .unwrap();
1379
1380 loader
1381 .import_pages(10, 5, "test3", BootPageAcceptance::Exclusive, &data)
1382 .unwrap();
1383
1384 assert_eq!(
1385 loader.imported_regions(),
1386 vec![
1387 ImportedRegionDescriptor::new(15, 5, true),
1388 ImportedRegionDescriptor::new(10, 5, true),
1389 ImportedRegionDescriptor::new(0, 5, true),
1390 ]
1391 );
1392
1393 loader
1394 .import_pages(20, 10, "test1", BootPageAcceptance::Exclusive, &data)
1395 .unwrap();
1396
1397 loader
1398 .import_pages(30, 1, "test2", BootPageAcceptance::Exclusive, &data)
1399 .unwrap();
1400
1401 assert_eq!(
1402 loader.imported_regions(),
1403 vec![
1404 ImportedRegionDescriptor::new(30, 1, true),
1405 ImportedRegionDescriptor::new(20, 10, true),
1406 ImportedRegionDescriptor::new(15, 5, true),
1407 ImportedRegionDescriptor::new(10, 5, true),
1408 ImportedRegionDescriptor::new(0, 5, true),
1409 ]
1410 );
1411 }
1412}