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