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