1pub 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
57fn 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 const LOADER_TYPE_UNREGISTERED: u8 = 0xff;
72
73 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 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 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 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
250pub struct AcpiTables {
258 pub rsdp: Vec<u8>,
260 pub tables: Vec<u8>,
262}
263
264const GDT_BASE: u64 = 0x1000;
275pub const ZERO_PAGE_BASE: u64 = 0x2000;
276const CMDLINE_BASE: u64 = 0x3000;
277const CR3_BASE: u64 = 0x4000;
278const LOW_METADATA_END: u64 = CR3_BASE + PAGE_TABLE_MAX_BYTES as u64;
281const ACPI_TABLES_BASE: u64 = LOW_METADATA_END + 0x1000;
284pub const RSDP_BASE: u64 = 0xe0000;
288const SMBIOS_FSEGMENT_BASE: u64 = 0xf0000;
293const KERNEL_BASE: u64 = 0x100000;
295
296#[derive(Debug, Clone, Copy)]
298pub struct SnpBootConfig {
299 pub c_bit: u8,
301}
302
303const SNP_BOOT_PAGE_COUNT: u64 = 5;
304
305fn 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
313const _: () = {
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 AfterKernel,
328 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#[derive(Debug, Default)]
340pub struct KernelInfo {
341 pub gpa: u64,
343 pub size: u64,
345 pub entrypoint: u64,
347}
348
349#[derive(Debug, Default)]
351pub struct InitrdInfo {
352 pub gpa: u64,
354 pub size: u64,
356}
357
358#[derive(Debug, Default)]
360pub struct LoadInfo {
361 pub kernel: KernelInfo,
363 pub initrd: Option<InitrdInfo>,
365 pub dtb: Option<std::ops::Range<u64>>,
367 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 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
468fn 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
483fn 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
524pub 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
587fn 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 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
650fn 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 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 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 import_reg(X86Register::Rip(load_info.kernel.entrypoint))?;
782 import_reg(X86Register::Rsi(ZERO_PAGE_BASE))?;
783
784 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 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
821pub 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 let acpi_tables = build_acpi(LOW_METADATA_END);
849
850 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
869pub 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#[bitfield(u64)]
918struct Aarch64ImageFlags {
919 #[bits(1)]
921 pub big_endian: bool,
922 #[bits(2)]
928 pub page_size: Aarch64ImagePageSize,
929 #[bits(1)]
936 pub any_start_address: bool,
937 #[bits(60)]
939 pub _padding: u64,
940}
941
942#[derive(Debug, IntoBytes, Immutable, KnownLayout, FromBytes)]
945#[repr(C)]
946struct Aarch64ImageHeader {
947 _code0: u32,
949 _code1: u32,
951 text_offset: u64,
953 image_size: u64,
955 flags: u64,
957 _res2: u64,
959 _res3: u64,
961 _res4: u64,
963 magic: [u8; 4],
965 _res5: u32,
967}
968
969const AARCH64_MAGIC_NUMBER: &[u8] = b"ARM\x64";
970
971pub 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 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
1099pub 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 .with_m(false)
1131 .with_c(true)
1133 .with_i(true)
1135 .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 .with_epd0(1)
1153 .with_epd1(1)
1155 .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 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 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; let smbios_len = 0x100; 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 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 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 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 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 #[derive(Default)]
1343 struct RecordingImporter {
1344 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 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 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 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 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 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 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}