1#![forbid(unsafe_code)]
5#![expect(missing_docs)]
6
7#[cfg(all(not(test), feature = "encryption"))]
8crypto::ensure_single_backend!();
9
10mod storage_backend;
18#[cfg(feature = "test_helpers")]
19mod test;
20mod uefi_nvram;
21mod vmgs_json;
22
23#[cfg(feature = "test_helpers")]
24use crate::test::TestOperation;
25use anyhow::Result;
26use clap::Args;
27use clap::Parser;
28use clap::Subcommand;
29use disk_backend::Disk;
30use disk_vhd1::Vhd1Disk;
31use fs_err::File;
32use pal_async::DefaultPool;
33use std::io::prelude::*;
34use std::path::Path;
35use std::path::PathBuf;
36use thiserror::Error;
37use uefi_nvram::UefiNvramOperation;
38use vmgs::Error as VmgsError;
39use vmgs::GspType;
40use vmgs::Vmgs;
41use vmgs::vmgs_helpers::get_active_header;
42use vmgs::vmgs_helpers::read_headers;
43use vmgs::vmgs_helpers::validate_header;
44use vmgs_format::EncryptionAlgorithm;
45use vmgs_format::FileId;
46use vmgs_format::VMGS_BYTES_PER_BLOCK;
47use vmgs_format::VMGS_DEFAULT_CAPACITY;
48use vmgs_format::VMGS_ENCRYPTION_KEY_SIZE;
49use vmgs_format::VmgsHeader;
50
51const ONE_MEGA_BYTE: u64 = 1024 * 1024;
52const ONE_GIGA_BYTE: u64 = ONE_MEGA_BYTE * 1024;
53const VHD_DISK_FOOTER_PACKED_SIZE: u64 = 512;
54
55#[derive(Debug, Error)]
56pub(crate) enum Error {
57 #[error("VMGS file IO")]
58 VmgsFile(#[source] std::io::Error),
59 #[error("VHD file error")]
60 Vhd1(#[source] disk_vhd1::OpenError),
61 #[error("Invalid disk")]
62 InvalidDisk(#[source] disk_backend::InvalidDisk),
63 #[error("Internal VMGS error")]
64 Vmgs(#[from] VmgsError),
65 #[error("VMGS file already exists")]
66 FileExists,
67 #[cfg(feature = "encryption")]
68 #[error("Adding encryption key")]
69 EncryptionKey(#[source] VmgsError),
70 #[error("Data file / STDOUT IO")]
71 DataFile(#[source] std::io::Error),
72 #[error("The VMGS file has zero size")]
73 ZeroSize,
74 #[error("Invalid VMGS file size: {0} {1}")]
75 InvalidVmgsFileSize(u64, String),
76 #[error("Key file IO")]
77 KeyFile(#[source] std::io::Error),
78 #[error("Key must be {0} bytes long, is {1} bytes instead")]
79 InvalidKeySize(u64, u64),
80 #[error("File is not encrypted")]
81 NotEncrypted,
82 #[error("File must be decrypted to perform this operation but no key was provided")]
83 EncryptedNoKey,
84 #[error("VmgsStorageBackend")]
85 VmgsStorageBackend(#[from] storage_backend::EncryptionNotSupported),
86 #[error("NVRAM storage")]
87 NvramStorage(#[from] uefi_nvram_storage::NvramStorageError),
88 #[error("UEFI NVRAM variable parsing")]
89 NvramParsing(#[from] uefi_nvram_specvars::ParseError),
90 #[error("NVRAM entry not found: {0}")]
91 MissingNvramEntry(ucs2::Ucs2LeVec),
92 #[error("GUID parsing")]
93 Guid(#[from] guid::ParseError),
94 #[error("JSON parsing")]
95 SerdeJson(#[from] serde_json::Error),
96 #[error("Bad JSON contents: {0}")]
97 Json(String),
98 #[error("File ID {0:?} already exists. Use `--allow-overwrite` to ignore.")]
99 FileIdExists(FileId),
100 #[error("VMGS file is encrypted using GspById")]
101 GspByIdEncryption,
102 #[error("VMGS file is encrypted using an unknown encryption scheme")]
103 GspUnknown,
104 #[error("VMGS file is using an unknown encryption algorithm")]
105 EncryptionUnknown,
106 #[error("Unable to parse IGVM file")]
107 IgvmFile(#[source] anyhow::Error),
108}
109
110#[derive(Debug, Clone, Copy)]
120#[repr(i32)]
121enum ExitCode {
122 Error = 1,
123 NotEncrypted = 2,
124 Empty = 3,
125 NotFound = 4,
126 V1Format = 5,
127 GspById = 6,
128 GspUnknown = 7,
129}
130
131#[derive(Debug, Clone, Copy, clap::ValueEnum)]
132#[repr(u32)]
133pub(crate) enum ResourceCode {
134 #[value(name = "NONCONFIDENTIAL")]
135 NonConfidential = 13510,
136 #[value(name = "SNP")]
137 Snp = 13515,
138 #[value(name = "SNP_NO_HCL")]
139 SnpNoHcl = 13516,
140 #[value(name = "TDX")]
141 Tdx = 13520,
142 #[value(name = "TDX_NO_HCL")]
143 TdxNoHcl = 13521,
144}
145
146#[derive(Args)]
147struct FilePathArg {
148 #[clap(short = 'f', long, alias = "filepath")]
150 file_path: PathBuf,
151}
152
153#[derive(Args)]
154struct KeyPathArg {
155 #[clap(short = 'k', long, alias = "keypath")]
157 key_path: Option<PathBuf>,
158}
159
160#[derive(Args)]
161struct FileIdArg {
162 #[clap(short = 'i', long, alias = "fileid", value_parser = parse_file_id)]
164 file_id: FileId,
165}
166
167#[derive(Parser)]
168#[clap(name = "vmgstool", about = "Tool to interact with VMGS files.")]
169#[clap(long_about = r#"Tool to interact with VMGS files.
170
171Unless otherwise noted, everything written to STDOUT and STDERR is unstable
172and subject to change. Automated consumers of VmgsTool should generally parse
173only the exit code. In some cases, the STDOUT of specific subcommands may be
174made stable (ex: query-size). STDERR is for human-readable debug messages and
175is never stable."#)]
176struct CliArgs {
177 #[clap(short = 'v', long)]
180 verbose: bool,
181
182 #[clap(subcommand)]
183 opt: Options,
184}
185
186#[derive(Subcommand)]
187enum Options {
188 Create {
193 #[command(flatten)]
194 file_path: FilePathArg,
195 #[clap(short = 's', long, alias = "filesize")]
197 file_size: Option<u64>,
198 #[clap(
202 short = 'k',
203 long,
204 alias = "keypath",
205 requires = "encryption_algorithm"
206 )]
207 key_path: Option<PathBuf>,
208 #[clap(short = 'e', long, alias = "encryptionalgorithm", requires = "key_path", value_parser = parse_encryption_algorithm)]
212 encryption_algorithm: Option<EncryptionAlgorithm>,
213 #[clap(long, alias = "forcecreate")]
216 force_create: bool,
217 },
218 Write {
222 #[command(flatten)]
223 file_path: FilePathArg,
224 #[clap(short = 'd', long, alias = "datapath")]
226 data_path: PathBuf,
227 #[command(flatten)]
228 file_id: FileIdArg,
229 #[command(flatten)]
230 key_path: KeyPathArg,
231 #[clap(long, alias = "allowoverwrite")]
233 allow_overwrite: bool,
234 },
235 Dump {
241 #[command(flatten)]
242 file_path: FilePathArg,
243 #[clap(short = 'd', long, alias = "datapath")]
245 data_path: Option<PathBuf>,
246 #[command(flatten)]
247 file_id: FileIdArg,
248 #[command(flatten)]
249 key_path: KeyPathArg,
250 #[clap(long, conflicts_with = "data_path")]
252 raw_stdout: bool,
253 },
254 DumpHeaders {
256 #[command(flatten)]
257 file_path: FilePathArg,
258 },
259 QuerySize {
263 #[command(flatten)]
264 file_path: FilePathArg,
265 #[command(flatten)]
266 file_id: FileIdArg,
267 },
268 UpdateKey {
272 #[command(flatten)]
273 file_path: FilePathArg,
274 #[clap(short = 'k', long, alias = "keypath")]
276 key_path: PathBuf,
277 #[clap(short = 'n', long, alias = "newkeypath")]
279 new_key_path: PathBuf,
280 #[clap(short = 'e', long, alias = "encryptionalgorithm", value_parser = parse_encryption_algorithm)]
282 encryption_algorithm: EncryptionAlgorithm,
283 },
284 Encrypt {
286 #[command(flatten)]
287 file_path: FilePathArg,
288 #[clap(short = 'k', long, alias = "keypath")]
290 key_path: PathBuf,
291 #[clap(short = 'e', long, alias = "encryptionalgorithm", value_parser = parse_encryption_algorithm)]
293 encryption_algorithm: EncryptionAlgorithm,
294 },
295 QueryEncryption {
297 #[command(flatten)]
298 file_path: FilePathArg,
299 },
300 Move {
302 #[command(flatten)]
303 file_path: FilePathArg,
304 #[clap(long, alias = "src", value_parser = parse_file_id)]
306 src_file_id: FileId,
307 #[clap(long, alias = "dst", value_parser = parse_file_id)]
309 dst_file_id: FileId,
310 #[command(flatten)]
311 key_path: KeyPathArg,
312 #[clap(long, alias = "allowoverwrite")]
314 allow_overwrite: bool,
315 },
316 Delete {
318 #[command(flatten)]
319 file_path: FilePathArg,
320 #[command(flatten)]
321 file_id: FileIdArg,
322 },
323 DumpFileTable {
325 #[command(flatten)]
326 file_path: FilePathArg,
327 #[command(flatten)]
328 key_path: KeyPathArg,
329 },
330 UefiNvram {
332 #[clap(subcommand)]
333 operation: UefiNvramOperation,
334 },
335 CopyIgvmfile {
337 #[command(flatten)]
338 file_path: FilePathArg,
339 #[clap(short = 'd', long, alias = "datapath")]
341 data_path: PathBuf,
342 #[clap(long, alias = "allowoverwrite")]
344 allow_overwrite: bool,
345 #[clap(short = 'r', long, alias = "resourcecode", value_enum)]
347 resource_code: ResourceCode,
348 },
349 #[cfg(feature = "test_helpers")]
350 Test {
352 #[clap(subcommand)]
353 operation: TestOperation,
354 },
355}
356
357fn parse_file_id(file_id: &str) -> Result<FileId, std::num::ParseIntError> {
358 Ok(match file_id {
359 "FILE_TABLE" => FileId::FILE_TABLE,
360 "BIOS_NVRAM" => FileId::BIOS_NVRAM,
361 "TPM_PPI" => FileId::TPM_PPI,
362 "TPM_NVRAM" => FileId::TPM_NVRAM,
363 "RTC_SKEW" => FileId::RTC_SKEW,
364 "ATTEST" => FileId::ATTEST,
365 "KEY_PROTECTOR" => FileId::KEY_PROTECTOR,
366 "VM_UNIQUE_ID" => FileId::VM_UNIQUE_ID,
367 "GUEST_FIRMWARE" => FileId::GUEST_FIRMWARE,
368 "CUSTOM_UEFI" => FileId::CUSTOM_UEFI,
369 "GUEST_WATCHDOG" => FileId::GUEST_WATCHDOG,
370 "HW_KEY_PROTECTOR" => FileId::HW_KEY_PROTECTOR,
371 "GUEST_SECRET_KEY" => FileId::GUEST_SECRET_KEY,
372 "HIBERNATION_TOKEN" => FileId::HIBERNATION_TOKEN,
373 "PLATFORM_SEED" => FileId::PLATFORM_SEED,
374 "PROVENANCE_DOC" => FileId::PROVENANCE_DOC,
375 "TPM_NVRAM_BACKUP" => FileId::TPM_NVRAM_BACKUP,
376 "EXTENDED_FILE_TABLE" => FileId::EXTENDED_FILE_TABLE,
377 "TPM_185_NVRAM" => FileId::TPM_185_NVRAM,
378 v => FileId(v.parse::<u32>()?),
379 })
380}
381
382fn parse_encryption_algorithm(algorithm: &str) -> Result<EncryptionAlgorithm, &'static str> {
383 match algorithm {
384 "AES_GCM" => Ok(EncryptionAlgorithm::AES_GCM),
385 _ => Err("Encryption algorithm not supported"),
386 }
387}
388
389fn extract_version(ver: u32) -> String {
390 let major = (ver >> 16) & 0xFF;
391 let minor = ver & 0xFF;
392 format!("{major}.{minor}")
393}
394
395fn parse_legacy_args() -> Vec<String> {
396 use std::env;
397 let mut args: Vec<String> = env::args().collect();
398 if let Some(cmd) = args.get(1) {
399 let cmd_lower = cmd.to_ascii_lowercase();
400 let new_cmd = match &cmd_lower[..] {
401 "-c" | "-create" => Some("create"),
402 "-w" | "-write" => Some("write"),
403 "-r" | "-dump" => Some("dump"),
404 "-rh" | "-dumpheaders" => Some("dump-headers"),
405 "-qs" | "-querysize" => Some("query-size"),
406 "-uk" | "-updatekey" => Some("update-key"),
407 "-e" | "-encrypt" => Some("encrypt"),
408 _ => None,
409 };
410
411 if let Some(new_cmd) = new_cmd {
412 eprintln!("Warning: Using legacy arguments. Please migrate to the new syntax.");
414 args[1] = new_cmd.to_string();
415
416 let mut index = 2;
417 while let Some(arg) = args.get(index) {
418 let arg_lower = arg.to_ascii_lowercase();
419 if let Some(new_arg) = match &arg_lower[..] {
420 "-f" | "-filepath" => Some("--file-path"),
421 "-s" | "-filesize" => Some("--file-size"),
422 "-i" | "-fileid" => Some("--file-id"),
423 "-d" | "-datapath" => Some("--data-path"),
424 "-ow" | "-allowoverwrite" => Some("--allow-overwrite"),
425 "-k" | "-keypath" => Some("--key-path"),
426 "-n" | "-newkeypath" => Some("--new-key-path"),
427 "-ea" | "-encryptionalgorithm" => Some("--encryption-algorithm"),
428 "-fc" | "-forcecreate" => Some("--force-create"),
429 _ => None,
430 } {
431 args[index] = new_arg.to_string();
432 }
433 index += 1;
434 }
435 }
436 }
437 args
438}
439
440pub fn init_tracing(verbose: bool) {
442 use tracing::level_filters::LevelFilter;
443 use tracing_subscriber::filter::Targets;
444 use tracing_subscriber::layer::SubscriberExt;
445 use tracing_subscriber::util::SubscriberInitExt;
446
447 let targets = if verbose {
448 Targets::new().with_default(LevelFilter::TRACE)
449 } else {
450 Targets::new()
451 .with_default(LevelFilter::OFF)
452 .with_target("vmgstool", LevelFilter::INFO)
453 };
454
455 tracing_subscriber::fmt()
456 .with_ansi(false)
457 .log_internal_errors(true)
458 .with_writer(std::io::stderr)
459 .with_max_level(LevelFilter::TRACE)
460 .finish()
461 .with(targets)
462 .init();
463}
464
465fn main() {
466 DefaultPool::run_with(async |_| match do_main().await {
467 Ok(_) => tracing::info!("The operation completed successfully."),
468 Err(e) => {
469 let exit_code = match e {
470 Error::NotEncrypted => ExitCode::NotEncrypted,
471 Error::GspByIdEncryption => ExitCode::GspById,
472 Error::GspUnknown => ExitCode::GspUnknown,
473 Error::Vmgs(VmgsError::EmptyFile) | Error::ZeroSize => ExitCode::Empty,
474 Error::Vmgs(VmgsError::FileInfoNotAllocated(_)) => ExitCode::NotFound,
475 Error::Vmgs(VmgsError::V1Format) => ExitCode::V1Format,
476 _ => ExitCode::Error,
477 };
478
479 match e {
480 Error::NotEncrypted | Error::GspByIdEncryption | Error::GspUnknown => {}
482 Error::Vmgs(inner)
484 if matches!(
485 inner,
486 VmgsError::EmptyFile | VmgsError::FileInfoNotAllocated(_)
487 ) =>
488 {
489 tracing::info!("{}", inner)
490 }
491 e => {
493 tracing::error!("{}", e);
494 let mut error_source = std::error::Error::source(&e);
495 while let Some(e2) = error_source {
496 tracing::error!("{}", e2);
497 error_source = e2.source();
498 }
499 }
500 };
501
502 tracing::info!(
503 "The operation completed with exit code: {} ({:?})",
504 exit_code as i32,
505 exit_code
506 );
507
508 std::process::exit(exit_code as i32);
509 }
510 })
511}
512
513async fn do_main() -> Result<(), Error> {
514 let args = CliArgs::parse_from(parse_legacy_args());
515 init_tracing(args.verbose);
516
517 match args.opt {
518 Options::Create {
519 file_path,
520 file_size,
521 key_path,
522 encryption_algorithm,
523 force_create,
524 } => {
525 let encryption_alg_key = encryption_algorithm.map(|x| (x, key_path.unwrap()));
526 vmgs_file_create(
527 file_path.file_path,
528 file_size,
529 force_create,
530 encryption_alg_key,
531 )
532 .await
533 .map(|_| ())
534 }
535 Options::Dump {
536 file_path,
537 data_path,
538 file_id,
539 key_path,
540 raw_stdout,
541 } => {
542 vmgs_file_read(
543 file_path.file_path,
544 data_path,
545 file_id.file_id,
546 key_path.key_path,
547 raw_stdout,
548 )
549 .await
550 }
551 Options::Write {
552 file_path,
553 data_path,
554 file_id,
555 key_path,
556 allow_overwrite,
557 } => {
558 vmgs_file_write(
559 file_path.file_path,
560 data_path,
561 file_id.file_id,
562 key_path.key_path,
563 allow_overwrite,
564 )
565 .await
566 }
567 Options::DumpHeaders { file_path } => vmgs_file_dump_headers(file_path.file_path).await,
568 Options::QuerySize { file_path, file_id } => {
569 vmgs_file_query_file_size(file_path.file_path, file_id.file_id)
570 .await
571 .map(|_| ())
572 }
573 Options::UpdateKey {
574 file_path,
575 key_path,
576 new_key_path,
577 encryption_algorithm,
578 } => {
579 vmgs_file_update_key(
580 file_path.file_path,
581 encryption_algorithm,
582 Some(key_path),
583 new_key_path,
584 )
585 .await
586 }
587 Options::Encrypt {
588 file_path,
589 key_path,
590 encryption_algorithm,
591 } => {
592 vmgs_file_update_key(
593 file_path.file_path,
594 encryption_algorithm,
595 None as Option<PathBuf>,
596 key_path,
597 )
598 .await
599 }
600 Options::QueryEncryption { file_path } => {
601 vmgs_file_query_encryption(file_path.file_path).await
602 }
603 Options::Move {
604 file_path,
605 src_file_id,
606 dst_file_id,
607 key_path,
608 allow_overwrite,
609 } => {
610 vmgs_file_move(
611 file_path.file_path,
612 src_file_id,
613 dst_file_id,
614 key_path.key_path,
615 allow_overwrite,
616 )
617 .await
618 }
619 Options::Delete { file_path, file_id } => {
620 vmgs_file_delete(file_path.file_path, file_id.file_id).await
621 }
622 Options::DumpFileTable {
623 file_path,
624 key_path,
625 } => vmgs_file_dump_file_table(file_path.file_path, key_path.key_path).await,
626 Options::UefiNvram { operation } => uefi_nvram::do_command(operation).await,
627 Options::CopyIgvmfile {
628 file_path,
629 data_path,
630 allow_overwrite,
631 resource_code,
632 } => {
633 vmgs_file_copy_igvmfile(
634 file_path.file_path,
635 data_path,
636 allow_overwrite,
637 resource_code,
638 )
639 .await
640 }
641 #[cfg(feature = "test_helpers")]
642 Options::Test { operation } => test::do_command(operation).await,
643 }
644}
645
646async fn vmgs_file_update_key(
647 file_path: impl AsRef<Path>,
648 encryption_alg: EncryptionAlgorithm,
649 key_path: Option<impl AsRef<Path>>,
650 new_key_path: impl AsRef<Path>,
651) -> Result<(), Error> {
652 let new_encryption_key = read_key_path(new_key_path)?;
653 let mut vmgs = vmgs_file_open(file_path, key_path, OpenMode::ReadWriteRequire).await?;
654
655 vmgs_update_key(&mut vmgs, encryption_alg, new_encryption_key.as_ref()).await
656}
657
658#[cfg_attr(not(feature = "encryption"), expect(unused_variables))]
659async fn vmgs_update_key(
660 vmgs: &mut Vmgs,
661 encryption_alg: EncryptionAlgorithm,
662 new_encryption_key: &[u8],
663) -> Result<(), Error> {
664 #[cfg(not(feature = "encryption"))]
665 unreachable!("encryption requires the encryption feature");
666 #[cfg(feature = "encryption")]
667 {
668 tracing::info!("Updating encryption key");
669 vmgs.update_encryption_key(new_encryption_key, encryption_alg)
670 .await
671 .map_err(Error::EncryptionKey)?;
672
673 Ok(())
674 }
675}
676
677async fn vmgs_file_create(
678 path: impl AsRef<Path>,
679 file_size: Option<u64>,
680 force_create: bool,
681 encryption_alg_key: Option<(EncryptionAlgorithm, impl AsRef<Path>)>,
682) -> Result<Vmgs, Error> {
683 let disk = vhdfiledisk_create(path, file_size, force_create)?;
684
685 let encryption_key = encryption_alg_key
686 .as_ref()
687 .map(|(_, key_path)| read_key_path(key_path))
688 .transpose()?;
689 let encryption_alg_key =
690 encryption_alg_key.map(|(alg, _)| (alg, encryption_key.as_ref().unwrap()));
691
692 let vmgs = vmgs_create(disk, encryption_alg_key).await?;
693
694 Ok(vmgs)
695}
696
697fn vhdfiledisk_create(
698 path: impl AsRef<Path>,
699 req_file_size: Option<u64>,
700 force_create: bool,
701) -> Result<Disk, Error> {
702 const MIN_VMGS_FILE_SIZE: u64 = 4 * VMGS_BYTES_PER_BLOCK as u64;
703 const SECTOR_SIZE: u64 = 512;
704
705 let file_size = req_file_size.unwrap_or(VMGS_DEFAULT_CAPACITY);
707 if file_size < MIN_VMGS_FILE_SIZE || !file_size.is_multiple_of(SECTOR_SIZE) {
708 return Err(Error::InvalidVmgsFileSize(
709 file_size,
710 format!(
711 "Must be a multiple of {} and at least {}",
712 SECTOR_SIZE, MIN_VMGS_FILE_SIZE
713 ),
714 ));
715 }
716
717 let exists = Path::new(path.as_ref()).exists();
720
721 tracing::info!("Creating file: {}", path.as_ref().display());
723 let file = match fs_err::OpenOptions::new()
724 .read(true)
725 .write(true)
726 .create(true)
727 .create_new(!force_create)
728 .open(path.as_ref())
729 {
730 Ok(file) => file,
731 Err(err) if err.kind() == std::io::ErrorKind::AlreadyExists => {
732 return Err(Error::FileExists);
733 }
734 Err(err) => return Err(Error::VmgsFile(err)),
735 };
736
737 let existing_size = exists
739 .then(|| {
740 Ok(file
741 .metadata()?
742 .len()
743 .checked_sub(VHD_DISK_FOOTER_PACKED_SIZE))
744 })
745 .transpose()
746 .map_err(Error::VmgsFile)?
747 .flatten();
748 let needs_resize =
749 !exists || existing_size.is_none_or(|existing_size| file_size != existing_size);
750
751 let default_label = if file_size == VMGS_DEFAULT_CAPACITY {
753 " (default)"
754 } else {
755 ""
756 };
757 if needs_resize {
758 tracing::info!(
759 "Setting file size to {}{}{}",
760 file_size,
761 default_label,
762 existing_size
763 .map(|s| format!(" (previous size: {s})"))
764 .unwrap_or_default(),
765 );
766 file.set_len(file_size).map_err(Error::VmgsFile)?;
767 } else {
768 tracing::info!(
769 "File size is already {}{}, skipping resize",
770 file_size,
771 default_label
772 );
773 }
774
775 let disk = if needs_resize {
777 None
778 } else {
779 Vhd1Disk::open_fixed(file.try_clone().map_err(Error::VmgsFile)?.into(), false)
780 .inspect_err(|e| tracing::info!("No valid VHD header found in existing file: {e:#}"))
781 .ok()
782 };
783
784 let disk = match disk {
786 Some(disk) => {
787 tracing::info!("Valid VHD footer already exists, skipping VHD format");
788 disk
789 }
790 None => {
791 tracing::info!("Formatting VHD");
792 Vhd1Disk::make_fixed(file.file()).map_err(Error::Vhd1)?;
793 Vhd1Disk::open_fixed(file.into(), false).map_err(Error::Vhd1)?
794 }
795 };
796
797 Disk::new(disk).map_err(Error::InvalidDisk)
798}
799
800#[cfg_attr(
801 not(feature = "encryption"),
802 expect(unused_mut),
803 expect(unused_variables)
804)]
805async fn vmgs_create(
806 disk: Disk,
807 encryption_alg_key: Option<(EncryptionAlgorithm, &[u8; VMGS_ENCRYPTION_KEY_SIZE])>,
808) -> Result<Vmgs, Error> {
809 tracing::info!("Formatting VMGS");
810 let mut vmgs = Vmgs::format_new(disk, None).await?;
811
812 if let Some((algorithm, encryption_key)) = encryption_alg_key {
813 tracing::info!("Adding encryption key");
814 #[cfg(feature = "encryption")]
815 vmgs.update_encryption_key(encryption_key, algorithm)
816 .await
817 .map_err(Error::EncryptionKey)?;
818 #[cfg(not(feature = "encryption"))]
819 unreachable!("Encryption requires the encryption feature");
820 }
821
822 Ok(vmgs)
823}
824
825async fn vmgs_file_write(
826 file_path: impl AsRef<Path>,
827 data_path: impl AsRef<Path>,
828 file_id: FileId,
829 key_path: Option<impl AsRef<Path>>,
830 allow_overwrite: bool,
831) -> Result<(), Error> {
832 tracing::info!(
833 "Opening source (raw data file): {}",
834 data_path.as_ref().display()
835 );
836
837 let mut file = File::open(data_path.as_ref()).map_err(Error::DataFile)?;
838 let mut buf = Vec::new();
839
840 file.read_to_end(&mut buf).map_err(Error::DataFile)?;
841
842 tracing::info!("Read {} bytes", buf.len());
843
844 let encrypt = key_path.is_some();
845 let mut vmgs = vmgs_file_open(file_path, key_path, OpenMode::ReadWriteIgnore).await?;
846
847 vmgs_write(&mut vmgs, file_id, &buf, encrypt, allow_overwrite).await?;
848
849 Ok(())
850}
851
852async fn vmgs_write(
853 vmgs: &mut Vmgs,
854 file_id: FileId,
855 data: &[u8],
856 encrypt: bool,
857 allow_overwrite: bool,
858) -> Result<(), Error> {
859 tracing::info!("Writing {}", file_id);
860
861 if let Ok(info) = vmgs.get_file_info(file_id) {
862 if !allow_overwrite && info.valid_bytes > 0 {
863 return Err(Error::FileIdExists(file_id));
864 }
865 if !encrypt && info.encrypted {
866 tracing::warn!("Overwriting encrypted file with plaintext data")
867 }
868 }
869
870 if encrypt {
871 #[cfg(feature = "encryption")]
872 vmgs.write_file_encrypted(file_id, data).await?;
873 #[cfg(not(feature = "encryption"))]
874 unreachable!("Encryption requires the encryption feature");
875 } else {
876 vmgs.write_file_allow_overwrite_encrypted(file_id, data)
877 .await?;
878 }
879
880 Ok(())
881}
882
883async fn vmgs_file_read(
885 file_path: impl AsRef<Path>,
886 data_path: Option<impl AsRef<Path>>,
887 file_id: FileId,
888 key_path: Option<impl AsRef<Path>>,
889 raw_stdout: bool,
890) -> Result<(), Error> {
891 let decrypt = key_path.is_some();
892 let mut vmgs = vmgs_file_open(file_path, key_path, OpenMode::ReadOnlyWarn).await?;
893
894 let file_info = vmgs.get_file_info(file_id)?;
895 if !decrypt && file_info.encrypted {
896 tracing::warn!("Reading encrypted file without decrypting");
897 }
898
899 let buf = vmgs_read(&mut vmgs, file_id, decrypt).await?;
900
901 tracing::info!("Read {} bytes", buf.len());
902 if buf.len() != file_info.valid_bytes as usize {
903 tracing::warn!("Bytes read from VMGS doesn't match file info");
904 }
905
906 if let Some(path) = data_path {
907 tracing::info!("Writing contents to {}", path.as_ref().display());
908 let mut file = File::create(path.as_ref()).map_err(Error::DataFile)?;
909 file.write_all(&buf).map_err(Error::DataFile)?;
910 } else {
911 tracing::info!("Writing contents to stdout");
912 if raw_stdout {
913 let mut stdout = std::io::stdout();
914 stdout.write_all(&buf).map_err(Error::DataFile)?;
915 } else {
916 for c in buf.chunks(16) {
917 for b in c {
918 print!("0x{:02x},", b);
919 }
920 println!(
921 "{:missing$}// {}",
922 ' ',
923 c.iter()
924 .map(|c| if c.is_ascii_graphic() {
925 *c as char
926 } else {
927 '.'
928 })
929 .collect::<String>(),
930 missing = (16 - c.len()) * 5 + 1
931 );
932 }
933 }
934 }
935
936 Ok(())
937}
938
939async fn vmgs_read(vmgs: &mut Vmgs, file_id: FileId, decrypt: bool) -> Result<Vec<u8>, Error> {
940 tracing::info!("Reading {}", file_id);
941 Ok(if decrypt {
942 vmgs.read_file(file_id).await?
943 } else {
944 vmgs.read_file_raw(file_id).await?
945 })
946}
947
948async fn vmgs_file_move(
949 file_path: impl AsRef<Path>,
950 src: FileId,
951 dst: FileId,
952 key_path: Option<impl AsRef<Path>>,
953 allow_overwrite: bool,
954) -> Result<(), Error> {
955 let mut vmgs = vmgs_file_open(file_path, key_path, OpenMode::ReadWriteRequire).await?;
956
957 vmgs_move(&mut vmgs, src, dst, allow_overwrite).await
958}
959
960async fn vmgs_move(
961 vmgs: &mut Vmgs,
962 src: FileId,
963 dst: FileId,
964 allow_overwrite: bool,
965) -> Result<(), Error> {
966 tracing::info!("Moving {} to {}", src, dst);
967
968 vmgs.move_file(src, dst, allow_overwrite).await?;
969
970 Ok(())
971}
972
973async fn vmgs_file_delete(file_path: impl AsRef<Path>, file_id: FileId) -> Result<(), Error> {
974 let mut vmgs = vmgs_file_open(
975 file_path,
976 None as Option<PathBuf>,
977 OpenMode::ReadWriteIgnore,
978 )
979 .await?;
980
981 vmgs_delete(&mut vmgs, file_id).await
982}
983
984async fn vmgs_delete(vmgs: &mut Vmgs, file_id: FileId) -> Result<(), Error> {
985 tracing::info!("Deleting {}", file_id);
986
987 vmgs.delete_file(file_id).await?;
988
989 Ok(())
990}
991
992async fn vmgs_file_dump_file_table(
993 file_path: impl AsRef<Path>,
994 key_path: Option<impl AsRef<Path>>,
995) -> Result<(), Error> {
996 let vmgs = vmgs_file_open(file_path, key_path, OpenMode::ReadOnlyWarn).await?;
997
998 vmgs_dump_file_table(&vmgs)
999}
1000
1001fn vmgs_dump_file_table(vmgs: &Vmgs) -> Result<(), Error> {
1002 println!("FILE TABLE");
1003 println!(
1004 "{0:^7} {1:^25} {2:^9} {3:^9} {4:^9}",
1005 "File ID", "File Name", "Allocated", "Valid", "Encrypted",
1006 );
1007 println!(
1008 "{} {} {} {} {}",
1009 "-".repeat(7),
1010 "-".repeat(25),
1011 "-".repeat(9),
1012 "-".repeat(9),
1013 "-".repeat(9),
1014 );
1015 for (file_id, file_info) in vmgs.dump_file_table() {
1016 println!(
1017 "{0:>7} {1:^25?} {2:>9} {3:>9} {4:^9}",
1018 file_id.0,
1019 file_id,
1020 file_info.allocated_bytes,
1021 file_info.valid_bytes,
1022 file_info.encrypted,
1023 );
1024 }
1025
1026 Ok(())
1027}
1028
1029async fn vmgs_file_dump_headers(file_path: impl AsRef<Path>) -> Result<(), Error> {
1030 tracing::info!("Opening VMGS File: {}", file_path.as_ref().display());
1031
1032 let file = File::open(file_path.as_ref()).map_err(Error::VmgsFile)?;
1033 let disk = vhdfiledisk_open(file, OpenMode::ReadOnlyIgnore)?;
1034
1035 let (headers, res0) = match read_headers(disk).await {
1036 Ok(headers) => (Some(headers), Ok(())),
1037 Err((e, headers)) => (headers, Err(e.into())),
1038 };
1039
1040 if let Some(headers) = headers {
1041 let res1 = vmgs_dump_headers(&headers.0, &headers.1);
1042 if res0.is_err() { res0 } else { res1 }
1043 } else {
1044 res0
1045 }
1046}
1047
1048fn vmgs_dump_headers(header1: &VmgsHeader, header2: &VmgsHeader) -> Result<(), Error> {
1049 println!("FILE HEADERS");
1050 println!("{0:<23} {1:^70} {2:^70}", "Field", "Header 1", "Header 2");
1051 println!("{} {} {}", "-".repeat(23), "-".repeat(70), "-".repeat(70));
1052
1053 let signature1 = format!("{:#018x}", header1.signature);
1054 let signature2 = format!("{:#018x}", header2.signature);
1055 println!(
1056 "{0:<23} {1:>70} {2:>70}",
1057 "Signature:", signature1, signature2
1058 );
1059
1060 println!(
1061 "{0:<23} {1:>70} {2:>70}",
1062 "Version:",
1063 extract_version(header1.version),
1064 extract_version(header2.version)
1065 );
1066 println!(
1067 "{0:<23} {1:>70x} {2:>70x}",
1068 "Checksum:", header1.checksum, header2.checksum
1069 );
1070 println!(
1071 "{0:<23} {1:>70} {2:>70}",
1072 "Sequence:", header1.sequence, header2.sequence
1073 );
1074 println!(
1075 "{0:<23} {1:>70} {2:>70}",
1076 "HeaderSize:", header1.header_size, header2.header_size
1077 );
1078
1079 let file_table_offset1 = format!("{:#010x}", header1.file_table_offset);
1080 let file_table_offset2 = format!("{:#010x}", header2.file_table_offset);
1081 println!(
1082 "{0:<23} {1:>70} {2:>70}",
1083 "FileTableOffset:", file_table_offset1, file_table_offset2
1084 );
1085
1086 println!(
1087 "{0:<23} {1:>70} {2:>70}",
1088 "FileTableSize:", header1.file_table_size, header2.file_table_size
1089 );
1090
1091 let encryption_algorithm1 = format!("{:#06x}", header1.encryption_algorithm.0);
1092 let encryption_algorithm2 = format!("{:#06x}", header2.encryption_algorithm.0);
1093 println!(
1094 "{0:<23} {1:>70} {2:>70}",
1095 "EncryptionAlgorithm:", encryption_algorithm1, encryption_algorithm2
1096 );
1097
1098 let markers1 = format!("{:#06x}", header1.markers.into_bits());
1099 let markers2 = format!("{:#06x}", header2.markers.into_bits());
1100
1101 println!("{0:<23} {1:>70} {2:>70}", "Markers:", markers1, markers2);
1102
1103 println!("{0:<23}", "MetadataKey1:");
1104
1105 let key1_nonce = format!("0x{}", hex::encode(header1.metadata_keys[0].nonce));
1106 let key2_nonce = format!("0x{}", hex::encode(header2.metadata_keys[0].nonce));
1107 println!(
1108 " {0:<19} {1:>70} {2:>70}",
1109 "Nonce:", key1_nonce, key2_nonce
1110 );
1111
1112 let key1_reserved = format!("{:#010x}", header1.metadata_keys[0].reserved);
1113 let key2_reserved = format!("{:#010x}", header2.metadata_keys[0].reserved);
1114 println!(
1115 " {0:<19} {1:>70} {2:>70}",
1116 "Reserved:", key1_reserved, key2_reserved
1117 );
1118
1119 let key1_auth_tag = format!(
1120 "0x{}",
1121 hex::encode(header1.metadata_keys[0].authentication_tag)
1122 );
1123 let key2_auth_tag = format!(
1124 "0x{}",
1125 hex::encode(header2.metadata_keys[0].authentication_tag)
1126 );
1127 println!(
1128 " {0:<19} {1:>70} {2:>70}",
1129 "AuthenticationTag:", key1_auth_tag, key2_auth_tag
1130 );
1131
1132 let key1_encryption_key = format!("0x{}", hex::encode(header1.metadata_keys[0].encryption_key));
1133 let key2_encryption_key = format!("0x{}", hex::encode(header2.metadata_keys[0].encryption_key));
1134 println!(
1135 " {0:<19} {1:>70} {2:>70}",
1136 "EncryptionKey:", key1_encryption_key, key2_encryption_key
1137 );
1138
1139 println!("{0:<23}", "MetadataKey2:");
1140 let key1_nonce = format!("0x{}", hex::encode(header1.metadata_keys[1].nonce));
1141 let key2_nonce = format!("0x{}", hex::encode(header2.metadata_keys[1].nonce));
1142 println!(
1143 " {0:<19} {1:>70} {2:>70}",
1144 "Nonce:", key1_nonce, key2_nonce
1145 );
1146
1147 let key1_reserved = format!("0x{:#010x}", header1.metadata_keys[1].reserved);
1148 let key2_reserved = format!("0x{:#010x}", header2.metadata_keys[1].reserved);
1149 println!(
1150 " {0:<19} {1:>70} {2:>70}",
1151 "Reserved:", key1_reserved, key2_reserved
1152 );
1153
1154 let key1_auth_tag = format!(
1155 "0x{}",
1156 hex::encode(header1.metadata_keys[1].authentication_tag)
1157 );
1158 let key2_auth_tag = format!(
1159 "0x{}",
1160 hex::encode(header2.metadata_keys[1].authentication_tag)
1161 );
1162 println!(
1163 " {0:<19} {1:>70} {2:>70}",
1164 "AuthenticationTag:", key1_auth_tag, key2_auth_tag
1165 );
1166
1167 let key1_encryption_key = format!("0x{}", hex::encode(header1.metadata_keys[1].encryption_key));
1168 let key2_encryption_key = format!("0x{}", hex::encode(header2.metadata_keys[1].encryption_key));
1169 println!(
1170 " {0:<19} {1:>70} {2:>70}",
1171 "EncryptionKey:", key1_encryption_key, key2_encryption_key
1172 );
1173
1174 let key1_reserved1 = format!("0x{:#010x}", header1.reserved_1);
1175 let key2_reserved1 = format!("0x{:#010x}", header2.reserved_1);
1176 println!(
1177 "{0:<23} {1:>70} {2:>70}",
1178 "Reserved:", key1_reserved1, key2_reserved1
1179 );
1180
1181 println!("{} {} {}\n", "-".repeat(23), "-".repeat(70), "-".repeat(70));
1182
1183 print!("Verifying header 1... ");
1184 let header1_result = validate_header(header1);
1185 match &header1_result {
1186 Ok(_) => println!("[VALID]"),
1187 Err(e) => println!("[INVALID] Error: {}", e),
1188 }
1189
1190 print!("Verifying header 2... ");
1191 let header2_result = validate_header(header2);
1192 match &header2_result {
1193 Ok(_) => println!("[VALID]"),
1194 Err(e) => println!("[INVALID] Error: {}", e),
1195 }
1196
1197 match get_active_header(header1_result, header2_result) {
1198 Ok(active_index) => match active_index {
1199 0 => println!("Active header is 1"),
1200 1 => println!("Active header is 2"),
1201 _ => unreachable!(),
1202 },
1203 Err(e) => {
1204 println!("Unable to determine active header");
1205 return Err(Error::Vmgs(e));
1206 }
1207 }
1208
1209 Ok(())
1210}
1211
1212#[derive(Copy, Clone, Debug, PartialEq, Eq)]
1213#[expect(clippy::enum_variant_names)]
1214enum OpenMode {
1215 ReadOnlyIgnore,
1217 ReadOnlyWarn,
1219 ReadWriteIgnore,
1221 ReadWriteRequire,
1223}
1224
1225impl OpenMode {
1226 fn write(&self) -> bool {
1227 match self {
1228 OpenMode::ReadOnlyIgnore | OpenMode::ReadOnlyWarn => false,
1229 OpenMode::ReadWriteIgnore | OpenMode::ReadWriteRequire => true,
1230 }
1231 }
1232}
1233
1234async fn vmgs_file_open(
1235 file_path: impl AsRef<Path>,
1236 key_path: Option<impl AsRef<Path>>,
1237 open_mode: OpenMode,
1238) -> Result<Vmgs, Error> {
1239 tracing::info!("Opening VMGS File: {}", file_path.as_ref().display());
1240 let file = fs_err::OpenOptions::new()
1241 .read(true)
1242 .write(open_mode.write())
1243 .open(file_path.as_ref())
1244 .map_err(Error::VmgsFile)?;
1245
1246 let disk = vhdfiledisk_open(file, open_mode)?;
1247
1248 let encryption_key = key_path.map(read_key_path).transpose()?;
1249
1250 let res = vmgs_open(disk, encryption_key.as_ref(), open_mode).await;
1251
1252 if matches!(
1253 res,
1254 Err(Error::Vmgs(VmgsError::InvalidFormat(_)))
1255 | Err(Error::Vmgs(VmgsError::CorruptFormat(_)))
1256 ) {
1257 tracing::error!("VMGS is corrupted or invalid. Dumping headers.");
1258 let _ = vmgs_file_dump_headers(file_path.as_ref()).await;
1259 }
1260
1261 res
1262}
1263
1264#[cfg_attr(
1265 not(feature = "encryption"),
1266 expect(unused_mut),
1267 expect(unused_variables)
1268)]
1269async fn vmgs_open(
1270 disk: Disk,
1271 encryption_key: Option<&[u8; VMGS_ENCRYPTION_KEY_SIZE]>,
1272 open_mode: OpenMode,
1273) -> Result<Vmgs, Error> {
1274 let mut vmgs: Vmgs = Vmgs::open(disk, None).await?;
1275
1276 if let Some(encryption_key) = encryption_key {
1277 #[cfg(feature = "encryption")]
1278 vmgs.unlock_with_encryption_key(encryption_key).await?;
1279 #[cfg(not(feature = "encryption"))]
1280 unreachable!("Encryption requires the encryption feature");
1281 } else if vmgs.encrypted() {
1282 match open_mode {
1283 OpenMode::ReadWriteRequire => return Err(Error::EncryptedNoKey),
1284 OpenMode::ReadOnlyWarn => tracing::warn!(
1285 "Opening encrypted VMGS file without decrypting. File ID encryption status may be inaccurate."
1286 ),
1287 OpenMode::ReadOnlyIgnore | OpenMode::ReadWriteIgnore => {}
1288 }
1289 }
1290
1291 Ok(vmgs)
1292}
1293
1294fn read_key_path(path: impl AsRef<Path>) -> Result<[u8; VMGS_ENCRYPTION_KEY_SIZE], Error> {
1295 tracing::info!("Reading encryption key: {}", path.as_ref().display());
1296 let metadata = fs_err::metadata(&path).map_err(Error::KeyFile)?;
1297 if metadata.len() != VMGS_ENCRYPTION_KEY_SIZE as u64 {
1298 return Err(Error::InvalidKeySize(
1299 VMGS_ENCRYPTION_KEY_SIZE as u64,
1300 metadata.len(),
1301 ));
1302 }
1303
1304 let bytes = fs_err::read(&path).map_err(Error::KeyFile)?;
1305 let bytes_sized = bytes.try_into().map_err(|bytes: Vec<u8>| {
1306 Error::InvalidKeySize(VMGS_ENCRYPTION_KEY_SIZE as u64, bytes.len() as u64)
1307 })?;
1308 Ok(bytes_sized)
1309}
1310
1311async fn vmgs_file_query_file_size(
1312 file_path: impl AsRef<Path>,
1313 file_id: FileId,
1314) -> Result<u64, Error> {
1315 let vmgs = vmgs_file_open(file_path, None as Option<PathBuf>, OpenMode::ReadOnlyIgnore).await?;
1316
1317 vmgs_query_file_size(&vmgs, file_id)
1318}
1319
1320fn vmgs_query_file_size(vmgs: &Vmgs, file_id: FileId) -> Result<u64, Error> {
1321 let file_size = vmgs.get_file_info(file_id)?.valid_bytes;
1322
1323 tracing::info!("{} has a size of {}", file_id, file_size);
1324
1325 println!("{file_size}");
1327
1328 Ok(file_size)
1329}
1330
1331async fn vmgs_file_query_encryption(file_path: impl AsRef<Path>) -> Result<(), Error> {
1332 let vmgs = vmgs_file_open(file_path, None as Option<PathBuf>, OpenMode::ReadOnlyIgnore).await?;
1333
1334 let encryption_alg = vmgs.get_encryption_algorithm();
1335 tracing::info!("Encryption algorithm: {:?}", encryption_alg);
1336 let gsp_type = vmgs_get_gsp_type(&vmgs);
1337 tracing::info!("Guest state protection type: {:?}", gsp_type);
1338
1339 match (encryption_alg, gsp_type) {
1340 (EncryptionAlgorithm::NONE, _) => Err(Error::NotEncrypted),
1341 (EncryptionAlgorithm::AES_GCM, GspType::GspKey) => Ok(()),
1342 (EncryptionAlgorithm::AES_GCM, GspType::GspById) => Err(Error::GspByIdEncryption),
1343 (EncryptionAlgorithm::AES_GCM, GspType::None) => Err(Error::GspUnknown),
1344 _ => Err(Error::EncryptionUnknown),
1345 }
1346}
1347
1348fn vmgs_get_gsp_type(vmgs: &Vmgs) -> GspType {
1349 if vmgs.check_file_allocated(FileId::KEY_PROTECTOR) {
1350 GspType::GspKey
1351 } else if vmgs.check_file_allocated(FileId::VM_UNIQUE_ID) {
1352 GspType::GspById
1353 } else {
1354 GspType::None
1355 }
1356}
1357
1358fn vhdfiledisk_open(file: File, open_mode: OpenMode) -> Result<Disk, Error> {
1359 let file_size = file.metadata().map_err(Error::VmgsFile)?.len();
1360 validate_size(file_size)?;
1361
1362 let disk = Disk::new(
1363 Vhd1Disk::open_fixed(file.into(), open_mode == OpenMode::ReadOnlyWarn)
1364 .map_err(Error::Vhd1)?,
1365 )
1366 .map_err(Error::InvalidDisk)?;
1367
1368 Ok(disk)
1369}
1370
1371fn validate_size(file_size: u64) -> Result<(), Error> {
1372 const MAX_VMGS_FILE_SIZE: u64 = 4 * ONE_GIGA_BYTE;
1373
1374 if file_size > MAX_VMGS_FILE_SIZE {
1375 return Err(Error::InvalidVmgsFileSize(
1376 file_size,
1377 format!("Must be less than {}", MAX_VMGS_FILE_SIZE),
1378 ));
1379 }
1380
1381 if file_size == 0 {
1382 return Err(Error::ZeroSize);
1383 }
1384
1385 if file_size < VHD_DISK_FOOTER_PACKED_SIZE {
1386 return Err(Error::InvalidVmgsFileSize(
1387 file_size,
1388 format!("Must be greater than {}", VHD_DISK_FOOTER_PACKED_SIZE),
1389 ));
1390 }
1391
1392 Ok(())
1393}
1394
1395async fn vmgs_file_copy_igvmfile(
1396 file_path: impl AsRef<Path>,
1397 data_path: impl AsRef<Path>,
1398 allow_overwrite: bool,
1399 resource_code: ResourceCode,
1400) -> Result<(), Error> {
1401 let mut vmgs = vmgs_file_open(file_path, None::<PathBuf>, OpenMode::ReadWriteIgnore).await?;
1402
1403 tracing::info!("Reading IGVM file from: {}", data_path.as_ref().display());
1404
1405 let bytes = read_igvmfile(data_path.as_ref(), resource_code).await?;
1406
1407 vmgs_write(
1408 &mut vmgs,
1409 FileId::GUEST_FIRMWARE,
1410 &bytes,
1411 false,
1413 allow_overwrite,
1414 )
1415 .await?;
1416
1417 Ok(())
1418}
1419
1420async fn read_igvmfile(
1421 dll_path: impl AsRef<Path>,
1422 resource_code: ResourceCode,
1423) -> Result<Vec<u8>, Error> {
1424 use std::io::{Read, Seek, SeekFrom};
1425
1426 let dll_path = dll_path.as_ref();
1427 let file = File::open(dll_path).map_err(Error::DataFile)?;
1428
1429 let resource_id = resource_code as u32;
1431 let descriptor = resource_dll_parser::DllResourceDescriptor::new(b"VMFW", resource_id);
1432 let (start, len) = resource_dll_parser::try_find_resource_from_dll(&file, &descriptor)
1433 .map_err(Error::IgvmFile)?
1434 .ok_or_else(|| {
1435 Error::IgvmFile(anyhow::anyhow!(
1436 "Unable to read IGVM resource 'VMFW' id {} from '{}': file is not a valid PE DLL",
1437 resource_id,
1438 dll_path.display()
1439 ))
1440 })?;
1441
1442 const MAX_IGVM_SIZE: usize = 256 * 1024 * 1024; if len > MAX_IGVM_SIZE {
1445 return Err(Error::IgvmFile(anyhow::anyhow!(
1446 "IGVM resource size {} in '{}' exceeds maximum allowed size of {} bytes",
1447 len,
1448 dll_path.display(),
1449 MAX_IGVM_SIZE
1450 )));
1451 }
1452
1453 let mut file = file;
1455 file.seek(SeekFrom::Start(start)).map_err(Error::DataFile)?;
1456
1457 let mut bytes = vec![0u8; len];
1458 file.read_exact(&mut bytes).map_err(Error::DataFile)?;
1459
1460 tracing::info!("Successfully loaded IGVM file from DLL");
1461 tracing::info!("Read {} bytes", bytes.len());
1462
1463 Ok(bytes)
1464}
1465
1466#[cfg(test)]
1467mod tests {
1468 use super::*;
1469 use pal_async::async_test;
1470 use tempfile::tempdir;
1471
1472 const ONE_MEGA_BYTE: u64 = 1024 * 1024;
1473
1474 pub(crate) async fn test_vmgs_create(
1475 path: impl AsRef<Path>,
1476 file_size: Option<u64>,
1477 force_create: bool,
1478 encryption_alg_key: Option<(EncryptionAlgorithm, &[u8; VMGS_ENCRYPTION_KEY_SIZE])>,
1479 ) -> Result<(), Error> {
1480 let disk = vhdfiledisk_create(path, file_size, force_create)?;
1481 let _ = vmgs_create(disk, encryption_alg_key).await?;
1482 Ok(())
1483 }
1484
1485 pub(crate) async fn test_vmgs_open(
1486 path: impl AsRef<Path>,
1487 open_mode: OpenMode,
1488 encryption_key: Option<&[u8; VMGS_ENCRYPTION_KEY_SIZE]>,
1489 ) -> Result<Vmgs, Error> {
1490 let file = fs_err::OpenOptions::new()
1491 .read(true)
1492 .write(open_mode.write())
1493 .open(path.as_ref())
1494 .map_err(Error::VmgsFile)?;
1495 let disk = vhdfiledisk_open(file, open_mode)?;
1496 let vmgs = vmgs_open(disk, encryption_key, open_mode).await?;
1497 Ok(vmgs)
1498 }
1499
1500 async fn test_vmgs_query_file_size(
1501 file_path: impl AsRef<Path>,
1502 file_id: FileId,
1503 ) -> Result<u64, Error> {
1504 let vmgs =
1505 vmgs_file_open(file_path, None as Option<PathBuf>, OpenMode::ReadOnlyIgnore).await?;
1506
1507 vmgs_query_file_size(&vmgs, file_id)
1508 }
1509
1510 #[cfg(feature = "encryption")]
1511 async fn test_vmgs_query_encryption(
1512 file_path: impl AsRef<Path>,
1513 ) -> Result<EncryptionAlgorithm, Error> {
1514 let vmgs =
1515 vmgs_file_open(file_path, None as Option<PathBuf>, OpenMode::ReadOnlyIgnore).await?;
1516
1517 Ok(vmgs.get_encryption_algorithm())
1518 }
1519
1520 #[cfg(feature = "encryption")]
1521 async fn test_vmgs_update_key(
1522 file_path: impl AsRef<Path>,
1523 encryption_alg: EncryptionAlgorithm,
1524 encryption_key: Option<&[u8; VMGS_ENCRYPTION_KEY_SIZE]>,
1525 new_encryption_key: &[u8; VMGS_ENCRYPTION_KEY_SIZE],
1526 ) -> Result<(), Error> {
1527 let mut vmgs =
1528 test_vmgs_open(file_path, OpenMode::ReadWriteRequire, encryption_key).await?;
1529
1530 vmgs_update_key(&mut vmgs, encryption_alg, new_encryption_key).await
1531 }
1532
1533 fn new_path() -> (tempfile::TempDir, PathBuf) {
1535 let dir = tempdir().unwrap();
1536 let file_path = dir.path().join("test.vmgs");
1537 (dir, file_path)
1538 }
1539
1540 #[async_test]
1541 async fn read_invalid_file() {
1542 let (_dir, path) = new_path();
1543
1544 let result = test_vmgs_open(path, OpenMode::ReadOnlyWarn, None).await;
1545
1546 assert!(result.is_err());
1547 }
1548
1549 #[async_test]
1550 async fn read_empty_file() {
1551 let (_dir, path) = new_path();
1552
1553 test_vmgs_create(&path, None, false, None).await.unwrap();
1554
1555 let mut vmgs = test_vmgs_open(path, OpenMode::ReadOnlyWarn, None)
1556 .await
1557 .unwrap();
1558 let result = vmgs_read(&mut vmgs, FileId::FILE_TABLE, false).await;
1559 assert!(result.is_err());
1560 }
1561
1562 #[async_test]
1563 async fn read_write_file() {
1564 let (_dir, path) = new_path();
1565 let buf = b"Plain text data".to_vec();
1566
1567 test_vmgs_create(&path, None, false, None).await.unwrap();
1568
1569 let mut vmgs = test_vmgs_open(path, OpenMode::ReadWriteRequire, None)
1570 .await
1571 .unwrap();
1572
1573 vmgs_write(&mut vmgs, FileId::ATTEST, &buf, false, false)
1574 .await
1575 .unwrap();
1576 let read_buf = vmgs_read(&mut vmgs, FileId::ATTEST, false).await.unwrap();
1577
1578 assert_eq!(buf, read_buf);
1579 }
1580
1581 #[async_test]
1582 async fn multiple_write_file() {
1583 let (_dir, path) = new_path();
1584 let buf_1 = b"Random super sensitive data".to_vec();
1585 let buf_2 = b"Other super secret data".to_vec();
1586 let buf_3 = b"I'm storing so much data".to_vec();
1587
1588 test_vmgs_create(&path, None, false, None).await.unwrap();
1589
1590 let mut vmgs = test_vmgs_open(path, OpenMode::ReadWriteRequire, None)
1591 .await
1592 .unwrap();
1593
1594 vmgs_write(&mut vmgs, FileId::BIOS_NVRAM, &buf_1, false, false)
1595 .await
1596 .unwrap();
1597 let read_buf_1 = vmgs_read(&mut vmgs, FileId::BIOS_NVRAM, false)
1598 .await
1599 .unwrap();
1600
1601 assert_eq!(buf_1, read_buf_1);
1602
1603 vmgs_write(&mut vmgs, FileId::TPM_PPI, &buf_2, false, false)
1604 .await
1605 .unwrap();
1606 let read_buf_2 = vmgs_read(&mut vmgs, FileId::TPM_PPI, false).await.unwrap();
1607
1608 assert_eq!(buf_2, read_buf_2);
1609
1610 let result = vmgs_write(&mut vmgs, FileId::BIOS_NVRAM, &buf_3, false, false).await;
1611 assert!(result.is_err());
1612
1613 vmgs_write(&mut vmgs, FileId::BIOS_NVRAM, &buf_3, false, true)
1614 .await
1615 .unwrap();
1616 let read_buf_3 = vmgs_read(&mut vmgs, FileId::BIOS_NVRAM, false)
1617 .await
1618 .unwrap();
1619
1620 assert_eq!(buf_2, read_buf_2);
1621 assert_eq!(buf_3, read_buf_3);
1622 }
1623
1624 #[cfg(feature = "encryption")]
1625 #[async_test]
1626 async fn read_write_encrypted_file() {
1627 let (_dir, path) = new_path();
1628 let encryption_key = [5; VMGS_ENCRYPTION_KEY_SIZE];
1629 let buf_1 = b"123".to_vec();
1630
1631 test_vmgs_create(
1632 &path,
1633 None,
1634 false,
1635 Some((EncryptionAlgorithm::AES_GCM, &encryption_key)),
1636 )
1637 .await
1638 .unwrap();
1639
1640 let mut vmgs = test_vmgs_open(path, OpenMode::ReadWriteRequire, Some(&encryption_key))
1641 .await
1642 .unwrap();
1643
1644 vmgs_write(&mut vmgs, FileId::BIOS_NVRAM, &buf_1, true, false)
1645 .await
1646 .unwrap();
1647 let read_buf = vmgs_read(&mut vmgs, FileId::BIOS_NVRAM, true)
1648 .await
1649 .unwrap();
1650
1651 assert!(read_buf == buf_1);
1652
1653 vmgs_write(&mut vmgs, FileId::TPM_PPI, &buf_1, false, false)
1655 .await
1656 .unwrap();
1657
1658 let _encrypted_read = vmgs_read(&mut vmgs, FileId::BIOS_NVRAM, false)
1660 .await
1661 .unwrap();
1662 }
1663
1664 #[cfg(feature = "encryption")]
1665 #[async_test]
1666 async fn encrypted_read_write_plain_file() {
1667 let (_dir, path) = new_path();
1670 let encryption_key = [5; VMGS_ENCRYPTION_KEY_SIZE];
1671
1672 test_vmgs_create(&path, None, false, None).await.unwrap();
1673
1674 let result = test_vmgs_open(path, OpenMode::ReadWriteRequire, Some(&encryption_key)).await;
1675
1676 assert!(result.is_err());
1677 }
1678
1679 #[cfg(feature = "encryption")]
1680 #[async_test]
1681 async fn plain_read_write_encrypted_file() {
1682 let (_dir, path) = new_path();
1683 let encryption_key = [5; VMGS_ENCRYPTION_KEY_SIZE];
1684 let buf_1 = b"123".to_vec();
1685
1686 test_vmgs_create(
1687 &path,
1688 None,
1689 false,
1690 Some((EncryptionAlgorithm::AES_GCM, &encryption_key)),
1691 )
1692 .await
1693 .unwrap();
1694
1695 let mut vmgs = test_vmgs_open(path, OpenMode::ReadWriteIgnore, None)
1696 .await
1697 .unwrap();
1698
1699 vmgs_write(&mut vmgs, FileId::VM_UNIQUE_ID, &buf_1, false, false)
1700 .await
1701 .unwrap();
1702 let read_buf = vmgs_read(&mut vmgs, FileId::VM_UNIQUE_ID, false)
1703 .await
1704 .unwrap();
1705
1706 assert!(read_buf == buf_1);
1707 }
1708
1709 #[async_test]
1710 async fn query_size() {
1711 let (_dir, path) = new_path();
1712 let buf = b"Plain text data".to_vec();
1713
1714 test_vmgs_create(&path, None, false, None).await.unwrap();
1715
1716 {
1717 let mut vmgs = test_vmgs_open(&path, OpenMode::ReadWriteRequire, None)
1718 .await
1719 .unwrap();
1720
1721 vmgs_write(&mut vmgs, FileId::ATTEST, &buf, false, false)
1722 .await
1723 .unwrap();
1724 }
1725
1726 let file_size = test_vmgs_query_file_size(&path, FileId::ATTEST)
1727 .await
1728 .unwrap();
1729 assert_eq!(file_size, buf.len() as u64);
1730 }
1731
1732 #[cfg(feature = "encryption")]
1733 #[async_test]
1734 async fn query_encrypted_file() {
1735 let (_dir, path) = new_path();
1736 let encryption_key = [5; VMGS_ENCRYPTION_KEY_SIZE];
1737 let buf_1 = b"123".to_vec();
1738
1739 test_vmgs_create(
1740 &path,
1741 None,
1742 false,
1743 Some((EncryptionAlgorithm::AES_GCM, &encryption_key)),
1744 )
1745 .await
1746 .unwrap();
1747
1748 {
1749 let mut vmgs = test_vmgs_open(&path, OpenMode::ReadWriteRequire, Some(&encryption_key))
1750 .await
1751 .unwrap();
1752
1753 vmgs_write(&mut vmgs, FileId::BIOS_NVRAM, &buf_1, true, false)
1754 .await
1755 .unwrap();
1756 }
1757
1758 let file_size = test_vmgs_query_file_size(&path, FileId::BIOS_NVRAM)
1759 .await
1760 .unwrap();
1761 assert_eq!(file_size, buf_1.len() as u64);
1762 }
1763
1764 #[async_test]
1765 async fn test_validate_vmgs_file_not_empty() {
1766 let buf: Vec<u8> = (0..255).collect();
1767 let (_dir, path) = new_path();
1768
1769 {
1771 fs_err::OpenOptions::new()
1772 .write(true)
1773 .create_new(true)
1774 .open(&path)
1775 .unwrap();
1776 }
1777
1778 {
1780 let result = test_vmgs_open(&path, OpenMode::ReadOnlyWarn, None).await;
1781 assert!(matches!(result, Err(Error::ZeroSize)));
1782 }
1783
1784 {
1786 vhdfiledisk_create(&path, None, true).unwrap();
1787 }
1788
1789 {
1791 let result = test_vmgs_open(&path, OpenMode::ReadOnlyWarn, None).await;
1792 assert!(matches!(result, Err(Error::Vmgs(VmgsError::EmptyFile))));
1793 }
1794
1795 {
1797 let mut file = fs_err::OpenOptions::new()
1798 .read(true)
1799 .write(true)
1800 .open(&path)
1801 .unwrap();
1802 file.seek(std::io::SeekFrom::Start(1024)).unwrap();
1803 file.write_all(&buf).unwrap();
1804 }
1805
1806 {
1808 let result = test_vmgs_open(&path, OpenMode::ReadOnlyWarn, None).await;
1809 matches!(result, Err(Error::Vmgs(VmgsError::CorruptFormat(_))));
1810 }
1811
1812 {
1814 test_vmgs_create(&path, None, true, None).await.unwrap();
1815 }
1816
1817 {
1819 test_vmgs_open(&path, OpenMode::ReadOnlyWarn, None)
1820 .await
1821 .unwrap();
1822 }
1823 }
1824
1825 #[async_test]
1826 async fn test_misaligned_size() {
1827 let (_dir, path) = new_path();
1828 let result = test_vmgs_create(&path, Some(65537), false, None).await;
1830 assert!(result.is_err());
1831 assert!(!path.exists());
1832 }
1833
1834 #[async_test]
1835 async fn test_forcecreate() {
1836 let (_dir, path) = new_path();
1837 let result = test_vmgs_create(&path, Some(4194304), false, None).await;
1838 assert!(result.is_ok());
1839 let result = test_vmgs_create(&path, Some(4194304), false, None).await;
1841 assert!(result.is_err());
1842 let result = test_vmgs_create(&path, Some(8388608), true, None).await;
1844 assert!(result.is_ok());
1845 }
1846
1847 #[cfg(feature = "encryption")]
1848 #[async_test]
1849 async fn test_update_encryption_key() {
1850 let (_dir, path) = new_path();
1851 let encryption_key = [5; VMGS_ENCRYPTION_KEY_SIZE];
1852 let new_encryption_key = [6; VMGS_ENCRYPTION_KEY_SIZE];
1853 let buf_1 = b"123".to_vec();
1854
1855 test_vmgs_create(
1856 &path,
1857 None,
1858 false,
1859 Some((EncryptionAlgorithm::AES_GCM, &encryption_key)),
1860 )
1861 .await
1862 .unwrap();
1863
1864 {
1865 let mut vmgs = test_vmgs_open(&path, OpenMode::ReadWriteRequire, Some(&encryption_key))
1866 .await
1867 .unwrap();
1868
1869 vmgs_write(&mut vmgs, FileId::BIOS_NVRAM, &buf_1, true, false)
1870 .await
1871 .unwrap();
1872 }
1873
1874 test_vmgs_update_key(
1875 &path,
1876 EncryptionAlgorithm::AES_GCM,
1877 Some(&encryption_key),
1878 &new_encryption_key,
1879 )
1880 .await
1881 .unwrap();
1882
1883 {
1884 let mut vmgs = test_vmgs_open(&path, OpenMode::ReadOnlyWarn, Some(&new_encryption_key))
1885 .await
1886 .unwrap();
1887
1888 let read_buf = vmgs_read(&mut vmgs, FileId::BIOS_NVRAM, true)
1889 .await
1890 .unwrap();
1891 assert!(read_buf == buf_1);
1892 }
1893
1894 let result = test_vmgs_open(&path, OpenMode::ReadOnlyWarn, Some(&encryption_key)).await;
1896 assert!(result.is_err());
1897 }
1898
1899 #[cfg(feature = "encryption")]
1900 #[async_test]
1901 async fn test_add_encryption_key() {
1902 let (_dir, path) = new_path();
1903 let encryption_key = [5; VMGS_ENCRYPTION_KEY_SIZE];
1904 let buf_1 = b"123".to_vec();
1905
1906 test_vmgs_create(&path, None, false, None).await.unwrap();
1907
1908 test_vmgs_update_key(&path, EncryptionAlgorithm::AES_GCM, None, &encryption_key)
1909 .await
1910 .unwrap();
1911
1912 let mut vmgs = test_vmgs_open(&path, OpenMode::ReadWriteRequire, Some(&encryption_key))
1913 .await
1914 .unwrap();
1915
1916 vmgs_write(&mut vmgs, FileId::BIOS_NVRAM, &buf_1, true, false)
1917 .await
1918 .unwrap();
1919
1920 let read_buf = vmgs_read(&mut vmgs, FileId::BIOS_NVRAM, true)
1921 .await
1922 .unwrap();
1923
1924 assert!(read_buf == buf_1);
1925 }
1926
1927 #[cfg(feature = "encryption")]
1928 #[async_test]
1929 async fn test_query_encryption_update() {
1930 let (_dir, path) = new_path();
1931 let encryption_key = [5; VMGS_ENCRYPTION_KEY_SIZE];
1932
1933 test_vmgs_create(&path, None, false, None).await.unwrap();
1934
1935 let encryption_algorithm = test_vmgs_query_encryption(&path).await.unwrap();
1936 assert_eq!(encryption_algorithm, EncryptionAlgorithm::NONE);
1937
1938 test_vmgs_update_key(&path, EncryptionAlgorithm::AES_GCM, None, &encryption_key)
1939 .await
1940 .unwrap();
1941
1942 let encryption_algorithm = test_vmgs_query_encryption(&path).await.unwrap();
1943 assert_eq!(encryption_algorithm, EncryptionAlgorithm::AES_GCM);
1944 }
1945
1946 #[cfg(feature = "encryption")]
1947 #[async_test]
1948 async fn test_query_encryption_new() {
1949 let (_dir, path) = new_path();
1950 let encryption_key = [5; VMGS_ENCRYPTION_KEY_SIZE];
1951
1952 test_vmgs_create(
1953 &path,
1954 None,
1955 false,
1956 Some((EncryptionAlgorithm::AES_GCM, &encryption_key)),
1957 )
1958 .await
1959 .unwrap();
1960
1961 let encryption_algorithm = test_vmgs_query_encryption(&path).await.unwrap();
1962 assert_eq!(encryption_algorithm, EncryptionAlgorithm::AES_GCM);
1963 }
1964
1965 #[async_test]
1966 async fn move_delete_file() {
1967 let (_dir, path) = new_path();
1968 let buf = b"Plain text data".to_vec();
1969
1970 test_vmgs_create(&path, None, false, None).await.unwrap();
1971
1972 let mut vmgs = test_vmgs_open(path, OpenMode::ReadWriteRequire, None)
1973 .await
1974 .unwrap();
1975
1976 vmgs_write(&mut vmgs, FileId::TPM_NVRAM, &buf, false, false)
1977 .await
1978 .unwrap();
1979 let read_buf = vmgs_read(&mut vmgs, FileId::TPM_NVRAM, false)
1980 .await
1981 .unwrap();
1982 assert_eq!(buf, read_buf);
1983
1984 vmgs_move(
1985 &mut vmgs,
1986 FileId::TPM_NVRAM,
1987 FileId::TPM_NVRAM_BACKUP,
1988 false,
1989 )
1990 .await
1991 .unwrap();
1992 vmgs_read(&mut vmgs, FileId::TPM_NVRAM, false)
1993 .await
1994 .unwrap_err();
1995 let read_buf = vmgs_read(&mut vmgs, FileId::TPM_NVRAM_BACKUP, false)
1996 .await
1997 .unwrap();
1998 assert_eq!(buf, read_buf);
1999 vmgs_delete(&mut vmgs, FileId::TPM_NVRAM_BACKUP)
2000 .await
2001 .unwrap();
2002 vmgs_read(&mut vmgs, FileId::TPM_NVRAM_BACKUP, false)
2003 .await
2004 .unwrap_err();
2005 }
2006
2007 #[cfg(feature = "encryption")]
2008 #[async_test]
2009 async fn move_delete_file_encrypted() {
2010 let (_dir, path) = new_path();
2011 let encryption_key = [5; VMGS_ENCRYPTION_KEY_SIZE];
2012 let buf_1 = b"123".to_vec();
2013 let buf_2 = b"456".to_vec();
2014
2015 test_vmgs_create(
2016 &path,
2017 None,
2018 false,
2019 Some((EncryptionAlgorithm::AES_GCM, &encryption_key)),
2020 )
2021 .await
2022 .unwrap();
2023
2024 {
2025 let mut vmgs = test_vmgs_open(&path, OpenMode::ReadWriteRequire, Some(&encryption_key))
2026 .await
2027 .unwrap();
2028
2029 vmgs_write(&mut vmgs, FileId::BIOS_NVRAM, &buf_2, true, false)
2030 .await
2031 .unwrap();
2032 vmgs_write(&mut vmgs, FileId::TPM_NVRAM, &buf_1, true, false)
2033 .await
2034 .unwrap();
2035 let read_buf = vmgs_read(&mut vmgs, FileId::TPM_NVRAM, true).await.unwrap();
2036 assert!(read_buf == buf_1);
2037
2038 vmgs_move(
2039 &mut vmgs,
2040 FileId::TPM_NVRAM,
2041 FileId::TPM_NVRAM_BACKUP,
2042 false,
2043 )
2044 .await
2045 .unwrap();
2046 let read_buf = vmgs_read(&mut vmgs, FileId::TPM_NVRAM_BACKUP, true)
2047 .await
2048 .unwrap();
2049 assert!(read_buf == buf_1);
2050 }
2051
2052 {
2054 let mut vmgs = test_vmgs_open(&path, OpenMode::ReadWriteIgnore, None)
2055 .await
2056 .unwrap();
2057 vmgs_delete(&mut vmgs, FileId::TPM_NVRAM_BACKUP)
2058 .await
2059 .unwrap();
2060 vmgs_read(&mut vmgs, FileId::TPM_NVRAM_BACKUP, false)
2061 .await
2062 .unwrap_err();
2063 }
2064
2065 {
2067 let mut vmgs = test_vmgs_open(&path, OpenMode::ReadWriteRequire, Some(&encryption_key))
2068 .await
2069 .unwrap();
2070 let read_buf = vmgs_read(&mut vmgs, FileId::BIOS_NVRAM, true)
2071 .await
2072 .unwrap();
2073 assert!(read_buf == buf_2);
2074 }
2075 }
2076
2077 fn create_test_vmfw_dll(payload: &[u8], resource_id: u32) -> Vec<u8> {
2080 const DOS_HEADER_SIZE: usize = 64;
2082 const PE_SIG_SIZE: usize = 4;
2083 const COFF_HEADER_SIZE: usize = 20;
2084 const OPTIONAL_HEADER_SIZE: usize = 240;
2085 const HEADERS_SIZE: usize = 0x200; const RSRC_SECTION_SIZE: usize = 0x200;
2087
2088 let mut pe = vec![0u8; HEADERS_SIZE + RSRC_SECTION_SIZE];
2089
2090 pe[0..2].copy_from_slice(b"MZ"); pe[60..64].copy_from_slice(&64u32.to_le_bytes()); let mut offset = DOS_HEADER_SIZE;
2095
2096 pe[offset..offset + PE_SIG_SIZE].copy_from_slice(b"PE\0\0");
2098 offset += PE_SIG_SIZE;
2099
2100 pe[offset..offset + 2].copy_from_slice(&0x8664u16.to_le_bytes()); pe[offset + 2..offset + 4].copy_from_slice(&1u16.to_le_bytes()); pe[offset + 16..offset + 18].copy_from_slice(&240u16.to_le_bytes()); pe[offset + 18..offset + 20].copy_from_slice(&0x2022u16.to_le_bytes()); offset += COFF_HEADER_SIZE;
2106
2107 let opt_start = offset;
2109 pe[opt_start..opt_start + 2].copy_from_slice(&0x20bu16.to_le_bytes()); pe[opt_start + 56..opt_start + 60].copy_from_slice(&0x3000u32.to_le_bytes()); pe[opt_start + 60..opt_start + 64].copy_from_slice(&0x200u32.to_le_bytes()); pe[opt_start + 108..opt_start + 112].copy_from_slice(&16u32.to_le_bytes()); let rsrc_dir_offset = opt_start + 112 + 2 * 8;
2116 pe[rsrc_dir_offset..rsrc_dir_offset + 4].copy_from_slice(&0x1000u32.to_le_bytes());
2117 pe[rsrc_dir_offset + 4..rsrc_dir_offset + 8].copy_from_slice(&0x200u32.to_le_bytes());
2118 offset += OPTIONAL_HEADER_SIZE;
2119
2120 pe[offset..offset + 8].copy_from_slice(b".rsrc\0\0\0");
2122 pe[offset + 8..offset + 12].copy_from_slice(&0x200u32.to_le_bytes()); pe[offset + 12..offset + 16].copy_from_slice(&0x1000u32.to_le_bytes()); pe[offset + 16..offset + 20].copy_from_slice(&0x200u32.to_le_bytes()); pe[offset + 20..offset + 24].copy_from_slice(&0x200u32.to_le_bytes()); pe[offset + 36..offset + 40].copy_from_slice(&0x40000040u32.to_le_bytes()); let rsrc_base = HEADERS_SIZE;
2130
2131 pe[rsrc_base + 12..rsrc_base + 14].copy_from_slice(&1u16.to_le_bytes()); pe[rsrc_base + 0x10..rsrc_base + 0x14].copy_from_slice(&0x80000018u32.to_le_bytes());
2147 pe[rsrc_base + 0x14..rsrc_base + 0x18].copy_from_slice(&0x80000028u32.to_le_bytes());
2148
2149 pe[rsrc_base + 0x18..rsrc_base + 0x1a].copy_from_slice(&4u16.to_le_bytes());
2151 pe[rsrc_base + 0x1a..rsrc_base + 0x22]
2152 .copy_from_slice(&[b'V', 0, b'M', 0, b'F', 0, b'W', 0]);
2153
2154 pe[rsrc_base + 0x28 + 14..rsrc_base + 0x28 + 16].copy_from_slice(&1u16.to_le_bytes()); pe[rsrc_base + 0x38..rsrc_base + 0x3c].copy_from_slice(&resource_id.to_le_bytes());
2159 pe[rsrc_base + 0x3c..rsrc_base + 0x40].copy_from_slice(&0x80000040u32.to_le_bytes());
2160
2161 pe[rsrc_base + 0x40 + 14..rsrc_base + 0x40 + 16].copy_from_slice(&1u16.to_le_bytes()); pe[rsrc_base + 0x50..rsrc_base + 0x54].copy_from_slice(&0x0409u32.to_le_bytes()); pe[rsrc_base + 0x54..rsrc_base + 0x58].copy_from_slice(&0x58u32.to_le_bytes());
2167
2168 let data_rva = 0x1000u32 + 0x68; pe[rsrc_base + 0x58..rsrc_base + 0x5c].copy_from_slice(&data_rva.to_le_bytes());
2171 pe[rsrc_base + 0x5c..rsrc_base + 0x60]
2172 .copy_from_slice(&(payload.len() as u32).to_le_bytes());
2173
2174 let payload_offset = rsrc_base + 0x68;
2176 let required_len = payload_offset + payload.len();
2177 if required_len > pe.len() {
2178 pe.resize(required_len, 0);
2179 }
2180 pe[payload_offset..required_len].copy_from_slice(payload);
2181
2182 pe
2183 }
2184
2185 #[async_test]
2186 async fn read_write_igvmfile() {
2187 let dir = tempdir().unwrap();
2188 let path = dir.path().join("test.vmgs");
2189
2190 let expected_payload = b"TEST_IGVM_FIRMWARE_PAYLOAD_DATA";
2192 let dll_data = create_test_vmfw_dll(expected_payload, ResourceCode::Snp as u32);
2193
2194 let dll_path = dir.path().join("test_vmfw.dll");
2196 fs_err::write(&dll_path, &dll_data).unwrap();
2197
2198 test_vmgs_create(&path, Some(ONE_MEGA_BYTE * 8), false, None)
2199 .await
2200 .unwrap();
2201
2202 let mut vmgs = test_vmgs_open(&path, OpenMode::ReadWriteIgnore, None)
2203 .await
2204 .unwrap();
2205
2206 let buf = read_igvmfile(dll_path, ResourceCode::Snp).await.unwrap();
2207
2208 assert_eq!(buf, expected_payload);
2209
2210 vmgs_write(&mut vmgs, FileId::GUEST_FIRMWARE, &buf, false, false)
2211 .await
2212 .unwrap();
2213
2214 let read_buf = vmgs_read(&mut vmgs, FileId::GUEST_FIRMWARE, false)
2215 .await
2216 .unwrap();
2217
2218 assert_eq!(buf, read_buf);
2219 }
2220}