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