1mod file_loader;
7mod identity_mapping;
8mod signed_measurement;
9mod vp_context_builder;
10
11use crate::file_loader::IgvmLoader;
12use crate::file_loader::LoaderIsolationType;
13use anyhow::Context;
14use anyhow::bail;
15use clap::Parser;
16use file_loader::IgvmLoaderRegister;
17use file_loader::IgvmVtlLoader;
18use igvm::IgvmFile;
19use igvm_defs::IGVM_FIXED_HEADER;
20use igvm_defs::SnpPolicy;
21use igvm_defs::TdxPolicy;
22use igvmfilegen_config::Config;
23use igvmfilegen_config::ConfigIsolationType;
24use igvmfilegen_config::Image;
25use igvmfilegen_config::LinuxImage;
26use igvmfilegen_config::ResourceType;
27use igvmfilegen_config::Resources;
28use igvmfilegen_config::SnpInjectionType;
29use igvmfilegen_config::UefiConfigType;
30use loader::importer::Aarch64Register;
31use loader::importer::GuestArch;
32use loader::importer::GuestArchKind;
33use loader::importer::ImageLoad;
34use loader::importer::X86Register;
35use loader::linux::InitrdConfig;
36use loader::paravisor::CommandLineType;
37use loader::paravisor::Vtl0Config;
38use loader::paravisor::Vtl0Linux;
39use std::io::Write;
40use std::path::PathBuf;
41use tracing_subscriber::EnvFilter;
42use tracing_subscriber::filter::LevelFilter;
43use underhill_confidentiality::OPENHCL_CONFIDENTIAL_DEBUG_ENV_VAR_NAME;
44use zerocopy::FromBytes;
45use zerocopy::IntoBytes;
46
47#[derive(Parser)]
48#[clap(name = "igvmfilegen", about = "Tool to generate IGVM files")]
49enum Options {
50 Dump {
53 #[clap(short, long = "filepath")]
55 file_path: PathBuf,
56 },
57 Manifest {
59 #[clap(short, long = "manifest")]
61 manifest: PathBuf,
62 #[clap(short, long = "resources")]
65 resources: PathBuf,
66 #[clap(short = 'o', long)]
68 output: PathBuf,
69 #[clap(long)]
71 debug_validation: bool,
72 },
73}
74
75fn main() -> anyhow::Result<()> {
79 let opts = Options::parse();
80 let filter = if std::env::var(EnvFilter::DEFAULT_ENV).is_ok() {
81 EnvFilter::from_default_env()
82 } else {
83 EnvFilter::default().add_directive(LevelFilter::INFO.into())
84 };
85 tracing_subscriber::fmt()
86 .log_internal_errors(true)
87 .with_writer(std::io::stderr)
88 .with_env_filter(filter)
89 .init();
90
91 match opts {
92 Options::Dump { file_path } => {
93 let image = fs_err::read(file_path).context("reading input file")?;
94 let fixed_header = IGVM_FIXED_HEADER::read_from_prefix(image.as_bytes())
95 .expect("Invalid fixed header")
96 .0; let igvm_data = IgvmFile::new_from_binary(&image, None).expect("should be valid");
99 println!("Total file size: {} bytes\n", fixed_header.total_file_size);
100 println!("{:#X?}", fixed_header);
101 println!("{}", igvm_data);
102 Ok(())
103 }
104 Options::Manifest {
105 manifest,
106 resources,
107 output,
108 debug_validation,
109 } => {
110 let config: Config = serde_json::from_str(
112 &fs_err::read_to_string(manifest).context("reading manifest")?,
113 )
114 .context("parsing manifest")?;
115
116 let resources: Resources = serde_json::from_str(
119 &fs_err::read_to_string(resources).context("reading resources")?,
120 )
121 .context("parsing resources")?;
122
123 let required_resources = config.required_resources();
124 resources
125 .check_required(&required_resources)
126 .context("required resources not specified")?;
127
128 tracing::info!(
129 ?config,
130 ?resources,
131 "Building igvm file with given config and resources"
132 );
133
134 match config.guest_arch {
136 igvmfilegen_config::GuestArch::X64 => create_igvm_file::<X86Register>(
137 config,
138 resources,
139 debug_validation || cfg!(debug_assertions),
140 output,
141 ),
142 igvmfilegen_config::GuestArch::Aarch64 => create_igvm_file::<Aarch64Register>(
143 config,
144 resources,
145 debug_validation || cfg!(debug_assertions),
146 output,
147 ),
148 }
149 }
150 }
151}
152
153fn create_igvm_file<R: IgvmfilegenRegister + GuestArch + 'static>(
155 igvm_config: Config,
156 resources: Resources,
157 debug_validation: bool,
158 output: PathBuf,
159) -> anyhow::Result<()> {
160 tracing::debug!(?igvm_config, "Creating IGVM file",);
161
162 let mut igvm_file: Option<IgvmFile> = None;
163 let mut map_files = Vec::new();
164 let base_path = output.file_stem().unwrap();
165 for config in igvm_config.guest_configs {
166 if config.max_vtl != 2 && config.max_vtl != 0 {
168 bail!("max_vtl must be 2 or 0");
169 }
170
171 let isolation_string = match config.isolation_type {
172 ConfigIsolationType::None => "none",
173 ConfigIsolationType::Vbs { .. } => "vbs",
174 ConfigIsolationType::Snp { .. } => "snp",
175 ConfigIsolationType::Tdx { .. } => "tdx",
176 };
177 let loader_isolation_type = match config.isolation_type {
178 ConfigIsolationType::None => LoaderIsolationType::None,
179 ConfigIsolationType::Vbs { enable_debug } => LoaderIsolationType::Vbs { enable_debug },
180 ConfigIsolationType::Snp {
181 shared_gpa_boundary_bits,
182 policy,
183 enable_debug,
184 injection_type,
185 } => LoaderIsolationType::Snp {
186 shared_gpa_boundary_bits,
187 policy: SnpPolicy::from(policy).with_debug(enable_debug as u8),
188 injection_type: match injection_type {
189 SnpInjectionType::Normal => vp_context_builder::snp::InjectionType::Normal,
190 SnpInjectionType::Restricted => {
191 vp_context_builder::snp::InjectionType::Restricted
192 }
193 },
194 },
195 ConfigIsolationType::Tdx {
196 enable_debug,
197 sept_ve_disable,
198 } => LoaderIsolationType::Tdx {
199 policy: TdxPolicy::new()
200 .with_debug_allowed(enable_debug as u8)
201 .with_sept_ve_disable(sept_ve_disable as u8),
202 },
203 };
204
205 let with_paravisor = config.max_vtl == 2;
207
208 let mut loader = IgvmLoader::<R>::new(with_paravisor, loader_isolation_type);
209
210 load_image(&mut loader.loader(), &config.image, &resources)?;
211
212 let igvm_output = loader
213 .finalize(config.guest_svn)
214 .context("finalizing loader")?;
215
216 match &mut igvm_file {
218 Some(file) => file
219 .merge_simple(igvm_output.guest)
220 .context("merging guest into overall igvm file")?,
221 None => igvm_file = Some(igvm_output.guest),
222 }
223
224 map_files.push(igvm_output.map);
225
226 if let Some(doc) = igvm_output.doc {
227 let doc_path = {
230 let mut name = base_path.to_os_string();
231 name.push("-");
232 name.push(isolation_string);
233 name.push(".json");
234 output.with_file_name(name)
235 };
236 tracing::info!(
237 path = %doc_path.display(),
238 "Writing document json file",
239 );
240 let mut doc_file = fs_err::OpenOptions::new()
241 .create(true)
242 .write(true)
243 .open(doc_path)
244 .context("creating doc file")?;
245
246 writeln!(
247 doc_file,
248 "{}",
249 serde_json::to_string(&doc).expect("json string")
250 )
251 .context("writing doc file")?;
252 }
253 }
254
255 let mut igvm_binary = Vec::new();
256 let igvm_file = igvm_file.expect("should have an igvm file");
257 igvm_file
258 .serialize(&mut igvm_binary)
259 .context("serializing igvm")?;
260
261 if debug_validation {
263 debug_validate_igvm_file(&igvm_file, &igvm_binary);
264 }
265
266 tracing::info!(
268 path = %output.display(),
269 "Writing output IGVM file",
270 );
271 fs_err::File::create(&output)
272 .context("creating igvm file")?
273 .write_all(&igvm_binary)
274 .context("writing igvm file")?;
275
276 let map_path = {
279 let mut name = output.file_name().expect("has name").to_owned();
280 name.push(".map");
281 output.with_file_name(name)
282 };
283 tracing::info!(
284 path = %map_path.display(),
285 "Writing output map file",
286 );
287 let mut map_file = fs_err::OpenOptions::new()
288 .create(true)
289 .write(true)
290 .open(map_path)
291 .context("creating map file")?;
292
293 for map in map_files {
294 writeln!(map_file, "{}", map).context("writing map file")?;
295 }
296
297 Ok(())
298}
299
300fn debug_validate_igvm_file(igvm_file: &IgvmFile, binary_file: &[u8]) {
303 use igvm::IgvmDirectiveHeader;
304 tracing::info!("Debug validation of serialized IGVM file.");
305
306 let igvm_reserialized = IgvmFile::new_from_binary(binary_file, None).expect("should be valid");
307
308 for (a, b) in igvm_file
309 .platforms()
310 .iter()
311 .zip(igvm_reserialized.platforms().iter())
312 {
313 assert_eq!(a, b);
314 }
315
316 for (a, b) in igvm_file
317 .initializations()
318 .iter()
319 .zip(igvm_reserialized.initializations().iter())
320 {
321 assert_eq!(a, b);
322 }
323
324 for (a, b) in igvm_file
325 .directives()
326 .iter()
327 .zip(igvm_reserialized.directives().iter())
328 {
329 match (a, b) {
330 (
331 IgvmDirectiveHeader::PageData {
332 gpa: a_gpa,
333 flags: a_flags,
334 data_type: a_data_type,
335 data: a_data,
336 compatibility_mask: a_compmask,
337 },
338 IgvmDirectiveHeader::PageData {
339 gpa: b_gpa,
340 flags: b_flags,
341 data_type: b_data_type,
342 data: b_data,
343 compatibility_mask: b_compmask,
344 },
345 ) => {
346 assert!(
347 a_gpa == b_gpa
348 && a_flags == b_flags
349 && a_data_type == b_data_type
350 && a_compmask == b_compmask
351 );
352
353 for i in 0..b_data.len() {
355 if i < a_data.len() {
356 assert_eq!(a_data[i], b_data[i]);
357 } else {
358 assert_eq!(0, b_data[i]);
359 }
360 }
361 }
362 (
363 IgvmDirectiveHeader::ParameterArea {
364 number_of_bytes: a_number_of_bytes,
365 parameter_area_index: a_parameter_area_index,
366 initial_data: a_initial_data,
367 },
368 IgvmDirectiveHeader::ParameterArea {
369 number_of_bytes: b_number_of_bytes,
370 parameter_area_index: b_parameter_area_index,
371 initial_data: b_initial_data,
372 },
373 ) => {
374 assert!(
375 a_number_of_bytes == b_number_of_bytes
376 && a_parameter_area_index == b_parameter_area_index
377 );
378
379 for i in 0..b_initial_data.len() {
381 if i < a_initial_data.len() {
382 assert_eq!(a_initial_data[i], b_initial_data[i]);
383 } else {
384 assert_eq!(0, b_initial_data[i]);
385 }
386 }
387 }
388 _ => assert_eq!(a, b),
389 }
390 }
391}
392
393trait IgvmfilegenRegister: IgvmLoaderRegister + 'static {
397 fn load_uefi(
398 importer: &mut dyn ImageLoad<Self>,
399 image: &[u8],
400 config: loader::uefi::ConfigType,
401 ) -> Result<loader::uefi::LoadInfo, loader::uefi::Error>;
402
403 fn load_linux_kernel_and_initrd<F>(
404 importer: &mut impl ImageLoad<Self>,
405 kernel_image: &mut F,
406 kernel_minimum_start_address: u64,
407 initrd: Option<InitrdConfig<'_>>,
408 device_tree_blob: Option<&[u8]>,
409 ) -> Result<loader::linux::LoadInfo, loader::linux::Error>
410 where
411 F: std::io::Read + std::io::Seek,
412 Self: GuestArch;
413
414 fn load_openhcl<F>(
415 importer: &mut dyn ImageLoad<Self>,
416 kernel_image: &mut F,
417 shim: &mut F,
418 sidecar: Option<&mut F>,
419 command_line: CommandLineType<'_>,
420 initrd: Option<&[u8]>,
421 memory_page_base: Option<u64>,
422 memory_page_count: u64,
423 vtl0_config: Vtl0Config<'_>,
424 ) -> Result<(), loader::paravisor::Error>
425 where
426 F: std::io::Read + std::io::Seek;
427}
428
429impl IgvmfilegenRegister for X86Register {
430 fn load_uefi(
431 importer: &mut dyn ImageLoad<Self>,
432 image: &[u8],
433 config: loader::uefi::ConfigType,
434 ) -> Result<loader::uefi::LoadInfo, loader::uefi::Error> {
435 loader::uefi::x86_64::load(importer, image, config)
436 }
437
438 fn load_linux_kernel_and_initrd<F>(
439 importer: &mut impl ImageLoad<Self>,
440 kernel_image: &mut F,
441 kernel_minimum_start_address: u64,
442 initrd: Option<InitrdConfig<'_>>,
443 _device_tree_blob: Option<&[u8]>,
444 ) -> Result<loader::linux::LoadInfo, loader::linux::Error>
445 where
446 F: std::io::Read + std::io::Seek,
447 {
448 loader::linux::load_kernel_and_initrd_x64(
449 importer,
450 kernel_image,
451 kernel_minimum_start_address,
452 initrd,
453 )
454 }
455
456 fn load_openhcl<F>(
457 importer: &mut dyn ImageLoad<Self>,
458 kernel_image: &mut F,
459 shim: &mut F,
460 sidecar: Option<&mut F>,
461 command_line: CommandLineType<'_>,
462 initrd: Option<&[u8]>,
463 memory_page_base: Option<u64>,
464 memory_page_count: u64,
465 vtl0_config: Vtl0Config<'_>,
466 ) -> Result<(), loader::paravisor::Error>
467 where
468 F: std::io::Read + std::io::Seek,
469 {
470 loader::paravisor::load_openhcl_x64(
471 importer,
472 kernel_image,
473 shim,
474 sidecar,
475 command_line,
476 initrd,
477 memory_page_base,
478 memory_page_count,
479 vtl0_config,
480 )
481 }
482}
483
484impl IgvmfilegenRegister for Aarch64Register {
485 fn load_uefi(
486 importer: &mut dyn ImageLoad<Self>,
487 image: &[u8],
488 config: loader::uefi::ConfigType,
489 ) -> Result<loader::uefi::LoadInfo, loader::uefi::Error> {
490 loader::uefi::aarch64::load(importer, image, config)
491 }
492
493 fn load_linux_kernel_and_initrd<F>(
494 importer: &mut impl ImageLoad<Self>,
495 kernel_image: &mut F,
496 kernel_minimum_start_address: u64,
497 initrd: Option<InitrdConfig<'_>>,
498 device_tree_blob: Option<&[u8]>,
499 ) -> Result<loader::linux::LoadInfo, loader::linux::Error>
500 where
501 F: std::io::Read + std::io::Seek,
502 {
503 loader::linux::load_kernel_and_initrd_arm64(
504 importer,
505 kernel_image,
506 kernel_minimum_start_address,
507 initrd,
508 device_tree_blob,
509 )
510 }
511
512 fn load_openhcl<F>(
513 importer: &mut dyn ImageLoad<Self>,
514 kernel_image: &mut F,
515 shim: &mut F,
516 _sidecar: Option<&mut F>,
517 command_line: CommandLineType<'_>,
518 initrd: Option<&[u8]>,
519 memory_page_base: Option<u64>,
520 memory_page_count: u64,
521 vtl0_config: Vtl0Config<'_>,
522 ) -> Result<(), loader::paravisor::Error>
523 where
524 F: std::io::Read + std::io::Seek,
525 {
526 loader::paravisor::load_openhcl_arm64(
527 importer,
528 kernel_image,
529 shim,
530 command_line,
531 initrd,
532 memory_page_base,
533 memory_page_count,
534 vtl0_config,
535 )
536 }
537}
538
539fn load_image<'a, R: IgvmfilegenRegister + GuestArch + 'static>(
541 loader: &mut IgvmVtlLoader<'_, R>,
542 config: &'a Image,
543 resources: &'a Resources,
544) -> anyhow::Result<()> {
545 tracing::debug!(?config, "loading into VTL0");
546
547 match *config {
548 Image::None => {
549 }
551 Image::Uefi { config_type } => {
552 load_uefi(loader, resources, config_type)?;
553 }
554 Image::Linux(ref linux) => {
555 load_linux(loader, linux, resources)?;
556 }
557 Image::Openhcl {
558 ref command_line,
559 static_command_line,
560 memory_page_base,
561 memory_page_count,
562 uefi,
563 ref linux,
564 } => {
565 if uefi && linux.is_some() {
566 anyhow::bail!("cannot include both UEFI and Linux images in OpenHCL image");
567 }
568
569 let kernel_path = resources
570 .get(ResourceType::UnderhillKernel)
571 .expect("validated present");
572 let mut kernel = fs_err::File::open(kernel_path).context(format!(
573 "reading underhill kernel image at {}",
574 kernel_path.display()
575 ))?;
576
577 let initrd = {
578 let initrd_path = resources
579 .get(ResourceType::UnderhillInitrd)
580 .expect("validated present");
581 Some(fs_err::read(initrd_path).context(format!(
582 "reading underhill initrd at {}",
583 initrd_path.display()
584 ))?)
585 };
586
587 let shim_path = resources
588 .get(ResourceType::OpenhclBoot)
589 .expect("validated present");
590 let mut shim = fs_err::File::open(shim_path)
591 .context(format!("reading underhill shim at {}", shim_path.display()))?;
592
593 let mut sidecar =
594 if let Some(sidecar_path) = resources.get(ResourceType::UnderhillSidecar) {
595 Some(fs_err::File::open(sidecar_path).context("reading AP kernel")?)
596 } else {
597 None
598 };
599
600 let initrd_slice = initrd.as_deref();
601
602 let vtl0_load_config = if uefi {
610 let mut inner_loader = loader.nested_loader();
611 let load_info = load_uefi(&mut inner_loader, resources, UefiConfigType::None)?;
612 let vp_context = inner_loader.take_vp_context();
613 Vtl0Config {
614 supports_pcat: loader.loader().arch() == GuestArchKind::X86_64,
615 supports_uefi: Some((load_info, vp_context)),
616 supports_linux: None,
617 }
618 } else if let Some(linux) = linux {
619 let load_info = load_linux(&mut loader.nested_loader(), linux, resources)?;
620 Vtl0Config {
621 supports_pcat: false,
622 supports_uefi: None,
623 supports_linux: Some(Vtl0Linux {
624 command_line: &linux.command_line,
625 load_info,
626 }),
627 }
628 } else {
629 Vtl0Config {
630 supports_pcat: false,
631 supports_uefi: None,
632 supports_linux: None,
633 }
634 };
635
636 let command_line = if loader.loader().confidential_debug() {
637 tracing::info!("enabling underhill confidential debug environment flag");
638 format!(
639 "{command_line} {}=1",
640 OPENHCL_CONFIDENTIAL_DEBUG_ENV_VAR_NAME
641 )
642 } else {
643 command_line.clone()
644 };
645
646 let command_line = if static_command_line {
647 CommandLineType::Static(&command_line)
648 } else {
649 CommandLineType::HostAppendable(&command_line)
650 };
651
652 R::load_openhcl(
653 loader,
654 &mut kernel,
655 &mut shim,
656 sidecar.as_mut(),
657 command_line,
658 initrd_slice,
659 memory_page_base,
660 memory_page_count,
661 vtl0_load_config,
662 )
663 .context("underhill kernel loader")?;
664 }
665 };
666
667 Ok(())
668}
669
670fn load_uefi<R: IgvmfilegenRegister + GuestArch + 'static>(
671 loader: &mut IgvmVtlLoader<'_, R>,
672 resources: &Resources,
673 config_type: UefiConfigType,
674) -> Result<loader::uefi::LoadInfo, anyhow::Error> {
675 let image_path = resources
676 .get(ResourceType::Uefi)
677 .expect("validated present");
678 let image = fs_err::read(image_path)
679 .context(format!("reading uefi image at {}", image_path.display()))?;
680 let config = match config_type {
681 UefiConfigType::None => loader::uefi::ConfigType::None,
682 UefiConfigType::Igvm => loader::uefi::ConfigType::Igvm,
683 };
684 let load_info = R::load_uefi(loader, &image, config).context("uefi loader")?;
685 Ok(load_info)
686}
687
688fn load_linux<R: IgvmfilegenRegister + GuestArch + 'static>(
689 loader: &mut IgvmVtlLoader<'_, R>,
690 config: &LinuxImage,
691 resources: &Resources,
692) -> Result<loader::linux::LoadInfo, anyhow::Error> {
693 let LinuxImage {
694 use_initrd,
695 command_line: _,
696 } = *config;
697 let kernel_path = resources
698 .get(ResourceType::LinuxKernel)
699 .expect("validated present");
700 let mut kernel = fs_err::File::open(kernel_path).context(format!(
701 "reading vtl0 kernel image at {}",
702 kernel_path.display()
703 ))?;
704 let initrd_vec = if use_initrd {
705 let initrd_path = resources
706 .get(ResourceType::LinuxInitrd)
707 .expect("validated present");
708 fs_err::read(initrd_path)
709 .context(format!("reading vtl0 initrd at {}", initrd_path.display()))?
710 } else {
711 Vec::new()
712 };
713 let initrd = if initrd_vec.is_empty() {
714 None
715 } else {
716 Some(InitrdConfig {
717 initrd_address: loader::linux::InitrdAddressType::AfterKernel,
718 initrd: &initrd_vec,
719 })
720 };
721 let load_info = R::load_linux_kernel_and_initrd(loader, &mut kernel, 0, initrd, None)
722 .context("loading linux kernel and initrd")?;
723 Ok(load_info)
724}