Skip to main content

loader/
paravisor.rs

1// Copyright (c) Microsoft Corporation.
2// Licensed under the MIT License.
3
4//! Paravisor specific loader definitions and implementation.
5
6use crate::common::ChunkBuf;
7use crate::common::ImportFileRegion;
8use crate::common::ReadSeek;
9use crate::cpuid::HV_PSP_CPUID_PAGE;
10use crate::importer::Aarch64Register;
11use crate::importer::BootPageAcceptance;
12use crate::importer::IgvmParameterType;
13use crate::importer::ImageLoad;
14use crate::importer::IsolationConfig;
15use crate::importer::IsolationType;
16use crate::importer::SegmentRegister;
17use crate::importer::StartupMemoryType;
18use crate::importer::TableRegister;
19use crate::importer::X86Register;
20use crate::linux::InitrdAddressType;
21use crate::linux::InitrdConfig;
22use crate::linux::InitrdInfo;
23use crate::linux::KernelInfo;
24use crate::linux::load_kernel_and_initrd_arm64;
25use aarch64defs::Cpsr64;
26use aarch64defs::IntermPhysAddrSize;
27use aarch64defs::SctlrEl1;
28use aarch64defs::TranslationBaseEl1;
29use aarch64defs::TranslationControlEl1;
30use aarch64defs::TranslationGranule0;
31use aarch64defs::TranslationGranule1;
32use hvdef::HV_PAGE_SIZE;
33use hvdef::Vtl;
34use igvm::registers::AArch64Register;
35use loader_defs::paravisor::*;
36use loader_defs::shim::ShimParamsRaw;
37use memory_range::MemoryRange;
38use page_table::aarch64::Arm64PageSize;
39use page_table::aarch64::MemoryAttributeEl1;
40use page_table::aarch64::MemoryAttributeIndirectionEl1;
41use page_table::x64::MappedRange;
42use page_table::x64::PAGE_TABLE_MAX_BYTES;
43use page_table::x64::PAGE_TABLE_MAX_COUNT;
44use page_table::x64::PageTable;
45use page_table::x64::PageTableBuilder;
46use page_table::x64::X64_LARGE_PAGE_SIZE;
47use page_table::x64::align_up_to_large_page_size;
48use page_table::x64::align_up_to_page_size;
49use page_table::x64::calculate_pde_table_count;
50use product_policy::ProductPolicy;
51use product_policy::encode_product_policy;
52use std::io::Read;
53use std::io::Seek;
54use thiserror::Error;
55use x86defs::GdtEntry;
56use x86defs::SegmentSelector;
57use x86defs::X64_BUSY_TSS_SEGMENT_ATTRIBUTES;
58use x86defs::X64_DEFAULT_CODE_SEGMENT_ATTRIBUTES;
59use x86defs::X64_DEFAULT_DATA_SEGMENT_ATTRIBUTES;
60use x86defs::cpuid::CpuidFunction;
61use zerocopy::FromZeros;
62use zerocopy::IntoBytes;
63
64#[derive(Debug)]
65pub struct Vtl0Linux<'a> {
66    pub command_line: &'a std::ffi::CString,
67    pub load_info: crate::linux::LoadInfo,
68}
69
70#[derive(Debug)]
71pub struct Vtl0Config<'a> {
72    pub supports_pcat: bool,
73    /// The load info and the VP context page.
74    pub supports_uefi: Option<(crate::uefi::LoadInfo, Vec<u8>)>,
75    pub supports_linux: Option<Vtl0Linux<'a>>,
76}
77
78// See HclDefs.h
79pub const HCL_SECURE_VTL: Vtl = Vtl::Vtl2;
80
81/// Size of the persisted region (2MB).
82const PERSISTED_REGION_SIZE: u64 = 2 * 1024 * 1024;
83
84fn avoid_page_table_large_page_boundary(offset: u64, large_page_size: u64) -> u64 {
85    if offset.is_multiple_of(large_page_size) {
86        offset + HV_PAGE_SIZE
87    } else {
88        offset
89    }
90}
91
92#[derive(Debug, Error)]
93pub enum Error {
94    #[error("memory is unaligned: {0}")]
95    MemoryUnaligned(u64),
96    #[error("command line too large: {0}")]
97    CommandLineSize(usize),
98    #[error("kernel load error")]
99    Kernel(#[source] crate::linux::Error),
100    #[error("shim load error")]
101    Shim(#[source] crate::elf::Error),
102    #[error("invalid initrd size: {0}")]
103    InvalidInitrdSize(u64),
104    #[error("memory used: {0} is greater than available")]
105    NotEnoughMemory(u64),
106    #[error("importer error")]
107    Importer(#[from] anyhow::Error),
108    #[error("failed to import initrd")]
109    ImportInitrd(#[source] crate::common::ImportFileRegionError),
110    #[error("failed to read initrd for CRC")]
111    InitrdRead(#[source] std::io::Error),
112    #[error("PageTableBuilder: {0}")]
113    PageTableBuilder(#[from] page_table::Error),
114}
115
116/// Encode and validate a [`ProductPolicy`] for inclusion in the
117/// measured VTL2 config region.
118///
119/// Panics if the policy violates product invariants (see
120/// [`validate_product_policy_for_build`]) or if the encoded body
121/// exceeds [`PRODUCT_POLICY_MAX_SIZE_BYTES`].
122fn encode_product_policy_bytes(policy: &ProductPolicy) -> Vec<u8> {
123    validate_product_policy_for_build(policy);
124    let bytes = encode_product_policy(policy);
125    let max = PRODUCT_POLICY_MAX_SIZE_BYTES;
126    assert!(
127        bytes.len() <= max,
128        "product policy mesh-encoded size {} bytes exceeds the static measured-config-region budget of {} bytes; bump PARAVISOR_MEASURED_VTL2_CONFIG_SIZE_PAGES (currently {}) and accept the attestation-measurement change",
129        bytes.len(),
130        max,
131        PARAVISOR_MEASURED_VTL2_CONFIG_SIZE_PAGES,
132    );
133    bytes
134}
135
136/// Enforce product-specific build-time invariants on a
137/// [`ProductPolicy`]. Violations panic.
138fn validate_product_policy_for_build(policy: &ProductPolicy) {
139    match policy {
140        ProductPolicy::Sivm(sivm) => {
141            if sivm.require_secure_boot
142                && (sivm.require_secure_boot_vars || sivm.require_bcd_integrity)
143            {
144                assert!(
145                    !sivm.custom_uefi_json.is_empty(),
146                    "product policy requires non-empty custom_uefi_json"
147                );
148            }
149        }
150        ProductPolicy::Cwcow(cwcow) => {
151            if cwcow.require_secure_boot
152                && (cwcow.require_secure_boot_vars || cwcow.require_bcd_integrity)
153            {
154                assert!(
155                    !cwcow.custom_uefi_json.is_empty(),
156                    "product policy requires non-empty custom_uefi_json"
157                );
158            }
159        }
160    }
161}
162
163/// Build the fixed-size measured VTL2 config region image: the struct
164/// followed by the optional (encoded) product policy body, zero-padded
165/// to `PARAVISOR_MEASURED_VTL2_CONFIG_SIZE_PAGES * HV_PAGE_SIZE`. Every
166/// byte is measured.
167fn build_measured_vtl2_config_region(
168    mut config: ParavisorMeasuredVtl2Config,
169    product_policy: Option<&ProductPolicy>,
170) -> Vec<u8> {
171    let policy_bytes = product_policy.map(encode_product_policy_bytes);
172    let policy_bytes = policy_bytes.as_deref().unwrap_or(&[]);
173    config.product_policy_size = policy_bytes.len() as u32;
174
175    let buf_bytes = (PARAVISOR_MEASURED_VTL2_CONFIG_SIZE_PAGES as usize) * (HV_PAGE_SIZE as usize);
176    let mut buf = vec![0u8; buf_bytes];
177
178    let struct_bytes = config.as_bytes();
179    buf[..struct_bytes.len()].copy_from_slice(struct_bytes);
180    if !policy_bytes.is_empty() {
181        let off = PRODUCT_POLICY_INLINE_OFFSET;
182        buf[off..off + policy_bytes.len()].copy_from_slice(policy_bytes);
183    }
184    buf
185}
186
187/// Kernel Command line type.
188pub enum CommandLineType<'a> {
189    /// The command line is a static string.
190    Static(&'a str),
191    /// The command line is dynamic and host appendable via the chosen node in
192    /// device tree, with initial data specified by the provided CStr. An empty
193    /// base_string may be provided to allow the host to specify the full kernel
194    /// command line.
195    HostAppendable(&'a str),
196}
197
198/// Load the underhill kernel on x64.
199///
200/// An optional initrd may be specified.
201///
202/// An optional `memory_page_base` may be specified. This will disable
203/// relocation support for underhill.
204pub fn load_openhcl_x64<F>(
205    importer: &mut dyn ImageLoad<X86Register>,
206    kernel_image: &mut F,
207    shim: &mut F,
208    sidecar: Option<&mut F>,
209    command_line: CommandLineType<'_>,
210    mut initrd: Option<(&mut dyn ReadSeek, u64)>,
211    memory_page_base: Option<u64>,
212    memory_page_count: u64,
213    vtl0_config: Vtl0Config<'_>,
214    product_policy: Option<&ProductPolicy>,
215) -> Result<(), Error>
216where
217    F: Read + Seek,
218{
219    let IsolationConfig {
220        isolation_type,
221        paravisor_present,
222        shared_gpa_boundary_bits,
223    } = importer.isolation_config();
224
225    // If no explicit memory base is specified, load with relocation support.
226    let with_relocation = memory_page_base.is_none() && isolation_type == IsolationType::None;
227
228    let memory_start_address = memory_page_base
229        .map(|page_number| page_number * HV_PAGE_SIZE)
230        .unwrap_or(PARAVISOR_DEFAULT_MEMORY_BASE_ADDRESS);
231
232    let memory_size = memory_page_count * HV_PAGE_SIZE;
233
234    // OpenHCL is laid out as the following:
235    // --- High Memory, 2MB aligned ---
236    // free space
237    //
238    // page tables
239    // 16 pages reserved for bootshim heap
240    // 8K bootshim logs
241    // IGVM parameters
242    // reserved vtl2 ranges
243    // initrd
244    // openhcl_boot
245    // sidecar, if configured
246    // - pad to next 2MB -
247    // kernel
248    // optional 2mb bounce buf for CVM
249    // persisted state region
250    // --- Low memory, 2MB aligned ---
251
252    // Paravisor memory ranges must be 2MB (large page) aligned.
253    if !memory_start_address.is_multiple_of(X64_LARGE_PAGE_SIZE) {
254        return Err(Error::MemoryUnaligned(memory_start_address));
255    }
256
257    if !memory_size.is_multiple_of(X64_LARGE_PAGE_SIZE) {
258        return Err(Error::MemoryUnaligned(memory_size));
259    }
260
261    // The whole memory range must be present and VTL2 protectable for the
262    // underhill kernel to work.
263    importer.verify_startup_memory_available(
264        memory_start_address / HV_PAGE_SIZE,
265        memory_page_count,
266        if paravisor_present {
267            StartupMemoryType::Vtl2ProtectableRam
268        } else {
269            StartupMemoryType::Ram
270        },
271    )?;
272
273    let kernel_acceptance = match isolation_type {
274        IsolationType::Snp | IsolationType::Tdx => BootPageAcceptance::Shared,
275        _ => BootPageAcceptance::Exclusive,
276    };
277
278    let mut offset = memory_start_address;
279
280    // Reserve the first 2MB for a potential persisted state region. The first
281    // 4K page is always the persisted state header, and the bootshim may decide
282    // to use the the remaining pages for the protobuf payload.
283    let persisted_region_base = offset;
284    let persisted_region_size = PERSISTED_REGION_SIZE;
285    offset += persisted_region_size;
286
287    // If hardware isolated, reserve a 2MB range for bounce buffering shared
288    // pages. This is done first because we know the start address is 2MB
289    // aligned, with the next consumers wanting 2MB aligned ranges. This is
290    // reserved at load time in order to guarantee the pagetables have entries
291    // for this identity mapping.
292    //
293    // Leave this as a gap, as there's no need to accept or describe this range
294    // in the IGVM file.
295    let bounce_buffer = if matches!(isolation_type, IsolationType::Snp | IsolationType::Tdx) {
296        let bounce_buffer_gpa = offset;
297        assert_eq!(bounce_buffer_gpa % X64_LARGE_PAGE_SIZE, 0);
298        let range = MemoryRange::new(bounce_buffer_gpa..bounce_buffer_gpa + X64_LARGE_PAGE_SIZE);
299
300        offset += range.len();
301        Some(range)
302    } else {
303        None
304    };
305
306    tracing::trace!(offset, "loading the kernel");
307
308    // The x86_64 uncompressed kernel we use doesn't show any difference
309    // in the code sections upon flipping CONFIG_RELOCATABLE. In total,
310    // there are 6 places where a difference is found: dates in the Linux
311    // banner, GNU build ID, and metadata entries in the empty initrd image
312    // (it always is embedded into the kernel). No sections with relocations
313    // appear if CONFIG_RELOCATABLE is set.
314    // Assume that at least the kernel entry contains PIC and no loader
315    // assistance with the relocations records (if any) is required.
316    let load_info = crate::elf::load_static_elf(
317        importer,
318        kernel_image,
319        offset,
320        0,
321        true,
322        kernel_acceptance,
323        "underhill-kernel",
324    )
325    .map_err(|e| Error::Kernel(crate::linux::Error::ElfLoader(e)))?;
326    tracing::trace!("Kernel loaded at {load_info:x?}");
327    let crate::elf::LoadInfo {
328        minimum_address_used: _min_addr,
329        next_available_address: mut offset,
330        entrypoint: kernel_entrypoint,
331    } = load_info;
332
333    assert_eq!(offset & (HV_PAGE_SIZE - 1), 0);
334
335    // If an AP kernel was provided, load it next.
336    let (sidecar_size, sidecar_entrypoint) = if let Some(sidecar) = sidecar {
337        // Sidecar load addr must be 2MB aligned
338        offset = align_up_to_large_page_size(offset);
339
340        let load_info = crate::elf::load_static_elf(
341            importer,
342            sidecar,
343            0,
344            offset,
345            false,
346            BootPageAcceptance::Exclusive,
347            "sidecar-kernel",
348        )
349        .map_err(|e| Error::Kernel(crate::linux::Error::ElfLoader(e)))?;
350
351        (
352            load_info.next_available_address - offset,
353            load_info.entrypoint,
354        )
355    } else {
356        (0, 0)
357    };
358
359    let sidecar_base = offset;
360    offset += sidecar_size;
361
362    let load_info = crate::elf::load_static_elf(
363        importer,
364        shim,
365        0,
366        offset,
367        false,
368        BootPageAcceptance::Exclusive,
369        "underhill-boot-shim",
370    )
371    .map_err(Error::Shim)?;
372    tracing::trace!("The boot shim loaded at {load_info:x?}");
373    let crate::elf::LoadInfo {
374        minimum_address_used: shim_base_addr,
375        next_available_address: mut offset,
376        entrypoint: shim_entry_address,
377    } = load_info;
378
379    // Compute initrd CRC before the file reference is consumed by the importer.
380    let mut buf = ChunkBuf::new();
381    let initrd_crc = if let Some((ref mut initrd_file, initrd_len)) = initrd {
382        buf.crc32(*initrd_file, initrd_len)
383            .map_err(Error::InitrdRead)?
384    } else {
385        crc32fast::hash(&[])
386    };
387
388    // Optionally import initrd if specified.
389    let ramdisk = if let Some((initrd_file, initrd_len)) = initrd {
390        let initrd_base = offset;
391        let initrd_size = align_up_to_page_size(initrd_len);
392
393        buf.import_file_region(
394            importer,
395            ImportFileRegion {
396                file: initrd_file,
397                file_offset: 0,
398                file_length: initrd_len,
399                gpa: initrd_base,
400                memory_length: initrd_len,
401                acceptance: kernel_acceptance,
402                tag: "underhill-initrd",
403            },
404        )
405        .map_err(Error::ImportInitrd)?;
406
407        offset += initrd_size;
408        Some((initrd_base, initrd_len))
409    } else {
410        None
411    };
412
413    let gdt_base_address = offset;
414    let gdt_size = HV_PAGE_SIZE;
415    offset += gdt_size;
416
417    let boot_params_base = offset;
418    let boot_params_size = HV_PAGE_SIZE;
419
420    offset += boot_params_size;
421
422    let cmdline_base = offset;
423    let (cmdline, policy) = match command_line {
424        CommandLineType::Static(val) => (val, CommandLinePolicy::STATIC),
425        CommandLineType::HostAppendable(val) => (val, CommandLinePolicy::APPEND_CHOSEN),
426    };
427
428    if cmdline.len() > COMMAND_LINE_SIZE {
429        return Err(Error::CommandLineSize(cmdline.len()));
430    }
431
432    let mut static_command_line = [0; COMMAND_LINE_SIZE];
433    static_command_line[..cmdline.len()].copy_from_slice(cmdline.as_bytes());
434    let paravisor_command_line = ParavisorCommandLine {
435        policy,
436        static_command_line_len: cmdline.len() as u16,
437        static_command_line,
438    };
439
440    importer.import_pages(
441        cmdline_base / HV_PAGE_SIZE,
442        1,
443        "underhill-command-line",
444        BootPageAcceptance::Exclusive,
445        paravisor_command_line.as_bytes(),
446    )?;
447
448    offset += HV_PAGE_SIZE;
449
450    // Reserve space for the VTL2 reserved region.
451    let reserved_region_size = PARAVISOR_RESERVED_VTL2_PAGE_COUNT_MAX * HV_PAGE_SIZE;
452    let reserved_region_start = offset;
453    offset += reserved_region_size;
454
455    tracing::debug!(reserved_region_start);
456
457    let parameter_region_size = PARAVISOR_VTL2_CONFIG_REGION_PAGE_COUNT_MAX * HV_PAGE_SIZE;
458    let parameter_region_start = offset;
459    offset += parameter_region_size;
460
461    tracing::debug!(parameter_region_start);
462
463    // Reserve 8K for the bootshim log buffer. Import these pages so they are
464    // available early without extra acceptance calls.
465    let bootshim_log_size = HV_PAGE_SIZE * 2;
466    let bootshim_log_start = offset;
467    offset += bootshim_log_size;
468
469    importer.import_pages(
470        bootshim_log_start / HV_PAGE_SIZE,
471        bootshim_log_size / HV_PAGE_SIZE,
472        "ohcl-boot-shim-log-buffer",
473        BootPageAcceptance::Exclusive,
474        &[],
475    )?;
476
477    // Reserve 16 pages for a bootshim heap. This is only used to parse the
478    // protobuf payload from the previous instance in a servicing boot.
479    //
480    // Import these pages as it greatly simplifies the early startup code in the
481    // bootshim for isolated guests. This allows the bootshim to use these pages
482    // early on without extra acceptance calls.
483    let heap_start = offset;
484    let heap_size = 16 * HV_PAGE_SIZE;
485    importer.import_pages(
486        heap_start / HV_PAGE_SIZE,
487        heap_size / HV_PAGE_SIZE,
488        "ohcl-boot-shim-heap",
489        BootPageAcceptance::Exclusive,
490        &[],
491    )?;
492    offset += heap_size;
493
494    // Some loaders only fix up identity map entries that overlap the relocation
495    // region, so keep the page table region in the same large page as it.
496    offset = avoid_page_table_large_page_boundary(offset, X64_LARGE_PAGE_SIZE);
497
498    // The end of memory used by the loader, excluding pagetables.
499    let end_of_underhill_mem = offset;
500
501    // Page tables live at the end of VTL2 ram used by the bootshim.
502    //
503    // Size the available page table memory as 5 pages + 2 * 1GB of memory. This
504    // allows underhill to be mapped across a 512 GB boundary when using more
505    // than 1 GB, as the PDPTE will span 2 PML4E entries. Each GB of memory
506    // mapped requires 1 page for 2MB pages. Give 2 extra base pages and 1
507    // additional page per GB of mapped memory to allow the page table
508    // relocation code to be simpler, and not need to reclaim free pages from
509    // tables that have no valid entries.
510    //
511    // FUTURE: It would be better to change it so the shim only needs to map
512    //         itself, kernel, initrd and IGVM parameters. This requires
513    //         changing how the e820 map is constructed for the kernel along
514    //         with changing the contract on where the IGVM parameters live
515    //         within VTL2's memory.
516    let local_map = match isolation_type {
517        IsolationType::Snp | IsolationType::Tdx => {
518            Some((PARAVISOR_LOCAL_MAP_VA, PARAVISOR_LOCAL_MAP_SIZE))
519        }
520        _ => None,
521    };
522
523    let page_table_base_page_count = 5;
524    let page_table_dynamic_page_count = {
525        // Double the count to allow for simpler reconstruction.
526        calculate_pde_table_count(memory_start_address, memory_size) * 2
527            + local_map.map_or(0, |v| calculate_pde_table_count(v.0, v.1))
528    };
529    let page_table_isolation_page_count = match isolation_type {
530        IsolationType::Tdx => {
531            // TDX requires up to an extra 3 pages to map the reset vector as a
532            // 4K page.
533            3
534        }
535        _ => 0,
536    };
537    let page_table_page_count = page_table_base_page_count
538        + page_table_dynamic_page_count
539        + page_table_isolation_page_count;
540    let page_table_region_size = HV_PAGE_SIZE * page_table_page_count;
541    let page_table_region_start = offset;
542    offset += page_table_region_size;
543
544    tracing::debug!(page_table_region_start, page_table_region_size);
545
546    // Construct the memory ranges that will be identity mapped
547    let mut ranges: Vec<MappedRange> = Vec::new();
548
549    ranges.push(MappedRange::new(
550        memory_start_address,
551        memory_start_address + memory_size,
552    ));
553
554    if let Some((local_map_start, size)) = local_map {
555        ranges.push(MappedRange::new(local_map_start, local_map_start + size));
556    }
557
558    if isolation_type == IsolationType::Tdx {
559        const RESET_VECTOR_ADDR: u64 = 0xffff_f000;
560        ranges.push(MappedRange::new(
561            RESET_VECTOR_ADDR,
562            RESET_VECTOR_ADDR + page_table::x64::X64_PAGE_SIZE,
563        ));
564    }
565
566    ranges.sort_by_key(|r| r.start());
567
568    // Initialize the page table builder, and build the page table
569    let mut page_table_work_buffer: Vec<PageTable> =
570        vec![PageTable::new_zeroed(); PAGE_TABLE_MAX_COUNT];
571    let mut page_table: Vec<u8> = vec![0; PAGE_TABLE_MAX_BYTES];
572    let mut page_table_builder = PageTableBuilder::new(
573        page_table_region_start,
574        page_table_work_buffer.as_mut_slice(),
575        page_table.as_mut_slice(),
576        ranges.as_slice(),
577    )?;
578
579    if isolation_type == IsolationType::Snp {
580        page_table_builder = page_table_builder.with_confidential_bit(51);
581    }
582
583    let page_table = page_table_builder.build()?;
584
585    assert!((page_table.len() as u64).is_multiple_of(HV_PAGE_SIZE));
586    let page_table_page_base = page_table_region_start / HV_PAGE_SIZE;
587    assert!(page_table.len() as u64 <= page_table_region_size);
588    let offset = offset;
589
590    if with_relocation {
591        // Indicate relocation information. Don't include page table region.
592        importer.relocation_region(
593            memory_start_address,
594            end_of_underhill_mem - memory_start_address,
595            X64_LARGE_PAGE_SIZE,
596            PARAVISOR_DEFAULT_MEMORY_BASE_ADDRESS,
597            1 << 48,
598            true,
599            true,
600            0, // BSP
601        )?;
602
603        // Tell the loader page table relocation information.
604        importer.page_table_relocation(
605            page_table_region_start,
606            page_table_region_size / HV_PAGE_SIZE,
607            page_table.len() as u64 / HV_PAGE_SIZE,
608            0,
609        )?;
610    }
611
612    // The memory used by the loader must be smaller than the memory available.
613    if offset > memory_start_address + memory_size {
614        return Err(Error::NotEnoughMemory(offset - memory_start_address));
615    }
616
617    let (initrd_base, initrd_size) = ramdisk.unwrap_or((0, 0));
618    // Shim parameters for locations are relative to the base of where the shim is loaded.
619    let calculate_shim_offset = |addr: u64| addr.wrapping_sub(shim_base_addr) as i64;
620    let shim_params = ShimParamsRaw {
621        kernel_entry_offset: calculate_shim_offset(kernel_entrypoint),
622        cmdline_offset: calculate_shim_offset(cmdline_base),
623        initrd_offset: calculate_shim_offset(initrd_base),
624        initrd_size,
625        initrd_crc,
626        supported_isolation_type: match isolation_type {
627            // To the shim, None and VBS isolation are the same. The shim
628            // queries CPUID when running to determine if page acceptance needs
629            // to be done.
630            IsolationType::None | IsolationType::Vbs => {
631                loader_defs::shim::SupportedIsolationType::VBS
632            }
633            IsolationType::Snp => loader_defs::shim::SupportedIsolationType::SNP,
634            IsolationType::Tdx => loader_defs::shim::SupportedIsolationType::TDX,
635        },
636        memory_start_offset: calculate_shim_offset(memory_start_address),
637        memory_size,
638        parameter_region_offset: calculate_shim_offset(parameter_region_start),
639        parameter_region_size,
640        vtl2_reserved_region_offset: calculate_shim_offset(reserved_region_start),
641        vtl2_reserved_region_size: reserved_region_size,
642        sidecar_offset: calculate_shim_offset(sidecar_base),
643        sidecar_size,
644        sidecar_entry_offset: calculate_shim_offset(sidecar_entrypoint),
645        used_start: calculate_shim_offset(memory_start_address),
646        used_end: calculate_shim_offset(offset),
647        bounce_buffer_start: bounce_buffer.map_or(0, |r| calculate_shim_offset(r.start())),
648        bounce_buffer_size: bounce_buffer.map_or(0, |r| r.len()),
649        log_buffer_start: calculate_shim_offset(bootshim_log_start),
650        log_buffer_size: bootshim_log_size,
651        heap_start_offset: calculate_shim_offset(heap_start),
652        heap_size,
653        persisted_state_region_offset: calculate_shim_offset(persisted_region_base),
654        persisted_state_region_size: persisted_region_size,
655    };
656
657    tracing::debug!(boot_params_base, "shim gpa");
658
659    importer
660        .import_pages(
661            boot_params_base / HV_PAGE_SIZE,
662            boot_params_size / HV_PAGE_SIZE,
663            "underhill-shim-params",
664            BootPageAcceptance::Exclusive,
665            shim_params.as_bytes(),
666        )
667        .map_err(Error::Importer)?;
668
669    importer.import_pages(
670        page_table_page_base,
671        page_table_page_count,
672        "underhill-page-tables",
673        BootPageAcceptance::Exclusive,
674        page_table,
675    )?;
676
677    // Set selectors and control registers
678    // Setup two selectors and segment registers.
679    // ds, es, fs, gs, ss are linearSelector
680    // cs is linearCode64Selector
681
682    // GDT is laid out as (counting by the small entries):
683    //  0: null descriptor,
684    //  1: null descriptor,
685    //  2: linear code64 descriptor,
686    //  3. linear descriptor for data
687    //  4: here you can add more descriptors.
688
689    let default_data_attributes: u16 = X64_DEFAULT_DATA_SEGMENT_ATTRIBUTES.into();
690    let default_code64_attributes: u16 = X64_DEFAULT_CODE_SEGMENT_ATTRIBUTES.into();
691    let gdt = [
692        // A large null descriptor.
693        GdtEntry::new_zeroed(),
694        GdtEntry::new_zeroed(),
695        // Code descriptor for the long mode.
696        GdtEntry {
697            limit_low: 0xffff,
698            attr_low: default_code64_attributes as u8,
699            attr_high: (default_code64_attributes >> 8) as u8,
700            ..GdtEntry::new_zeroed()
701        },
702        // Data descriptor.
703        GdtEntry {
704            limit_low: 0xffff,
705            attr_low: default_data_attributes as u8,
706            attr_high: (default_data_attributes >> 8) as u8,
707            ..GdtEntry::new_zeroed()
708        },
709    ];
710
711    const LINEAR_CODE64_DESCRIPTOR_INDEX: usize = 2;
712    const LINEAR_DATA_DESCRIPTOR_INDEX: usize = 3;
713    const RPL: u8 = 0x00; // requested priviledge level: the highest
714
715    let linear_code64_descriptor_selector =
716        SegmentSelector::from_gdt_index(LINEAR_CODE64_DESCRIPTOR_INDEX as u16, RPL);
717    let linear_data_descriptor_selector =
718        SegmentSelector::from_gdt_index(LINEAR_DATA_DESCRIPTOR_INDEX as u16, RPL);
719
720    importer.import_pages(
721        gdt_base_address / HV_PAGE_SIZE,
722        gdt_size / HV_PAGE_SIZE,
723        "underhill-gdt",
724        BootPageAcceptance::Exclusive,
725        gdt.as_bytes(),
726    )?;
727
728    let mut import_reg = |register| {
729        importer
730            .import_vp_register(register)
731            .map_err(Error::Importer)
732    };
733
734    // Import GDTR and selectors.
735    import_reg(X86Register::Gdtr(TableRegister {
736        base: gdt_base_address,
737        limit: (size_of_val(&gdt) - 1) as u16,
738    }))?;
739
740    let ds = SegmentRegister {
741        selector: linear_data_descriptor_selector.into_bits(),
742        base: 0,
743        limit: 0xffffffff,
744        attributes: default_data_attributes,
745    };
746    import_reg(X86Register::Ds(ds))?;
747    import_reg(X86Register::Es(ds))?;
748    import_reg(X86Register::Fs(ds))?;
749    import_reg(X86Register::Gs(ds))?;
750    import_reg(X86Register::Ss(ds))?;
751
752    let cs = SegmentRegister {
753        selector: linear_code64_descriptor_selector.into_bits(),
754        base: 0,
755        limit: 0xffffffff,
756        attributes: default_code64_attributes,
757    };
758    import_reg(X86Register::Cs(cs))?;
759
760    // TODO: Workaround an OS repo bug where enabling a higher VTL zeros TR
761    //       instead of setting it to the reset default state. Manually set it
762    //       to the reset default state until the OS repo is fixed.
763    //
764    //       In the future, we should just not set this at all.
765    import_reg(X86Register::Tr(SegmentRegister {
766        selector: 0x0000,
767        base: 0x00000000,
768        limit: 0x0000FFFF,
769        attributes: X64_BUSY_TSS_SEGMENT_ATTRIBUTES.into(),
770    }))?;
771
772    // Set system registers to state expected by the boot shim, 64 bit mode with
773    // paging enabled.
774
775    // Set CR0
776    import_reg(X86Register::Cr0(
777        x86defs::X64_CR0_PG | x86defs::X64_CR0_PE | x86defs::X64_CR0_NE,
778    ))?;
779
780    // Set CR3 to point to page table
781    import_reg(X86Register::Cr3(page_table_region_start))?;
782
783    // Set CR4
784    import_reg(X86Register::Cr4(
785        x86defs::X64_CR4_PAE | x86defs::X64_CR4_MCE | x86defs::X64_CR4_OSXSAVE,
786    ))?;
787
788    // Set EFER to LMA, LME, and NXE for 64 bit mode.
789    import_reg(X86Register::Efer(
790        x86defs::X64_EFER_LMA | x86defs::X64_EFER_LME | x86defs::X64_EFER_NXE,
791    ))?;
792
793    // Set PAT
794    import_reg(X86Register::Pat(x86defs::X86X_MSR_DEFAULT_PAT))?;
795
796    // Setup remaining registers
797    // Set %rsi to relative location of boot_params_base
798    let relative_boot_params_base = boot_params_base - shim_base_addr;
799    import_reg(X86Register::Rsi(relative_boot_params_base))?;
800
801    // Set %rip to the shim entry point.
802    import_reg(X86Register::Rip(shim_entry_address))?;
803
804    // Load parameter regions.
805    let config_region_page_base = parameter_region_start / HV_PAGE_SIZE;
806
807    // Slit
808    let slit_page_base = config_region_page_base + PARAVISOR_CONFIG_SLIT_PAGE_INDEX;
809    let slit_parameter_area = importer.create_parameter_area(
810        slit_page_base,
811        PARAVISOR_CONFIG_SLIT_SIZE_PAGES as u32,
812        "underhill-slit",
813    )?;
814    importer.import_parameter(slit_parameter_area, 0, IgvmParameterType::Slit)?;
815
816    // Pptt
817    let pptt_page_base = config_region_page_base + PARAVISOR_CONFIG_PPTT_PAGE_INDEX;
818    let pptt_parameter_area = importer.create_parameter_area(
819        pptt_page_base,
820        PARAVISOR_CONFIG_PPTT_SIZE_PAGES as u32,
821        "underhill-pptt",
822    )?;
823    importer.import_parameter(pptt_parameter_area, 0, IgvmParameterType::Pptt)?;
824
825    // device tree
826    let dt_page_base = config_region_page_base + PARAVISOR_CONFIG_DEVICE_TREE_PAGE_INDEX;
827    let dt_parameter_area = importer.create_parameter_area(
828        dt_page_base,
829        PARAVISOR_CONFIG_DEVICE_TREE_SIZE_PAGES as u32,
830        "underhill-device-tree",
831    )?;
832    importer.import_parameter(dt_parameter_area, 0, IgvmParameterType::DeviceTree)?;
833
834    if isolation_type == IsolationType::Snp {
835        let reserved_region_page_base = reserved_region_start / HV_PAGE_SIZE;
836        let secrets_page_base: u64 =
837            reserved_region_page_base + PARAVISOR_RESERVED_VTL2_SNP_SECRETS_PAGE_INDEX;
838        importer.import_pages(
839            secrets_page_base,
840            PARAVISOR_RESERVED_VTL2_SNP_SECRETS_SIZE_PAGES,
841            "underhill-snp-secrets-page",
842            BootPageAcceptance::SecretsPage,
843            &[],
844        )?;
845
846        let cpuid_page = create_snp_cpuid_page();
847        let cpuid_page_base =
848            reserved_region_page_base + PARAVISOR_RESERVED_VTL2_SNP_CPUID_PAGE_INDEX;
849        importer.import_pages(
850            cpuid_page_base,
851            1,
852            "underhill-snp-cpuid-page",
853            BootPageAcceptance::CpuidPage,
854            cpuid_page.as_bytes(),
855        )?;
856
857        importer.import_pages(
858            cpuid_page_base + 1,
859            1,
860            "underhill-snp-cpuid-extended-state-page",
861            BootPageAcceptance::CpuidExtendedStatePage,
862            &[],
863        )?;
864
865        let vmsa_page_base =
866            reserved_region_page_base + PARAVISOR_RESERVED_VTL2_SNP_VMSA_PAGE_INDEX;
867        importer.set_vp_context_page(vmsa_page_base)?;
868    }
869
870    // Load measured config.
871    // The measured config is at page 0. Free pages start at page 1.
872    let mut free_page = 1;
873    let mut measured_config = ParavisorMeasuredVtl0Config {
874        magic: ParavisorMeasuredVtl0Config::MAGIC,
875        ..FromZeros::new_zeroed()
876    };
877
878    let Vtl0Config {
879        supports_pcat,
880        supports_uefi,
881        supports_linux,
882    } = vtl0_config;
883
884    if supports_pcat {
885        measured_config.supported_vtl0.set_pcat_supported(true);
886    }
887
888    if let Some((uefi, vp_context)) = &supports_uefi {
889        measured_config.supported_vtl0.set_uefi_supported(true);
890        let vp_context_page = free_page;
891        free_page += 1;
892        measured_config.uefi_info = UefiInfo {
893            firmware: PageRegionDescriptor {
894                base_page_number: uefi.firmware_base / HV_PAGE_SIZE,
895                page_count: uefi.total_size / HV_PAGE_SIZE,
896            },
897            vtl0_vp_context: PageRegionDescriptor {
898                base_page_number: vp_context_page,
899                page_count: 1,
900            },
901        };
902
903        // Deposit the UEFI vp context.
904        importer.import_pages(
905            vp_context_page,
906            1,
907            "openhcl-uefi-vp-context",
908            BootPageAcceptance::Exclusive,
909            vp_context,
910        )?;
911    }
912
913    if let Some(linux) = supports_linux {
914        measured_config
915            .supported_vtl0
916            .set_linux_direct_supported(true);
917
918        let kernel_region = PageRegionDescriptor::new(
919            linux.load_info.kernel.gpa / HV_PAGE_SIZE,
920            align_up_to_page_size(linux.load_info.kernel.size) / HV_PAGE_SIZE,
921        );
922
923        let (initrd_region, initrd_size) = match linux.load_info.initrd {
924            Some(info) => {
925                if info.gpa % HV_PAGE_SIZE != 0 {
926                    return Err(Error::MemoryUnaligned(info.gpa));
927                }
928                (
929                    // initrd info is aligned up to the next page.
930                    PageRegionDescriptor::new(
931                        info.gpa / HV_PAGE_SIZE,
932                        align_up_to_page_size(info.size) / HV_PAGE_SIZE,
933                    ),
934                    info.size,
935                )
936            }
937            None => (PageRegionDescriptor::EMPTY, 0),
938        };
939
940        let command_line_page = free_page;
941        // free_page += 1;
942
943        // Import the command line as a C string.
944        importer
945            .import_pages(
946                command_line_page,
947                1,
948                "underhill-vtl0-linux-command-line",
949                BootPageAcceptance::Exclusive,
950                linux.command_line.as_bytes_with_nul(),
951            )
952            .map_err(Error::Importer)?;
953        let command_line = PageRegionDescriptor::new(command_line_page, 1);
954
955        measured_config.linux_info = LinuxInfo {
956            kernel_region,
957            kernel_entrypoint: linux.load_info.kernel.entrypoint,
958            initrd_region,
959            initrd_size,
960            command_line,
961        };
962    }
963
964    importer
965        .import_pages(
966            PARAVISOR_VTL0_MEASURED_CONFIG_BASE_PAGE_X64,
967            1,
968            "underhill-measured-config",
969            BootPageAcceptance::Exclusive,
970            measured_config.as_bytes(),
971        )
972        .map_err(Error::Importer)?;
973
974    let vtl2_measured_config = ParavisorMeasuredVtl2Config {
975        magic: ParavisorMeasuredVtl2Config::MAGIC,
976        vtom_offset_bit: shared_gpa_boundary_bits.unwrap_or(0),
977        padding: [0; 7],
978        product_policy_size: 0,
979        reserved: [0; 4],
980    };
981
982    let region_image = build_measured_vtl2_config_region(vtl2_measured_config, product_policy);
983
984    importer
985        .import_pages(
986            config_region_page_base + PARAVISOR_MEASURED_VTL2_CONFIG_PAGE_INDEX,
987            PARAVISOR_MEASURED_VTL2_CONFIG_SIZE_PAGES,
988            "underhill-vtl2-measured-config",
989            BootPageAcceptance::Exclusive,
990            &region_image,
991        )
992        .map_err(Error::Importer)?;
993
994    let imported_region_base =
995        config_region_page_base + PARAVISOR_MEASURED_VTL2_CONFIG_ACCEPTED_MEMORY_PAGE_INDEX;
996
997    importer.set_imported_regions_config_page(imported_region_base);
998
999    // Also announce the per-page expected-hashes region. The IGVM file
1000    // loader populates it in finalize alongside the imported-regions page
1001    // (both regions are derived from the same set of shared pages). See
1002    // `openhcl_boot::verify_imported_regions_hash` diagnostic changes.
1003    let expected_page_hashes_base =
1004        config_region_page_base + PARAVISOR_MEASURED_VTL2_CONFIG_PAGE_HASHES_PAGE_INDEX;
1005    importer.set_expected_page_hashes_config_page(expected_page_hashes_base);
1006    Ok(())
1007}
1008
1009/// Create a hypervisor SNP CPUID page with the default values.
1010fn create_snp_cpuid_page() -> HV_PSP_CPUID_PAGE {
1011    let mut cpuid_page = HV_PSP_CPUID_PAGE::default();
1012
1013    // TODO SNP: The list used here is based earlier Microsoft projects.
1014    // 1. ExtendedStateEnumeration should be part of BootPageAcceptance::CpuidExtendedStatePage,
1015    // but it is unclear whether Linux supports a second page. The need for the second page is that
1016    // the entries in it are actually based on supported features on a specific host.
1017    // 2. ExtendedStateEnumeration should specify Xfem = 3
1018    for (i, required_leaf) in crate::cpuid::SNP_REQUIRED_CPUID_LEAF_LIST_PARAVISOR
1019        .iter()
1020        .enumerate()
1021    {
1022        let entry = &mut cpuid_page.cpuid_leaf_info[i];
1023        entry.eax_in = required_leaf.eax;
1024        entry.ecx_in = required_leaf.ecx;
1025        if required_leaf.eax == CpuidFunction::ExtendedStateEnumeration.0 {
1026            entry.xfem_in = 1;
1027        }
1028        cpuid_page.count += 1;
1029    }
1030
1031    cpuid_page
1032}
1033
1034/// Load the underhill kernel on arm64.
1035///
1036/// An optional initrd may be specified.
1037///
1038/// An optional `memory_page_base` may be specified. This will disable
1039/// relocation support for underhill.
1040pub fn load_openhcl_arm64<F>(
1041    importer: &mut dyn ImageLoad<Aarch64Register>,
1042    kernel_image: &mut F,
1043    shim: &mut F,
1044    command_line: CommandLineType<'_>,
1045    mut initrd: Option<(&mut dyn ReadSeek, u64)>,
1046    memory_page_base: Option<u64>,
1047    memory_page_count: u64,
1048    vtl0_config: Vtl0Config<'_>,
1049    product_policy: Option<&ProductPolicy>,
1050) -> Result<(), Error>
1051where
1052    F: Read + Seek,
1053{
1054    let Vtl0Config {
1055        supports_pcat,
1056        supports_uefi,
1057        supports_linux,
1058    } = vtl0_config;
1059
1060    assert!(!supports_pcat);
1061    assert!(supports_uefi.is_some() || supports_linux.is_some());
1062
1063    let paravisor_present = importer.isolation_config().paravisor_present;
1064
1065    // If no explicit memory base is specified, load with relocation support.
1066    let with_relocation = memory_page_base.is_none();
1067
1068    let memory_start_address = memory_page_base
1069        .map(|page_number| page_number * HV_PAGE_SIZE)
1070        .unwrap_or(PARAVISOR_DEFAULT_MEMORY_BASE_ADDRESS);
1071
1072    let memory_size = memory_page_count * HV_PAGE_SIZE;
1073
1074    // Paravisor memory ranges must be 2MB (large page) aligned.
1075    if !memory_start_address.is_multiple_of(u64::from(Arm64PageSize::Large)) {
1076        return Err(Error::MemoryUnaligned(memory_start_address));
1077    }
1078
1079    if !memory_size.is_multiple_of(u64::from(Arm64PageSize::Large)) {
1080        return Err(Error::MemoryUnaligned(memory_size));
1081    }
1082
1083    // The whole memory range must be present and VTL2 protectable for the
1084    // underhill kernel to work.
1085    importer.verify_startup_memory_available(
1086        memory_start_address / HV_PAGE_SIZE,
1087        memory_page_count,
1088        if paravisor_present {
1089            StartupMemoryType::Vtl2ProtectableRam
1090        } else {
1091            StartupMemoryType::Ram
1092        },
1093    )?;
1094
1095    let mut next_addr = memory_start_address;
1096
1097    // Reserve the first 2MB for a potential persisted state region. The first
1098    // 4K page is always the persisted state header, and the bootshim may decide
1099    // to use the the remaining pages for the protobuf payload.
1100    let persisted_region_base = next_addr;
1101    let persisted_region_size = PERSISTED_REGION_SIZE;
1102    next_addr += persisted_region_size;
1103
1104    tracing::trace!(next_addr, "loading the kernel");
1105
1106    // Compute initrd CRC before the file reference is consumed by the loader.
1107    let initrd_crc = if let Some((ref mut initrd_file, initrd_len)) = initrd {
1108        ChunkBuf::new()
1109            .crc32(*initrd_file, initrd_len)
1110            .map_err(Error::InitrdRead)?
1111    } else {
1112        crc32fast::hash(&[])
1113    };
1114
1115    // The aarch64 Linux kernel image is most commonly found as a flat binary with a
1116    // header rather than an ELF.
1117    // DeviceTree is generated dynamically by the boot shim.
1118    let initrd_address_type = InitrdAddressType::AfterKernel;
1119    let initrd_config = initrd.map(|(initrd_file, initrd_size)| InitrdConfig {
1120        initrd_address: initrd_address_type,
1121        initrd: initrd_file,
1122        size: initrd_size,
1123    });
1124    let device_tree_blob = None;
1125    let crate::linux::LoadInfo {
1126        kernel:
1127            KernelInfo {
1128                gpa: kernel_base,
1129                size: kernel_size,
1130                entrypoint: kernel_entry_point,
1131            },
1132        initrd: initrd_info,
1133        dtb,
1134        ..
1135    } = load_kernel_and_initrd_arm64(
1136        importer,
1137        kernel_image,
1138        next_addr,
1139        initrd_config,
1140        device_tree_blob,
1141    )
1142    .map_err(Error::Kernel)?;
1143
1144    assert!(
1145        dtb.is_none(),
1146        "DeviceTree is generated dynamically by the boot shim."
1147    );
1148
1149    tracing::trace!(kernel_base, "kernel loaded");
1150
1151    let InitrdInfo {
1152        gpa: initrd_gpa,
1153        size: initrd_size,
1154    } = if let Some(initrd_info) = initrd_info {
1155        assert!(initrd_address_type == InitrdAddressType::AfterKernel);
1156        next_addr = initrd_info.gpa + initrd_info.size;
1157        initrd_info
1158    } else {
1159        next_addr = kernel_base + kernel_size;
1160        InitrdInfo { gpa: 0, size: 0 }
1161    };
1162
1163    next_addr = align_up_to_page_size(next_addr);
1164
1165    tracing::trace!(next_addr, "loading the boot shim");
1166
1167    let crate::elf::LoadInfo {
1168        minimum_address_used: shim_base_addr,
1169        next_available_address: mut next_addr,
1170        entrypoint: shim_entry_point,
1171    } = crate::elf::load_static_elf(
1172        importer,
1173        shim,
1174        0,
1175        next_addr,
1176        false,
1177        BootPageAcceptance::Exclusive,
1178        "underhill-boot-shim",
1179    )
1180    .map_err(Error::Shim)?;
1181
1182    tracing::trace!(shim_base_addr, "boot shim loaded");
1183
1184    tracing::trace!(next_addr, "loading the command line");
1185
1186    let cmdline_base = next_addr;
1187    let (cmdline, policy) = match command_line {
1188        CommandLineType::Static(val) => (val, CommandLinePolicy::STATIC),
1189        CommandLineType::HostAppendable(val) => (val, CommandLinePolicy::APPEND_CHOSEN),
1190    };
1191
1192    if cmdline.len() > COMMAND_LINE_SIZE {
1193        return Err(Error::CommandLineSize(cmdline.len()));
1194    }
1195
1196    let mut static_command_line = [0; COMMAND_LINE_SIZE];
1197    static_command_line[..cmdline.len()].copy_from_slice(cmdline.as_bytes());
1198    let paravisor_command_line = ParavisorCommandLine {
1199        policy,
1200        static_command_line_len: cmdline.len() as u16,
1201        static_command_line,
1202    };
1203
1204    importer.import_pages(
1205        cmdline_base / HV_PAGE_SIZE,
1206        1,
1207        "underhill-command-line",
1208        BootPageAcceptance::Exclusive,
1209        paravisor_command_line.as_bytes(),
1210    )?;
1211
1212    next_addr += HV_PAGE_SIZE;
1213
1214    tracing::trace!(next_addr, "loading the boot shim parameters");
1215
1216    let shim_params_base = next_addr;
1217    let shim_params_size = HV_PAGE_SIZE;
1218
1219    next_addr += shim_params_size;
1220
1221    let parameter_region_size = PARAVISOR_VTL2_CONFIG_REGION_PAGE_COUNT_MAX * HV_PAGE_SIZE;
1222    let parameter_region_start = next_addr;
1223    next_addr += parameter_region_size;
1224
1225    tracing::debug!(parameter_region_start);
1226
1227    // Reserve 8K for the bootshim log buffer.
1228    let bootshim_log_size = HV_PAGE_SIZE * 2;
1229    let bootshim_log_start = next_addr;
1230    next_addr += bootshim_log_size;
1231
1232    importer.import_pages(
1233        bootshim_log_start / HV_PAGE_SIZE,
1234        bootshim_log_size / HV_PAGE_SIZE,
1235        "ohcl-boot-shim-log-buffer",
1236        BootPageAcceptance::Exclusive,
1237        &[],
1238    )?;
1239
1240    // Reserve 16 pages for a bootshim heap. This is only used to parse the
1241    // protobuf payload from the previous instance in a servicing boot.
1242    //
1243    // Import these pages as it greatly simplifies the early startup code in the
1244    // bootshim for isolated guests. This allows the bootshim to use these pages
1245    // early on without extra acceptance calls.
1246    let heap_start = next_addr;
1247    let heap_size = 16 * HV_PAGE_SIZE;
1248    importer.import_pages(
1249        heap_start / HV_PAGE_SIZE,
1250        heap_size / HV_PAGE_SIZE,
1251        "ohcl-boot-shim-heap",
1252        BootPageAcceptance::Exclusive,
1253        &[],
1254    )?;
1255    next_addr += heap_size;
1256
1257    // Some loaders only fix up identity map entries that overlap the relocation
1258    // region, so keep the page table region in the same large page as it.
1259    next_addr = avoid_page_table_large_page_boundary(next_addr, u64::from(Arm64PageSize::Large));
1260
1261    // The end of memory used by the loader, excluding pagetables.
1262    let end_of_underhill_mem = next_addr;
1263
1264    // Page tables live at the end of the VTL2 imported region, which allows it
1265    // to be relocated separately.
1266    let page_table_base_page_count = 5;
1267    let page_table_dynamic_page_count = 2 * page_table_base_page_count;
1268    let page_table_page_count = page_table_base_page_count + page_table_dynamic_page_count;
1269    let page_table_region_size = HV_PAGE_SIZE * page_table_page_count;
1270    let page_table_region_start = next_addr;
1271    next_addr += page_table_region_size;
1272
1273    tracing::debug!(page_table_region_start, page_table_region_size);
1274
1275    let next_addr = next_addr;
1276
1277    // The memory used by the loader must be smaller than the memory available.
1278    if next_addr > memory_start_address + memory_size {
1279        return Err(Error::NotEnoughMemory(next_addr - memory_start_address));
1280    }
1281
1282    // Shim parameters for locations are relative to the base of where the shim is loaded.
1283    let calculate_shim_offset = |addr: u64| -> i64 { addr.wrapping_sub(shim_base_addr) as i64 };
1284    let shim_params = ShimParamsRaw {
1285        kernel_entry_offset: calculate_shim_offset(kernel_entry_point),
1286        cmdline_offset: calculate_shim_offset(cmdline_base),
1287        initrd_offset: calculate_shim_offset(initrd_gpa),
1288        initrd_size,
1289        initrd_crc,
1290        supported_isolation_type: match importer.isolation_config().isolation_type {
1291            IsolationType::None | IsolationType::Vbs => {
1292                loader_defs::shim::SupportedIsolationType::VBS
1293            }
1294            _ => panic!("only None and VBS are supported for ARM64"),
1295        },
1296        memory_start_offset: calculate_shim_offset(memory_start_address),
1297        memory_size,
1298        parameter_region_offset: calculate_shim_offset(parameter_region_start),
1299        parameter_region_size,
1300        vtl2_reserved_region_offset: 0,
1301        vtl2_reserved_region_size: 0,
1302        sidecar_offset: 0,
1303        sidecar_size: 0,
1304        sidecar_entry_offset: 0,
1305        used_start: calculate_shim_offset(memory_start_address),
1306        used_end: calculate_shim_offset(next_addr),
1307        bounce_buffer_start: 0,
1308        bounce_buffer_size: 0,
1309        log_buffer_start: calculate_shim_offset(bootshim_log_start),
1310        log_buffer_size: bootshim_log_size,
1311        heap_start_offset: calculate_shim_offset(heap_start),
1312        heap_size,
1313        persisted_state_region_offset: calculate_shim_offset(persisted_region_base),
1314        persisted_state_region_size: persisted_region_size,
1315    };
1316
1317    importer
1318        .import_pages(
1319            shim_params_base / HV_PAGE_SIZE,
1320            shim_params_size / HV_PAGE_SIZE,
1321            "underhill-shim-params",
1322            BootPageAcceptance::Exclusive,
1323            shim_params.as_bytes(),
1324        )
1325        .map_err(Error::Importer)?;
1326
1327    let mut measured_config = ParavisorMeasuredVtl0Config {
1328        magic: ParavisorMeasuredVtl0Config::MAGIC,
1329        ..FromZeros::new_zeroed()
1330    };
1331
1332    if let Some((uefi, vp_context)) = &supports_uefi {
1333        measured_config.supported_vtl0.set_uefi_supported(true);
1334        let vp_context_page = PARAVISOR_VTL0_MEASURED_CONFIG_BASE_PAGE_AARCH64 + 1;
1335        measured_config.uefi_info = UefiInfo {
1336            firmware: PageRegionDescriptor {
1337                base_page_number: uefi.firmware_base / HV_PAGE_SIZE,
1338                page_count: uefi.total_size / HV_PAGE_SIZE,
1339            },
1340            vtl0_vp_context: PageRegionDescriptor {
1341                base_page_number: vp_context_page,
1342                page_count: 1,
1343            },
1344        };
1345
1346        // Deposit the UEFI vp context.
1347        importer.import_pages(
1348            vp_context_page,
1349            1,
1350            "openhcl-uefi-vp-context",
1351            BootPageAcceptance::Exclusive,
1352            vp_context,
1353        )?;
1354    }
1355
1356    importer
1357        .import_pages(
1358            PARAVISOR_VTL0_MEASURED_CONFIG_BASE_PAGE_AARCH64,
1359            1,
1360            "underhill-measured-config",
1361            BootPageAcceptance::Exclusive,
1362            measured_config.as_bytes(),
1363        )
1364        .map_err(Error::Importer)?;
1365
1366    tracing::trace!(page_table_region_start, "loading the page tables");
1367
1368    let memory_attribute_indirection = MemoryAttributeIndirectionEl1([
1369        MemoryAttributeEl1::Device_nGnRnE,
1370        MemoryAttributeEl1::Normal_NonCacheable,
1371        MemoryAttributeEl1::Normal_WriteThrough,
1372        MemoryAttributeEl1::Normal_WriteBack,
1373        MemoryAttributeEl1::Device_nGnRnE,
1374        MemoryAttributeEl1::Device_nGnRnE,
1375        MemoryAttributeEl1::Device_nGnRnE,
1376        MemoryAttributeEl1::Device_nGnRnE,
1377    ]);
1378    let mut page_tables: Vec<u8> = vec![0; page_table_region_size as usize];
1379    let page_tables = page_table::aarch64::build_identity_page_tables_aarch64(
1380        page_table_region_start,
1381        memory_start_address,
1382        memory_size,
1383        memory_attribute_indirection,
1384        page_tables.as_mut_slice(),
1385    );
1386    assert!((page_tables.len() as u64).is_multiple_of(HV_PAGE_SIZE));
1387    let page_table_page_base = page_table_region_start / HV_PAGE_SIZE;
1388    assert!(page_tables.len() as u64 <= page_table_region_size);
1389    assert!(page_table_region_size as usize > page_tables.len());
1390
1391    if with_relocation {
1392        // Indicate relocation information. Don't include page table region.
1393        importer.relocation_region(
1394            memory_start_address,
1395            end_of_underhill_mem - memory_start_address,
1396            Arm64PageSize::Large.into(),
1397            PARAVISOR_DEFAULT_MEMORY_BASE_ADDRESS,
1398            1 << 48,
1399            true,
1400            false,
1401            0, // BSP
1402        )?;
1403
1404        // Tell the loader page table relocation information.
1405        importer.page_table_relocation(
1406            page_table_region_start,
1407            page_table_region_size / HV_PAGE_SIZE,
1408            page_tables.len() as u64 / HV_PAGE_SIZE,
1409            0,
1410        )?;
1411    }
1412
1413    importer.import_pages(
1414        page_table_page_base,
1415        page_table_page_count,
1416        "underhill-page-tables",
1417        BootPageAcceptance::Exclusive,
1418        page_tables,
1419    )?;
1420
1421    tracing::trace!("Importing register state");
1422
1423    let mut import_reg = |register| {
1424        importer
1425            .import_vp_register(register)
1426            .map_err(Error::Importer)
1427    };
1428
1429    // Set %X0 to relative location of boot_params_base
1430    let relative_boot_params_base = shim_params_base - shim_base_addr;
1431    import_reg(AArch64Register::X0(relative_boot_params_base).into())?;
1432
1433    // Set %pc to the shim entry point.
1434    import_reg(AArch64Register::Pc(shim_entry_point).into())?;
1435
1436    // System registers
1437
1438    import_reg(AArch64Register::Cpsr(Cpsr64::new().with_sp(true).with_el(1).into()).into())?;
1439
1440    // This is what Hyper-V uses. qemu/KVM, and qemu/max use slightly
1441    // different flags.
1442    // KVM sets these in addition to what the Hyper-V uses:
1443    //
1444    // .with_sa(true)
1445    // .with_itd(true)
1446    // .with_sed(true)
1447    //
1448    // Windows sets:
1449    //
1450    // .with_sa(true)
1451    // .with_sa0(true)
1452    // .with_n_aa(true)
1453    // .with_sed(true)
1454    // .with_dze(true)
1455    // .with_en_ib(true)
1456    // .with_dssbs(true)
1457    //
1458    // Maybe could enforce the `s`tack `a`lignment, here, too. Depends on
1459    // the compiler generating code aligned accesses for the stack.
1460    //
1461    // Hyper-V sets:
1462    import_reg(
1463        AArch64Register::SctlrEl1(
1464            SctlrEl1::new()
1465                // MMU enable for EL1&0 stage 1 address translation.
1466                // It can be turned off in VTL2 for debugging.
1467                // The family of the `at` instructions and the `PAR_EL1` register are
1468                // useful for debugging MMU issues.
1469                .with_m(true)
1470                // Stage 1 Cacheability control, for data accesses.
1471                .with_c(true)
1472                // Stage 1 Cacheability control, for code.
1473                .with_i(true)
1474                // Reserved flags, must be set
1475                .with_eos(true)
1476                .with_tscxt(true)
1477                .with_eis(true)
1478                .with_span(true)
1479                .with_n_tlsmd(true)
1480                .with_lsmaoe(true)
1481                .into(),
1482        )
1483        .into(),
1484    )?;
1485
1486    // Hyper-V UEFI and qemu/KVM use the same value for TCR_EL1.
1487    // They set `t0sz` to `28` as they map memory pretty low.
1488    // In the paravisor case, need more flexibility.
1489    // For the details, refer to the "Learning the architecture" series
1490    // on the ARM website.
1491    import_reg(
1492        AArch64Register::TcrEl1(
1493            TranslationControlEl1::new()
1494                .with_t0sz(0x11)
1495                .with_irgn0(1)
1496                .with_orgn0(1)
1497                .with_sh0(3)
1498                .with_tg0(TranslationGranule0::TG_4KB)
1499                // Disable TTBR1_EL1 walks (i.e. the upper half).
1500                .with_epd1(1)
1501                // Due to erratum #822227, need to set a valid TG1 regardless of EPD1.
1502                .with_tg1(TranslationGranule1::TG_4KB)
1503                .with_ips(IntermPhysAddrSize::IPA_48_BITS_256_TB)
1504                .into(),
1505        )
1506        .into(),
1507    )?;
1508
1509    // The Memory Attribute Indirection
1510    import_reg(AArch64Register::MairEl1(memory_attribute_indirection.into()).into())?;
1511    import_reg(
1512        AArch64Register::Ttbr0El1(
1513            TranslationBaseEl1::new()
1514                .with_baddr(page_table_region_start)
1515                .into(),
1516        )
1517        .into(),
1518    )?;
1519
1520    // VBAR is in the undefined state, setting it to 0 albeit
1521    // without the vector exception table. The shim can configure that on its own
1522    // if need be.
1523    import_reg(AArch64Register::VbarEl1(0).into())?;
1524
1525    // Load parameter regions.
1526    let config_region_page_base = parameter_region_start / HV_PAGE_SIZE;
1527
1528    // Slit
1529    let slit_page_base = config_region_page_base + PARAVISOR_CONFIG_SLIT_PAGE_INDEX;
1530    let slit_parameter_area = importer.create_parameter_area(
1531        slit_page_base,
1532        PARAVISOR_CONFIG_SLIT_SIZE_PAGES as u32,
1533        "underhill-slit",
1534    )?;
1535    importer.import_parameter(slit_parameter_area, 0, IgvmParameterType::Slit)?;
1536
1537    // Pptt
1538    let pptt_page_base = config_region_page_base + PARAVISOR_CONFIG_PPTT_PAGE_INDEX;
1539    let pptt_parameter_area = importer.create_parameter_area(
1540        pptt_page_base,
1541        PARAVISOR_CONFIG_PPTT_SIZE_PAGES as u32,
1542        "underhill-pptt",
1543    )?;
1544    importer.import_parameter(pptt_parameter_area, 0, IgvmParameterType::Pptt)?;
1545
1546    // device tree
1547    let dt_page_base = config_region_page_base + PARAVISOR_CONFIG_DEVICE_TREE_PAGE_INDEX;
1548    let dt_parameter_area = importer.create_parameter_area(
1549        dt_page_base,
1550        PARAVISOR_CONFIG_DEVICE_TREE_SIZE_PAGES as u32,
1551        "underhill-device-tree",
1552    )?;
1553    importer.import_parameter(dt_parameter_area, 0, IgvmParameterType::DeviceTree)?;
1554
1555    let vtl2_measured_config = ParavisorMeasuredVtl2Config {
1556        magic: ParavisorMeasuredVtl2Config::MAGIC,
1557        vtom_offset_bit: 0,
1558        padding: [0; 7],
1559        product_policy_size: 0,
1560        reserved: [0; 4],
1561    };
1562
1563    let region_image = build_measured_vtl2_config_region(vtl2_measured_config, product_policy);
1564
1565    importer
1566        .import_pages(
1567            config_region_page_base + PARAVISOR_MEASURED_VTL2_CONFIG_PAGE_INDEX,
1568            PARAVISOR_MEASURED_VTL2_CONFIG_SIZE_PAGES,
1569            "underhill-vtl2-measured-config",
1570            BootPageAcceptance::Exclusive,
1571            &region_image,
1572        )
1573        .map_err(Error::Importer)?;
1574
1575    let imported_region_base =
1576        config_region_page_base + PARAVISOR_MEASURED_VTL2_CONFIG_ACCEPTED_MEMORY_PAGE_INDEX;
1577
1578    importer.set_imported_regions_config_page(imported_region_base);
1579
1580    // Also announce the per-page expected-hashes region (see comments in
1581    // the x86 sibling above).
1582    let expected_page_hashes_base =
1583        config_region_page_base + PARAVISOR_MEASURED_VTL2_CONFIG_PAGE_HASHES_PAGE_INDEX;
1584    importer.set_expected_page_hashes_config_page(expected_page_hashes_base);
1585
1586    Ok(())
1587}
1588
1589#[cfg(test)]
1590mod page_table_layout_tests {
1591    use super::*;
1592
1593    #[test]
1594    fn page_table_region_avoids_large_page_boundary() {
1595        for large_page_size in [X64_LARGE_PAGE_SIZE, u64::from(Arm64PageSize::Large)] {
1596            assert_eq!(
1597                avoid_page_table_large_page_boundary(large_page_size, large_page_size),
1598                large_page_size + HV_PAGE_SIZE
1599            );
1600            assert_eq!(
1601                avoid_page_table_large_page_boundary(
1602                    large_page_size - HV_PAGE_SIZE,
1603                    large_page_size
1604                ),
1605                large_page_size - HV_PAGE_SIZE
1606            );
1607        }
1608    }
1609}
1610
1611#[cfg(test)]
1612mod product_policy_tests {
1613    use super::*;
1614    use product_policy::decode_product_policy;
1615    use product_policy::sivm::SivmPolicy;
1616    use zerocopy::FromBytes;
1617
1618    // ---------------------------------------------------------------
1619    // Encoding helper round trips
1620    // ---------------------------------------------------------------
1621
1622    fn empty_config() -> ParavisorMeasuredVtl2Config {
1623        ParavisorMeasuredVtl2Config {
1624            magic: ParavisorMeasuredVtl2Config::MAGIC,
1625            vtom_offset_bit: 0,
1626            padding: [0; 7],
1627            product_policy_size: 0,
1628            reserved: [0; 4],
1629        }
1630    }
1631
1632    #[test]
1633    fn encode_product_policy_bytes_round_trip() {
1634        let policy = ProductPolicy::Sivm(SivmPolicy {
1635            require_ephemeral_vmgs: true,
1636            require_secure_boot: true,
1637            custom_uefi_json: vec![0xAA, 0xBB, 0xCC, 0xDD],
1638            ..Default::default()
1639        });
1640        let bytes = encode_product_policy_bytes(&policy);
1641        let decoded = decode_product_policy(&bytes).unwrap();
1642        // Test that the decoded policy matches the original policy
1643        assert_eq!(decoded, policy);
1644    }
1645
1646    #[test]
1647    #[should_panic(expected = "non-empty custom_uefi_json")]
1648    fn encode_product_policy_bytes_panics_on_empty_custom_uefi_json() {
1649        let policy = ProductPolicy::Sivm(SivmPolicy {
1650            require_ephemeral_vmgs: true,
1651            require_secure_boot: true,
1652            require_secure_boot_vars: true,
1653            require_bcd_integrity: true,
1654            custom_uefi_json: vec![],
1655        });
1656        let _ = encode_product_policy_bytes(&policy);
1657    }
1658
1659    #[test]
1660    #[should_panic(expected = "exceeds the static measured-config-region budget")]
1661    fn encode_product_policy_bytes_panics_on_oversize() {
1662        let oversize_body = PRODUCT_POLICY_MAX_SIZE_BYTES + 1;
1663        let policy = ProductPolicy::Sivm(SivmPolicy {
1664            custom_uefi_json: vec![0u8; oversize_body],
1665            ..Default::default()
1666        });
1667        let _ = encode_product_policy_bytes(&policy);
1668    }
1669
1670    #[test]
1671    fn build_region_absent_records_zero_size_in_struct() {
1672        let cfg = empty_config();
1673        let region = build_measured_vtl2_config_region(cfg, None);
1674        assert_eq!(
1675            region.len(),
1676            (PARAVISOR_MEASURED_VTL2_CONFIG_SIZE_PAGES as usize) * (HV_PAGE_SIZE as usize)
1677        );
1678        let (decoded_cfg, _) = ParavisorMeasuredVtl2Config::ref_from_prefix(&region).unwrap();
1679        assert_eq!(decoded_cfg.magic, ParavisorMeasuredVtl2Config::MAGIC);
1680        assert_eq!(decoded_cfg.product_policy_size, 0);
1681        assert!(
1682            region[PRODUCT_POLICY_INLINE_OFFSET..]
1683                .iter()
1684                .all(|&b| b == 0)
1685        );
1686    }
1687
1688    #[test]
1689    fn build_region_present_records_policy_size_in_struct() {
1690        let cfg = empty_config();
1691        let policy = ProductPolicy::Sivm(SivmPolicy {
1692            require_secure_boot: true,
1693            custom_uefi_json: vec![1, 2, 3, 4],
1694            ..Default::default()
1695        });
1696        let bytes = encode_product_policy_bytes(&policy);
1697        let region = build_measured_vtl2_config_region(cfg, Some(&policy));
1698        assert_eq!(
1699            region.len(),
1700            (PARAVISOR_MEASURED_VTL2_CONFIG_SIZE_PAGES as usize) * (HV_PAGE_SIZE as usize)
1701        );
1702        let (decoded_cfg, _) = ParavisorMeasuredVtl2Config::ref_from_prefix(&region).unwrap();
1703        assert_eq!(decoded_cfg.product_policy_size, bytes.len() as u32);
1704        assert_eq!(
1705            &region[PRODUCT_POLICY_INLINE_OFFSET..PRODUCT_POLICY_INLINE_OFFSET + bytes.len()],
1706            bytes.as_slice()
1707        );
1708        let decoded = decode_product_policy(
1709            &region[PRODUCT_POLICY_INLINE_OFFSET..PRODUCT_POLICY_INLINE_OFFSET + bytes.len()],
1710        )
1711        .unwrap();
1712        // Test that the decoded policy matches the original policy
1713        assert_eq!(decoded, policy);
1714    }
1715}