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