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