1use crate::common::ChunkBuf;
7use crate::common::ImportFileRegion;
8use crate::common::ReadSeek;
9use crate::cpuid::HV_PSP_CPUID_PAGE;
10use crate::importer::Aarch64Register;
11use crate::importer::BootPageAcceptance;
12use crate::importer::IgvmParameterType;
13use crate::importer::ImageLoad;
14use crate::importer::IsolationConfig;
15use crate::importer::IsolationType;
16use crate::importer::SegmentRegister;
17use crate::importer::StartupMemoryType;
18use crate::importer::TableRegister;
19use crate::importer::X86Register;
20use crate::linux::InitrdAddressType;
21use crate::linux::InitrdConfig;
22use crate::linux::InitrdInfo;
23use crate::linux::KernelInfo;
24use crate::linux::load_kernel_and_initrd_arm64;
25use aarch64defs::Cpsr64;
26use aarch64defs::IntermPhysAddrSize;
27use aarch64defs::SctlrEl1;
28use aarch64defs::TranslationBaseEl1;
29use aarch64defs::TranslationControlEl1;
30use aarch64defs::TranslationGranule0;
31use aarch64defs::TranslationGranule1;
32use hvdef::HV_PAGE_SIZE;
33use hvdef::Vtl;
34use igvm::registers::AArch64Register;
35use loader_defs::paravisor::*;
36use loader_defs::shim::ShimParamsRaw;
37use memory_range::MemoryRange;
38use page_table::aarch64::Arm64PageSize;
39use page_table::aarch64::MemoryAttributeEl1;
40use page_table::aarch64::MemoryAttributeIndirectionEl1;
41use page_table::x64::MappedRange;
42use page_table::x64::PAGE_TABLE_MAX_BYTES;
43use page_table::x64::PAGE_TABLE_MAX_COUNT;
44use page_table::x64::PageTable;
45use page_table::x64::PageTableBuilder;
46use page_table::x64::X64_LARGE_PAGE_SIZE;
47use page_table::x64::align_up_to_large_page_size;
48use page_table::x64::align_up_to_page_size;
49use page_table::x64::calculate_pde_table_count;
50use product_policy::ProductPolicy;
51use product_policy::encode_product_policy;
52use std::io::Read;
53use std::io::Seek;
54use thiserror::Error;
55use x86defs::GdtEntry;
56use x86defs::SegmentSelector;
57use x86defs::X64_BUSY_TSS_SEGMENT_ATTRIBUTES;
58use x86defs::X64_DEFAULT_CODE_SEGMENT_ATTRIBUTES;
59use x86defs::X64_DEFAULT_DATA_SEGMENT_ATTRIBUTES;
60use x86defs::cpuid::CpuidFunction;
61use zerocopy::FromZeros;
62use zerocopy::IntoBytes;
63
64#[derive(Debug)]
65pub struct Vtl0Linux<'a> {
66 pub command_line: &'a std::ffi::CString,
67 pub load_info: crate::linux::LoadInfo,
68}
69
70#[derive(Debug)]
71pub struct Vtl0Config<'a> {
72 pub supports_pcat: bool,
73 pub supports_uefi: Option<(crate::uefi::LoadInfo, Vec<u8>)>,
75 pub supports_linux: Option<Vtl0Linux<'a>>,
76}
77
78pub const HCL_SECURE_VTL: Vtl = Vtl::Vtl2;
80
81const PERSISTED_REGION_SIZE: u64 = 2 * 1024 * 1024;
83
84fn avoid_page_table_large_page_boundary(offset: u64, large_page_size: u64) -> u64 {
85 if offset.is_multiple_of(large_page_size) {
86 offset + HV_PAGE_SIZE
87 } else {
88 offset
89 }
90}
91
92#[derive(Debug, Error)]
93pub enum Error {
94 #[error("memory is unaligned: {0}")]
95 MemoryUnaligned(u64),
96 #[error("command line too large: {0}")]
97 CommandLineSize(usize),
98 #[error("kernel load error")]
99 Kernel(#[source] crate::linux::Error),
100 #[error("shim load error")]
101 Shim(#[source] crate::elf::Error),
102 #[error("invalid initrd size: {0}")]
103 InvalidInitrdSize(u64),
104 #[error("memory used: {0} is greater than available")]
105 NotEnoughMemory(u64),
106 #[error("importer error")]
107 Importer(#[from] anyhow::Error),
108 #[error("failed to import initrd")]
109 ImportInitrd(#[source] crate::common::ImportFileRegionError),
110 #[error("failed to read initrd for CRC")]
111 InitrdRead(#[source] std::io::Error),
112 #[error("PageTableBuilder: {0}")]
113 PageTableBuilder(#[from] page_table::Error),
114}
115
116fn encode_product_policy_bytes(policy: &ProductPolicy) -> Vec<u8> {
123 validate_product_policy_for_build(policy);
124 let bytes = encode_product_policy(policy);
125 let max = PRODUCT_POLICY_MAX_SIZE_BYTES;
126 assert!(
127 bytes.len() <= max,
128 "product policy mesh-encoded size {} bytes exceeds the static measured-config-region budget of {} bytes; bump PARAVISOR_MEASURED_VTL2_CONFIG_SIZE_PAGES (currently {}) and accept the attestation-measurement change",
129 bytes.len(),
130 max,
131 PARAVISOR_MEASURED_VTL2_CONFIG_SIZE_PAGES,
132 );
133 bytes
134}
135
136fn validate_product_policy_for_build(policy: &ProductPolicy) {
139 match policy {
140 ProductPolicy::Sivm(sivm) => {
141 if sivm.require_secure_boot
142 && (sivm.require_secure_boot_vars || sivm.require_bcd_integrity)
143 {
144 assert!(
145 !sivm.custom_uefi_json.is_empty(),
146 "product policy requires non-empty custom_uefi_json"
147 );
148 }
149 }
150 ProductPolicy::Cwcow(cwcow) => {
151 if cwcow.require_secure_boot
152 && (cwcow.require_secure_boot_vars || cwcow.require_bcd_integrity)
153 {
154 assert!(
155 !cwcow.custom_uefi_json.is_empty(),
156 "product policy requires non-empty custom_uefi_json"
157 );
158 }
159 }
160 }
161}
162
163fn build_measured_vtl2_config_region(
168 mut config: ParavisorMeasuredVtl2Config,
169 product_policy: Option<&ProductPolicy>,
170) -> Vec<u8> {
171 let policy_bytes = product_policy.map(encode_product_policy_bytes);
172 let policy_bytes = policy_bytes.as_deref().unwrap_or(&[]);
173 config.product_policy_size = policy_bytes.len() as u32;
174
175 let buf_bytes = (PARAVISOR_MEASURED_VTL2_CONFIG_SIZE_PAGES as usize) * (HV_PAGE_SIZE as usize);
176 let mut buf = vec![0u8; buf_bytes];
177
178 let struct_bytes = config.as_bytes();
179 buf[..struct_bytes.len()].copy_from_slice(struct_bytes);
180 if !policy_bytes.is_empty() {
181 let off = PRODUCT_POLICY_INLINE_OFFSET;
182 buf[off..off + policy_bytes.len()].copy_from_slice(policy_bytes);
183 }
184 buf
185}
186
187pub enum CommandLineType<'a> {
189 Static(&'a str),
191 HostAppendable(&'a str),
196}
197
198pub fn load_openhcl_x64<F>(
205 importer: &mut dyn ImageLoad<X86Register>,
206 kernel_image: &mut F,
207 shim: &mut F,
208 sidecar: Option<&mut F>,
209 command_line: CommandLineType<'_>,
210 mut initrd: Option<(&mut dyn ReadSeek, u64)>,
211 memory_page_base: Option<u64>,
212 memory_page_count: u64,
213 vtl0_config: Vtl0Config<'_>,
214 product_policy: Option<&ProductPolicy>,
215) -> Result<(), Error>
216where
217 F: Read + Seek,
218{
219 let IsolationConfig {
220 isolation_type,
221 paravisor_present,
222 shared_gpa_boundary_bits,
223 } = importer.isolation_config();
224
225 let with_relocation = memory_page_base.is_none() && isolation_type == IsolationType::None;
227
228 let memory_start_address = memory_page_base
229 .map(|page_number| page_number * HV_PAGE_SIZE)
230 .unwrap_or(PARAVISOR_DEFAULT_MEMORY_BASE_ADDRESS);
231
232 let memory_size = memory_page_count * HV_PAGE_SIZE;
233
234 if !memory_start_address.is_multiple_of(X64_LARGE_PAGE_SIZE) {
254 return Err(Error::MemoryUnaligned(memory_start_address));
255 }
256
257 if !memory_size.is_multiple_of(X64_LARGE_PAGE_SIZE) {
258 return Err(Error::MemoryUnaligned(memory_size));
259 }
260
261 importer.verify_startup_memory_available(
264 memory_start_address / HV_PAGE_SIZE,
265 memory_page_count,
266 if paravisor_present {
267 StartupMemoryType::Vtl2ProtectableRam
268 } else {
269 StartupMemoryType::Ram
270 },
271 )?;
272
273 let kernel_acceptance = match isolation_type {
274 IsolationType::Snp | IsolationType::Tdx => BootPageAcceptance::Shared,
275 _ => BootPageAcceptance::Exclusive,
276 };
277
278 let mut offset = memory_start_address;
279
280 let persisted_region_base = offset;
284 let persisted_region_size = PERSISTED_REGION_SIZE;
285 offset += persisted_region_size;
286
287 let bounce_buffer = if matches!(isolation_type, IsolationType::Snp | IsolationType::Tdx) {
296 let bounce_buffer_gpa = offset;
297 assert_eq!(bounce_buffer_gpa % X64_LARGE_PAGE_SIZE, 0);
298 let range = MemoryRange::new(bounce_buffer_gpa..bounce_buffer_gpa + X64_LARGE_PAGE_SIZE);
299
300 offset += range.len();
301 Some(range)
302 } else {
303 None
304 };
305
306 tracing::trace!(offset, "loading the kernel");
307
308 let load_info = crate::elf::load_static_elf(
317 importer,
318 kernel_image,
319 offset,
320 0,
321 true,
322 kernel_acceptance,
323 "underhill-kernel",
324 )
325 .map_err(|e| Error::Kernel(crate::linux::Error::ElfLoader(e)))?;
326 tracing::trace!("Kernel loaded at {load_info:x?}");
327 let crate::elf::LoadInfo {
328 minimum_address_used: _min_addr,
329 next_available_address: mut offset,
330 entrypoint: kernel_entrypoint,
331 } = load_info;
332
333 assert_eq!(offset & (HV_PAGE_SIZE - 1), 0);
334
335 let (sidecar_size, sidecar_entrypoint) = if let Some(sidecar) = sidecar {
337 offset = align_up_to_large_page_size(offset);
339
340 let load_info = crate::elf::load_static_elf(
341 importer,
342 sidecar,
343 0,
344 offset,
345 false,
346 BootPageAcceptance::Exclusive,
347 "sidecar-kernel",
348 )
349 .map_err(|e| Error::Kernel(crate::linux::Error::ElfLoader(e)))?;
350
351 (
352 load_info.next_available_address - offset,
353 load_info.entrypoint,
354 )
355 } else {
356 (0, 0)
357 };
358
359 let sidecar_base = offset;
360 offset += sidecar_size;
361
362 let load_info = crate::elf::load_static_elf(
363 importer,
364 shim,
365 0,
366 offset,
367 false,
368 BootPageAcceptance::Exclusive,
369 "underhill-boot-shim",
370 )
371 .map_err(Error::Shim)?;
372 tracing::trace!("The boot shim loaded at {load_info:x?}");
373 let crate::elf::LoadInfo {
374 minimum_address_used: shim_base_addr,
375 next_available_address: mut offset,
376 entrypoint: shim_entry_address,
377 } = load_info;
378
379 let mut buf = ChunkBuf::new();
381 let initrd_crc = if let Some((ref mut initrd_file, initrd_len)) = initrd {
382 buf.crc32(*initrd_file, initrd_len)
383 .map_err(Error::InitrdRead)?
384 } else {
385 crc32fast::hash(&[])
386 };
387
388 let ramdisk = if let Some((initrd_file, initrd_len)) = initrd {
390 let initrd_base = offset;
391 let initrd_size = align_up_to_page_size(initrd_len);
392
393 buf.import_file_region(
394 importer,
395 ImportFileRegion {
396 file: initrd_file,
397 file_offset: 0,
398 file_length: initrd_len,
399 gpa: initrd_base,
400 memory_length: initrd_len,
401 acceptance: kernel_acceptance,
402 tag: "underhill-initrd",
403 },
404 )
405 .map_err(Error::ImportInitrd)?;
406
407 offset += initrd_size;
408 Some((initrd_base, initrd_len))
409 } else {
410 None
411 };
412
413 let gdt_base_address = offset;
414 let gdt_size = HV_PAGE_SIZE;
415 offset += gdt_size;
416
417 let boot_params_base = offset;
418 let boot_params_size = HV_PAGE_SIZE;
419
420 offset += boot_params_size;
421
422 let cmdline_base = offset;
423 let (cmdline, policy) = match command_line {
424 CommandLineType::Static(val) => (val, CommandLinePolicy::STATIC),
425 CommandLineType::HostAppendable(val) => (val, CommandLinePolicy::APPEND_CHOSEN),
426 };
427
428 if cmdline.len() > COMMAND_LINE_SIZE {
429 return Err(Error::CommandLineSize(cmdline.len()));
430 }
431
432 let mut static_command_line = [0; COMMAND_LINE_SIZE];
433 static_command_line[..cmdline.len()].copy_from_slice(cmdline.as_bytes());
434 let paravisor_command_line = ParavisorCommandLine {
435 policy,
436 static_command_line_len: cmdline.len() as u16,
437 static_command_line,
438 };
439
440 importer.import_pages(
441 cmdline_base / HV_PAGE_SIZE,
442 1,
443 "underhill-command-line",
444 BootPageAcceptance::Exclusive,
445 paravisor_command_line.as_bytes(),
446 )?;
447
448 offset += HV_PAGE_SIZE;
449
450 let reserved_region_size = PARAVISOR_RESERVED_VTL2_PAGE_COUNT_MAX * HV_PAGE_SIZE;
452 let reserved_region_start = offset;
453 offset += reserved_region_size;
454
455 tracing::debug!(reserved_region_start);
456
457 let parameter_region_size = PARAVISOR_VTL2_CONFIG_REGION_PAGE_COUNT_MAX * HV_PAGE_SIZE;
458 let parameter_region_start = offset;
459 offset += parameter_region_size;
460
461 tracing::debug!(parameter_region_start);
462
463 let bootshim_log_size = HV_PAGE_SIZE * 2;
466 let bootshim_log_start = offset;
467 offset += bootshim_log_size;
468
469 importer.import_pages(
470 bootshim_log_start / HV_PAGE_SIZE,
471 bootshim_log_size / HV_PAGE_SIZE,
472 "ohcl-boot-shim-log-buffer",
473 BootPageAcceptance::Exclusive,
474 &[],
475 )?;
476
477 let heap_start = offset;
484 let heap_size = 16 * HV_PAGE_SIZE;
485 importer.import_pages(
486 heap_start / HV_PAGE_SIZE,
487 heap_size / HV_PAGE_SIZE,
488 "ohcl-boot-shim-heap",
489 BootPageAcceptance::Exclusive,
490 &[],
491 )?;
492 offset += heap_size;
493
494 offset = avoid_page_table_large_page_boundary(offset, X64_LARGE_PAGE_SIZE);
497
498 let end_of_underhill_mem = offset;
500
501 let local_map = match isolation_type {
517 IsolationType::Snp | IsolationType::Tdx => {
518 Some((PARAVISOR_LOCAL_MAP_VA, PARAVISOR_LOCAL_MAP_SIZE))
519 }
520 _ => None,
521 };
522
523 let page_table_base_page_count = 5;
524 let page_table_dynamic_page_count = {
525 calculate_pde_table_count(memory_start_address, memory_size) * 2
527 + local_map.map_or(0, |v| calculate_pde_table_count(v.0, v.1))
528 };
529 let page_table_isolation_page_count = match isolation_type {
530 IsolationType::Tdx => {
531 3
534 }
535 _ => 0,
536 };
537 let page_table_page_count = page_table_base_page_count
538 + page_table_dynamic_page_count
539 + page_table_isolation_page_count;
540 let page_table_region_size = HV_PAGE_SIZE * page_table_page_count;
541 let page_table_region_start = offset;
542 offset += page_table_region_size;
543
544 tracing::debug!(page_table_region_start, page_table_region_size);
545
546 let mut ranges: Vec<MappedRange> = Vec::new();
548
549 ranges.push(MappedRange::new(
550 memory_start_address,
551 memory_start_address + memory_size,
552 ));
553
554 if let Some((local_map_start, size)) = local_map {
555 ranges.push(MappedRange::new(local_map_start, local_map_start + size));
556 }
557
558 if isolation_type == IsolationType::Tdx {
559 const RESET_VECTOR_ADDR: u64 = 0xffff_f000;
560 ranges.push(MappedRange::new(
561 RESET_VECTOR_ADDR,
562 RESET_VECTOR_ADDR + page_table::x64::X64_PAGE_SIZE,
563 ));
564 }
565
566 ranges.sort_by_key(|r| r.start());
567
568 let mut page_table_work_buffer: Vec<PageTable> =
570 vec![PageTable::new_zeroed(); PAGE_TABLE_MAX_COUNT];
571 let mut page_table: Vec<u8> = vec![0; PAGE_TABLE_MAX_BYTES];
572 let mut page_table_builder = PageTableBuilder::new(
573 page_table_region_start,
574 page_table_work_buffer.as_mut_slice(),
575 page_table.as_mut_slice(),
576 ranges.as_slice(),
577 )?;
578
579 if isolation_type == IsolationType::Snp {
580 page_table_builder = page_table_builder.with_confidential_bit(51);
581 }
582
583 let page_table = page_table_builder.build()?;
584
585 assert!((page_table.len() as u64).is_multiple_of(HV_PAGE_SIZE));
586 let page_table_page_base = page_table_region_start / HV_PAGE_SIZE;
587 assert!(page_table.len() as u64 <= page_table_region_size);
588 let offset = offset;
589
590 if with_relocation {
591 importer.relocation_region(
593 memory_start_address,
594 end_of_underhill_mem - memory_start_address,
595 X64_LARGE_PAGE_SIZE,
596 PARAVISOR_DEFAULT_MEMORY_BASE_ADDRESS,
597 1 << 48,
598 true,
599 true,
600 0, )?;
602
603 importer.page_table_relocation(
605 page_table_region_start,
606 page_table_region_size / HV_PAGE_SIZE,
607 page_table.len() as u64 / HV_PAGE_SIZE,
608 0,
609 )?;
610 }
611
612 if offset > memory_start_address + memory_size {
614 return Err(Error::NotEnoughMemory(offset - memory_start_address));
615 }
616
617 let (initrd_base, initrd_size) = ramdisk.unwrap_or((0, 0));
618 let calculate_shim_offset = |addr: u64| addr.wrapping_sub(shim_base_addr) as i64;
620 let shim_params = ShimParamsRaw {
621 kernel_entry_offset: calculate_shim_offset(kernel_entrypoint),
622 cmdline_offset: calculate_shim_offset(cmdline_base),
623 initrd_offset: calculate_shim_offset(initrd_base),
624 initrd_size,
625 initrd_crc,
626 supported_isolation_type: match isolation_type {
627 IsolationType::None | IsolationType::Vbs => {
631 loader_defs::shim::SupportedIsolationType::VBS
632 }
633 IsolationType::Snp => loader_defs::shim::SupportedIsolationType::SNP,
634 IsolationType::Tdx => loader_defs::shim::SupportedIsolationType::TDX,
635 },
636 memory_start_offset: calculate_shim_offset(memory_start_address),
637 memory_size,
638 parameter_region_offset: calculate_shim_offset(parameter_region_start),
639 parameter_region_size,
640 vtl2_reserved_region_offset: calculate_shim_offset(reserved_region_start),
641 vtl2_reserved_region_size: reserved_region_size,
642 sidecar_offset: calculate_shim_offset(sidecar_base),
643 sidecar_size,
644 sidecar_entry_offset: calculate_shim_offset(sidecar_entrypoint),
645 used_start: calculate_shim_offset(memory_start_address),
646 used_end: calculate_shim_offset(offset),
647 bounce_buffer_start: bounce_buffer.map_or(0, |r| calculate_shim_offset(r.start())),
648 bounce_buffer_size: bounce_buffer.map_or(0, |r| r.len()),
649 log_buffer_start: calculate_shim_offset(bootshim_log_start),
650 log_buffer_size: bootshim_log_size,
651 heap_start_offset: calculate_shim_offset(heap_start),
652 heap_size,
653 persisted_state_region_offset: calculate_shim_offset(persisted_region_base),
654 persisted_state_region_size: persisted_region_size,
655 };
656
657 tracing::debug!(boot_params_base, "shim gpa");
658
659 importer
660 .import_pages(
661 boot_params_base / HV_PAGE_SIZE,
662 boot_params_size / HV_PAGE_SIZE,
663 "underhill-shim-params",
664 BootPageAcceptance::Exclusive,
665 shim_params.as_bytes(),
666 )
667 .map_err(Error::Importer)?;
668
669 importer.import_pages(
670 page_table_page_base,
671 page_table_page_count,
672 "underhill-page-tables",
673 BootPageAcceptance::Exclusive,
674 page_table,
675 )?;
676
677 let default_data_attributes: u16 = X64_DEFAULT_DATA_SEGMENT_ATTRIBUTES.into();
690 let default_code64_attributes: u16 = X64_DEFAULT_CODE_SEGMENT_ATTRIBUTES.into();
691 let gdt = [
692 GdtEntry::new_zeroed(),
694 GdtEntry::new_zeroed(),
695 GdtEntry {
697 limit_low: 0xffff,
698 attr_low: default_code64_attributes as u8,
699 attr_high: (default_code64_attributes >> 8) as u8,
700 ..GdtEntry::new_zeroed()
701 },
702 GdtEntry {
704 limit_low: 0xffff,
705 attr_low: default_data_attributes as u8,
706 attr_high: (default_data_attributes >> 8) as u8,
707 ..GdtEntry::new_zeroed()
708 },
709 ];
710
711 const LINEAR_CODE64_DESCRIPTOR_INDEX: usize = 2;
712 const LINEAR_DATA_DESCRIPTOR_INDEX: usize = 3;
713 const RPL: u8 = 0x00; let linear_code64_descriptor_selector =
716 SegmentSelector::from_gdt_index(LINEAR_CODE64_DESCRIPTOR_INDEX as u16, RPL);
717 let linear_data_descriptor_selector =
718 SegmentSelector::from_gdt_index(LINEAR_DATA_DESCRIPTOR_INDEX as u16, RPL);
719
720 importer.import_pages(
721 gdt_base_address / HV_PAGE_SIZE,
722 gdt_size / HV_PAGE_SIZE,
723 "underhill-gdt",
724 BootPageAcceptance::Exclusive,
725 gdt.as_bytes(),
726 )?;
727
728 let mut import_reg = |register| {
729 importer
730 .import_vp_register(register)
731 .map_err(Error::Importer)
732 };
733
734 import_reg(X86Register::Gdtr(TableRegister {
736 base: gdt_base_address,
737 limit: (size_of_val(&gdt) - 1) as u16,
738 }))?;
739
740 let ds = SegmentRegister {
741 selector: linear_data_descriptor_selector.into_bits(),
742 base: 0,
743 limit: 0xffffffff,
744 attributes: default_data_attributes,
745 };
746 import_reg(X86Register::Ds(ds))?;
747 import_reg(X86Register::Es(ds))?;
748 import_reg(X86Register::Fs(ds))?;
749 import_reg(X86Register::Gs(ds))?;
750 import_reg(X86Register::Ss(ds))?;
751
752 let cs = SegmentRegister {
753 selector: linear_code64_descriptor_selector.into_bits(),
754 base: 0,
755 limit: 0xffffffff,
756 attributes: default_code64_attributes,
757 };
758 import_reg(X86Register::Cs(cs))?;
759
760 import_reg(X86Register::Tr(SegmentRegister {
766 selector: 0x0000,
767 base: 0x00000000,
768 limit: 0x0000FFFF,
769 attributes: X64_BUSY_TSS_SEGMENT_ATTRIBUTES.into(),
770 }))?;
771
772 import_reg(X86Register::Cr0(
777 x86defs::X64_CR0_PG | x86defs::X64_CR0_PE | x86defs::X64_CR0_NE,
778 ))?;
779
780 import_reg(X86Register::Cr3(page_table_region_start))?;
782
783 import_reg(X86Register::Cr4(
785 x86defs::X64_CR4_PAE | x86defs::X64_CR4_MCE | x86defs::X64_CR4_OSXSAVE,
786 ))?;
787
788 import_reg(X86Register::Efer(
790 x86defs::X64_EFER_LMA | x86defs::X64_EFER_LME | x86defs::X64_EFER_NXE,
791 ))?;
792
793 import_reg(X86Register::Pat(x86defs::X86X_MSR_DEFAULT_PAT))?;
795
796 let relative_boot_params_base = boot_params_base - shim_base_addr;
799 import_reg(X86Register::Rsi(relative_boot_params_base))?;
800
801 import_reg(X86Register::Rip(shim_entry_address))?;
803
804 let config_region_page_base = parameter_region_start / HV_PAGE_SIZE;
806
807 let slit_page_base = config_region_page_base + PARAVISOR_CONFIG_SLIT_PAGE_INDEX;
809 let slit_parameter_area = importer.create_parameter_area(
810 slit_page_base,
811 PARAVISOR_CONFIG_SLIT_SIZE_PAGES as u32,
812 "underhill-slit",
813 )?;
814 importer.import_parameter(slit_parameter_area, 0, IgvmParameterType::Slit)?;
815
816 let pptt_page_base = config_region_page_base + PARAVISOR_CONFIG_PPTT_PAGE_INDEX;
818 let pptt_parameter_area = importer.create_parameter_area(
819 pptt_page_base,
820 PARAVISOR_CONFIG_PPTT_SIZE_PAGES as u32,
821 "underhill-pptt",
822 )?;
823 importer.import_parameter(pptt_parameter_area, 0, IgvmParameterType::Pptt)?;
824
825 let dt_page_base = config_region_page_base + PARAVISOR_CONFIG_DEVICE_TREE_PAGE_INDEX;
827 let dt_parameter_area = importer.create_parameter_area(
828 dt_page_base,
829 PARAVISOR_CONFIG_DEVICE_TREE_SIZE_PAGES as u32,
830 "underhill-device-tree",
831 )?;
832 importer.import_parameter(dt_parameter_area, 0, IgvmParameterType::DeviceTree)?;
833
834 if isolation_type == IsolationType::Snp {
835 let reserved_region_page_base = reserved_region_start / HV_PAGE_SIZE;
836 let secrets_page_base: u64 =
837 reserved_region_page_base + PARAVISOR_RESERVED_VTL2_SNP_SECRETS_PAGE_INDEX;
838 importer.import_pages(
839 secrets_page_base,
840 PARAVISOR_RESERVED_VTL2_SNP_SECRETS_SIZE_PAGES,
841 "underhill-snp-secrets-page",
842 BootPageAcceptance::SecretsPage,
843 &[],
844 )?;
845
846 let cpuid_page = create_snp_cpuid_page();
847 let cpuid_page_base =
848 reserved_region_page_base + PARAVISOR_RESERVED_VTL2_SNP_CPUID_PAGE_INDEX;
849 importer.import_pages(
850 cpuid_page_base,
851 1,
852 "underhill-snp-cpuid-page",
853 BootPageAcceptance::CpuidPage,
854 cpuid_page.as_bytes(),
855 )?;
856
857 importer.import_pages(
858 cpuid_page_base + 1,
859 1,
860 "underhill-snp-cpuid-extended-state-page",
861 BootPageAcceptance::CpuidExtendedStatePage,
862 &[],
863 )?;
864
865 let vmsa_page_base =
866 reserved_region_page_base + PARAVISOR_RESERVED_VTL2_SNP_VMSA_PAGE_INDEX;
867 importer.set_vp_context_page(vmsa_page_base)?;
868 }
869
870 let mut free_page = 1;
873 let mut measured_config = ParavisorMeasuredVtl0Config {
874 magic: ParavisorMeasuredVtl0Config::MAGIC,
875 ..FromZeros::new_zeroed()
876 };
877
878 let Vtl0Config {
879 supports_pcat,
880 supports_uefi,
881 supports_linux,
882 } = vtl0_config;
883
884 if supports_pcat {
885 measured_config.supported_vtl0.set_pcat_supported(true);
886 }
887
888 if let Some((uefi, vp_context)) = &supports_uefi {
889 measured_config.supported_vtl0.set_uefi_supported(true);
890 let vp_context_page = free_page;
891 free_page += 1;
892 measured_config.uefi_info = UefiInfo {
893 firmware: PageRegionDescriptor {
894 base_page_number: uefi.firmware_base / HV_PAGE_SIZE,
895 page_count: uefi.total_size / HV_PAGE_SIZE,
896 },
897 vtl0_vp_context: PageRegionDescriptor {
898 base_page_number: vp_context_page,
899 page_count: 1,
900 },
901 };
902
903 importer.import_pages(
905 vp_context_page,
906 1,
907 "openhcl-uefi-vp-context",
908 BootPageAcceptance::Exclusive,
909 vp_context,
910 )?;
911 }
912
913 if let Some(linux) = supports_linux {
914 measured_config
915 .supported_vtl0
916 .set_linux_direct_supported(true);
917
918 let kernel_region = PageRegionDescriptor::new(
919 linux.load_info.kernel.gpa / HV_PAGE_SIZE,
920 align_up_to_page_size(linux.load_info.kernel.size) / HV_PAGE_SIZE,
921 );
922
923 let (initrd_region, initrd_size) = match linux.load_info.initrd {
924 Some(info) => {
925 if info.gpa % HV_PAGE_SIZE != 0 {
926 return Err(Error::MemoryUnaligned(info.gpa));
927 }
928 (
929 PageRegionDescriptor::new(
931 info.gpa / HV_PAGE_SIZE,
932 align_up_to_page_size(info.size) / HV_PAGE_SIZE,
933 ),
934 info.size,
935 )
936 }
937 None => (PageRegionDescriptor::EMPTY, 0),
938 };
939
940 let command_line_page = free_page;
941 importer
945 .import_pages(
946 command_line_page,
947 1,
948 "underhill-vtl0-linux-command-line",
949 BootPageAcceptance::Exclusive,
950 linux.command_line.as_bytes_with_nul(),
951 )
952 .map_err(Error::Importer)?;
953 let command_line = PageRegionDescriptor::new(command_line_page, 1);
954
955 measured_config.linux_info = LinuxInfo {
956 kernel_region,
957 kernel_entrypoint: linux.load_info.kernel.entrypoint,
958 initrd_region,
959 initrd_size,
960 command_line,
961 };
962 }
963
964 importer
965 .import_pages(
966 PARAVISOR_VTL0_MEASURED_CONFIG_BASE_PAGE_X64,
967 1,
968 "underhill-measured-config",
969 BootPageAcceptance::Exclusive,
970 measured_config.as_bytes(),
971 )
972 .map_err(Error::Importer)?;
973
974 let vtl2_measured_config = ParavisorMeasuredVtl2Config {
975 magic: ParavisorMeasuredVtl2Config::MAGIC,
976 vtom_offset_bit: shared_gpa_boundary_bits.unwrap_or(0),
977 padding: [0; 7],
978 product_policy_size: 0,
979 reserved: [0; 4],
980 };
981
982 let region_image = build_measured_vtl2_config_region(vtl2_measured_config, product_policy);
983
984 importer
985 .import_pages(
986 config_region_page_base + PARAVISOR_MEASURED_VTL2_CONFIG_PAGE_INDEX,
987 PARAVISOR_MEASURED_VTL2_CONFIG_SIZE_PAGES,
988 "underhill-vtl2-measured-config",
989 BootPageAcceptance::Exclusive,
990 ®ion_image,
991 )
992 .map_err(Error::Importer)?;
993
994 let imported_region_base =
995 config_region_page_base + PARAVISOR_MEASURED_VTL2_CONFIG_ACCEPTED_MEMORY_PAGE_INDEX;
996
997 importer.set_imported_regions_config_page(imported_region_base);
998
999 let expected_page_hashes_base =
1004 config_region_page_base + PARAVISOR_MEASURED_VTL2_CONFIG_PAGE_HASHES_PAGE_INDEX;
1005 importer.set_expected_page_hashes_config_page(expected_page_hashes_base);
1006 Ok(())
1007}
1008
1009fn create_snp_cpuid_page() -> HV_PSP_CPUID_PAGE {
1011 let mut cpuid_page = HV_PSP_CPUID_PAGE::default();
1012
1013 for (i, required_leaf) in crate::cpuid::SNP_REQUIRED_CPUID_LEAF_LIST_PARAVISOR
1019 .iter()
1020 .enumerate()
1021 {
1022 let entry = &mut cpuid_page.cpuid_leaf_info[i];
1023 entry.eax_in = required_leaf.eax;
1024 entry.ecx_in = required_leaf.ecx;
1025 if required_leaf.eax == CpuidFunction::ExtendedStateEnumeration.0 {
1026 entry.xfem_in = 1;
1027 }
1028 cpuid_page.count += 1;
1029 }
1030
1031 cpuid_page
1032}
1033
1034pub fn load_openhcl_arm64<F>(
1041 importer: &mut dyn ImageLoad<Aarch64Register>,
1042 kernel_image: &mut F,
1043 shim: &mut F,
1044 command_line: CommandLineType<'_>,
1045 mut initrd: Option<(&mut dyn ReadSeek, u64)>,
1046 memory_page_base: Option<u64>,
1047 memory_page_count: u64,
1048 vtl0_config: Vtl0Config<'_>,
1049 product_policy: Option<&ProductPolicy>,
1050) -> Result<(), Error>
1051where
1052 F: Read + Seek,
1053{
1054 let Vtl0Config {
1055 supports_pcat,
1056 supports_uefi,
1057 supports_linux,
1058 } = vtl0_config;
1059
1060 assert!(!supports_pcat);
1061 assert!(supports_uefi.is_some() || supports_linux.is_some());
1062
1063 let paravisor_present = importer.isolation_config().paravisor_present;
1064
1065 let with_relocation = memory_page_base.is_none();
1067
1068 let memory_start_address = memory_page_base
1069 .map(|page_number| page_number * HV_PAGE_SIZE)
1070 .unwrap_or(PARAVISOR_DEFAULT_MEMORY_BASE_ADDRESS);
1071
1072 let memory_size = memory_page_count * HV_PAGE_SIZE;
1073
1074 if !memory_start_address.is_multiple_of(u64::from(Arm64PageSize::Large)) {
1076 return Err(Error::MemoryUnaligned(memory_start_address));
1077 }
1078
1079 if !memory_size.is_multiple_of(u64::from(Arm64PageSize::Large)) {
1080 return Err(Error::MemoryUnaligned(memory_size));
1081 }
1082
1083 importer.verify_startup_memory_available(
1086 memory_start_address / HV_PAGE_SIZE,
1087 memory_page_count,
1088 if paravisor_present {
1089 StartupMemoryType::Vtl2ProtectableRam
1090 } else {
1091 StartupMemoryType::Ram
1092 },
1093 )?;
1094
1095 let mut next_addr = memory_start_address;
1096
1097 let persisted_region_base = next_addr;
1101 let persisted_region_size = PERSISTED_REGION_SIZE;
1102 next_addr += persisted_region_size;
1103
1104 tracing::trace!(next_addr, "loading the kernel");
1105
1106 let initrd_crc = if let Some((ref mut initrd_file, initrd_len)) = initrd {
1108 ChunkBuf::new()
1109 .crc32(*initrd_file, initrd_len)
1110 .map_err(Error::InitrdRead)?
1111 } else {
1112 crc32fast::hash(&[])
1113 };
1114
1115 let initrd_address_type = InitrdAddressType::AfterKernel;
1119 let initrd_config = initrd.map(|(initrd_file, initrd_size)| InitrdConfig {
1120 initrd_address: initrd_address_type,
1121 initrd: initrd_file,
1122 size: initrd_size,
1123 });
1124 let device_tree_blob = None;
1125 let crate::linux::LoadInfo {
1126 kernel:
1127 KernelInfo {
1128 gpa: kernel_base,
1129 size: kernel_size,
1130 entrypoint: kernel_entry_point,
1131 },
1132 initrd: initrd_info,
1133 dtb,
1134 ..
1135 } = load_kernel_and_initrd_arm64(
1136 importer,
1137 kernel_image,
1138 next_addr,
1139 initrd_config,
1140 device_tree_blob,
1141 )
1142 .map_err(Error::Kernel)?;
1143
1144 assert!(
1145 dtb.is_none(),
1146 "DeviceTree is generated dynamically by the boot shim."
1147 );
1148
1149 tracing::trace!(kernel_base, "kernel loaded");
1150
1151 let InitrdInfo {
1152 gpa: initrd_gpa,
1153 size: initrd_size,
1154 } = if let Some(initrd_info) = initrd_info {
1155 assert!(initrd_address_type == InitrdAddressType::AfterKernel);
1156 next_addr = initrd_info.gpa + initrd_info.size;
1157 initrd_info
1158 } else {
1159 next_addr = kernel_base + kernel_size;
1160 InitrdInfo { gpa: 0, size: 0 }
1161 };
1162
1163 next_addr = align_up_to_page_size(next_addr);
1164
1165 tracing::trace!(next_addr, "loading the boot shim");
1166
1167 let crate::elf::LoadInfo {
1168 minimum_address_used: shim_base_addr,
1169 next_available_address: mut next_addr,
1170 entrypoint: shim_entry_point,
1171 } = crate::elf::load_static_elf(
1172 importer,
1173 shim,
1174 0,
1175 next_addr,
1176 false,
1177 BootPageAcceptance::Exclusive,
1178 "underhill-boot-shim",
1179 )
1180 .map_err(Error::Shim)?;
1181
1182 tracing::trace!(shim_base_addr, "boot shim loaded");
1183
1184 tracing::trace!(next_addr, "loading the command line");
1185
1186 let cmdline_base = next_addr;
1187 let (cmdline, policy) = match command_line {
1188 CommandLineType::Static(val) => (val, CommandLinePolicy::STATIC),
1189 CommandLineType::HostAppendable(val) => (val, CommandLinePolicy::APPEND_CHOSEN),
1190 };
1191
1192 if cmdline.len() > COMMAND_LINE_SIZE {
1193 return Err(Error::CommandLineSize(cmdline.len()));
1194 }
1195
1196 let mut static_command_line = [0; COMMAND_LINE_SIZE];
1197 static_command_line[..cmdline.len()].copy_from_slice(cmdline.as_bytes());
1198 let paravisor_command_line = ParavisorCommandLine {
1199 policy,
1200 static_command_line_len: cmdline.len() as u16,
1201 static_command_line,
1202 };
1203
1204 importer.import_pages(
1205 cmdline_base / HV_PAGE_SIZE,
1206 1,
1207 "underhill-command-line",
1208 BootPageAcceptance::Exclusive,
1209 paravisor_command_line.as_bytes(),
1210 )?;
1211
1212 next_addr += HV_PAGE_SIZE;
1213
1214 tracing::trace!(next_addr, "loading the boot shim parameters");
1215
1216 let shim_params_base = next_addr;
1217 let shim_params_size = HV_PAGE_SIZE;
1218
1219 next_addr += shim_params_size;
1220
1221 let parameter_region_size = PARAVISOR_VTL2_CONFIG_REGION_PAGE_COUNT_MAX * HV_PAGE_SIZE;
1222 let parameter_region_start = next_addr;
1223 next_addr += parameter_region_size;
1224
1225 tracing::debug!(parameter_region_start);
1226
1227 let bootshim_log_size = HV_PAGE_SIZE * 2;
1229 let bootshim_log_start = next_addr;
1230 next_addr += bootshim_log_size;
1231
1232 importer.import_pages(
1233 bootshim_log_start / HV_PAGE_SIZE,
1234 bootshim_log_size / HV_PAGE_SIZE,
1235 "ohcl-boot-shim-log-buffer",
1236 BootPageAcceptance::Exclusive,
1237 &[],
1238 )?;
1239
1240 let heap_start = next_addr;
1247 let heap_size = 16 * HV_PAGE_SIZE;
1248 importer.import_pages(
1249 heap_start / HV_PAGE_SIZE,
1250 heap_size / HV_PAGE_SIZE,
1251 "ohcl-boot-shim-heap",
1252 BootPageAcceptance::Exclusive,
1253 &[],
1254 )?;
1255 next_addr += heap_size;
1256
1257 next_addr = avoid_page_table_large_page_boundary(next_addr, u64::from(Arm64PageSize::Large));
1260
1261 let end_of_underhill_mem = next_addr;
1263
1264 let page_table_base_page_count = 5;
1267 let page_table_dynamic_page_count = 2 * page_table_base_page_count;
1268 let page_table_page_count = page_table_base_page_count + page_table_dynamic_page_count;
1269 let page_table_region_size = HV_PAGE_SIZE * page_table_page_count;
1270 let page_table_region_start = next_addr;
1271 next_addr += page_table_region_size;
1272
1273 tracing::debug!(page_table_region_start, page_table_region_size);
1274
1275 let next_addr = next_addr;
1276
1277 if next_addr > memory_start_address + memory_size {
1279 return Err(Error::NotEnoughMemory(next_addr - memory_start_address));
1280 }
1281
1282 let calculate_shim_offset = |addr: u64| -> i64 { addr.wrapping_sub(shim_base_addr) as i64 };
1284 let shim_params = ShimParamsRaw {
1285 kernel_entry_offset: calculate_shim_offset(kernel_entry_point),
1286 cmdline_offset: calculate_shim_offset(cmdline_base),
1287 initrd_offset: calculate_shim_offset(initrd_gpa),
1288 initrd_size,
1289 initrd_crc,
1290 supported_isolation_type: match importer.isolation_config().isolation_type {
1291 IsolationType::None | IsolationType::Vbs => {
1292 loader_defs::shim::SupportedIsolationType::VBS
1293 }
1294 _ => panic!("only None and VBS are supported for ARM64"),
1295 },
1296 memory_start_offset: calculate_shim_offset(memory_start_address),
1297 memory_size,
1298 parameter_region_offset: calculate_shim_offset(parameter_region_start),
1299 parameter_region_size,
1300 vtl2_reserved_region_offset: 0,
1301 vtl2_reserved_region_size: 0,
1302 sidecar_offset: 0,
1303 sidecar_size: 0,
1304 sidecar_entry_offset: 0,
1305 used_start: calculate_shim_offset(memory_start_address),
1306 used_end: calculate_shim_offset(next_addr),
1307 bounce_buffer_start: 0,
1308 bounce_buffer_size: 0,
1309 log_buffer_start: calculate_shim_offset(bootshim_log_start),
1310 log_buffer_size: bootshim_log_size,
1311 heap_start_offset: calculate_shim_offset(heap_start),
1312 heap_size,
1313 persisted_state_region_offset: calculate_shim_offset(persisted_region_base),
1314 persisted_state_region_size: persisted_region_size,
1315 };
1316
1317 importer
1318 .import_pages(
1319 shim_params_base / HV_PAGE_SIZE,
1320 shim_params_size / HV_PAGE_SIZE,
1321 "underhill-shim-params",
1322 BootPageAcceptance::Exclusive,
1323 shim_params.as_bytes(),
1324 )
1325 .map_err(Error::Importer)?;
1326
1327 let mut measured_config = ParavisorMeasuredVtl0Config {
1328 magic: ParavisorMeasuredVtl0Config::MAGIC,
1329 ..FromZeros::new_zeroed()
1330 };
1331
1332 if let Some((uefi, vp_context)) = &supports_uefi {
1333 measured_config.supported_vtl0.set_uefi_supported(true);
1334 let vp_context_page = PARAVISOR_VTL0_MEASURED_CONFIG_BASE_PAGE_AARCH64 + 1;
1335 measured_config.uefi_info = UefiInfo {
1336 firmware: PageRegionDescriptor {
1337 base_page_number: uefi.firmware_base / HV_PAGE_SIZE,
1338 page_count: uefi.total_size / HV_PAGE_SIZE,
1339 },
1340 vtl0_vp_context: PageRegionDescriptor {
1341 base_page_number: vp_context_page,
1342 page_count: 1,
1343 },
1344 };
1345
1346 importer.import_pages(
1348 vp_context_page,
1349 1,
1350 "openhcl-uefi-vp-context",
1351 BootPageAcceptance::Exclusive,
1352 vp_context,
1353 )?;
1354 }
1355
1356 importer
1357 .import_pages(
1358 PARAVISOR_VTL0_MEASURED_CONFIG_BASE_PAGE_AARCH64,
1359 1,
1360 "underhill-measured-config",
1361 BootPageAcceptance::Exclusive,
1362 measured_config.as_bytes(),
1363 )
1364 .map_err(Error::Importer)?;
1365
1366 tracing::trace!(page_table_region_start, "loading the page tables");
1367
1368 let memory_attribute_indirection = MemoryAttributeIndirectionEl1([
1369 MemoryAttributeEl1::Device_nGnRnE,
1370 MemoryAttributeEl1::Normal_NonCacheable,
1371 MemoryAttributeEl1::Normal_WriteThrough,
1372 MemoryAttributeEl1::Normal_WriteBack,
1373 MemoryAttributeEl1::Device_nGnRnE,
1374 MemoryAttributeEl1::Device_nGnRnE,
1375 MemoryAttributeEl1::Device_nGnRnE,
1376 MemoryAttributeEl1::Device_nGnRnE,
1377 ]);
1378 let mut page_tables: Vec<u8> = vec![0; page_table_region_size as usize];
1379 let page_tables = page_table::aarch64::build_identity_page_tables_aarch64(
1380 page_table_region_start,
1381 memory_start_address,
1382 memory_size,
1383 memory_attribute_indirection,
1384 page_tables.as_mut_slice(),
1385 );
1386 assert!((page_tables.len() as u64).is_multiple_of(HV_PAGE_SIZE));
1387 let page_table_page_base = page_table_region_start / HV_PAGE_SIZE;
1388 assert!(page_tables.len() as u64 <= page_table_region_size);
1389 assert!(page_table_region_size as usize > page_tables.len());
1390
1391 if with_relocation {
1392 importer.relocation_region(
1394 memory_start_address,
1395 end_of_underhill_mem - memory_start_address,
1396 Arm64PageSize::Large.into(),
1397 PARAVISOR_DEFAULT_MEMORY_BASE_ADDRESS,
1398 1 << 48,
1399 true,
1400 false,
1401 0, )?;
1403
1404 importer.page_table_relocation(
1406 page_table_region_start,
1407 page_table_region_size / HV_PAGE_SIZE,
1408 page_tables.len() as u64 / HV_PAGE_SIZE,
1409 0,
1410 )?;
1411 }
1412
1413 importer.import_pages(
1414 page_table_page_base,
1415 page_table_page_count,
1416 "underhill-page-tables",
1417 BootPageAcceptance::Exclusive,
1418 page_tables,
1419 )?;
1420
1421 tracing::trace!("Importing register state");
1422
1423 let mut import_reg = |register| {
1424 importer
1425 .import_vp_register(register)
1426 .map_err(Error::Importer)
1427 };
1428
1429 let relative_boot_params_base = shim_params_base - shim_base_addr;
1431 import_reg(AArch64Register::X0(relative_boot_params_base).into())?;
1432
1433 import_reg(AArch64Register::Pc(shim_entry_point).into())?;
1435
1436 import_reg(AArch64Register::Cpsr(Cpsr64::new().with_sp(true).with_el(1).into()).into())?;
1439
1440 import_reg(
1463 AArch64Register::SctlrEl1(
1464 SctlrEl1::new()
1465 .with_m(true)
1470 .with_c(true)
1472 .with_i(true)
1474 .with_eos(true)
1476 .with_tscxt(true)
1477 .with_eis(true)
1478 .with_span(true)
1479 .with_n_tlsmd(true)
1480 .with_lsmaoe(true)
1481 .into(),
1482 )
1483 .into(),
1484 )?;
1485
1486 import_reg(
1492 AArch64Register::TcrEl1(
1493 TranslationControlEl1::new()
1494 .with_t0sz(0x11)
1495 .with_irgn0(1)
1496 .with_orgn0(1)
1497 .with_sh0(3)
1498 .with_tg0(TranslationGranule0::TG_4KB)
1499 .with_epd1(1)
1501 .with_tg1(TranslationGranule1::TG_4KB)
1503 .with_ips(IntermPhysAddrSize::IPA_48_BITS_256_TB)
1504 .into(),
1505 )
1506 .into(),
1507 )?;
1508
1509 import_reg(AArch64Register::MairEl1(memory_attribute_indirection.into()).into())?;
1511 import_reg(
1512 AArch64Register::Ttbr0El1(
1513 TranslationBaseEl1::new()
1514 .with_baddr(page_table_region_start)
1515 .into(),
1516 )
1517 .into(),
1518 )?;
1519
1520 import_reg(AArch64Register::VbarEl1(0).into())?;
1524
1525 let config_region_page_base = parameter_region_start / HV_PAGE_SIZE;
1527
1528 let slit_page_base = config_region_page_base + PARAVISOR_CONFIG_SLIT_PAGE_INDEX;
1530 let slit_parameter_area = importer.create_parameter_area(
1531 slit_page_base,
1532 PARAVISOR_CONFIG_SLIT_SIZE_PAGES as u32,
1533 "underhill-slit",
1534 )?;
1535 importer.import_parameter(slit_parameter_area, 0, IgvmParameterType::Slit)?;
1536
1537 let pptt_page_base = config_region_page_base + PARAVISOR_CONFIG_PPTT_PAGE_INDEX;
1539 let pptt_parameter_area = importer.create_parameter_area(
1540 pptt_page_base,
1541 PARAVISOR_CONFIG_PPTT_SIZE_PAGES as u32,
1542 "underhill-pptt",
1543 )?;
1544 importer.import_parameter(pptt_parameter_area, 0, IgvmParameterType::Pptt)?;
1545
1546 let dt_page_base = config_region_page_base + PARAVISOR_CONFIG_DEVICE_TREE_PAGE_INDEX;
1548 let dt_parameter_area = importer.create_parameter_area(
1549 dt_page_base,
1550 PARAVISOR_CONFIG_DEVICE_TREE_SIZE_PAGES as u32,
1551 "underhill-device-tree",
1552 )?;
1553 importer.import_parameter(dt_parameter_area, 0, IgvmParameterType::DeviceTree)?;
1554
1555 let vtl2_measured_config = ParavisorMeasuredVtl2Config {
1556 magic: ParavisorMeasuredVtl2Config::MAGIC,
1557 vtom_offset_bit: 0,
1558 padding: [0; 7],
1559 product_policy_size: 0,
1560 reserved: [0; 4],
1561 };
1562
1563 let region_image = build_measured_vtl2_config_region(vtl2_measured_config, product_policy);
1564
1565 importer
1566 .import_pages(
1567 config_region_page_base + PARAVISOR_MEASURED_VTL2_CONFIG_PAGE_INDEX,
1568 PARAVISOR_MEASURED_VTL2_CONFIG_SIZE_PAGES,
1569 "underhill-vtl2-measured-config",
1570 BootPageAcceptance::Exclusive,
1571 ®ion_image,
1572 )
1573 .map_err(Error::Importer)?;
1574
1575 let imported_region_base =
1576 config_region_page_base + PARAVISOR_MEASURED_VTL2_CONFIG_ACCEPTED_MEMORY_PAGE_INDEX;
1577
1578 importer.set_imported_regions_config_page(imported_region_base);
1579
1580 let expected_page_hashes_base =
1583 config_region_page_base + PARAVISOR_MEASURED_VTL2_CONFIG_PAGE_HASHES_PAGE_INDEX;
1584 importer.set_expected_page_hashes_config_page(expected_page_hashes_base);
1585
1586 Ok(())
1587}
1588
1589#[cfg(test)]
1590mod page_table_layout_tests {
1591 use super::*;
1592
1593 #[test]
1594 fn page_table_region_avoids_large_page_boundary() {
1595 for large_page_size in [X64_LARGE_PAGE_SIZE, u64::from(Arm64PageSize::Large)] {
1596 assert_eq!(
1597 avoid_page_table_large_page_boundary(large_page_size, large_page_size),
1598 large_page_size + HV_PAGE_SIZE
1599 );
1600 assert_eq!(
1601 avoid_page_table_large_page_boundary(
1602 large_page_size - HV_PAGE_SIZE,
1603 large_page_size
1604 ),
1605 large_page_size - HV_PAGE_SIZE
1606 );
1607 }
1608 }
1609}
1610
1611#[cfg(test)]
1612mod product_policy_tests {
1613 use super::*;
1614 use product_policy::decode_product_policy;
1615 use product_policy::sivm::SivmPolicy;
1616 use zerocopy::FromBytes;
1617
1618 fn empty_config() -> ParavisorMeasuredVtl2Config {
1623 ParavisorMeasuredVtl2Config {
1624 magic: ParavisorMeasuredVtl2Config::MAGIC,
1625 vtom_offset_bit: 0,
1626 padding: [0; 7],
1627 product_policy_size: 0,
1628 reserved: [0; 4],
1629 }
1630 }
1631
1632 #[test]
1633 fn encode_product_policy_bytes_round_trip() {
1634 let policy = ProductPolicy::Sivm(SivmPolicy {
1635 require_ephemeral_vmgs: true,
1636 require_secure_boot: true,
1637 custom_uefi_json: vec![0xAA, 0xBB, 0xCC, 0xDD],
1638 ..Default::default()
1639 });
1640 let bytes = encode_product_policy_bytes(&policy);
1641 let decoded = decode_product_policy(&bytes).unwrap();
1642 assert_eq!(decoded, policy);
1644 }
1645
1646 #[test]
1647 #[should_panic(expected = "non-empty custom_uefi_json")]
1648 fn encode_product_policy_bytes_panics_on_empty_custom_uefi_json() {
1649 let policy = ProductPolicy::Sivm(SivmPolicy {
1650 require_ephemeral_vmgs: true,
1651 require_secure_boot: true,
1652 require_secure_boot_vars: true,
1653 require_bcd_integrity: true,
1654 custom_uefi_json: vec![],
1655 });
1656 let _ = encode_product_policy_bytes(&policy);
1657 }
1658
1659 #[test]
1660 #[should_panic(expected = "exceeds the static measured-config-region budget")]
1661 fn encode_product_policy_bytes_panics_on_oversize() {
1662 let oversize_body = PRODUCT_POLICY_MAX_SIZE_BYTES + 1;
1663 let policy = ProductPolicy::Sivm(SivmPolicy {
1664 custom_uefi_json: vec![0u8; oversize_body],
1665 ..Default::default()
1666 });
1667 let _ = encode_product_policy_bytes(&policy);
1668 }
1669
1670 #[test]
1671 fn build_region_absent_records_zero_size_in_struct() {
1672 let cfg = empty_config();
1673 let region = build_measured_vtl2_config_region(cfg, None);
1674 assert_eq!(
1675 region.len(),
1676 (PARAVISOR_MEASURED_VTL2_CONFIG_SIZE_PAGES as usize) * (HV_PAGE_SIZE as usize)
1677 );
1678 let (decoded_cfg, _) = ParavisorMeasuredVtl2Config::ref_from_prefix(®ion).unwrap();
1679 assert_eq!(decoded_cfg.magic, ParavisorMeasuredVtl2Config::MAGIC);
1680 assert_eq!(decoded_cfg.product_policy_size, 0);
1681 assert!(
1682 region[PRODUCT_POLICY_INLINE_OFFSET..]
1683 .iter()
1684 .all(|&b| b == 0)
1685 );
1686 }
1687
1688 #[test]
1689 fn build_region_present_records_policy_size_in_struct() {
1690 let cfg = empty_config();
1691 let policy = ProductPolicy::Sivm(SivmPolicy {
1692 require_secure_boot: true,
1693 custom_uefi_json: vec![1, 2, 3, 4],
1694 ..Default::default()
1695 });
1696 let bytes = encode_product_policy_bytes(&policy);
1697 let region = build_measured_vtl2_config_region(cfg, Some(&policy));
1698 assert_eq!(
1699 region.len(),
1700 (PARAVISOR_MEASURED_VTL2_CONFIG_SIZE_PAGES as usize) * (HV_PAGE_SIZE as usize)
1701 );
1702 let (decoded_cfg, _) = ParavisorMeasuredVtl2Config::ref_from_prefix(®ion).unwrap();
1703 assert_eq!(decoded_cfg.product_policy_size, bytes.len() as u32);
1704 assert_eq!(
1705 ®ion[PRODUCT_POLICY_INLINE_OFFSET..PRODUCT_POLICY_INLINE_OFFSET + bytes.len()],
1706 bytes.as_slice()
1707 );
1708 let decoded = decode_product_policy(
1709 ®ion[PRODUCT_POLICY_INLINE_OFFSET..PRODUCT_POLICY_INLINE_OFFSET + bytes.len()],
1710 )
1711 .unwrap();
1712 assert_eq!(decoded, policy);
1714 }
1715}