1use crate::common::ChunkBuf;
7use crate::common::ImportFileRegion;
8use crate::common::ImportFileRegionError;
9use crate::common::ReadSeek;
10use crate::common::import_default_gdt;
11use crate::elf::load_static_elf;
12use crate::importer::Aarch64Register;
13use crate::importer::BootPageAcceptance;
14use crate::importer::GuestArch;
15use crate::importer::ImageLoad;
16use crate::importer::X86Register;
17use aarch64defs::Cpsr64;
18use aarch64defs::IntermPhysAddrSize;
19use aarch64defs::SctlrEl1;
20use aarch64defs::TranslationBaseEl1;
21use aarch64defs::TranslationControlEl1;
22use aarch64defs::TranslationGranule0;
23use aarch64defs::TranslationGranule1;
24use bitfield_struct::bitfield;
25use hvdef::HV_PAGE_SIZE;
26use loader_defs::linux as defs;
27use memory_range::MemoryRange;
28use page_table::IdentityMapSize;
29use page_table::x64::IdentityMapBuilder;
30use page_table::x64::PAGE_TABLE_MAX_BYTES;
31use page_table::x64::PAGE_TABLE_MAX_COUNT;
32use page_table::x64::PageTable;
33use page_table::x64::X64_LARGE_PAGE_SIZE;
34use page_table::x64::align_up_to_page_size;
35use std::ffi::CString;
36use std::io::Read;
37use std::io::Seek;
38use std::io::SeekFrom;
39use std::mem::size_of;
40use thiserror::Error;
41use vm_topology::memory::MemoryLayout;
42use x86defs::cpuid::CpuidFunction;
43use zerocopy::FromBytes;
44use zerocopy::FromZeros;
45use zerocopy::Immutable;
46use zerocopy::IntoBytes;
47use zerocopy::KnownLayout;
48
49struct ZeroPageBuildResult {
50 boot_params: defs::boot_params,
51 additional_pages: Option<MemoryRange>,
52}
53
54fn build_zero_page(
56 mem_layout: &MemoryLayout,
57 acpi_len: usize,
58 smbios_struct_len: usize,
59 additional_page_count: u64,
60 cmdline: &CString,
61 initrd_base: u32,
62 initrd_size: u32,
63 bzimage_header: Option<&defs::setup_header>,
64) -> Result<ZeroPageBuildResult, Error> {
65 const LOADER_TYPE_UNREGISTERED: u8 = 0xff;
69
70 let mut hdr = match bzimage_header {
73 Some(orig) => *orig,
74 None => defs::setup_header {
75 boot_flag: 0xaa55.into(),
76 header: 0x53726448.into(),
77 kernel_alignment: 0x100000.into(),
78 ..FromZeros::new_zeroed()
79 },
80 };
81
82 hdr.type_of_loader = LOADER_TYPE_UNREGISTERED;
84 hdr.cmd_line_ptr = CMDLINE_BASE.try_into().expect("must fit in u32");
85 hdr.cmdline_size = (cmdline.as_bytes().len() as u64)
86 .try_into()
87 .expect("must fit in u32");
88 hdr.ramdisk_image = initrd_base.into();
89 hdr.ramdisk_size = initrd_size.into();
90
91 let mut p = defs::boot_params {
92 hdr,
93 ..FromZeros::new_zeroed()
94 };
95
96 let mut ram = mem_layout.ram().iter().cloned();
97 let range = ram.next().expect("at least one ram range");
98 assert_eq!(range.range.start(), 0);
99 assert!(range.range.end() >= 0x100000);
100
101 const ONE_MB: u64 = 0x100000;
120 let aligned_acpi_len = align_up_to_page_size(acpi_len as u64);
121 let acpi_end = ACPI_TABLES_BASE + aligned_acpi_len;
122 let aligned_smbios_len = align_up_to_page_size(smbios_struct_len as u64);
123 let smbios_end = acpi_end + aligned_smbios_len;
124 let additional_size = additional_page_count
125 .checked_mul(HV_PAGE_SIZE)
126 .ok_or(Error::LowTablesTooLarge(u64::MAX, RSDP_BASE))?;
127 let additional_end = smbios_end
128 .checked_add(additional_size)
129 .ok_or(Error::LowTablesTooLarge(u64::MAX, RSDP_BASE))?;
130 if additional_end > RSDP_BASE {
131 return Err(Error::LowTablesTooLarge(additional_end, RSDP_BASE));
132 }
133 let additional_pages =
134 (additional_size != 0).then(|| MemoryRange::new(smbios_end..additional_end));
135
136 let e820_cap = p.e820_map.len();
141 let mut n = 0;
142 let mut push = |addr: u64, size: u64, typ: u32| -> Result<(), Error> {
143 if size == 0 {
144 return Ok(());
145 }
146 let entry = p
147 .e820_map
148 .get_mut(n)
149 .ok_or(Error::TooManyMemoryRanges(e820_cap))?;
150 *entry = defs::e820entry {
151 addr: addr.into(),
152 size: size.into(),
153 typ: typ.into(),
154 };
155 n += 1;
156 Ok(())
157 };
158 push(0, ACPI_TABLES_BASE, defs::E820_RAM)?;
159 push(ACPI_TABLES_BASE, aligned_acpi_len, defs::E820_ACPI)?;
160 push(acpi_end, aligned_smbios_len, defs::E820_RESERVED)?;
161 push(smbios_end, additional_size, defs::E820_RESERVED)?;
162 push(additional_end, RSDP_BASE - additional_end, defs::E820_RAM)?;
163 push(RSDP_BASE, ONE_MB - RSDP_BASE, defs::E820_RESERVED)?;
164 push(ONE_MB, range.range.end() - ONE_MB, defs::E820_RAM)?;
165 for range in ram {
166 push(range.range.start(), range.range.len(), defs::E820_RAM)?;
167 }
168 p.e820_entries = n as u8;
169
170 Ok(ZeroPageBuildResult {
171 boot_params: p,
172 additional_pages,
173 })
174}
175
176#[derive(Debug, Error)]
177pub enum FlatLoaderError {
178 #[error("unsupported ELF File byte order")]
179 BigEndianElfOnLittle,
180 #[error("error reading kernel data structure")]
181 BadImageMagic,
182 #[error("big-endian kernel image is not supported")]
183 BigEndianKernelImage,
184 #[error("only images with 4K pages are supported")]
185 FourKibPageImageIsRequired,
186 #[error("the kernel is required to run in the low memory; not supported")]
187 LowMemoryKernel,
188 #[error("failed to read kernel image")]
189 ReadKernelImage,
190 #[error("failed to seek to file offset as pointed by the ELF program header")]
191 SeekKernelStart,
192 #[error("failed to seek to offset of kernel image")]
193 SeekKernelImage,
194}
195
196#[derive(Debug, Error)]
197pub enum Error {
198 #[error("elf loader error")]
199 ElfLoader(#[source] crate::elf::Error),
200 #[error("bzImage parse error")]
201 BzImage(#[source] crate::bzimage::Error),
202 #[error("flat loader error")]
203 FlatLoader(#[source] FlatLoaderError),
204 #[error("Address is not page aligned")]
205 UnalignedAddress(u64),
206 #[error("importer error")]
207 Importer(#[source] anyhow::Error),
208 #[error("failed to import initrd")]
209 ImportInitrd(#[source] ImportFileRegionError),
210 #[error("initrd address after kernel {0:#x} cannot be aligned to 2 MiB")]
211 InitrdAddressTooHigh(u64),
212 #[error("failed to import bzImage payload")]
213 ImportBzImage(#[source] ImportFileRegionError),
214 #[error("PageTableBuilder: {0}")]
215 PageTableBuilder(#[from] page_table::Error),
216 #[error("kernel command line ({0} bytes) exceeds its {1:#x}-byte slot")]
217 CommandLineTooLong(usize, u64),
218 #[error("low-memory tables and boot pages end at {0:#x}, past the reserved region at {1:#x}")]
219 LowTablesTooLarge(u64, u64),
220 #[error("SNP CC blob address {0:#x} does not fit in the Linux boot protocol field")]
221 SnpCcBlobAddressTooHigh(u64),
222 #[error("too many memory ranges to fit in the {0}-entry e820 map")]
223 TooManyMemoryRanges(usize),
224 #[error("acpi tables are empty")]
225 EmptyAcpiTables,
226}
227
228pub struct AcpiTables {
236 pub rsdp: Vec<u8>,
238 pub tables: Vec<u8>,
240}
241
242const GDT_BASE: u64 = 0x1000;
253pub const ZERO_PAGE_BASE: u64 = 0x2000;
254const CMDLINE_BASE: u64 = 0x3000;
255const CR3_BASE: u64 = 0x4000;
256const LOW_METADATA_END: u64 = CR3_BASE + PAGE_TABLE_MAX_BYTES as u64;
259const ACPI_TABLES_BASE: u64 = LOW_METADATA_END + 0x1000;
262pub const RSDP_BASE: u64 = 0xe0000;
266const SMBIOS_FSEGMENT_BASE: u64 = 0xf0000;
271const KERNEL_BASE: u64 = 0x100000;
273
274#[derive(Debug, Clone, Copy)]
276pub struct SnpBootConfig {
277 pub c_bit: u8,
279}
280
281const SNP_BOOT_PAGE_COUNT: u64 = 5;
282
283fn smbios_struct_table_base(acpi_tables_len: usize) -> u64 {
288 ACPI_TABLES_BASE + align_up_to_page_size(acpi_tables_len as u64)
289}
290
291const _: () = {
294 assert!(GDT_BASE < ZERO_PAGE_BASE);
295 assert!(ZERO_PAGE_BASE < CMDLINE_BASE);
296 assert!(CMDLINE_BASE < CR3_BASE);
297 assert!(ACPI_TABLES_BASE < RSDP_BASE);
298 assert!(RSDP_BASE < SMBIOS_FSEGMENT_BASE);
299 assert!(SMBIOS_FSEGMENT_BASE < KERNEL_BASE);
300};
301
302#[derive(Debug, PartialEq, Eq, Clone, Copy)]
303pub enum InitrdAddressType {
304 AfterKernel,
306 Address(u64),
308}
309
310pub struct InitrdConfig<'a> {
311 pub initrd_address: InitrdAddressType,
312 pub initrd: &'a mut dyn ReadSeek,
313 pub size: u64,
314}
315
316#[derive(Debug, Default)]
318pub struct KernelInfo {
319 pub gpa: u64,
321 pub size: u64,
323 pub entrypoint: u64,
325}
326
327#[derive(Debug, Default)]
329pub struct InitrdInfo {
330 pub gpa: u64,
332 pub size: u64,
334}
335
336#[derive(Debug, Default)]
338pub struct LoadInfo {
339 pub kernel: KernelInfo,
341 pub initrd: Option<InitrdInfo>,
343 pub dtb: Option<std::ops::Range<u64>>,
345 pub bzimage_setup_header: Option<defs::setup_header>,
349}
350
351fn import_snp_boot_pages(
352 importer: &mut impl ImageLoad<X86Register>,
353 range: MemoryRange,
354) -> Result<u64, Error> {
355 assert_eq!(range.len(), SNP_BOOT_PAGE_COUNT * HV_PAGE_SIZE);
356 let secrets_address = range.start();
357 let cpuid_address = range.start() + HV_PAGE_SIZE;
358 let cc_blob_address = range.start() + 2 * HV_PAGE_SIZE;
359 let cc_setup_data_address = range.start() + 3 * HV_PAGE_SIZE;
360 let vmsa_address = range.start() + 4 * HV_PAGE_SIZE;
361
362 importer
363 .import_pages(
364 secrets_address / HV_PAGE_SIZE,
365 1,
366 "linux-snp-secrets",
367 BootPageAcceptance::SecretsPage,
368 &[],
369 )
370 .map_err(Error::Importer)?;
371 let mut cpuid_page = crate::cpuid::HV_PSP_CPUID_PAGE::default();
372 for (index, leaf) in crate::cpuid::SNP_REQUIRED_CPUID_LEAF_LIST_PARAVISOR
376 .iter()
377 .enumerate()
378 {
379 let entry = &mut cpuid_page.cpuid_leaf_info[index];
380 entry.eax_in = leaf.eax;
381 entry.ecx_in = leaf.ecx;
382 if leaf.eax == CpuidFunction::ExtendedStateEnumeration.0 && leaf.ecx <= 1 {
383 entry.xfem_in = 1;
384 }
385 }
386 cpuid_page.count = crate::cpuid::SNP_REQUIRED_CPUID_LEAF_LIST_PARAVISOR.len() as u32;
387 importer
388 .import_pages(
389 cpuid_address / HV_PAGE_SIZE,
390 1,
391 "linux-snp-cpuid",
392 BootPageAcceptance::CpuidPage,
393 cpuid_page.as_bytes(),
394 )
395 .map_err(Error::Importer)?;
396
397 let cc_blob = defs::cc_blob_sev_info {
398 magic: defs::CC_BLOB_SEV_INFO_MAGIC,
399 version: 0,
400 _reserved: 0,
401 secrets_phys: secrets_address,
402 secrets_len: HV_PAGE_SIZE as u32,
403 _rsvd1: 0,
404 cpuid_phys: cpuid_address,
405 cpuid_len: HV_PAGE_SIZE as u32,
406 _rsvd2: 0,
407 };
408 importer
409 .import_pages(
410 cc_blob_address / HV_PAGE_SIZE,
411 1,
412 "linux-snp-cc-blob",
413 BootPageAcceptance::Exclusive,
414 cc_blob.as_bytes(),
415 )
416 .map_err(Error::Importer)?;
417
418 let cc_blob_address = u32::try_from(cc_blob_address)
419 .map_err(|_| Error::SnpCcBlobAddressTooHigh(cc_blob_address))?;
420 let cc_setup_data = defs::cc_setup_data {
421 header: defs::setup_data {
422 next: 0,
423 ty: defs::SETUP_CC_BLOB,
424 len: (size_of::<defs::cc_setup_data>() - size_of::<defs::setup_data>()) as u32,
425 },
426 cc_blob_address,
427 _padding: [0; 3],
428 };
429 importer
430 .import_pages(
431 cc_setup_data_address / HV_PAGE_SIZE,
432 1,
433 "linux-snp-cc-setup-data",
434 BootPageAcceptance::Exclusive,
435 cc_setup_data.as_bytes(),
436 )
437 .map_err(Error::Importer)?;
438
439 importer
440 .set_vp_context_page(vmsa_address / HV_PAGE_SIZE)
441 .map_err(Error::Importer)?;
442
443 Ok(cc_setup_data_address)
444}
445
446fn check_address_alignment(address: u64) -> Result<(), Error> {
448 if !address.is_multiple_of(HV_PAGE_SIZE) {
449 Err(Error::UnalignedAddress(address))
450 } else {
451 Ok(())
452 }
453}
454
455fn align_initrd_after_kernel(next_addr: u64) -> Result<u64, Error> {
456 next_addr
457 .checked_next_multiple_of(X64_LARGE_PAGE_SIZE)
458 .ok_or(Error::InitrdAddressTooHigh(next_addr))
459}
460
461fn import_initrd<R: GuestArch>(
463 initrd: Option<InitrdConfig<'_>>,
464 next_addr: u64,
465 importer: &mut dyn ImageLoad<R>,
466) -> Result<Option<InitrdInfo>, Error> {
467 let initrd_info = match initrd {
468 Some(cfg) => {
469 let initrd_address = match cfg.initrd_address {
470 InitrdAddressType::AfterKernel => align_initrd_after_kernel(next_addr)?,
471 InitrdAddressType::Address(addr) => addr,
472 };
473
474 tracing::trace!(initrd_address, "loading initrd");
475 check_address_alignment(initrd_address)?;
476
477 ChunkBuf::new()
478 .import_file_region(
479 importer,
480 ImportFileRegion {
481 file: cfg.initrd,
482 file_offset: 0,
483 file_length: cfg.size,
484 gpa: initrd_address,
485 memory_length: cfg.size,
486 acceptance: BootPageAcceptance::Exclusive,
487 tag: "linux-initrd",
488 },
489 )
490 .map_err(Error::ImportInitrd)?;
491
492 Some(InitrdInfo {
493 gpa: initrd_address,
494 size: cfg.size,
495 })
496 }
497 None => None,
498 };
499 Ok(initrd_info)
500}
501
502pub fn load_kernel_and_initrd_x64<F>(
518 importer: &mut dyn ImageLoad<X86Register>,
519 kernel_image: &mut F,
520 kernel_minimum_start_address: u64,
521 initrd: Option<InitrdConfig<'_>>,
522) -> Result<LoadInfo, Error>
523where
524 F: Read + Seek,
525{
526 tracing::trace!(kernel_minimum_start_address, "loading x86_64 kernel");
527
528 if crate::bzimage::is_bzimage(kernel_image).map_err(Error::BzImage)? {
529 tracing::info!("detected bzImage format, loading via Linux boot protocol");
530 return load_bzimage(importer, kernel_image, kernel_minimum_start_address, initrd);
531 }
532
533 let elf_load_info = load_static_elf(
534 importer,
535 kernel_image,
536 kernel_minimum_start_address,
537 0,
538 false,
539 BootPageAcceptance::Exclusive,
540 "linux-kernel",
541 )
542 .map_err(Error::ElfLoader)?;
543
544 let crate::elf::LoadInfo {
545 minimum_address_used: min_addr,
546 next_available_address: next_addr,
547 entrypoint,
548 } = elf_load_info;
549 tracing::trace!(min_addr, next_addr, entrypoint, "loaded kernel");
550
551 let initrd_info = import_initrd(initrd, next_addr, importer)?;
552
553 Ok(LoadInfo {
554 kernel: KernelInfo {
555 gpa: min_addr,
556 size: next_addr - min_addr,
557 entrypoint,
558 },
559 initrd: initrd_info,
560 dtb: None,
561 bzimage_setup_header: None,
562 })
563}
564
565fn load_bzimage(
569 importer: &mut dyn ImageLoad<X86Register>,
570 kernel_image: &mut (impl Read + Seek),
571 kernel_start_address: u64,
572 initrd: Option<InitrdConfig<'_>>,
573) -> Result<LoadInfo, Error> {
574 let info = crate::bzimage::parse_bzimage(kernel_image).map_err(Error::BzImage)?;
575
576 check_address_alignment(kernel_start_address)?;
577
578 let payload_offset = (info.setup_sects as u64 + 1) * 512;
579 let payload_len = info.protected_mode_size;
580 let payload_memory_len = align_up_to_page_size(payload_len);
581 let entrypoint = kernel_start_address + info.entry_offset;
582
583 tracing::info!(
584 kernel_start_address = format_args!("{:#x}", kernel_start_address),
585 payload_offset,
586 payload_len,
587 entrypoint = format_args!("{:#x}", entrypoint),
588 "loading bzImage payload into guest memory"
589 );
590
591 ChunkBuf::new()
592 .import_file_region(
593 importer,
594 ImportFileRegion {
595 file: kernel_image,
596 file_offset: payload_offset,
597 file_length: payload_len,
598 gpa: kernel_start_address,
599 memory_length: payload_memory_len,
600 acceptance: BootPageAcceptance::Exclusive,
601 tag: "linux-kernel",
602 },
603 )
604 .map_err(Error::ImportBzImage)?;
605
606 let next_addr = kernel_start_address + payload_memory_len;
609 let pref_address: u64 = info.setup_header.pref_address.into();
610 let init_end = kernel_start_address
611 .max(pref_address)
612 .saturating_add(info.init_size as u64);
613 let next_addr = next_addr.max(init_end);
614 let initrd_info = import_initrd(initrd, next_addr, importer)?;
615
616 Ok(LoadInfo {
617 kernel: KernelInfo {
618 gpa: kernel_start_address,
619 size: payload_memory_len,
620 entrypoint,
621 },
622 initrd: initrd_info,
623 dtb: None,
624 bzimage_setup_header: Some(info.setup_header),
625 })
626}
627
628fn import_config(
635 importer: &mut impl ImageLoad<X86Register>,
636 load_info: &LoadInfo,
637 cmdline: &CString,
638 mem_layout: &MemoryLayout,
639 acpi: &AcpiTables,
640 smbios: Option<&crate::smbios::BuiltSmbios>,
641 snp_boot: Option<SnpBootConfig>,
642) -> Result<(), Error> {
643 let raw_cmdline = cmdline.as_bytes_with_nul();
646 if raw_cmdline.len() as u64 > CR3_BASE - CMDLINE_BASE {
647 return Err(Error::CommandLineTooLong(
648 raw_cmdline.len(),
649 CR3_BASE - CMDLINE_BASE,
650 ));
651 }
652 if raw_cmdline.len() > 1 {
653 let cmdline_size_pages = align_up_to_page_size(raw_cmdline.len() as u64) / HV_PAGE_SIZE;
654 importer
655 .import_pages(
656 CMDLINE_BASE / HV_PAGE_SIZE,
657 cmdline_size_pages,
658 "linux-commandline",
659 BootPageAcceptance::Exclusive,
660 raw_cmdline,
661 )
662 .map_err(Error::Importer)?;
663 }
664
665 import_default_gdt(importer, GDT_BASE / HV_PAGE_SIZE).map_err(Error::Importer)?;
666 let mut page_table_work_buffer: Vec<PageTable> =
667 vec![PageTable::new_zeroed(); PAGE_TABLE_MAX_COUNT];
668 let mut page_table: Vec<u8> = vec![0; PAGE_TABLE_MAX_BYTES];
669 let mut page_table_builder = IdentityMapBuilder::new(
670 CR3_BASE,
671 IdentityMapSize::Size4Gb,
672 page_table_work_buffer.as_mut_slice(),
673 page_table.as_mut_slice(),
674 )?;
675 if let Some(snp_boot) = snp_boot {
676 page_table_builder = page_table_builder.with_confidential_bit(snp_boot.c_bit.into());
677 }
678 let page_table = page_table_builder.build();
679 assert!((page_table.len() as u64).is_multiple_of(HV_PAGE_SIZE));
680 importer
681 .import_pages(
682 CR3_BASE / HV_PAGE_SIZE,
683 page_table.len() as u64 / HV_PAGE_SIZE,
684 "linux-pagetables",
685 BootPageAcceptance::Exclusive,
686 page_table,
687 )
688 .map_err(Error::Importer)?;
689
690 if acpi.tables.is_empty() {
691 return Err(Error::EmptyAcpiTables);
692 }
693 let acpi_tables_size_pages = align_up_to_page_size(acpi.tables.len() as u64) / HV_PAGE_SIZE;
694 importer
695 .import_pages(
696 RSDP_BASE / HV_PAGE_SIZE,
697 1,
698 "linux-rsdp",
699 BootPageAcceptance::Exclusive,
700 &acpi.rsdp,
701 )
702 .map_err(Error::Importer)?;
703 importer
704 .import_pages(
705 ACPI_TABLES_BASE / HV_PAGE_SIZE,
706 acpi_tables_size_pages,
707 "linux-acpi-tables",
708 BootPageAcceptance::Exclusive,
709 &acpi.tables,
710 )
711 .map_err(Error::Importer)?;
712
713 let requested_page_count = snp_boot.map_or(0, |_| SNP_BOOT_PAGE_COUNT);
714 let ZeroPageBuildResult {
715 mut boot_params,
716 additional_pages,
717 } = build_zero_page(
718 mem_layout,
719 acpi.tables.len(),
720 smbios.map_or(0, |s| s.structure_table.len()),
721 requested_page_count,
722 cmdline,
723 load_info.initrd.as_ref().map(|info| info.gpa).unwrap_or(0) as u32,
724 load_info.initrd.as_ref().map(|info| info.size).unwrap_or(0) as u32,
725 load_info.bzimage_setup_header.as_ref(),
726 )?;
727 if let Some(allocated_range) = additional_pages {
728 boot_params.hdr.setup_data = import_snp_boot_pages(importer, allocated_range)?.into();
729 }
730 importer
731 .import_pages(
732 ZERO_PAGE_BASE / HV_PAGE_SIZE,
733 1,
734 "linux-zeropage",
735 BootPageAcceptance::Exclusive,
736 boot_params.as_bytes(),
737 )
738 .map_err(Error::Importer)?;
739
740 let mut import_reg = |register| {
742 importer
743 .import_vp_register(register)
744 .map_err(Error::Importer)
745 };
746
747 import_reg(X86Register::Cr0(x86defs::X64_CR0_PG | x86defs::X64_CR0_PE))?;
748 import_reg(X86Register::Cr3(CR3_BASE))?;
749 import_reg(X86Register::Cr4(x86defs::X64_CR4_PAE))?;
750 import_reg(X86Register::Efer(
751 x86defs::X64_EFER_SCE
752 | x86defs::X64_EFER_LME
753 | x86defs::X64_EFER_LMA
754 | x86defs::X64_EFER_NXE,
755 ))?;
756 import_reg(X86Register::Pat(x86defs::X86X_MSR_DEFAULT_PAT))?;
757
758 import_reg(X86Register::Rip(load_info.kernel.entrypoint))?;
760 import_reg(X86Register::Rsi(ZERO_PAGE_BASE))?;
761
762 import_reg(X86Register::MtrrDefType(0xc00))?;
765 import_reg(X86Register::MtrrFix64k00000(0x0606060606060606))?;
766 import_reg(X86Register::MtrrFix16k80000(0x0606060606060606))?;
767
768 if let Some(smbios) = smbios {
769 let anchor_pages = align_up_to_page_size(smbios.entry_point.len() as u64) / HV_PAGE_SIZE;
773 importer
774 .import_pages(
775 SMBIOS_FSEGMENT_BASE / HV_PAGE_SIZE,
776 anchor_pages,
777 "linux-smbios-anchor",
778 BootPageAcceptance::Exclusive,
779 &smbios.entry_point,
780 )
781 .map_err(Error::Importer)?;
782
783 let table_base = smbios_struct_table_base(acpi.tables.len());
784 let table_pages = align_up_to_page_size(smbios.structure_table.len() as u64) / HV_PAGE_SIZE;
785 importer
786 .import_pages(
787 table_base / HV_PAGE_SIZE,
788 table_pages,
789 "linux-smbios-tables",
790 BootPageAcceptance::Exclusive,
791 &smbios.structure_table,
792 )
793 .map_err(Error::Importer)?;
794 }
795
796 Ok(())
797}
798
799pub fn load_config_x86(
815 importer: &mut impl ImageLoad<X86Register>,
816 load_info: &LoadInfo,
817 cmdline: &CString,
818 mem_layout: &MemoryLayout,
819 build_acpi: impl FnOnce(u64) -> AcpiTables,
820 smbios: Option<crate::smbios::SmbiosTables<'_>>,
821 snp_boot: Option<SnpBootConfig>,
822) -> Result<(), Error> {
823 let acpi_tables = build_acpi(LOW_METADATA_END);
827
828 let smbios = smbios.map(|tables| {
833 crate::smbios::build(&tables, smbios_struct_table_base(acpi_tables.tables.len()))
834 });
835
836 import_config(
837 importer,
838 load_info,
839 cmdline,
840 mem_layout,
841 &acpi_tables,
842 smbios.as_ref(),
843 snp_boot,
844 )
845}
846
847pub fn load_x86<F>(
854 importer: &mut impl ImageLoad<X86Register>,
855 kernel_image: &mut F,
856 initrd: Option<InitrdConfig<'_>>,
857 cmdline: &CString,
858 mem_layout: &MemoryLayout,
859 build_acpi: impl FnOnce(u64) -> AcpiTables,
860 smbios: Option<crate::smbios::SmbiosTables<'_>>,
861 snp_boot: Option<SnpBootConfig>,
862) -> Result<LoadInfo, Error>
863where
864 F: Read + Seek,
865{
866 let load_info = load_kernel_and_initrd_x64(importer, kernel_image, KERNEL_BASE, initrd)?;
867 load_config_x86(
868 importer, &load_info, cmdline, mem_layout, build_acpi, smbios, snp_boot,
869 )?;
870 Ok(load_info)
871}
872
873open_enum::open_enum! {
874 #[derive(IntoBytes, Immutable, KnownLayout, FromBytes)]
875 pub enum Aarch64ImagePageSize: u64 {
876 UNSPECIFIED = 0,
877 PAGE4_K = 1,
878 PAGE16_K = 2,
879 PAGE64_K = 3,
880 }
881
882}
883
884impl Aarch64ImagePageSize {
885 const fn into_bits(self) -> u64 {
886 self.0
887 }
888
889 const fn from_bits(bits: u64) -> Self {
890 Self(bits)
891 }
892}
893
894#[bitfield(u64)]
896struct Aarch64ImageFlags {
897 #[bits(1)]
899 pub big_endian: bool,
900 #[bits(2)]
906 pub page_size: Aarch64ImagePageSize,
907 #[bits(1)]
914 pub any_start_address: bool,
915 #[bits(60)]
917 pub _padding: u64,
918}
919
920#[derive(Debug, IntoBytes, Immutable, KnownLayout, FromBytes)]
923#[repr(C)]
924struct Aarch64ImageHeader {
925 _code0: u32,
927 _code1: u32,
929 text_offset: u64,
931 image_size: u64,
933 flags: u64,
935 _res2: u64,
937 _res3: u64,
939 _res4: u64,
941 magic: [u8; 4],
943 _res5: u32,
945}
946
947const AARCH64_MAGIC_NUMBER: &[u8] = b"ARM\x64";
948
949pub fn load_kernel_and_initrd_arm64<F>(
961 importer: &mut dyn ImageLoad<Aarch64Register>,
962 kernel_image: &mut F,
963 kernel_minimum_start_address: u64,
964 initrd: Option<InitrdConfig<'_>>,
965 device_tree_blob: Option<&[u8]>,
966) -> Result<LoadInfo, Error>
967where
968 F: Read + Seek,
969{
970 tracing::trace!(kernel_minimum_start_address, "loading aarch64 kernel");
971
972 assert_eq!(
973 kernel_minimum_start_address & ((1 << 21) - 1),
974 0,
975 "Start offset must be aligned on the 2MiB boundary"
976 );
977
978 kernel_image
979 .seek(SeekFrom::Start(0))
980 .map_err(|_| Error::FlatLoader(FlatLoaderError::SeekKernelStart))?;
981
982 let mut header = Aarch64ImageHeader::new_zeroed();
983 kernel_image
984 .read_exact(header.as_mut_bytes())
985 .map_err(|_| Error::FlatLoader(FlatLoaderError::ReadKernelImage))?;
986
987 tracing::debug!("aarch64 kernel header {header:x?}");
988
989 if header.magic != AARCH64_MAGIC_NUMBER {
990 return Err(Error::FlatLoader(FlatLoaderError::BadImageMagic));
991 }
992
993 let flags = Aarch64ImageFlags::from(header.flags);
994 if flags.big_endian() {
995 return Err(Error::FlatLoader(FlatLoaderError::BigEndianKernelImage));
996 }
997 if flags.page_size() != Aarch64ImagePageSize::PAGE4_K {
998 return Err(Error::FlatLoader(
999 FlatLoaderError::FourKibPageImageIsRequired,
1000 ));
1001 }
1002 if !flags.any_start_address() {
1003 return Err(Error::FlatLoader(FlatLoaderError::LowMemoryKernel));
1004 }
1005
1006 kernel_image
1010 .seek(SeekFrom::Start(0))
1011 .map_err(|_| Error::FlatLoader(FlatLoaderError::SeekKernelStart))?;
1012
1013 let mut image = Vec::new();
1014 kernel_image
1015 .read_to_end(&mut image)
1016 .map_err(|_| Error::FlatLoader(FlatLoaderError::ReadKernelImage))?;
1017
1018 let kernel_load_offset = (kernel_minimum_start_address + header.text_offset) as usize;
1019 let kernel_size = if header.image_size != 0 {
1020 header.image_size
1021 } else {
1022 image.len() as u64
1023 };
1024
1025 let kernel_size = align_up_to_page_size(kernel_size);
1026 importer
1027 .import_pages(
1028 kernel_load_offset as u64 / HV_PAGE_SIZE,
1029 kernel_size / HV_PAGE_SIZE,
1030 "linux-kernel",
1031 BootPageAcceptance::Exclusive,
1032 &image,
1033 )
1034 .map_err(Error::Importer)?;
1035
1036 let next_addr = kernel_load_offset as u64 + kernel_size;
1037
1038 let (next_addr, dtb) = if let Some(device_tree_blob) = device_tree_blob {
1039 let dtb_addr = align_up_to_page_size(next_addr);
1040 tracing::trace!(dtb_addr, "loading device tree blob at {dtb_addr:x?}");
1041
1042 check_address_alignment(dtb_addr)?;
1043 let dtb_size_pages = align_up_to_page_size(device_tree_blob.len() as u64) / HV_PAGE_SIZE;
1044
1045 importer
1046 .import_pages(
1047 dtb_addr / HV_PAGE_SIZE,
1048 dtb_size_pages,
1049 "linux-device-tree",
1050 BootPageAcceptance::Exclusive,
1051 device_tree_blob,
1052 )
1053 .map_err(Error::Importer)?;
1054
1055 (
1056 dtb_addr + device_tree_blob.len() as u64,
1057 Some(dtb_addr..dtb_addr + device_tree_blob.len() as u64),
1058 )
1059 } else {
1060 (next_addr, None)
1061 };
1062
1063 let initrd_info = import_initrd(initrd, next_addr, importer)?;
1064
1065 Ok(LoadInfo {
1066 kernel: KernelInfo {
1067 gpa: kernel_minimum_start_address,
1068 size: kernel_size,
1069 entrypoint: kernel_load_offset as u64,
1070 },
1071 initrd: initrd_info,
1072 dtb,
1073 bzimage_setup_header: None,
1074 })
1075}
1076
1077pub fn set_direct_boot_registers_arm64(
1083 importer: &mut impl ImageLoad<Aarch64Register>,
1084 load_info: &LoadInfo,
1085) -> Result<(), Error> {
1086 let mut import_reg = |register| {
1087 importer
1088 .import_vp_register(register)
1089 .map_err(Error::Importer)
1090 };
1091
1092 import_reg(Aarch64Register::Pc(load_info.kernel.entrypoint))?;
1093 import_reg(Aarch64Register::Cpsr(
1094 Cpsr64::new()
1095 .with_sp(true)
1096 .with_el(1)
1097 .with_f(true)
1098 .with_i(true)
1099 .with_a(true)
1100 .with_d(true)
1101 .into(),
1102 ))?;
1103 import_reg(Aarch64Register::SctlrEl1(
1104 SctlrEl1::new()
1105 .with_m(false)
1109 .with_c(true)
1111 .with_i(true)
1113 .with_eos(true)
1115 .with_tscxt(true)
1116 .with_eis(true)
1117 .with_span(true)
1118 .with_n_tlsmd(true)
1119 .with_lsmaoe(true)
1120 .into(),
1121 ))?;
1122 import_reg(Aarch64Register::TcrEl1(
1123 TranslationControlEl1::new()
1124 .with_t0sz(0x11)
1125 .with_irgn0(1)
1126 .with_orgn0(1)
1127 .with_sh0(3)
1128 .with_tg0(TranslationGranule0::TG_4KB)
1129 .with_epd0(1)
1131 .with_epd1(1)
1133 .with_tg1(TranslationGranule1::TG_4KB)
1135 .with_ips(IntermPhysAddrSize::IPA_48_BITS_256_TB)
1136 .into(),
1137 ))?;
1138 import_reg(Aarch64Register::Ttbr0El1(TranslationBaseEl1::new().into()))?;
1139 import_reg(Aarch64Register::Ttbr1El1(TranslationBaseEl1::new().into()))?;
1140 import_reg(Aarch64Register::VbarEl1(0))?;
1141
1142 if let Some(dtb) = &load_info.dtb {
1143 import_reg(Aarch64Register::X0(dtb.start))?;
1144 }
1145
1146 Ok(())
1147}
1148#[cfg(test)]
1149mod tests {
1150 use super::*;
1151 use crate::importer::IgvmParameterType;
1152 use crate::importer::IsolationConfig;
1153 use crate::importer::ParameterAreaIndex;
1154 use crate::importer::StartupMemoryType;
1155 use std::io::Cursor;
1156 use test_with_tracing::test;
1157 use zerocopy::FromBytes;
1158
1159 const MB: u64 = 0x100000;
1160 const GB: u64 = 0x4000_0000;
1161
1162 fn make_layout(ram_size: u64) -> MemoryLayout {
1166 MemoryLayout::new(
1167 ram_size,
1168 &[MemoryRange::new(4 * GB - 128 * MB..4 * GB)],
1169 &[],
1170 &[],
1171 None,
1172 )
1173 .unwrap()
1174 }
1175
1176 fn assert_contiguous(p: &defs::boot_params, first_ram_end: u64) {
1181 let entries = p.e820_entries as usize;
1182 assert!(entries > 0);
1183 let mut expected_addr = 0u64;
1184 for i in 0..entries {
1185 let e = &p.e820_map[i];
1186 assert_ne!(u64::from(e.size), 0, "entry {i} is empty");
1187 assert_eq!(u64::from(e.addr), expected_addr, "gap/overlap at entry {i}");
1188 expected_addr = u64::from(e.addr) + u64::from(e.size);
1189 }
1190 assert_eq!(
1191 expected_addr, first_ram_end,
1192 "map ends at {expected_addr:#x}, expected {first_ram_end:#x}"
1193 );
1194 }
1195
1196 #[test]
1197 fn zero_page_layout_with_smbios() {
1198 let acpi_len = 0x1800; let smbios_len = 0x100; let p = build_zero_page(
1201 &make_layout(256 * MB),
1202 acpi_len,
1203 smbios_len,
1204 0,
1205 &CString::new("root=/dev/sda").unwrap(),
1206 0,
1207 0,
1208 None,
1209 )
1210 .unwrap()
1211 .boot_params;
1212
1213 let acpi_end = ACPI_TABLES_BASE + 0x2000;
1214 let smbios_end = acpi_end + 0x1000;
1215 let expected = [
1216 (0, ACPI_TABLES_BASE, defs::E820_RAM),
1217 (ACPI_TABLES_BASE, 0x2000, defs::E820_ACPI),
1218 (acpi_end, 0x1000, defs::E820_RESERVED),
1219 (smbios_end, RSDP_BASE - smbios_end, defs::E820_RAM),
1220 (RSDP_BASE, 0x100000 - RSDP_BASE, defs::E820_RESERVED),
1221 (0x100000, 256 * MB - 0x100000, defs::E820_RAM),
1222 ];
1223 assert_eq!(p.e820_entries as usize, expected.len());
1224 for (i, (addr, size, typ)) in expected.iter().enumerate() {
1225 let e = &p.e820_map[i];
1226 assert_eq!(u64::from(e.addr), *addr, "entry {i} addr");
1227 assert_eq!(u64::from(e.size), *size, "entry {i} size");
1228 assert_eq!(u32::from(e.typ), *typ, "entry {i} type");
1229 }
1230 assert_contiguous(&p, 256 * MB);
1231 }
1232
1233 #[test]
1234 fn zero_page_skips_empty_smbios_region() {
1235 let p = build_zero_page(
1238 &make_layout(256 * MB),
1239 0x1800,
1240 0,
1241 0,
1242 &CString::new("").unwrap(),
1243 0,
1244 0,
1245 None,
1246 )
1247 .unwrap()
1248 .boot_params;
1249
1250 let acpi_end = ACPI_TABLES_BASE + 0x2000;
1251 let expected = [
1252 (0, ACPI_TABLES_BASE, defs::E820_RAM),
1253 (ACPI_TABLES_BASE, 0x2000, defs::E820_ACPI),
1254 (acpi_end, RSDP_BASE - acpi_end, defs::E820_RAM),
1255 (RSDP_BASE, 0x100000 - RSDP_BASE, defs::E820_RESERVED),
1256 (0x100000, 256 * MB - 0x100000, defs::E820_RAM),
1257 ];
1258 assert_eq!(p.e820_entries as usize, expected.len());
1259 for (i, (addr, size, typ)) in expected.iter().enumerate() {
1260 let e = &p.e820_map[i];
1261 assert_eq!(u64::from(e.addr), *addr, "entry {i} addr");
1262 assert_eq!(u64::from(e.size), *size, "entry {i} size");
1263 assert_eq!(u32::from(e.typ), *typ, "entry {i} type");
1264 }
1265 assert_contiguous(&p, 256 * MB);
1266 }
1267
1268 #[test]
1269 fn zero_page_multiple_ram_ranges() {
1270 let p = build_zero_page(
1273 &make_layout(8 * GB),
1274 0x1000,
1275 0x1000,
1276 0,
1277 &CString::new("").unwrap(),
1278 0,
1279 0,
1280 None,
1281 )
1282 .unwrap()
1283 .boot_params;
1284 assert_eq!(p.e820_entries, 7);
1285 let last = &p.e820_map[6];
1286 assert_eq!(u64::from(last.addr), 4 * GB);
1287 assert_eq!(u32::from(last.typ), defs::E820_RAM);
1288 let below_gap = &p.e820_map[5];
1292 assert_eq!(
1293 u64::from(below_gap.addr) + u64::from(below_gap.size),
1294 4 * GB - 128 * MB
1295 );
1296 }
1297
1298 #[test]
1299 fn zero_page_tables_too_large() {
1300 let result = build_zero_page(
1302 &make_layout(256 * MB),
1303 (RSDP_BASE - ACPI_TABLES_BASE) as usize + 0x1000,
1304 0,
1305 0,
1306 &CString::new("").unwrap(),
1307 0,
1308 0,
1309 None,
1310 );
1311 match result {
1312 Err(Error::LowTablesTooLarge(..)) => {}
1313 other => panic!("expected LowTablesTooLarge, got {:?}", other.err()),
1314 }
1315 }
1316
1317 #[derive(Default)]
1321 struct RecordingImporter {
1322 pages: Vec<(String, u64, u64)>,
1324 imports: Vec<ImportRecord>,
1325 vp_context_page: Option<u64>,
1326 }
1327
1328 #[derive(Debug)]
1329 struct ImportRecord {
1330 page_base: u64,
1331 page_count: u64,
1332 tag: String,
1333 acceptance: BootPageAcceptance,
1334 data: Vec<u8>,
1335 }
1336
1337 impl RecordingImporter {
1338 fn page_base(&self, tag: &str) -> Option<u64> {
1339 self.pages
1340 .iter()
1341 .find(|(t, ..)| t == tag)
1342 .map(|(_, base, _)| *base)
1343 }
1344 }
1345
1346 impl ImageLoad<X86Register> for RecordingImporter {
1347 fn isolation_config(&self) -> IsolationConfig {
1348 IsolationConfig {
1349 paravisor_present: false,
1350 isolation_type: crate::importer::IsolationType::None,
1351 shared_gpa_boundary_bits: None,
1352 }
1353 }
1354
1355 fn create_parameter_area(
1356 &mut self,
1357 _page_base: u64,
1358 _page_count: u32,
1359 _debug_tag: &str,
1360 ) -> anyhow::Result<ParameterAreaIndex> {
1361 unimplemented!()
1362 }
1363
1364 fn create_parameter_area_with_data(
1365 &mut self,
1366 _page_base: u64,
1367 _page_count: u32,
1368 _debug_tag: &str,
1369 _initial_data: &[u8],
1370 ) -> anyhow::Result<ParameterAreaIndex> {
1371 unimplemented!()
1372 }
1373
1374 fn import_parameter(
1375 &mut self,
1376 _parameter_area: ParameterAreaIndex,
1377 _byte_offset: u32,
1378 _parameter_type: IgvmParameterType,
1379 ) -> anyhow::Result<()> {
1380 unimplemented!()
1381 }
1382
1383 fn import_pages(
1384 &mut self,
1385 page_base: u64,
1386 page_count: u64,
1387 debug_tag: &'static str,
1388 acceptance: BootPageAcceptance,
1389 data: &[u8],
1390 ) -> anyhow::Result<()> {
1391 self.pages
1392 .push((debug_tag.to_string(), page_base, page_count));
1393 self.imports.push(ImportRecord {
1394 page_base,
1395 page_count,
1396 tag: debug_tag.to_string(),
1397 acceptance,
1398 data: data.to_vec(),
1399 });
1400 Ok(())
1401 }
1402
1403 fn import_vp_register(&mut self, _register: X86Register) -> anyhow::Result<()> {
1404 Ok(())
1405 }
1406
1407 fn verify_startup_memory_available(
1408 &mut self,
1409 _page_base: u64,
1410 _page_count: u64,
1411 _memory_type: StartupMemoryType,
1412 ) -> anyhow::Result<()> {
1413 Ok(())
1414 }
1415
1416 fn set_vp_context_page(&mut self, page_base: u64) -> anyhow::Result<()> {
1417 self.vp_context_page = Some(page_base);
1418 Ok(())
1419 }
1420
1421 fn relocation_region(
1422 &mut self,
1423 _gpa: u64,
1424 _size_bytes: u64,
1425 _relocation_alignment: u64,
1426 _minimum_relocation_gpa: u64,
1427 _maximum_relocation_gpa: u64,
1428 _apply_rip_offset: bool,
1429 _apply_gdtr_offset: bool,
1430 _vp_index: u16,
1431 ) -> anyhow::Result<()> {
1432 unimplemented!()
1433 }
1434
1435 fn page_table_relocation(
1436 &mut self,
1437 _page_table_gpa: u64,
1438 _size_pages: u64,
1439 _used_pages: u64,
1440 _vp_index: u16,
1441 ) -> anyhow::Result<()> {
1442 unimplemented!()
1443 }
1444
1445 fn set_imported_regions_config_page(&mut self, _page_base: u64) {
1446 unimplemented!()
1447 }
1448 }
1449
1450 fn test_load_info() -> LoadInfo {
1451 LoadInfo {
1452 kernel: KernelInfo {
1453 gpa: KERNEL_BASE,
1454 size: 0x1000,
1455 entrypoint: KERNEL_BASE,
1456 },
1457 initrd: None,
1458 dtb: None,
1459 bzimage_setup_header: None,
1460 }
1461 }
1462
1463 #[test]
1464 fn import_config_places_tables_at_fixed_addresses() {
1465 let acpi = AcpiTables {
1466 rsdp: vec![0u8; 0x1000],
1467 tables: vec![0u8; 0x1800],
1468 };
1469 let smbios = crate::smbios::BuiltSmbios {
1470 entry_point: vec![0u8; crate::smbios::ENTRY_POINT_SIZE],
1471 structure_table: vec![0u8; 0x100],
1472 };
1473 let mut importer = RecordingImporter::default();
1474 import_config(
1475 &mut importer,
1476 &test_load_info(),
1477 &CString::new("console=ttyS0").unwrap(),
1478 &make_layout(256 * MB),
1479 &acpi,
1480 Some(&smbios),
1481 None,
1482 )
1483 .unwrap();
1484
1485 assert_eq!(
1488 importer.page_base("linux-rsdp"),
1489 Some(RSDP_BASE / HV_PAGE_SIZE)
1490 );
1491 assert_eq!(
1492 importer.page_base("linux-acpi-tables"),
1493 Some(ACPI_TABLES_BASE / HV_PAGE_SIZE)
1494 );
1495 assert_eq!(
1498 importer.page_base("linux-smbios-anchor"),
1499 Some(SMBIOS_FSEGMENT_BASE / HV_PAGE_SIZE)
1500 );
1501 assert_eq!(
1502 importer.page_base("linux-smbios-tables"),
1503 Some(smbios_struct_table_base(acpi.tables.len()) / HV_PAGE_SIZE)
1504 );
1505 assert_eq!(
1507 importer.page_base("linux-zeropage"),
1508 Some(ZERO_PAGE_BASE / HV_PAGE_SIZE)
1509 );
1510 assert_eq!(
1511 importer.page_base("linux-commandline"),
1512 Some(CMDLINE_BASE / HV_PAGE_SIZE)
1513 );
1514 assert_eq!(
1515 importer.page_base("linux-pagetables"),
1516 Some(CR3_BASE / HV_PAGE_SIZE)
1517 );
1518 }
1519
1520 #[test]
1521 fn import_config_rejects_oversized_command_line() {
1522 let acpi = AcpiTables {
1523 rsdp: vec![0u8; 0x1000],
1524 tables: vec![0u8; 0x1000],
1525 };
1526 let cmdline = CString::new(vec![b'a'; (CR3_BASE - CMDLINE_BASE) as usize]).unwrap();
1528 let mut importer = RecordingImporter::default();
1529 let err = import_config(
1530 &mut importer,
1531 &test_load_info(),
1532 &cmdline,
1533 &make_layout(256 * MB),
1534 &acpi,
1535 None,
1536 None,
1537 )
1538 .unwrap_err();
1539 assert!(matches!(err, Error::CommandLineTooLong(..)), "got {err:?}");
1540 assert!(importer.pages.is_empty(), "importer used before the check");
1541 }
1542
1543 #[test]
1544 fn import_config_sets_snp_c_bit_in_page_tables() {
1545 const C_BIT: u8 = 51;
1546 let acpi = AcpiTables {
1547 rsdp: vec![0u8; 0x1000],
1548 tables: vec![0u8; 0x1000],
1549 };
1550 let mut importer = RecordingImporter::default();
1551 import_config(
1552 &mut importer,
1553 &test_load_info(),
1554 &CString::new("").unwrap(),
1555 &make_layout(256 * MB),
1556 &acpi,
1557 None,
1558 Some(SnpBootConfig { c_bit: C_BIT }),
1559 )
1560 .unwrap();
1561
1562 let page_tables = importer
1563 .imports
1564 .iter()
1565 .find(|import| import.tag == "linux-pagetables")
1566 .unwrap();
1567 for entry in page_tables.data.chunks_exact(8).map(|entry| {
1568 u64::from_ne_bytes(entry.try_into().expect("page table entry is eight bytes"))
1569 }) {
1570 if entry & 1 != 0 {
1571 assert_ne!(entry & (1 << C_BIT), 0);
1572 }
1573 }
1574 }
1575
1576 #[test]
1577 fn import_config_rejects_empty_acpi_tables() {
1578 let acpi = AcpiTables {
1581 rsdp: vec![0u8; 0x1000],
1582 tables: Vec::new(),
1583 };
1584 let mut importer = RecordingImporter::default();
1585 let err = import_config(
1586 &mut importer,
1587 &test_load_info(),
1588 &CString::new("").unwrap(),
1589 &make_layout(256 * MB),
1590 &acpi,
1591 None,
1592 None,
1593 )
1594 .unwrap_err();
1595 assert!(matches!(err, Error::EmptyAcpiTables), "got {err:?}");
1596 }
1597
1598 #[test]
1599 fn imports_snp_boot_pages_with_linux_cc_blob() {
1600 let acpi_len = 0x1800;
1601 let smbios_len = 0x100;
1602 let ZeroPageBuildResult {
1603 boot_params,
1604 additional_pages,
1605 } = build_zero_page(
1606 &make_layout(256 * MB),
1607 acpi_len,
1608 smbios_len,
1609 SNP_BOOT_PAGE_COUNT,
1610 &CString::new("").unwrap(),
1611 0,
1612 0,
1613 None,
1614 )
1615 .unwrap();
1616 let allocated_range = additional_pages.unwrap();
1617 let mut importer = RecordingImporter::default();
1618
1619 let cc_setup_data_address = import_snp_boot_pages(&mut importer, allocated_range).unwrap();
1620
1621 assert_eq!(importer.imports.len(), 4);
1622 assert_eq!(
1623 allocated_range.start(),
1624 ACPI_TABLES_BASE
1625 + align_up_to_page_size(acpi_len as u64)
1626 + align_up_to_page_size(smbios_len as u64)
1627 );
1628 assert_eq!(
1629 importer.imports[0].page_base,
1630 allocated_range.start() / HV_PAGE_SIZE
1631 );
1632 assert_eq!(importer.imports[0].page_count, 1);
1633 assert_eq!(importer.imports[0].tag, "linux-snp-secrets");
1634 assert_eq!(
1635 importer.imports[0].acceptance,
1636 BootPageAcceptance::SecretsPage
1637 );
1638 assert_eq!(
1639 importer.imports[1].page_base,
1640 allocated_range.start() / HV_PAGE_SIZE + 1
1641 );
1642 assert_eq!(
1643 importer.imports[1].acceptance,
1644 BootPageAcceptance::CpuidPage
1645 );
1646 let cpuid_page =
1647 crate::cpuid::HV_PSP_CPUID_PAGE::read_from_bytes(&importer.imports[1].data).unwrap();
1648 assert_eq!(
1649 cpuid_page.count as usize,
1650 crate::cpuid::SNP_REQUIRED_CPUID_LEAF_LIST_PARAVISOR.len()
1651 );
1652 for (entry, leaf) in cpuid_page
1653 .cpuid_leaf_info
1654 .iter()
1655 .zip(crate::cpuid::SNP_REQUIRED_CPUID_LEAF_LIST_PARAVISOR)
1656 {
1657 assert_eq!(entry.eax_in, leaf.eax);
1658 assert_eq!(entry.ecx_in, leaf.ecx);
1659 assert_eq!(
1660 entry.xfem_in,
1661 u64::from(leaf.eax == CpuidFunction::ExtendedStateEnumeration.0 && leaf.ecx <= 1)
1662 );
1663 assert_eq!(entry.xss_in, 0);
1664 }
1665
1666 let cc_blob = defs::cc_blob_sev_info::read_from_bytes(&importer.imports[2].data).unwrap();
1667 assert_eq!(cc_blob.magic, defs::CC_BLOB_SEV_INFO_MAGIC);
1668 assert_eq!(cc_blob.version, 0);
1669 assert_eq!(cc_blob.secrets_phys, allocated_range.start());
1670 assert_eq!(cc_blob.secrets_len, HV_PAGE_SIZE as u32);
1671 assert_eq!(cc_blob.cpuid_phys, allocated_range.start() + HV_PAGE_SIZE);
1672 assert_eq!(cc_blob.cpuid_len, HV_PAGE_SIZE as u32);
1673
1674 let cc_setup_data =
1675 defs::cc_setup_data::read_from_bytes(&importer.imports[3].data).unwrap();
1676 assert_eq!(cc_setup_data.header.next, 0);
1677 assert_eq!(cc_setup_data.header.ty, defs::SETUP_CC_BLOB);
1678 assert_eq!(
1679 cc_setup_data.header.len,
1680 (size_of::<defs::cc_setup_data>() - size_of::<defs::setup_data>()) as u32
1681 );
1682 assert_eq!(
1683 cc_setup_data.cc_blob_address,
1684 u32::try_from(allocated_range.start() + 2 * HV_PAGE_SIZE).unwrap()
1685 );
1686 assert_eq!(
1687 importer.vp_context_page,
1688 Some(allocated_range.start() / HV_PAGE_SIZE + 4)
1689 );
1690 assert_eq!(
1691 allocated_range.end(),
1692 cc_setup_data_address + 2 * HV_PAGE_SIZE,
1693 );
1694 assert!(allocated_range.end() <= RSDP_BASE);
1695
1696 let smbios_reserved = &boot_params.e820_map[2];
1697 let acpi_end = ACPI_TABLES_BASE + align_up_to_page_size(acpi_len as u64);
1698 assert_eq!(u64::from(smbios_reserved.addr), acpi_end);
1699 assert_eq!(
1700 u64::from(smbios_reserved.size),
1701 align_up_to_page_size(smbios_len as u64)
1702 );
1703 let additional_reserved = &boot_params.e820_map[3];
1704 assert_eq!(u64::from(additional_reserved.addr), allocated_range.start());
1705 assert_eq!(u64::from(additional_reserved.size), allocated_range.len());
1706
1707 assert!(matches!(
1708 build_zero_page(
1709 &make_layout(256 * MB),
1710 (RSDP_BASE - ACPI_TABLES_BASE) as usize,
1711 0,
1712 SNP_BOOT_PAGE_COUNT,
1713 &CString::new("").unwrap(),
1714 0,
1715 0,
1716 None,
1717 ),
1718 Err(Error::LowTablesTooLarge(..))
1719 ));
1720 }
1721
1722 fn test_elf(entrypoint: u64, load_segments: &[(u64, u64)]) -> Cursor<Vec<u8>> {
1726 use object::LittleEndian;
1727 use object::U16;
1728 use object::U32;
1729 use object::U64;
1730 use object::elf;
1731
1732 let header_size = size_of::<elf::FileHeader64<LittleEndian>>();
1733 let header = elf::FileHeader64::<LittleEndian> {
1734 e_ident: elf::Ident {
1735 magic: elf::ELFMAG,
1736 class: elf::ELFCLASS64,
1737 data: elf::ELFDATA2LSB,
1738 version: elf::EV_CURRENT,
1739 os_abi: elf::ELFOSABI_NONE,
1740 abi_version: 0,
1741 padding: [0; 7],
1742 },
1743 e_type: U16::new(LittleEndian, elf::ET_EXEC),
1744 e_machine: U16::new(LittleEndian, elf::EM_X86_64),
1745 e_version: U32::new(LittleEndian, elf::EV_CURRENT.into()),
1746 e_entry: U64::new(LittleEndian, entrypoint),
1747 e_phoff: U64::new(LittleEndian, header_size as u64),
1748 e_shoff: U64::new(LittleEndian, 0),
1749 e_flags: U32::new(LittleEndian, 0),
1750 e_ehsize: U16::new(LittleEndian, header_size as u16),
1751 e_phentsize: U16::new(
1752 LittleEndian,
1753 size_of::<elf::ProgramHeader64<LittleEndian>>() as u16,
1754 ),
1755 e_phnum: U16::new(LittleEndian, load_segments.len().try_into().unwrap()),
1756 e_shentsize: U16::new(
1757 LittleEndian,
1758 size_of::<elf::SectionHeader64<LittleEndian>>() as u16,
1759 ),
1760 e_shnum: U16::new(LittleEndian, 0),
1761 e_shstrndx: U16::new(LittleEndian, 0),
1762 };
1763 let mut image = object::pod::bytes_of(&header).to_vec();
1764 for &(paddr, memsz) in load_segments {
1765 let program_header = elf::ProgramHeader64::<LittleEndian> {
1766 p_type: U32::new(LittleEndian, elf::PT_LOAD),
1767 p_flags: U32::new(LittleEndian, elf::PF_R | elf::PF_X),
1768 p_offset: U64::new(LittleEndian, 0),
1769 p_vaddr: U64::new(LittleEndian, paddr),
1770 p_paddr: U64::new(LittleEndian, paddr),
1771 p_filesz: U64::new(LittleEndian, 0),
1772 p_memsz: U64::new(LittleEndian, memsz),
1773 p_align: U64::new(LittleEndian, HV_PAGE_SIZE),
1774 };
1775 image.extend_from_slice(object::pod::bytes_of(&program_header));
1776 }
1777 Cursor::new(image)
1778 }
1779
1780 #[test]
1781 fn elf_without_load_segments_is_rejected() {
1782 let mut importer = RecordingImporter::default();
1783 let mut kernel_image = test_elf(KERNEL_BASE, &[]);
1784 let error = load_kernel_and_initrd_x64(&mut importer, &mut kernel_image, KERNEL_BASE, None)
1785 .unwrap_err();
1786 assert!(matches!(
1787 error,
1788 Error::ElfLoader(crate::elf::Error::NoLoadableSegments)
1789 ));
1790 assert!(importer.imports.is_empty());
1791 }
1792
1793 #[test]
1794 fn elf_with_only_empty_load_segment_is_rejected() {
1795 let mut importer = RecordingImporter::default();
1796 let mut kernel_image = test_elf(KERNEL_BASE + 1, &[(KERNEL_BASE + 1, 0)]);
1797 let error = load_kernel_and_initrd_x64(&mut importer, &mut kernel_image, KERNEL_BASE, None)
1798 .unwrap_err();
1799 assert!(matches!(
1800 error,
1801 Error::ElfLoader(crate::elf::Error::NoLoadableSegments)
1802 ));
1803 assert!(importer.imports.is_empty());
1804 }
1805
1806 #[test]
1807 fn elf_entrypoint_outside_load_segments_is_rejected() {
1808 let mut importer = RecordingImporter::default();
1809 let mut kernel_image = test_elf(
1810 KERNEL_BASE + HV_PAGE_SIZE,
1811 &[
1812 (KERNEL_BASE, HV_PAGE_SIZE),
1813 (KERNEL_BASE + 2 * HV_PAGE_SIZE, HV_PAGE_SIZE),
1814 ],
1815 );
1816 let error = load_kernel_and_initrd_x64(&mut importer, &mut kernel_image, KERNEL_BASE, None)
1817 .unwrap_err();
1818 assert!(matches!(
1819 error,
1820 Error::ElfLoader(crate::elf::Error::EntryPointNotInLoadableSegment { .. })
1821 ));
1822 assert!(importer.imports.is_empty());
1823 }
1824
1825 #[test]
1826 fn initrd_after_kernel_alignment_overflow_is_rejected() {
1827 let mut importer = RecordingImporter::default();
1828 let mut initrd = Cursor::new([0]);
1829 let error = import_initrd(
1830 Some(InitrdConfig {
1831 initrd_address: InitrdAddressType::AfterKernel,
1832 initrd: &mut initrd,
1833 size: 1,
1834 }),
1835 u64::MAX - HV_PAGE_SIZE + 1,
1836 &mut importer,
1837 )
1838 .unwrap_err();
1839 assert!(matches!(error, Error::InitrdAddressTooHigh(_)));
1840 assert!(importer.imports.is_empty());
1841 }
1842}