Skip to main content

igvmfilegen/
file_loader.rs

1// Copyright (c) Microsoft Corporation.
2// Licensed under the MIT License.
3
4//! Implements a loader that serializes the loaded state into the IGVM binary format.
5
6use 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/// Page table relocation information kept for debugging purposes.
74// Allow dead code because clippy doesn't count #[derive(Debug)] as non-dead code usage.
75#[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    /// Returns a loader for importing an inner image as part of the actual
124    /// (paravisor) image to load.
125    ///
126    /// Use `take_vp_context` on the returned loader to get the VP context that
127    /// the paravisor should load.
128    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        // TODO SNP: SNP Keys? Other data?
155    },
156    Tdx {
157        policy: TdxPolicy,
158    },
159}
160
161/// A trait to specialize behavior based on different register types for
162/// different architectures.
163pub trait IgvmLoaderRegister: VbsRegister {
164    /// Perform arch specific initialization.
165    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    /// Generate a measurement based on isolation type.
176    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    /// The IGVM file revision to use for the built igvm file.
185    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                // TODO SNP: assumed that shared_gpa_boundary is always available.
209                let shared_gpa_boundary =
210                    1 << shared_gpa_boundary_bits.expect("shared gpa boundary must be set");
211
212                // Add SNP Platform header
213                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                // NOTE: TDX always has a shared_gpa_boundary and has it at 47 bits.
240                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        // For now, x86 built files always uses V1 of the IGVM format. This is
307        // to maintain compatibility with older OS repo loaders that do not
308        // understand the V2 format.
309        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        // AArch64 IGVM files are always V2.
338        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/// A map file representing information about a given generated IGVM file from a
352/// loader.
353///
354/// This can be used to save additional information about the layout of the
355/// address space that importing an IGVM file will create.
356#[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    /// Emit this map file information to tracing::info.
366    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/// Returns output from finalize
489#[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                // Add VBS platform header
508                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        // Sort the page data directives by GPA to ensure the hash is consistent.
548        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        // Generate the hash of the unaccepted pages.
555        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                    // Measure the pages. If the data length is smaller than a page then zero extend
567                    // the data to a full page.
568                    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    /// Finalize the loader state, returning an IGVM file.
585    pub fn finalize(mut self, guest_svn: u32) -> anyhow::Result<IgvmOutput> {
586        // Finalize any VP state.
587        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        // Merge adjacent accepted ranges with the same tag and acceptance
608        // to undo fragmentation from chunked imports.
609        self.accepted_ranges
610            .merge_adjacent(range_map_vec::u64_is_adjacent);
611
612        // Put list of accepted pages into the config region, if there
613        if let Some(page_base) = self.imported_regions_config_page {
614            let mut imported_regions_data: Vec<_> = self.imported_regions();
615
616            // Add this config page as well
617            imported_regions_data.push(loader_defs::paravisor::ImportedRegionDescriptor::new(
618                page_base, 1, true,
619            ));
620
621            // The accepted regions have been guaranteed to not overlap,
622            // so just sort by the base page number
623            imported_regions_data.sort_by_key(|region| region.base_page_number);
624
625            // All shared pages have been imported. Generate the secure cryptographic hash of the unaccepted
626            // imported pages.
627            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            // Append the (sorted) imported region data.
638            imported_regions_page.extend_from_slice(imported_regions_data.as_bytes());
639
640            // This list should be measured
641            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        // Finalize parameter pages with insert directives.
652        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        // Merge the page_data_directives into the others directives. This must be done before
663        // generating the launch measurement.
664        self.directives.append(&mut self.page_data_directives);
665
666        // Generate the launch measurement for the isolation type being used.
667        // The measurement is output for external signing.
668        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        // Display a report about the build igvm file's layout.
677        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        // Create an IGVM file with the loader's internal state.
707        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    /// Accept a new page range with a given acceptance into the map of accepted
724    /// ranges.
725    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        // N.B. If the imported regions page grows too large, contiguous
757        // regions with the same acceptance type (but different tags) could
758        // be coalesced here to reduce the descriptor count.
759        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    /// The guest architecture used by this loader.
772    pub fn arch(&self) -> GuestArchKind {
773        R::arch()
774    }
775
776    /// Returns true if this is an isolated guest with debug enabled, false
777    /// otherwise.
778    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        // Pages must not overlap already accepted ranges
812        self.accept_new_range(page_base, page_count, debug_tag, acceptance)?;
813
814        // Page count must be larger or equal to data.
815        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        // VpContext imports are handled differently, as they have a different IGVM header
823        // type than normal data pages.
824        if acceptance == BootPageAcceptance::VpContext {
825            // This is only supported on SNP currently.
826            match self.isolation_type {
827                LoaderIsolationType::Snp { .. } => {}
828                _ => {
829                    anyhow::bail!("vpcontext acceptance only supported on SNP");
830                }
831            }
832
833            // Data size must match SNP VMSA size.
834            if data.len() != size_of::<SevVmsa>() {
835                anyhow::bail!("data len {:x} does not match VMSA size", data.len());
836            }
837
838            // Page count must be 1.
839            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")), // TODO: zerocopy: map_err (https://github.com/microsoft/openvmm/issues/759)
848            });
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                // Split data slice into data to be imported for this page and remaining.
877                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        // Allocate a new parameter area, that must not overlap other accepted ranges.
946        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        // Add the newly allocated parameter area index to headers.
962        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        // Set VTL2 protectable flag on isolation types which make sense
1069        // TODO SNP: Temporarily allow this on all isolation types to force the host to generate
1070        // the correct device tree structures.
1071        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        // can only be one set
1194        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        // cannot override other relocatable regions
1207        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}