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