Skip to main content

loader/
linux.rs

1// Copyright (c) Microsoft Corporation.
2// Licensed under the MIT License.
3
4//! Linux specific loader definitions and implementation.
5
6use crate::common::ChunkBuf;
7use crate::common::ImportFileRegion;
8use crate::common::ImportFileRegionError;
9use crate::common::ReadSeek;
10use crate::common::import_default_gdt;
11use crate::elf::load_static_elf;
12use crate::importer::Aarch64Register;
13use crate::importer::BootPageAcceptance;
14use crate::importer::GuestArch;
15use crate::importer::ImageLoad;
16use crate::importer::X86Register;
17use aarch64defs::Cpsr64;
18use aarch64defs::IntermPhysAddrSize;
19use aarch64defs::SctlrEl1;
20use aarch64defs::TranslationBaseEl1;
21use aarch64defs::TranslationControlEl1;
22use aarch64defs::TranslationGranule0;
23use aarch64defs::TranslationGranule1;
24use bitfield_struct::bitfield;
25use hvdef::HV_PAGE_SIZE;
26use loader_defs::linux as defs;
27use memory_range::MemoryRange;
28use page_table::IdentityMapSize;
29use page_table::x64::IdentityMapBuilder;
30use page_table::x64::PAGE_TABLE_MAX_BYTES;
31use page_table::x64::PAGE_TABLE_MAX_COUNT;
32use page_table::x64::PageTable;
33use page_table::x64::X64_LARGE_PAGE_SIZE;
34use page_table::x64::align_up_to_page_size;
35use std::ffi::CString;
36use std::io::Read;
37use std::io::Seek;
38use std::io::SeekFrom;
39use std::mem::size_of;
40use thiserror::Error;
41use vm_topology::memory::MemoryLayout;
42use x86defs::cpuid::CpuidFunction;
43use zerocopy::FromBytes;
44use zerocopy::FromZeros;
45use zerocopy::Immutable;
46use zerocopy::IntoBytes;
47use zerocopy::KnownLayout;
48
49struct ZeroPageBuildResult {
50    boot_params: defs::boot_params,
51    additional_pages: Option<MemoryRange>,
52}
53
54/// Construct a zero page from the following parameters.
55fn build_zero_page(
56    mem_layout: &MemoryLayout,
57    acpi_len: usize,
58    smbios_struct_len: usize,
59    additional_page_count: u64,
60    cmdline: &CString,
61    initrd_base: u32,
62    initrd_size: u32,
63    bzimage_header: Option<&defs::setup_header>,
64) -> Result<ZeroPageBuildResult, Error> {
65    // Loader type 0xff = unregistered bootloader, used for both ELF and
66    // bzImage paths since OpenVMM does not have a registered Linux
67    // bootloader ID.
68    const LOADER_TYPE_UNREGISTERED: u8 = 0xff;
69
70    // Start with the bzImage setup header if available, otherwise build
71    // a minimal default header.
72    let mut hdr = match bzimage_header {
73        Some(orig) => *orig,
74        None => defs::setup_header {
75            boot_flag: 0xaa55.into(),
76            header: 0x53726448.into(),
77            kernel_alignment: 0x100000.into(),
78            ..FromZeros::new_zeroed()
79        },
80    };
81
82    // Set bootloader-owned fields regardless of kernel format.
83    hdr.type_of_loader = LOADER_TYPE_UNREGISTERED;
84    hdr.cmd_line_ptr = CMDLINE_BASE.try_into().expect("must fit in u32");
85    hdr.cmdline_size = (cmdline.as_bytes().len() as u64)
86        .try_into()
87        .expect("must fit in u32");
88    hdr.ramdisk_image = initrd_base.into();
89    hdr.ramdisk_size = initrd_size.into();
90
91    let mut p = defs::boot_params {
92        hdr,
93        ..FromZeros::new_zeroed()
94    };
95
96    let mut ram = mem_layout.ram().iter().cloned();
97    let range = ram.next().expect("at least one ram range");
98    assert_eq!(range.range.start(), 0);
99    assert!(range.range.end() >= 0x100000);
100
101    // x86 low-memory layout for direct boot:
102    //   [0, acpi_base)          RAM       boot metadata: GDT, zero page, cmdline,
103    //                                     identity-map page tables
104    //   [acpi_base, acpi_end)   ACPI      RSDT/XSDT and all ACPI tables
105    //   [acpi_end, smbios_end)  RESERVED  SMBIOS structure table
106    //   [smbios_end, additional_end) RESERVED additional requested pages
107    //   [additional_end, 0xe0000)    RAM
108    //   [0xe0000, 0x100000)     RESERVED  legacy BIOS region holding the RSDP at
109    //                                     0xe0000 (found by the kernel's legacy
110    //                                     scan) and the SMBIOS _SM3_ anchor at
111    //                                     0xf0000 (found by the kernel's DMI scan)
112    //   [0x100000, end)         RAM
113    //
114    // The RSDP lives at the fixed 0xe0000 and the tables it points to live in
115    // reclaimable ACPI memory below; the kernel discovers the RSDP via its
116    // legacy scan, so no `acpi_rsdp_addr` (Linux 4.20+) is required. The SMBIOS
117    // structure table sits just above the ACPI tables in its own reserved
118    // region so it can grow well past the 64 KiB F-segment.
119    const ONE_MB: u64 = 0x100000;
120    let aligned_acpi_len = align_up_to_page_size(acpi_len as u64);
121    let acpi_end = ACPI_TABLES_BASE + aligned_acpi_len;
122    let aligned_smbios_len = align_up_to_page_size(smbios_struct_len as u64);
123    let smbios_end = acpi_end + aligned_smbios_len;
124    let additional_size = additional_page_count
125        .checked_mul(HV_PAGE_SIZE)
126        .ok_or(Error::LowTablesTooLarge(u64::MAX, RSDP_BASE))?;
127    let additional_end = smbios_end
128        .checked_add(additional_size)
129        .ok_or(Error::LowTablesTooLarge(u64::MAX, RSDP_BASE))?;
130    if additional_end > RSDP_BASE {
131        return Err(Error::LowTablesTooLarge(additional_end, RSDP_BASE));
132    }
133    let additional_pages =
134        (additional_size != 0).then(|| MemoryRange::new(smbios_end..additional_end));
135
136    // Emit the e820 entries in ascending address order. Zero-length regions
137    // (e.g. the SMBIOS reserved region when no SMBIOS tables are present) are
138    // skipped, and the fixed-size map is bounds-checked so an over-long memory
139    // layout is reported rather than panicking.
140    let e820_cap = p.e820_map.len();
141    let mut n = 0;
142    let mut push = |addr: u64, size: u64, typ: u32| -> Result<(), Error> {
143        if size == 0 {
144            return Ok(());
145        }
146        let entry = p
147            .e820_map
148            .get_mut(n)
149            .ok_or(Error::TooManyMemoryRanges(e820_cap))?;
150        *entry = defs::e820entry {
151            addr: addr.into(),
152            size: size.into(),
153            typ: typ.into(),
154        };
155        n += 1;
156        Ok(())
157    };
158    push(0, ACPI_TABLES_BASE, defs::E820_RAM)?;
159    push(ACPI_TABLES_BASE, aligned_acpi_len, defs::E820_ACPI)?;
160    push(acpi_end, aligned_smbios_len, defs::E820_RESERVED)?;
161    push(smbios_end, additional_size, defs::E820_RESERVED)?;
162    push(additional_end, RSDP_BASE - additional_end, defs::E820_RAM)?;
163    push(RSDP_BASE, ONE_MB - RSDP_BASE, defs::E820_RESERVED)?;
164    push(ONE_MB, range.range.end() - ONE_MB, defs::E820_RAM)?;
165    for range in ram {
166        push(range.range.start(), range.range.len(), defs::E820_RAM)?;
167    }
168    p.e820_entries = n as u8;
169
170    Ok(ZeroPageBuildResult {
171        boot_params: p,
172        additional_pages,
173    })
174}
175
176#[derive(Debug, Error)]
177pub enum FlatLoaderError {
178    #[error("unsupported ELF File byte order")]
179    BigEndianElfOnLittle,
180    #[error("error reading kernel data structure")]
181    BadImageMagic,
182    #[error("big-endian kernel image is not supported")]
183    BigEndianKernelImage,
184    #[error("only images with 4K pages are supported")]
185    FourKibPageImageIsRequired,
186    #[error("the kernel is required to run in the low memory; not supported")]
187    LowMemoryKernel,
188    #[error("failed to read kernel image")]
189    ReadKernelImage,
190    #[error("failed to seek to file offset as pointed by the ELF program header")]
191    SeekKernelStart,
192    #[error("failed to seek to offset of kernel image")]
193    SeekKernelImage,
194}
195
196#[derive(Debug, Error)]
197pub enum Error {
198    #[error("elf loader error")]
199    ElfLoader(#[source] crate::elf::Error),
200    #[error("bzImage parse error")]
201    BzImage(#[source] crate::bzimage::Error),
202    #[error("flat loader error")]
203    FlatLoader(#[source] FlatLoaderError),
204    #[error("Address is not page aligned")]
205    UnalignedAddress(u64),
206    #[error("importer error")]
207    Importer(#[source] anyhow::Error),
208    #[error("failed to import initrd")]
209    ImportInitrd(#[source] ImportFileRegionError),
210    #[error("initrd address after kernel {0:#x} cannot be aligned to 2 MiB")]
211    InitrdAddressTooHigh(u64),
212    #[error("failed to import bzImage payload")]
213    ImportBzImage(#[source] ImportFileRegionError),
214    #[error("PageTableBuilder: {0}")]
215    PageTableBuilder(#[from] page_table::Error),
216    #[error("kernel command line ({0} bytes) exceeds its {1:#x}-byte slot")]
217    CommandLineTooLong(usize, u64),
218    #[error("low-memory tables and boot pages end at {0:#x}, past the reserved region at {1:#x}")]
219    LowTablesTooLarge(u64, u64),
220    #[error("SNP CC blob address {0:#x} does not fit in the Linux boot protocol field")]
221    SnpCcBlobAddressTooHigh(u64),
222    #[error("too many memory ranges to fit in the {0}-entry e820 map")]
223    TooManyMemoryRanges(usize),
224    #[error("acpi tables are empty")]
225    EmptyAcpiTables,
226}
227
228/// ACPI tables to place in guest memory: a one-page RSDP plus the tables it
229/// points to.
230///
231/// Produced by the caller-supplied builder passed to [`load_x86`] /
232/// [`load_config_x86`]. The builder is handed a nominal RSDP address `gpa` and
233/// must return `tables` that are self-consistent for placement at `gpa +
234/// 0x1000`; the loader then re-homes the RSDP to the fixed legacy-scan address.
235pub struct AcpiTables {
236    /// The RSDP. Given a whole page.
237    pub rsdp: Vec<u8>,
238    /// The remaining tables pointed to by the RSDP.
239    pub tables: Vec<u8>,
240}
241
242// The loader owns the entire sub-1 MB x86 direct-boot memory map so that
243// callers supply only table *contents*, never addresses. The resulting e820
244// map (see `build_zero_page`) is:
245//
246//   [0, ACPI_TABLES_BASE)          RAM       GDT, zero page, cmdline, page tables
247//   [ACPI_TABLES_BASE, acpi_end)   ACPI      RSDT/XSDT and all ACPI tables
248//   [acpi_end, smbios_end)         RESERVED  SMBIOS structure table
249//   [smbios_end, RSDP_BASE)        RAM
250//   [RSDP_BASE, 0x100000)          RESERVED  RSDP (0xe0000) + _SM3_ anchor (0xf0000)
251//   [0x100000, end)                RAM       kernel and beyond
252const GDT_BASE: u64 = 0x1000;
253pub const ZERO_PAGE_BASE: u64 = 0x2000;
254const CMDLINE_BASE: u64 = 0x3000;
255const CR3_BASE: u64 = 0x4000;
256/// The identity-map page tables occupy `[CR3_BASE, CR3_BASE + PAGE_TABLE_MAX_BYTES)`;
257/// the boot metadata ends there.
258const LOW_METADATA_END: u64 = CR3_BASE + PAGE_TABLE_MAX_BYTES as u64;
259/// The ACPI builder is handed `LOW_METADATA_END` as a nominal RSDP page, so its
260/// tables live one page above.
261const ACPI_TABLES_BASE: u64 = LOW_METADATA_END + 0x1000;
262/// The RSDP is pinned at the fixed 0xe0000 so the kernel's legacy RSDP scan of
263/// `[0xe0000, 0x100000)` finds it, with no dependency on
264/// `boot_params.acpi_rsdp_addr` (Linux 4.20+).
265pub const RSDP_BASE: u64 = 0xe0000;
266/// The x86 kernel brute-force scans the F-segment `[0xf0000, 0x100000)` for the
267/// SMBIOS `_SM3_` DMI anchor, so the 24-byte entry point is pinned there. Its
268/// 64-bit structure-table pointer lets the (potentially large) structure table
269/// live in the low reserved area instead — see [`smbios_struct_table_base`].
270const SMBIOS_FSEGMENT_BASE: u64 = 0xf0000;
271/// The Linux x86 kernel loads at the conventional 1 MB mark.
272const KERNEL_BASE: u64 = 0x100000;
273
274/// Enables allocation of the SEV-SNP Linux boot protocol pages.
275#[derive(Debug, Clone, Copy)]
276pub struct SnpBootConfig {
277    /// The page-table bit that marks private memory.
278    pub c_bit: u8,
279}
280
281const SNP_BOOT_PAGE_COUNT: u64 = 5;
282
283/// The GPA of the SMBIOS structure table: immediately above the ACPI tables in
284/// the low reserved area. Only the `_SM3_` anchor stays in the F-segment; the
285/// structure table lives here, reachable via the anchor's 64-bit pointer, so it
286/// can grow well past the 64 KiB F-segment.
287fn smbios_struct_table_base(acpi_tables_len: usize) -> u64 {
288    ACPI_TABLES_BASE + align_up_to_page_size(acpi_tables_len as u64)
289}
290
291// Compile-time check that the fixed low-memory layout constants are ordered and
292// non-overlapping. A violation here is a code bug, caught at build time.
293const _: () = {
294    assert!(GDT_BASE < ZERO_PAGE_BASE);
295    assert!(ZERO_PAGE_BASE < CMDLINE_BASE);
296    assert!(CMDLINE_BASE < CR3_BASE);
297    assert!(ACPI_TABLES_BASE < RSDP_BASE);
298    assert!(RSDP_BASE < SMBIOS_FSEGMENT_BASE);
299    assert!(SMBIOS_FSEGMENT_BASE < KERNEL_BASE);
300};
301
302#[derive(Debug, PartialEq, Eq, Clone, Copy)]
303pub enum InitrdAddressType {
304    /// Load the initrd after the kernel at the next 2MB aligned address.
305    AfterKernel,
306    /// Load the initrd at the specified address.
307    Address(u64),
308}
309
310pub struct InitrdConfig<'a> {
311    pub initrd_address: InitrdAddressType,
312    pub initrd: &'a mut dyn ReadSeek,
313    pub size: u64,
314}
315
316/// Information returned about the kernel loaded.
317#[derive(Debug, Default)]
318pub struct KernelInfo {
319    /// The base gpa the kernel was loaded at.
320    pub gpa: u64,
321    /// The size in bytes of the region the kernel was loaded at.
322    pub size: u64,
323    /// The gpa of the entrypoint of the kernel.
324    pub entrypoint: u64,
325}
326
327/// Information returned about the initrd loaded.
328#[derive(Debug, Default)]
329pub struct InitrdInfo {
330    /// The gpa the initrd was loaded at.
331    pub gpa: u64,
332    /// The size in bytes of the initrd loaded. Note that the region imported is aligned up to page size.
333    pub size: u64,
334}
335
336/// Information returned about where certain parts were loaded.
337#[derive(Debug, Default)]
338pub struct LoadInfo {
339    /// The information about the kernel loaded.
340    pub kernel: KernelInfo,
341    /// The information about the initrd loaded.
342    pub initrd: Option<InitrdInfo>,
343    /// The information about the device tree blob loaded.
344    pub dtb: Option<std::ops::Range<u64>>,
345    /// If a bzImage was loaded, the original setup header from the image.
346    /// This must be placed into the zero page so the kernel's startup code
347    /// can read its own configuration.
348    pub bzimage_setup_header: Option<defs::setup_header>,
349}
350
351fn import_snp_boot_pages(
352    importer: &mut impl ImageLoad<X86Register>,
353    range: MemoryRange,
354) -> Result<u64, Error> {
355    assert_eq!(range.len(), SNP_BOOT_PAGE_COUNT * HV_PAGE_SIZE);
356    let secrets_address = range.start();
357    let cpuid_address = range.start() + HV_PAGE_SIZE;
358    let cc_blob_address = range.start() + 2 * HV_PAGE_SIZE;
359    let cc_setup_data_address = range.start() + 3 * HV_PAGE_SIZE;
360    let vmsa_address = range.start() + 4 * HV_PAGE_SIZE;
361
362    importer
363        .import_pages(
364            secrets_address / HV_PAGE_SIZE,
365            1,
366            "linux-snp-secrets",
367            BootPageAcceptance::SecretsPage,
368            &[],
369        )
370        .map_err(Error::Importer)?;
371    let mut cpuid_page = crate::cpuid::HV_PSP_CPUID_PAGE::default();
372    // TODO: Decide whether the paravisor leaf list is correct for enlightened
373    // direct-boot Linux SNP guests. It currently provides the extended-state
374    // subleaves Linux needs.
375    for (index, leaf) in crate::cpuid::SNP_REQUIRED_CPUID_LEAF_LIST_PARAVISOR
376        .iter()
377        .enumerate()
378    {
379        let entry = &mut cpuid_page.cpuid_leaf_info[index];
380        entry.eax_in = leaf.eax;
381        entry.ecx_in = leaf.ecx;
382        if leaf.eax == CpuidFunction::ExtendedStateEnumeration.0 && leaf.ecx <= 1 {
383            entry.xfem_in = 1;
384        }
385    }
386    cpuid_page.count = crate::cpuid::SNP_REQUIRED_CPUID_LEAF_LIST_PARAVISOR.len() as u32;
387    importer
388        .import_pages(
389            cpuid_address / HV_PAGE_SIZE,
390            1,
391            "linux-snp-cpuid",
392            BootPageAcceptance::CpuidPage,
393            cpuid_page.as_bytes(),
394        )
395        .map_err(Error::Importer)?;
396
397    let cc_blob = defs::cc_blob_sev_info {
398        magic: defs::CC_BLOB_SEV_INFO_MAGIC,
399        version: 0,
400        _reserved: 0,
401        secrets_phys: secrets_address,
402        secrets_len: HV_PAGE_SIZE as u32,
403        _rsvd1: 0,
404        cpuid_phys: cpuid_address,
405        cpuid_len: HV_PAGE_SIZE as u32,
406        _rsvd2: 0,
407    };
408    importer
409        .import_pages(
410            cc_blob_address / HV_PAGE_SIZE,
411            1,
412            "linux-snp-cc-blob",
413            BootPageAcceptance::Exclusive,
414            cc_blob.as_bytes(),
415        )
416        .map_err(Error::Importer)?;
417
418    let cc_blob_address = u32::try_from(cc_blob_address)
419        .map_err(|_| Error::SnpCcBlobAddressTooHigh(cc_blob_address))?;
420    let cc_setup_data = defs::cc_setup_data {
421        header: defs::setup_data {
422            next: 0,
423            ty: defs::SETUP_CC_BLOB,
424            len: (size_of::<defs::cc_setup_data>() - size_of::<defs::setup_data>()) as u32,
425        },
426        cc_blob_address,
427        _padding: [0; 3],
428    };
429    importer
430        .import_pages(
431            cc_setup_data_address / HV_PAGE_SIZE,
432            1,
433            "linux-snp-cc-setup-data",
434            BootPageAcceptance::Exclusive,
435            cc_setup_data.as_bytes(),
436        )
437        .map_err(Error::Importer)?;
438
439    importer
440        .set_vp_context_page(vmsa_address / HV_PAGE_SIZE)
441        .map_err(Error::Importer)?;
442
443    Ok(cc_setup_data_address)
444}
445
446/// Check if an address is aligned to a page.
447fn check_address_alignment(address: u64) -> Result<(), Error> {
448    if !address.is_multiple_of(HV_PAGE_SIZE) {
449        Err(Error::UnalignedAddress(address))
450    } else {
451        Ok(())
452    }
453}
454
455fn align_initrd_after_kernel(next_addr: u64) -> Result<u64, Error> {
456    next_addr
457        .checked_next_multiple_of(X64_LARGE_PAGE_SIZE)
458        .ok_or(Error::InitrdAddressTooHigh(next_addr))
459}
460
461/// Import initrd
462fn import_initrd<R: GuestArch>(
463    initrd: Option<InitrdConfig<'_>>,
464    next_addr: u64,
465    importer: &mut dyn ImageLoad<R>,
466) -> Result<Option<InitrdInfo>, Error> {
467    let initrd_info = match initrd {
468        Some(cfg) => {
469            let initrd_address = match cfg.initrd_address {
470                InitrdAddressType::AfterKernel => align_initrd_after_kernel(next_addr)?,
471                InitrdAddressType::Address(addr) => addr,
472            };
473
474            tracing::trace!(initrd_address, "loading initrd");
475            check_address_alignment(initrd_address)?;
476
477            ChunkBuf::new()
478                .import_file_region(
479                    importer,
480                    ImportFileRegion {
481                        file: cfg.initrd,
482                        file_offset: 0,
483                        file_length: cfg.size,
484                        gpa: initrd_address,
485                        memory_length: cfg.size,
486                        acceptance: BootPageAcceptance::Exclusive,
487                        tag: "linux-initrd",
488                    },
489                )
490                .map_err(Error::ImportInitrd)?;
491
492            Some(InitrdInfo {
493                gpa: initrd_address,
494                size: cfg.size,
495            })
496        }
497        None => None,
498    };
499    Ok(initrd_info)
500}
501
502/// Load only a Linux kernel and optional initrd to VTL0.
503/// This does not setup register state or any other config information.
504///
505/// The kernel image may be either an uncompressed ELF (`vmlinux`) or a
506/// compressed bzImage. If a bzImage is detected, the bzImage payload is
507/// loaded directly into guest memory and the kernel's own decompressor
508/// runs at boot time.
509///
510/// # Arguments
511///
512/// * `importer` - The importer to use.
513/// * `kernel_image` - Kernel image (uncompressed ELF or bzImage).
514/// * `kernel_minimum_start_address` - The minimum address the kernel can load at.
515///   It cannot contain an entrypoint or program headers that refer to memory below this address.
516/// * `initrd` - The initrd config, optional.
517pub fn load_kernel_and_initrd_x64<F>(
518    importer: &mut dyn ImageLoad<X86Register>,
519    kernel_image: &mut F,
520    kernel_minimum_start_address: u64,
521    initrd: Option<InitrdConfig<'_>>,
522) -> Result<LoadInfo, Error>
523where
524    F: Read + Seek,
525{
526    tracing::trace!(kernel_minimum_start_address, "loading x86_64 kernel");
527
528    if crate::bzimage::is_bzimage(kernel_image).map_err(Error::BzImage)? {
529        tracing::info!("detected bzImage format, loading via Linux boot protocol");
530        return load_bzimage(importer, kernel_image, kernel_minimum_start_address, initrd);
531    }
532
533    let elf_load_info = load_static_elf(
534        importer,
535        kernel_image,
536        kernel_minimum_start_address,
537        0,
538        false,
539        BootPageAcceptance::Exclusive,
540        "linux-kernel",
541    )
542    .map_err(Error::ElfLoader)?;
543
544    let crate::elf::LoadInfo {
545        minimum_address_used: min_addr,
546        next_available_address: next_addr,
547        entrypoint,
548    } = elf_load_info;
549    tracing::trace!(min_addr, next_addr, entrypoint, "loaded kernel");
550
551    let initrd_info = import_initrd(initrd, next_addr, importer)?;
552
553    Ok(LoadInfo {
554        kernel: KernelInfo {
555            gpa: min_addr,
556            size: next_addr - min_addr,
557            entrypoint,
558        },
559        initrd: initrd_info,
560        dtb: None,
561        bzimage_setup_header: None,
562    })
563}
564
565/// Load a bzImage by placing its payload directly into guest memory at the
566/// load address and following the Linux boot protocol. The kernel's built-in
567/// decompressor handles the rest at boot time.
568fn load_bzimage(
569    importer: &mut dyn ImageLoad<X86Register>,
570    kernel_image: &mut (impl Read + Seek),
571    kernel_start_address: u64,
572    initrd: Option<InitrdConfig<'_>>,
573) -> Result<LoadInfo, Error> {
574    let info = crate::bzimage::parse_bzimage(kernel_image).map_err(Error::BzImage)?;
575
576    check_address_alignment(kernel_start_address)?;
577
578    let payload_offset = (info.setup_sects as u64 + 1) * 512;
579    let payload_len = info.protected_mode_size;
580    let payload_memory_len = align_up_to_page_size(payload_len);
581    let entrypoint = kernel_start_address + info.entry_offset;
582
583    tracing::info!(
584        kernel_start_address = format_args!("{:#x}", kernel_start_address),
585        payload_offset,
586        payload_len,
587        entrypoint = format_args!("{:#x}", entrypoint),
588        "loading bzImage payload into guest memory"
589    );
590
591    ChunkBuf::new()
592        .import_file_region(
593            importer,
594            ImportFileRegion {
595                file: kernel_image,
596                file_offset: payload_offset,
597                file_length: payload_len,
598                gpa: kernel_start_address,
599                memory_length: payload_memory_len,
600                acceptance: BootPageAcceptance::Exclusive,
601                tag: "linux-kernel",
602            },
603        )
604        .map_err(Error::ImportBzImage)?;
605
606    // Place initrd after the kernel's init_size region to avoid being
607    // overwritten during decompression.
608    let next_addr = kernel_start_address + payload_memory_len;
609    let pref_address: u64 = info.setup_header.pref_address.into();
610    let init_end = kernel_start_address
611        .max(pref_address)
612        .saturating_add(info.init_size as u64);
613    let next_addr = next_addr.max(init_end);
614    let initrd_info = import_initrd(initrd, next_addr, importer)?;
615
616    Ok(LoadInfo {
617        kernel: KernelInfo {
618            gpa: kernel_start_address,
619            size: payload_memory_len,
620            entrypoint,
621        },
622        initrd: initrd_info,
623        dtb: None,
624        bzimage_setup_header: Some(info.setup_header),
625    })
626}
627
628/// Import the boot metadata, ACPI/SMBIOS tables, zero page, and initial
629/// registers for a kernel already described by `load_info`.
630///
631/// Internal helper shared by [`load_x86`] and [`load_config_x86`]. All guest
632/// addresses come from the module-level layout constants; callers supply only
633/// the table contents.
634fn import_config(
635    importer: &mut impl ImageLoad<X86Register>,
636    load_info: &LoadInfo,
637    cmdline: &CString,
638    mem_layout: &MemoryLayout,
639    acpi: &AcpiTables,
640    smbios: Option<&crate::smbios::BuiltSmbios>,
641    snp_boot: Option<SnpBootConfig>,
642) -> Result<(), Error> {
643    // Only import the cmdline if it actually contains something.
644    // TODO: This should use the IGVM parameter instead?
645    let raw_cmdline = cmdline.as_bytes_with_nul();
646    if raw_cmdline.len() as u64 > CR3_BASE - CMDLINE_BASE {
647        return Err(Error::CommandLineTooLong(
648            raw_cmdline.len(),
649            CR3_BASE - CMDLINE_BASE,
650        ));
651    }
652    if raw_cmdline.len() > 1 {
653        let cmdline_size_pages = align_up_to_page_size(raw_cmdline.len() as u64) / HV_PAGE_SIZE;
654        importer
655            .import_pages(
656                CMDLINE_BASE / HV_PAGE_SIZE,
657                cmdline_size_pages,
658                "linux-commandline",
659                BootPageAcceptance::Exclusive,
660                raw_cmdline,
661            )
662            .map_err(Error::Importer)?;
663    }
664
665    import_default_gdt(importer, GDT_BASE / HV_PAGE_SIZE).map_err(Error::Importer)?;
666    let mut page_table_work_buffer: Vec<PageTable> =
667        vec![PageTable::new_zeroed(); PAGE_TABLE_MAX_COUNT];
668    let mut page_table: Vec<u8> = vec![0; PAGE_TABLE_MAX_BYTES];
669    let mut page_table_builder = IdentityMapBuilder::new(
670        CR3_BASE,
671        IdentityMapSize::Size4Gb,
672        page_table_work_buffer.as_mut_slice(),
673        page_table.as_mut_slice(),
674    )?;
675    if let Some(snp_boot) = snp_boot {
676        page_table_builder = page_table_builder.with_confidential_bit(snp_boot.c_bit.into());
677    }
678    let page_table = page_table_builder.build();
679    assert!((page_table.len() as u64).is_multiple_of(HV_PAGE_SIZE));
680    importer
681        .import_pages(
682            CR3_BASE / HV_PAGE_SIZE,
683            page_table.len() as u64 / HV_PAGE_SIZE,
684            "linux-pagetables",
685            BootPageAcceptance::Exclusive,
686            page_table,
687        )
688        .map_err(Error::Importer)?;
689
690    if acpi.tables.is_empty() {
691        return Err(Error::EmptyAcpiTables);
692    }
693    let acpi_tables_size_pages = align_up_to_page_size(acpi.tables.len() as u64) / HV_PAGE_SIZE;
694    importer
695        .import_pages(
696            RSDP_BASE / HV_PAGE_SIZE,
697            1,
698            "linux-rsdp",
699            BootPageAcceptance::Exclusive,
700            &acpi.rsdp,
701        )
702        .map_err(Error::Importer)?;
703    importer
704        .import_pages(
705            ACPI_TABLES_BASE / HV_PAGE_SIZE,
706            acpi_tables_size_pages,
707            "linux-acpi-tables",
708            BootPageAcceptance::Exclusive,
709            &acpi.tables,
710        )
711        .map_err(Error::Importer)?;
712
713    let requested_page_count = snp_boot.map_or(0, |_| SNP_BOOT_PAGE_COUNT);
714    let ZeroPageBuildResult {
715        mut boot_params,
716        additional_pages,
717    } = build_zero_page(
718        mem_layout,
719        acpi.tables.len(),
720        smbios.map_or(0, |s| s.structure_table.len()),
721        requested_page_count,
722        cmdline,
723        load_info.initrd.as_ref().map(|info| info.gpa).unwrap_or(0) as u32,
724        load_info.initrd.as_ref().map(|info| info.size).unwrap_or(0) as u32,
725        load_info.bzimage_setup_header.as_ref(),
726    )?;
727    if let Some(allocated_range) = additional_pages {
728        boot_params.hdr.setup_data = import_snp_boot_pages(importer, allocated_range)?.into();
729    }
730    importer
731        .import_pages(
732            ZERO_PAGE_BASE / HV_PAGE_SIZE,
733            1,
734            "linux-zeropage",
735            BootPageAcceptance::Exclusive,
736            boot_params.as_bytes(),
737        )
738        .map_err(Error::Importer)?;
739
740    // Set common X64 registers. Segments already set by default gdt.
741    let mut import_reg = |register| {
742        importer
743            .import_vp_register(register)
744            .map_err(Error::Importer)
745    };
746
747    import_reg(X86Register::Cr0(x86defs::X64_CR0_PG | x86defs::X64_CR0_PE))?;
748    import_reg(X86Register::Cr3(CR3_BASE))?;
749    import_reg(X86Register::Cr4(x86defs::X64_CR4_PAE))?;
750    import_reg(X86Register::Efer(
751        x86defs::X64_EFER_SCE
752            | x86defs::X64_EFER_LME
753            | x86defs::X64_EFER_LMA
754            | x86defs::X64_EFER_NXE,
755    ))?;
756    import_reg(X86Register::Pat(x86defs::X86X_MSR_DEFAULT_PAT))?;
757
758    // Set rip to entry point and rsi to zero page.
759    import_reg(X86Register::Rip(load_info.kernel.entrypoint))?;
760    import_reg(X86Register::Rsi(ZERO_PAGE_BASE))?;
761
762    // No firmware will set MTRR values for the BSP.  Replicate what UEFI does here.
763    // (enable MTRRs, default MTRR is uncached, and set lowest 640KB as WB)
764    import_reg(X86Register::MtrrDefType(0xc00))?;
765    import_reg(X86Register::MtrrFix64k00000(0x0606060606060606))?;
766    import_reg(X86Register::MtrrFix16k80000(0x0606060606060606))?;
767
768    if let Some(smbios) = smbios {
769        // The `_SM3_` entry point (anchor) goes in the F-segment for the
770        // kernel's DMI scan; its 64-bit pointer targets the structure table in
771        // the low reserved area just above the ACPI tables.
772        let anchor_pages = align_up_to_page_size(smbios.entry_point.len() as u64) / HV_PAGE_SIZE;
773        importer
774            .import_pages(
775                SMBIOS_FSEGMENT_BASE / HV_PAGE_SIZE,
776                anchor_pages,
777                "linux-smbios-anchor",
778                BootPageAcceptance::Exclusive,
779                &smbios.entry_point,
780            )
781            .map_err(Error::Importer)?;
782
783        let table_base = smbios_struct_table_base(acpi.tables.len());
784        let table_pages = align_up_to_page_size(smbios.structure_table.len() as u64) / HV_PAGE_SIZE;
785        importer
786            .import_pages(
787                table_base / HV_PAGE_SIZE,
788                table_pages,
789                "linux-smbios-tables",
790                BootPageAcceptance::Exclusive,
791                &smbios.structure_table,
792            )
793            .map_err(Error::Importer)?;
794    }
795
796    Ok(())
797}
798
799/// Place the ACPI tables, SMBIOS tables, boot metadata, zero page, and initial
800/// registers for a Linux kernel that has *already* been loaded into guest
801/// memory (as described by `load_info`).
802///
803/// The loader owns the entire sub-1 MB memory map; callers supply only
804/// contents, never addresses:
805///
806/// * `cmdline` - the kernel command line.
807/// * `mem_layout` - the guest memory layout, used to build the e820 map.
808/// * `build_acpi` - a builder handed the nominal RSDP address; it must return
809///   ACPI tables self-consistent for placement one page above that address.
810///   The loader re-homes the RSDP to the fixed 0xe0000 legacy-scan location.
811/// * `smbios` - an optional SMBIOS identity; when present the loader assembles
812///   the `_SM3_` entry point and structure table into the F-segment.
813/// * `snp_boot` - optionally allocate SEV-SNP Linux boot protocol pages.
814pub fn load_config_x86(
815    importer: &mut impl ImageLoad<X86Register>,
816    load_info: &LoadInfo,
817    cmdline: &CString,
818    mem_layout: &MemoryLayout,
819    build_acpi: impl FnOnce(u64) -> AcpiTables,
820    smbios: Option<crate::smbios::SmbiosTables<'_>>,
821    snp_boot: Option<SnpBootConfig>,
822) -> Result<(), Error> {
823    // The builder lays out a nominal RSDP page at LOW_METADATA_END followed by
824    // the tables it points to; we keep only the tables (placed at
825    // ACPI_TABLES_BASE) and re-home the RSDP to the fixed scan location.
826    let acpi_tables = build_acpi(LOW_METADATA_END);
827
828    // Build the SMBIOS tables (if an identity was supplied) with the structure
829    // table addressed at its low-area home: the `_SM3_` anchor's 64-bit pointer
830    // references it there while the anchor itself lands in the F-segment for the
831    // kernel's DMI scan. See `smbios_struct_table_base` / `import_config`.
832    let smbios = smbios.map(|tables| {
833        crate::smbios::build(&tables, smbios_struct_table_base(acpi_tables.tables.len()))
834    });
835
836    import_config(
837        importer,
838        load_info,
839        cmdline,
840        mem_layout,
841        &acpi_tables,
842        smbios.as_ref(),
843        snp_boot,
844    )
845}
846
847/// Load a Linux kernel into VTL0 and place all of its supporting structures.
848///
849/// Loads the kernel (uncompressed ELF or bzImage) and optional initrd at the
850/// conventional 1 MB address, then delegates to [`load_config_x86`] to place
851/// the ACPI/SMBIOS tables, boot metadata, zero page, and initial registers.
852/// See [`load_config_x86`] for the `build_acpi`/`smbios` contract.
853pub fn load_x86<F>(
854    importer: &mut impl ImageLoad<X86Register>,
855    kernel_image: &mut F,
856    initrd: Option<InitrdConfig<'_>>,
857    cmdline: &CString,
858    mem_layout: &MemoryLayout,
859    build_acpi: impl FnOnce(u64) -> AcpiTables,
860    smbios: Option<crate::smbios::SmbiosTables<'_>>,
861    snp_boot: Option<SnpBootConfig>,
862) -> Result<LoadInfo, Error>
863where
864    F: Read + Seek,
865{
866    let load_info = load_kernel_and_initrd_x64(importer, kernel_image, KERNEL_BASE, initrd)?;
867    load_config_x86(
868        importer, &load_info, cmdline, mem_layout, build_acpi, smbios, snp_boot,
869    )?;
870    Ok(load_info)
871}
872
873open_enum::open_enum! {
874    #[derive(IntoBytes, Immutable, KnownLayout, FromBytes)]
875    pub enum Aarch64ImagePageSize: u64 {
876        UNSPECIFIED = 0,
877        PAGE4_K = 1,
878        PAGE16_K = 2,
879        PAGE64_K = 3,
880    }
881
882}
883
884impl Aarch64ImagePageSize {
885    const fn into_bits(self) -> u64 {
886        self.0
887    }
888
889    const fn from_bits(bits: u64) -> Self {
890        Self(bits)
891    }
892}
893
894/// Arm64 flat kernel `Image` flags.
895#[bitfield(u64)]
896struct Aarch64ImageFlags {
897    /// Bit 0:	Kernel endianness.  1 if BE, 0 if LE.
898    #[bits(1)]
899    pub big_endian: bool,
900    /// Bit 1-2:	Kernel Page size.
901    ///           0 - Unspecified.
902    ///           1 - 4K
903    ///           2 - 16K
904    ///           3 - 64K
905    #[bits(2)]
906    pub page_size: Aarch64ImagePageSize,
907    /// Bit 3:	Kernel physical placement
908    ///           0 - 2MB aligned base should be as close as possible
909    ///               to the base of DRAM, since memory below it is not
910    ///               accessible via the linear mapping
911    ///           1 - 2MB aligned base may be anywhere in physical
912    ///               memory
913    #[bits(1)]
914    pub any_start_address: bool,
915    /// Bits 4-63:	Reserved.
916    #[bits(60)]
917    pub _padding: u64,
918}
919
920// Kernel boot protocol is specified in the Linux kernel
921// Documentation/arm64/booting.txt.
922#[derive(Debug, IntoBytes, Immutable, KnownLayout, FromBytes)]
923#[repr(C)]
924struct Aarch64ImageHeader {
925    /// Executable code
926    _code0: u32,
927    /// Executable code
928    _code1: u32,
929    /// Image load offset, little endian
930    text_offset: u64,
931    /// Effective Image size, little endian
932    image_size: u64,
933    /// kernel flags, little endian
934    flags: u64,
935    /// reserved
936    _res2: u64,
937    /// reserved
938    _res3: u64,
939    /// reserved
940    _res4: u64,
941    /// Magic number, little endian, "ARM\x64"
942    magic: [u8; 4],
943    /// reserved (used for PE COFF offset)
944    _res5: u32,
945}
946
947const AARCH64_MAGIC_NUMBER: &[u8] = b"ARM\x64";
948
949/// Load only an arm64 the flat Linux kernel `Image` and optional initrd.
950/// This does not setup register state or any other config information.
951///
952/// # Arguments
953///
954/// * `importer` - The importer to use.
955/// * `kernel_image` - Uncompressed ELF image for the kernel.
956/// * `kernel_minimum_start_address` - The minimum address the kernel can load at.
957///   It cannot contain an entrypoint or program headers that refer to memory below this address.
958/// * `initrd` - The initrd config, optional.
959/// * `device_tree_blob` - The device tree blob, optional.
960pub fn load_kernel_and_initrd_arm64<F>(
961    importer: &mut dyn ImageLoad<Aarch64Register>,
962    kernel_image: &mut F,
963    kernel_minimum_start_address: u64,
964    initrd: Option<InitrdConfig<'_>>,
965    device_tree_blob: Option<&[u8]>,
966) -> Result<LoadInfo, Error>
967where
968    F: Read + Seek,
969{
970    tracing::trace!(kernel_minimum_start_address, "loading aarch64 kernel");
971
972    assert_eq!(
973        kernel_minimum_start_address & ((1 << 21) - 1),
974        0,
975        "Start offset must be aligned on the 2MiB boundary"
976    );
977
978    kernel_image
979        .seek(SeekFrom::Start(0))
980        .map_err(|_| Error::FlatLoader(FlatLoaderError::SeekKernelStart))?;
981
982    let mut header = Aarch64ImageHeader::new_zeroed();
983    kernel_image
984        .read_exact(header.as_mut_bytes())
985        .map_err(|_| Error::FlatLoader(FlatLoaderError::ReadKernelImage))?;
986
987    tracing::debug!("aarch64 kernel header {header:x?}");
988
989    if header.magic != AARCH64_MAGIC_NUMBER {
990        return Err(Error::FlatLoader(FlatLoaderError::BadImageMagic));
991    }
992
993    let flags = Aarch64ImageFlags::from(header.flags);
994    if flags.big_endian() {
995        return Err(Error::FlatLoader(FlatLoaderError::BigEndianKernelImage));
996    }
997    if flags.page_size() != Aarch64ImagePageSize::PAGE4_K {
998        return Err(Error::FlatLoader(
999            FlatLoaderError::FourKibPageImageIsRequired,
1000        ));
1001    }
1002    if !flags.any_start_address() {
1003        return Err(Error::FlatLoader(FlatLoaderError::LowMemoryKernel));
1004    }
1005
1006    // The `Image` must be placed `text_offset` bytes from a 2MB aligned base
1007    // address anywhere in usable system RAM and called there.
1008
1009    kernel_image
1010        .seek(SeekFrom::Start(0))
1011        .map_err(|_| Error::FlatLoader(FlatLoaderError::SeekKernelStart))?;
1012
1013    let mut image = Vec::new();
1014    kernel_image
1015        .read_to_end(&mut image)
1016        .map_err(|_| Error::FlatLoader(FlatLoaderError::ReadKernelImage))?;
1017
1018    let kernel_load_offset = (kernel_minimum_start_address + header.text_offset) as usize;
1019    let kernel_size = if header.image_size != 0 {
1020        header.image_size
1021    } else {
1022        image.len() as u64
1023    };
1024
1025    let kernel_size = align_up_to_page_size(kernel_size);
1026    importer
1027        .import_pages(
1028            kernel_load_offset as u64 / HV_PAGE_SIZE,
1029            kernel_size / HV_PAGE_SIZE,
1030            "linux-kernel",
1031            BootPageAcceptance::Exclusive,
1032            &image,
1033        )
1034        .map_err(Error::Importer)?;
1035
1036    let next_addr = kernel_load_offset as u64 + kernel_size;
1037
1038    let (next_addr, dtb) = if let Some(device_tree_blob) = device_tree_blob {
1039        let dtb_addr = align_up_to_page_size(next_addr);
1040        tracing::trace!(dtb_addr, "loading device tree blob at {dtb_addr:x?}");
1041
1042        check_address_alignment(dtb_addr)?;
1043        let dtb_size_pages = align_up_to_page_size(device_tree_blob.len() as u64) / HV_PAGE_SIZE;
1044
1045        importer
1046            .import_pages(
1047                dtb_addr / HV_PAGE_SIZE,
1048                dtb_size_pages,
1049                "linux-device-tree",
1050                BootPageAcceptance::Exclusive,
1051                device_tree_blob,
1052            )
1053            .map_err(Error::Importer)?;
1054
1055        (
1056            dtb_addr + device_tree_blob.len() as u64,
1057            Some(dtb_addr..dtb_addr + device_tree_blob.len() as u64),
1058        )
1059    } else {
1060        (next_addr, None)
1061    };
1062
1063    let initrd_info = import_initrd(initrd, next_addr, importer)?;
1064
1065    Ok(LoadInfo {
1066        kernel: KernelInfo {
1067            gpa: kernel_minimum_start_address,
1068            size: kernel_size,
1069            entrypoint: kernel_load_offset as u64,
1070        },
1071        initrd: initrd_info,
1072        dtb,
1073        bzimage_setup_header: None,
1074    })
1075}
1076
1077/// Load the configuration info and registers for the Linux kernel based on the provided LoadInfo.
1078/// Parameters:
1079/// * `importer` - The importer to use.
1080/// * `load_info` - The kernel load info that contains information on where the kernel and initrd are.
1081/// * `vtl` - The target VTL.
1082pub fn set_direct_boot_registers_arm64(
1083    importer: &mut impl ImageLoad<Aarch64Register>,
1084    load_info: &LoadInfo,
1085) -> Result<(), Error> {
1086    let mut import_reg = |register| {
1087        importer
1088            .import_vp_register(register)
1089            .map_err(Error::Importer)
1090    };
1091
1092    import_reg(Aarch64Register::Pc(load_info.kernel.entrypoint))?;
1093    import_reg(Aarch64Register::Cpsr(
1094        Cpsr64::new()
1095            .with_sp(true)
1096            .with_el(1)
1097            .with_f(true)
1098            .with_i(true)
1099            .with_a(true)
1100            .with_d(true)
1101            .into(),
1102    ))?;
1103    import_reg(Aarch64Register::SctlrEl1(
1104        SctlrEl1::new()
1105            // MMU is disabled for EL1&0 stage 1 address translation.
1106            // The family of the `at` instructions and the `PAR_EL1` register are
1107            // useful for debugging MMU issues when it's on.
1108            .with_m(false)
1109            // Stage 1 Cacheability control, for data accesses.
1110            .with_c(true)
1111            // Stage 1 Cacheability control, for code.
1112            .with_i(true)
1113            // Reserved flags, must be set
1114            .with_eos(true)
1115            .with_tscxt(true)
1116            .with_eis(true)
1117            .with_span(true)
1118            .with_n_tlsmd(true)
1119            .with_lsmaoe(true)
1120            .into(),
1121    ))?;
1122    import_reg(Aarch64Register::TcrEl1(
1123        TranslationControlEl1::new()
1124            .with_t0sz(0x11)
1125            .with_irgn0(1)
1126            .with_orgn0(1)
1127            .with_sh0(3)
1128            .with_tg0(TranslationGranule0::TG_4KB)
1129            // Disable TTBR0_EL1 walks (i.e. the lower half).
1130            .with_epd0(1)
1131            // Disable TTBR1_EL1 walks (i.e. the upper half).
1132            .with_epd1(1)
1133            // Due to erratum #822227, need to set a valid TG1 regardless of EPD1.
1134            .with_tg1(TranslationGranule1::TG_4KB)
1135            .with_ips(IntermPhysAddrSize::IPA_48_BITS_256_TB)
1136            .into(),
1137    ))?;
1138    import_reg(Aarch64Register::Ttbr0El1(TranslationBaseEl1::new().into()))?;
1139    import_reg(Aarch64Register::Ttbr1El1(TranslationBaseEl1::new().into()))?;
1140    import_reg(Aarch64Register::VbarEl1(0))?;
1141
1142    if let Some(dtb) = &load_info.dtb {
1143        import_reg(Aarch64Register::X0(dtb.start))?;
1144    }
1145
1146    Ok(())
1147}
1148#[cfg(test)]
1149mod tests {
1150    use super::*;
1151    use crate::importer::IgvmParameterType;
1152    use crate::importer::IsolationConfig;
1153    use crate::importer::ParameterAreaIndex;
1154    use crate::importer::StartupMemoryType;
1155    use std::io::Cursor;
1156    use test_with_tracing::test;
1157    use zerocopy::FromBytes;
1158
1159    const MB: u64 = 0x100000;
1160    const GB: u64 = 0x4000_0000;
1161
1162    /// A guest memory layout with `ram_size` bytes of RAM and a 128 MB MMIO gap
1163    /// below 4 GiB (matching a typical x86_64 config). RAM up to the gap forms
1164    /// the first range `[0, min(ram_size, 4 GiB - 128 MB))`.
1165    fn make_layout(ram_size: u64) -> MemoryLayout {
1166        MemoryLayout::new(
1167            ram_size,
1168            &[MemoryRange::new(4 * GB - 128 * MB..4 * GB)],
1169            &[],
1170            &[],
1171            None,
1172        )
1173        .unwrap()
1174    }
1175
1176    /// Asserts that `map[..entries]` exactly covers `[0, first_ram_end)` with no
1177    /// gaps or overlaps and strictly ascending addresses, and that no entry is
1178    /// empty. The first entry must start at 0 and the last must end precisely at
1179    /// `first_ram_end`.
1180    fn assert_contiguous(p: &defs::boot_params, first_ram_end: u64) {
1181        let entries = p.e820_entries as usize;
1182        assert!(entries > 0);
1183        let mut expected_addr = 0u64;
1184        for i in 0..entries {
1185            let e = &p.e820_map[i];
1186            assert_ne!(u64::from(e.size), 0, "entry {i} is empty");
1187            assert_eq!(u64::from(e.addr), expected_addr, "gap/overlap at entry {i}");
1188            expected_addr = u64::from(e.addr) + u64::from(e.size);
1189        }
1190        assert_eq!(
1191            expected_addr, first_ram_end,
1192            "map ends at {expected_addr:#x}, expected {first_ram_end:#x}"
1193        );
1194    }
1195
1196    #[test]
1197    fn zero_page_layout_with_smbios() {
1198        let acpi_len = 0x1800; // aligns up to 0x2000
1199        let smbios_len = 0x100; // aligns up to 0x1000
1200        let p = build_zero_page(
1201            &make_layout(256 * MB),
1202            acpi_len,
1203            smbios_len,
1204            0,
1205            &CString::new("root=/dev/sda").unwrap(),
1206            0,
1207            0,
1208            None,
1209        )
1210        .unwrap()
1211        .boot_params;
1212
1213        let acpi_end = ACPI_TABLES_BASE + 0x2000;
1214        let smbios_end = acpi_end + 0x1000;
1215        let expected = [
1216            (0, ACPI_TABLES_BASE, defs::E820_RAM),
1217            (ACPI_TABLES_BASE, 0x2000, defs::E820_ACPI),
1218            (acpi_end, 0x1000, defs::E820_RESERVED),
1219            (smbios_end, RSDP_BASE - smbios_end, defs::E820_RAM),
1220            (RSDP_BASE, 0x100000 - RSDP_BASE, defs::E820_RESERVED),
1221            (0x100000, 256 * MB - 0x100000, defs::E820_RAM),
1222        ];
1223        assert_eq!(p.e820_entries as usize, expected.len());
1224        for (i, (addr, size, typ)) in expected.iter().enumerate() {
1225            let e = &p.e820_map[i];
1226            assert_eq!(u64::from(e.addr), *addr, "entry {i} addr");
1227            assert_eq!(u64::from(e.size), *size, "entry {i} size");
1228            assert_eq!(u32::from(e.typ), *typ, "entry {i} type");
1229        }
1230        assert_contiguous(&p, 256 * MB);
1231    }
1232
1233    #[test]
1234    fn zero_page_skips_empty_smbios_region() {
1235        // With no SMBIOS structure table, the reserved SMBIOS region collapses
1236        // to zero length and must not appear as an empty e820 entry.
1237        let p = build_zero_page(
1238            &make_layout(256 * MB),
1239            0x1800,
1240            0,
1241            0,
1242            &CString::new("").unwrap(),
1243            0,
1244            0,
1245            None,
1246        )
1247        .unwrap()
1248        .boot_params;
1249
1250        let acpi_end = ACPI_TABLES_BASE + 0x2000;
1251        let expected = [
1252            (0, ACPI_TABLES_BASE, defs::E820_RAM),
1253            (ACPI_TABLES_BASE, 0x2000, defs::E820_ACPI),
1254            (acpi_end, RSDP_BASE - acpi_end, defs::E820_RAM),
1255            (RSDP_BASE, 0x100000 - RSDP_BASE, defs::E820_RESERVED),
1256            (0x100000, 256 * MB - 0x100000, defs::E820_RAM),
1257        ];
1258        assert_eq!(p.e820_entries as usize, expected.len());
1259        for (i, (addr, size, typ)) in expected.iter().enumerate() {
1260            let e = &p.e820_map[i];
1261            assert_eq!(u64::from(e.addr), *addr, "entry {i} addr");
1262            assert_eq!(u64::from(e.size), *size, "entry {i} size");
1263            assert_eq!(u32::from(e.typ), *typ, "entry {i} type");
1264        }
1265        assert_contiguous(&p, 256 * MB);
1266    }
1267
1268    #[test]
1269    fn zero_page_multiple_ram_ranges() {
1270        // 8 GiB of RAM splits around the 4 GiB MMIO gap into two ranges; the
1271        // second appears after the six fixed low-memory entries.
1272        let p = build_zero_page(
1273            &make_layout(8 * GB),
1274            0x1000,
1275            0x1000,
1276            0,
1277            &CString::new("").unwrap(),
1278            0,
1279            0,
1280            None,
1281        )
1282        .unwrap()
1283        .boot_params;
1284        assert_eq!(p.e820_entries, 7);
1285        let last = &p.e820_map[6];
1286        assert_eq!(u64::from(last.addr), 4 * GB);
1287        assert_eq!(u32::from(last.typ), defs::E820_RAM);
1288        // The six fixed low-memory entries are contiguous from 0; the second
1289        // RAM range sits above the 4 GiB MMIO gap, so contiguity legitimately
1290        // breaks there.
1291        let below_gap = &p.e820_map[5];
1292        assert_eq!(
1293            u64::from(below_gap.addr) + u64::from(below_gap.size),
1294            4 * GB - 128 * MB
1295        );
1296    }
1297
1298    #[test]
1299    fn zero_page_tables_too_large() {
1300        // ACPI tables large enough to run past the RSDP reserved region.
1301        let result = build_zero_page(
1302            &make_layout(256 * MB),
1303            (RSDP_BASE - ACPI_TABLES_BASE) as usize + 0x1000,
1304            0,
1305            0,
1306            &CString::new("").unwrap(),
1307            0,
1308            0,
1309            None,
1310        );
1311        match result {
1312            Err(Error::LowTablesTooLarge(..)) => {}
1313            other => panic!("expected LowTablesTooLarge, got {:?}", other.err()),
1314        }
1315    }
1316
1317    /// An importer that records `import_pages` placements and accepts registers,
1318    /// panicking on any other entry point (none of which the Linux config path
1319    /// exercises).
1320    #[derive(Default)]
1321    struct RecordingImporter {
1322        /// `(debug_tag, page_base, page_count)` for each imported region.
1323        pages: Vec<(String, u64, u64)>,
1324        imports: Vec<ImportRecord>,
1325        vp_context_page: Option<u64>,
1326    }
1327
1328    #[derive(Debug)]
1329    struct ImportRecord {
1330        page_base: u64,
1331        page_count: u64,
1332        tag: String,
1333        acceptance: BootPageAcceptance,
1334        data: Vec<u8>,
1335    }
1336
1337    impl RecordingImporter {
1338        fn page_base(&self, tag: &str) -> Option<u64> {
1339            self.pages
1340                .iter()
1341                .find(|(t, ..)| t == tag)
1342                .map(|(_, base, _)| *base)
1343        }
1344    }
1345
1346    impl ImageLoad<X86Register> for RecordingImporter {
1347        fn isolation_config(&self) -> IsolationConfig {
1348            IsolationConfig {
1349                paravisor_present: false,
1350                isolation_type: crate::importer::IsolationType::None,
1351                shared_gpa_boundary_bits: None,
1352            }
1353        }
1354
1355        fn create_parameter_area(
1356            &mut self,
1357            _page_base: u64,
1358            _page_count: u32,
1359            _debug_tag: &str,
1360        ) -> anyhow::Result<ParameterAreaIndex> {
1361            unimplemented!()
1362        }
1363
1364        fn create_parameter_area_with_data(
1365            &mut self,
1366            _page_base: u64,
1367            _page_count: u32,
1368            _debug_tag: &str,
1369            _initial_data: &[u8],
1370        ) -> anyhow::Result<ParameterAreaIndex> {
1371            unimplemented!()
1372        }
1373
1374        fn import_parameter(
1375            &mut self,
1376            _parameter_area: ParameterAreaIndex,
1377            _byte_offset: u32,
1378            _parameter_type: IgvmParameterType,
1379        ) -> anyhow::Result<()> {
1380            unimplemented!()
1381        }
1382
1383        fn import_pages(
1384            &mut self,
1385            page_base: u64,
1386            page_count: u64,
1387            debug_tag: &'static str,
1388            acceptance: BootPageAcceptance,
1389            data: &[u8],
1390        ) -> anyhow::Result<()> {
1391            self.pages
1392                .push((debug_tag.to_string(), page_base, page_count));
1393            self.imports.push(ImportRecord {
1394                page_base,
1395                page_count,
1396                tag: debug_tag.to_string(),
1397                acceptance,
1398                data: data.to_vec(),
1399            });
1400            Ok(())
1401        }
1402
1403        fn import_vp_register(&mut self, _register: X86Register) -> anyhow::Result<()> {
1404            Ok(())
1405        }
1406
1407        fn verify_startup_memory_available(
1408            &mut self,
1409            _page_base: u64,
1410            _page_count: u64,
1411            _memory_type: StartupMemoryType,
1412        ) -> anyhow::Result<()> {
1413            Ok(())
1414        }
1415
1416        fn set_vp_context_page(&mut self, page_base: u64) -> anyhow::Result<()> {
1417            self.vp_context_page = Some(page_base);
1418            Ok(())
1419        }
1420
1421        fn relocation_region(
1422            &mut self,
1423            _gpa: u64,
1424            _size_bytes: u64,
1425            _relocation_alignment: u64,
1426            _minimum_relocation_gpa: u64,
1427            _maximum_relocation_gpa: u64,
1428            _apply_rip_offset: bool,
1429            _apply_gdtr_offset: bool,
1430            _vp_index: u16,
1431        ) -> anyhow::Result<()> {
1432            unimplemented!()
1433        }
1434
1435        fn page_table_relocation(
1436            &mut self,
1437            _page_table_gpa: u64,
1438            _size_pages: u64,
1439            _used_pages: u64,
1440            _vp_index: u16,
1441        ) -> anyhow::Result<()> {
1442            unimplemented!()
1443        }
1444
1445        fn set_imported_regions_config_page(&mut self, _page_base: u64) {
1446            unimplemented!()
1447        }
1448    }
1449
1450    fn test_load_info() -> LoadInfo {
1451        LoadInfo {
1452            kernel: KernelInfo {
1453                gpa: KERNEL_BASE,
1454                size: 0x1000,
1455                entrypoint: KERNEL_BASE,
1456            },
1457            initrd: None,
1458            dtb: None,
1459            bzimage_setup_header: None,
1460        }
1461    }
1462
1463    #[test]
1464    fn import_config_places_tables_at_fixed_addresses() {
1465        let acpi = AcpiTables {
1466            rsdp: vec![0u8; 0x1000],
1467            tables: vec![0u8; 0x1800],
1468        };
1469        let smbios = crate::smbios::BuiltSmbios {
1470            entry_point: vec![0u8; crate::smbios::ENTRY_POINT_SIZE],
1471            structure_table: vec![0u8; 0x100],
1472        };
1473        let mut importer = RecordingImporter::default();
1474        import_config(
1475            &mut importer,
1476            &test_load_info(),
1477            &CString::new("console=ttyS0").unwrap(),
1478            &make_layout(256 * MB),
1479            &acpi,
1480            Some(&smbios),
1481            None,
1482        )
1483        .unwrap();
1484
1485        // The RSDP is re-homed to the fixed legacy-scan address, while the
1486        // tables it points to stay at the loader's chosen base.
1487        assert_eq!(
1488            importer.page_base("linux-rsdp"),
1489            Some(RSDP_BASE / HV_PAGE_SIZE)
1490        );
1491        assert_eq!(
1492            importer.page_base("linux-acpi-tables"),
1493            Some(ACPI_TABLES_BASE / HV_PAGE_SIZE)
1494        );
1495        // The SMBIOS anchor lands in the F-segment; the structure table sits
1496        // just above the ACPI tables.
1497        assert_eq!(
1498            importer.page_base("linux-smbios-anchor"),
1499            Some(SMBIOS_FSEGMENT_BASE / HV_PAGE_SIZE)
1500        );
1501        assert_eq!(
1502            importer.page_base("linux-smbios-tables"),
1503            Some(smbios_struct_table_base(acpi.tables.len()) / HV_PAGE_SIZE)
1504        );
1505        // Boot metadata at its fixed low-memory homes.
1506        assert_eq!(
1507            importer.page_base("linux-zeropage"),
1508            Some(ZERO_PAGE_BASE / HV_PAGE_SIZE)
1509        );
1510        assert_eq!(
1511            importer.page_base("linux-commandline"),
1512            Some(CMDLINE_BASE / HV_PAGE_SIZE)
1513        );
1514        assert_eq!(
1515            importer.page_base("linux-pagetables"),
1516            Some(CR3_BASE / HV_PAGE_SIZE)
1517        );
1518    }
1519
1520    #[test]
1521    fn import_config_rejects_oversized_command_line() {
1522        let acpi = AcpiTables {
1523            rsdp: vec![0u8; 0x1000],
1524            tables: vec![0u8; 0x1000],
1525        };
1526        // One byte too long once the NUL terminator is added.
1527        let cmdline = CString::new(vec![b'a'; (CR3_BASE - CMDLINE_BASE) as usize]).unwrap();
1528        let mut importer = RecordingImporter::default();
1529        let err = import_config(
1530            &mut importer,
1531            &test_load_info(),
1532            &cmdline,
1533            &make_layout(256 * MB),
1534            &acpi,
1535            None,
1536            None,
1537        )
1538        .unwrap_err();
1539        assert!(matches!(err, Error::CommandLineTooLong(..)), "got {err:?}");
1540        assert!(importer.pages.is_empty(), "importer used before the check");
1541    }
1542
1543    #[test]
1544    fn import_config_sets_snp_c_bit_in_page_tables() {
1545        const C_BIT: u8 = 51;
1546        let acpi = AcpiTables {
1547            rsdp: vec![0u8; 0x1000],
1548            tables: vec![0u8; 0x1000],
1549        };
1550        let mut importer = RecordingImporter::default();
1551        import_config(
1552            &mut importer,
1553            &test_load_info(),
1554            &CString::new("").unwrap(),
1555            &make_layout(256 * MB),
1556            &acpi,
1557            None,
1558            Some(SnpBootConfig { c_bit: C_BIT }),
1559        )
1560        .unwrap();
1561
1562        let page_tables = importer
1563            .imports
1564            .iter()
1565            .find(|import| import.tag == "linux-pagetables")
1566            .unwrap();
1567        for entry in page_tables.data.chunks_exact(8).map(|entry| {
1568            u64::from_ne_bytes(entry.try_into().expect("page table entry is eight bytes"))
1569        }) {
1570            if entry & 1 != 0 {
1571                assert_ne!(entry & (1 << C_BIT), 0);
1572            }
1573        }
1574    }
1575
1576    #[test]
1577    fn import_config_rejects_empty_acpi_tables() {
1578        // Empty tables would import zero pages; the loader must reject them
1579        // rather than feed page_count == 0 into the importer.
1580        let acpi = AcpiTables {
1581            rsdp: vec![0u8; 0x1000],
1582            tables: Vec::new(),
1583        };
1584        let mut importer = RecordingImporter::default();
1585        let err = import_config(
1586            &mut importer,
1587            &test_load_info(),
1588            &CString::new("").unwrap(),
1589            &make_layout(256 * MB),
1590            &acpi,
1591            None,
1592            None,
1593        )
1594        .unwrap_err();
1595        assert!(matches!(err, Error::EmptyAcpiTables), "got {err:?}");
1596    }
1597
1598    #[test]
1599    fn imports_snp_boot_pages_with_linux_cc_blob() {
1600        let acpi_len = 0x1800;
1601        let smbios_len = 0x100;
1602        let ZeroPageBuildResult {
1603            boot_params,
1604            additional_pages,
1605        } = build_zero_page(
1606            &make_layout(256 * MB),
1607            acpi_len,
1608            smbios_len,
1609            SNP_BOOT_PAGE_COUNT,
1610            &CString::new("").unwrap(),
1611            0,
1612            0,
1613            None,
1614        )
1615        .unwrap();
1616        let allocated_range = additional_pages.unwrap();
1617        let mut importer = RecordingImporter::default();
1618
1619        let cc_setup_data_address = import_snp_boot_pages(&mut importer, allocated_range).unwrap();
1620
1621        assert_eq!(importer.imports.len(), 4);
1622        assert_eq!(
1623            allocated_range.start(),
1624            ACPI_TABLES_BASE
1625                + align_up_to_page_size(acpi_len as u64)
1626                + align_up_to_page_size(smbios_len as u64)
1627        );
1628        assert_eq!(
1629            importer.imports[0].page_base,
1630            allocated_range.start() / HV_PAGE_SIZE
1631        );
1632        assert_eq!(importer.imports[0].page_count, 1);
1633        assert_eq!(importer.imports[0].tag, "linux-snp-secrets");
1634        assert_eq!(
1635            importer.imports[0].acceptance,
1636            BootPageAcceptance::SecretsPage
1637        );
1638        assert_eq!(
1639            importer.imports[1].page_base,
1640            allocated_range.start() / HV_PAGE_SIZE + 1
1641        );
1642        assert_eq!(
1643            importer.imports[1].acceptance,
1644            BootPageAcceptance::CpuidPage
1645        );
1646        let cpuid_page =
1647            crate::cpuid::HV_PSP_CPUID_PAGE::read_from_bytes(&importer.imports[1].data).unwrap();
1648        assert_eq!(
1649            cpuid_page.count as usize,
1650            crate::cpuid::SNP_REQUIRED_CPUID_LEAF_LIST_PARAVISOR.len()
1651        );
1652        for (entry, leaf) in cpuid_page
1653            .cpuid_leaf_info
1654            .iter()
1655            .zip(crate::cpuid::SNP_REQUIRED_CPUID_LEAF_LIST_PARAVISOR)
1656        {
1657            assert_eq!(entry.eax_in, leaf.eax);
1658            assert_eq!(entry.ecx_in, leaf.ecx);
1659            assert_eq!(
1660                entry.xfem_in,
1661                u64::from(leaf.eax == CpuidFunction::ExtendedStateEnumeration.0 && leaf.ecx <= 1)
1662            );
1663            assert_eq!(entry.xss_in, 0);
1664        }
1665
1666        let cc_blob = defs::cc_blob_sev_info::read_from_bytes(&importer.imports[2].data).unwrap();
1667        assert_eq!(cc_blob.magic, defs::CC_BLOB_SEV_INFO_MAGIC);
1668        assert_eq!(cc_blob.version, 0);
1669        assert_eq!(cc_blob.secrets_phys, allocated_range.start());
1670        assert_eq!(cc_blob.secrets_len, HV_PAGE_SIZE as u32);
1671        assert_eq!(cc_blob.cpuid_phys, allocated_range.start() + HV_PAGE_SIZE);
1672        assert_eq!(cc_blob.cpuid_len, HV_PAGE_SIZE as u32);
1673
1674        let cc_setup_data =
1675            defs::cc_setup_data::read_from_bytes(&importer.imports[3].data).unwrap();
1676        assert_eq!(cc_setup_data.header.next, 0);
1677        assert_eq!(cc_setup_data.header.ty, defs::SETUP_CC_BLOB);
1678        assert_eq!(
1679            cc_setup_data.header.len,
1680            (size_of::<defs::cc_setup_data>() - size_of::<defs::setup_data>()) as u32
1681        );
1682        assert_eq!(
1683            cc_setup_data.cc_blob_address,
1684            u32::try_from(allocated_range.start() + 2 * HV_PAGE_SIZE).unwrap()
1685        );
1686        assert_eq!(
1687            importer.vp_context_page,
1688            Some(allocated_range.start() / HV_PAGE_SIZE + 4)
1689        );
1690        assert_eq!(
1691            allocated_range.end(),
1692            cc_setup_data_address + 2 * HV_PAGE_SIZE,
1693        );
1694        assert!(allocated_range.end() <= RSDP_BASE);
1695
1696        let smbios_reserved = &boot_params.e820_map[2];
1697        let acpi_end = ACPI_TABLES_BASE + align_up_to_page_size(acpi_len as u64);
1698        assert_eq!(u64::from(smbios_reserved.addr), acpi_end);
1699        assert_eq!(
1700            u64::from(smbios_reserved.size),
1701            align_up_to_page_size(smbios_len as u64)
1702        );
1703        let additional_reserved = &boot_params.e820_map[3];
1704        assert_eq!(u64::from(additional_reserved.addr), allocated_range.start());
1705        assert_eq!(u64::from(additional_reserved.size), allocated_range.len());
1706
1707        assert!(matches!(
1708            build_zero_page(
1709                &make_layout(256 * MB),
1710                (RSDP_BASE - ACPI_TABLES_BASE) as usize,
1711                0,
1712                SNP_BOOT_PAGE_COUNT,
1713                &CString::new("").unwrap(),
1714                0,
1715                0,
1716                None,
1717            ),
1718            Err(Error::LowTablesTooLarge(..))
1719        ));
1720    }
1721
1722    /// Builds a minimal little-endian x86-64 ELF executable with one `PT_LOAD`
1723    /// program header, and no file contents, for each `(paddr, memsz)` in
1724    /// `load_segments`.
1725    fn test_elf(entrypoint: u64, load_segments: &[(u64, u64)]) -> Cursor<Vec<u8>> {
1726        use object::LittleEndian;
1727        use object::U16;
1728        use object::U32;
1729        use object::U64;
1730        use object::elf;
1731
1732        let header_size = size_of::<elf::FileHeader64<LittleEndian>>();
1733        let header = elf::FileHeader64::<LittleEndian> {
1734            e_ident: elf::Ident {
1735                magic: elf::ELFMAG,
1736                class: elf::ELFCLASS64,
1737                data: elf::ELFDATA2LSB,
1738                version: elf::EV_CURRENT,
1739                os_abi: elf::ELFOSABI_NONE,
1740                abi_version: 0,
1741                padding: [0; 7],
1742            },
1743            e_type: U16::new(LittleEndian, elf::ET_EXEC),
1744            e_machine: U16::new(LittleEndian, elf::EM_X86_64),
1745            e_version: U32::new(LittleEndian, elf::EV_CURRENT.into()),
1746            e_entry: U64::new(LittleEndian, entrypoint),
1747            e_phoff: U64::new(LittleEndian, header_size as u64),
1748            e_shoff: U64::new(LittleEndian, 0),
1749            e_flags: U32::new(LittleEndian, 0),
1750            e_ehsize: U16::new(LittleEndian, header_size as u16),
1751            e_phentsize: U16::new(
1752                LittleEndian,
1753                size_of::<elf::ProgramHeader64<LittleEndian>>() as u16,
1754            ),
1755            e_phnum: U16::new(LittleEndian, load_segments.len().try_into().unwrap()),
1756            e_shentsize: U16::new(
1757                LittleEndian,
1758                size_of::<elf::SectionHeader64<LittleEndian>>() as u16,
1759            ),
1760            e_shnum: U16::new(LittleEndian, 0),
1761            e_shstrndx: U16::new(LittleEndian, 0),
1762        };
1763        let mut image = object::pod::bytes_of(&header).to_vec();
1764        for &(paddr, memsz) in load_segments {
1765            let program_header = elf::ProgramHeader64::<LittleEndian> {
1766                p_type: U32::new(LittleEndian, elf::PT_LOAD),
1767                p_flags: U32::new(LittleEndian, elf::PF_R | elf::PF_X),
1768                p_offset: U64::new(LittleEndian, 0),
1769                p_vaddr: U64::new(LittleEndian, paddr),
1770                p_paddr: U64::new(LittleEndian, paddr),
1771                p_filesz: U64::new(LittleEndian, 0),
1772                p_memsz: U64::new(LittleEndian, memsz),
1773                p_align: U64::new(LittleEndian, HV_PAGE_SIZE),
1774            };
1775            image.extend_from_slice(object::pod::bytes_of(&program_header));
1776        }
1777        Cursor::new(image)
1778    }
1779
1780    #[test]
1781    fn elf_without_load_segments_is_rejected() {
1782        let mut importer = RecordingImporter::default();
1783        let mut kernel_image = test_elf(KERNEL_BASE, &[]);
1784        let error = load_kernel_and_initrd_x64(&mut importer, &mut kernel_image, KERNEL_BASE, None)
1785            .unwrap_err();
1786        assert!(matches!(
1787            error,
1788            Error::ElfLoader(crate::elf::Error::NoLoadableSegments)
1789        ));
1790        assert!(importer.imports.is_empty());
1791    }
1792
1793    #[test]
1794    fn elf_with_only_empty_load_segment_is_rejected() {
1795        let mut importer = RecordingImporter::default();
1796        let mut kernel_image = test_elf(KERNEL_BASE + 1, &[(KERNEL_BASE + 1, 0)]);
1797        let error = load_kernel_and_initrd_x64(&mut importer, &mut kernel_image, KERNEL_BASE, None)
1798            .unwrap_err();
1799        assert!(matches!(
1800            error,
1801            Error::ElfLoader(crate::elf::Error::NoLoadableSegments)
1802        ));
1803        assert!(importer.imports.is_empty());
1804    }
1805
1806    #[test]
1807    fn elf_entrypoint_outside_load_segments_is_rejected() {
1808        let mut importer = RecordingImporter::default();
1809        let mut kernel_image = test_elf(
1810            KERNEL_BASE + HV_PAGE_SIZE,
1811            &[
1812                (KERNEL_BASE, HV_PAGE_SIZE),
1813                (KERNEL_BASE + 2 * HV_PAGE_SIZE, HV_PAGE_SIZE),
1814            ],
1815        );
1816        let error = load_kernel_and_initrd_x64(&mut importer, &mut kernel_image, KERNEL_BASE, None)
1817            .unwrap_err();
1818        assert!(matches!(
1819            error,
1820            Error::ElfLoader(crate::elf::Error::EntryPointNotInLoadableSegment { .. })
1821        ));
1822        assert!(importer.imports.is_empty());
1823    }
1824
1825    #[test]
1826    fn initrd_after_kernel_alignment_overflow_is_rejected() {
1827        let mut importer = RecordingImporter::default();
1828        let mut initrd = Cursor::new([0]);
1829        let error = import_initrd(
1830            Some(InitrdConfig {
1831                initrd_address: InitrdAddressType::AfterKernel,
1832                initrd: &mut initrd,
1833                size: 1,
1834            }),
1835            u64::MAX - HV_PAGE_SIZE + 1,
1836            &mut importer,
1837        )
1838        .unwrap_err();
1839        assert!(matches!(error, Error::InitrdAddressTooHigh(_)));
1840        assert!(importer.imports.is_empty());
1841    }
1842}