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