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