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