vmgstool/
main.rs

1// Copyright (c) Microsoft Corporation.
2// Licensed under the MIT License.
3
4// The version in this crate's Cargo.toml file should be updated using the
5// semver standard when changes are made, which triggers CI to automatically
6// publish a new version.
7
8#![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/// Automation requires certain exit codes to be guaranteed
105/// main matches Error enum to ExitCode
106///
107/// - query-encryption must return NotEncrypted if file is not encrypted,
108///   GspById if the file contains a VMID, and GspUnknown if neither
109///   a VMID nor a key protector are present. Success indicates GspKey.
110/// - dump-headers must return Empty when the file is blank.
111/// - query-size must return NotFound when the file id is uninitialized.
112/// - Error is returned for all other errors.
113#[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    /// VMGS file path
128    #[clap(short = 'f', long, alias = "filepath")]
129    file_path: PathBuf,
130}
131
132#[derive(Args)]
133struct KeyPathArg {
134    /// Encryption key file path. The file must contain a key that is 32 bytes long.
135    #[clap(short = 'k', long, alias = "keypath")]
136    key_path: Option<PathBuf>,
137}
138
139#[derive(Args)]
140struct FileIdArg {
141    /// VMGS File ID
142    #[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    /// Print trace level traces from all crates, rather than just info level
157    /// traces from the vmgstool crate.
158    #[clap(short = 'v', long)]
159    verbose: bool,
160
161    #[clap(subcommand)]
162    opt: Options,
163}
164
165#[derive(Subcommand)]
166enum Options {
167    /// Create and initialize `filepath` as a VMGS file of size `filesize`.
168    ///
169    /// `keypath` and `encryptionalgorithm` must both be specified if encrypted
170    /// guest state is required.
171    Create {
172        #[command(flatten)]
173        file_path: FilePathArg,
174        /// VMGS file size, default = 4194816 (~4MB)
175        #[clap(short = 's', long, alias = "filesize")]
176        file_size: Option<u64>,
177        /// Encryption key file path. The file must contain a key that is 32 bytes long.
178        ///
179        /// `encryptionalgorithm` must also be specified when using this flag.
180        #[clap(
181            short = 'k',
182            long,
183            alias = "keypath",
184            requires = "encryption_algorithm"
185        )]
186        key_path: Option<PathBuf>,
187        /// Encryption algorithm. Currently AES_GCM is the only algorithm supported.
188        ///
189        /// `keypath` must also be specified when using this flag.
190        #[clap(short = 'e', long, alias = "encryptionalgorithm", requires = "key_path", value_parser = parse_encryption_algorithm)]
191        encryption_algorithm: Option<EncryptionAlgorithm>,
192        /// Force creation of the VMGS file. If the VMGS filepath already exists,
193        /// this flag allows an existing file to be overwritten.
194        #[clap(long, alias = "forcecreate")]
195        force_create: bool,
196    },
197    /// Write data into the specified file ID of the VMGS file.
198    ///
199    /// The proper key file must be specified to write encrypted data.
200    Write {
201        #[command(flatten)]
202        file_path: FilePathArg,
203        /// Data file path to read
204        #[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        /// Overwrite the VMGS data at `fileid`, even if it already exists with nonzero size
211        #[clap(long, alias = "allowoverwrite")]
212        allow_overwrite: bool,
213    },
214    /// Dump/read data from the specified file ID of the VMGS file.
215    ///
216    /// The proper key file must be specified to read encrypted data. If the data
217    /// is encrypted and no key is specified, the data will be dumped without
218    /// decrypting.
219    Dump {
220        #[command(flatten)]
221        file_path: FilePathArg,
222        /// Data file path to write
223        #[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        /// When dumping to stdout, dump data as raw bytes instead of ASCII hex
230        #[clap(long, conflicts_with = "data_path")]
231        raw_stdout: bool,
232    },
233    /// Dump headers of the VMGS file at `filepath` to the console.
234    DumpHeaders {
235        #[command(flatten)]
236        file_path: FilePathArg,
237    },
238    /// Get the size of the specified `fileid` within the VMGS file
239    ///
240    /// The STDOUT of this subcommand is stable and contains only the file size.
241    QuerySize {
242        #[command(flatten)]
243        file_path: FilePathArg,
244        #[command(flatten)]
245        file_id: FileIdArg,
246    },
247    /// Replace the current encryption key with a new provided key
248    ///
249    /// Both key files must contain a key that is 32 bytes long.
250    UpdateKey {
251        #[command(flatten)]
252        file_path: FilePathArg,
253        /// Current encryption key file path.
254        #[clap(short = 'k', long, alias = "keypath")]
255        key_path: PathBuf,
256        /// New encryption key file path.
257        #[clap(short = 'n', long, alias = "newkeypath")]
258        new_key_path: PathBuf,
259        /// Encryption algorithm. Currently AES_GCM is the only algorithm supported.
260        #[clap(short = 'e', long, alias = "encryptionalgorithm", value_parser = parse_encryption_algorithm)]
261        encryption_algorithm: EncryptionAlgorithm,
262    },
263    /// Encrypt an existing VMGS file
264    Encrypt {
265        #[command(flatten)]
266        file_path: FilePathArg,
267        /// Encryption key file path. The file must contain a key that is 32 bytes long.
268        #[clap(short = 'k', long, alias = "keypath")]
269        key_path: PathBuf,
270        /// Encryption algorithm. Currently AES_GCM is the only algorithm supported.
271        #[clap(short = 'e', long, alias = "encryptionalgorithm", value_parser = parse_encryption_algorithm)]
272        encryption_algorithm: EncryptionAlgorithm,
273    },
274    /// Query whether a VMGS file is encrypted
275    QueryEncryption {
276        #[command(flatten)]
277        file_path: FilePathArg,
278    },
279    /// Move data to a new file id
280    Move {
281        #[command(flatten)]
282        file_path: FilePathArg,
283        /// Source VMGS File ID
284        #[clap(long, alias = "src", value_parser = parse_file_id)]
285        src_file_id: FileId,
286        /// Destination VMGS File ID
287        #[clap(long, alias = "dst", value_parser = parse_file_id)]
288        dst_file_id: FileId,
289        #[command(flatten)]
290        key_path: KeyPathArg,
291        /// Overwrite the VMGS data at `dst_file_id`, even if it already exists
292        #[clap(long, alias = "allowoverwrite")]
293        allow_overwrite: bool,
294    },
295    /// Delete a file id
296    Delete {
297        #[command(flatten)]
298        file_path: FilePathArg,
299        #[command(flatten)]
300        file_id: FileIdArg,
301    },
302    /// Dump information about all the File IDs allocated in the VMGS file.
303    DumpFileTable {
304        #[command(flatten)]
305        file_path: FilePathArg,
306        #[command(flatten)]
307        key_path: KeyPathArg,
308    },
309    /// UEFI NVRAM operations
310    UefiNvram {
311        #[clap(subcommand)]
312        operation: UefiNvramOperation,
313    },
314    #[cfg(feature = "test_helpers")]
315    /// Create a test VMGS file
316    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            // The tracing subscriber has not been initialized yet.
377            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
404/// Initialize tracing
405pub 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                // all relevant info is already logged in `vmgs_file_query_encryption`
445                Error::NotEncrypted | Error::GspByIdEncryption | Error::GspUnknown => {}
446                // these are not necessarily errors, so just log the inner value as info
447                Error::Vmgs(inner)
448                    if matches!(
449                        inner,
450                        VmgsError::EmptyFile | VmgsError::FileInfoNotAllocated(_)
451                    ) =>
452                {
453                    tracing::info!("{}", inner)
454                }
455                // anything else is unexpected and should be logged as error
456                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    // validate the VHD size
656    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    // check if the file already exists so we know whether to try to preserve
668    // the size and footer later
669    let exists = Path::new(path.as_ref()).exists();
670
671    // open/create the file
672    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    // determine if a resize is necessary
688    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    // resize the file if necessary
702    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    // attempt to open the VHD file if it already existed
726    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    // format the VHD if necessary
735    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
829/// Get data from VMGS file, and write to `data_path`.
830async 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    /// Open read-only. Ignore encryption status.
1162    ReadOnlyIgnore,
1163    /// Open read-only. Warn if encrypted and no key was provided.
1164    ReadOnlyWarn,
1165    /// Open read-write. Ignore encryption status.
1166    ReadWriteIgnore,
1167    /// Open read-write. Fail if encrypted and no key was provided.
1168    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    // STABLE OUTPUT
1272    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    // Create a new test file path.
1407    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        // try to normal write encrypted VMGs
1527        vmgs_write(&mut vmgs, FileId::TPM_PPI, &buf_1, false, false)
1528            .await
1529            .unwrap();
1530
1531        // try to normal read encrypted FileId
1532        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        // You shouldn't be able to use encryption if you create the VMGS
1541        // file without encryption.
1542        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        // create an empty (zero-length) file
1643        {
1644            fs_err::OpenOptions::new()
1645                .write(true)
1646                .create_new(true)
1647                .open(&path)
1648                .unwrap();
1649        }
1650
1651        // verify the file is zero size
1652        {
1653            let result = test_vmgs_open(&path, OpenMode::ReadOnlyWarn, None).await;
1654            assert!(matches!(result, Err(Error::ZeroSize)));
1655        }
1656
1657        // create an empty vhd of default size
1658        {
1659            vhdfiledisk_create(&path, None, true).unwrap();
1660        }
1661
1662        // verify the file is empty (with non-zero size)
1663        {
1664            let result = test_vmgs_open(&path, OpenMode::ReadOnlyWarn, None).await;
1665            assert!(matches!(result, Err(Error::Vmgs(VmgsError::EmptyFile))));
1666        }
1667
1668        // write some invalid data to the file
1669        {
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        // verify the vmgs is identified as corrupted
1680        {
1681            let result = test_vmgs_open(&path, OpenMode::ReadOnlyWarn, None).await;
1682            matches!(result, Err(Error::Vmgs(VmgsError::CorruptFormat(_))));
1683        }
1684
1685        // create a valid vmgs
1686        {
1687            test_vmgs_create(&path, None, true, None).await.unwrap();
1688        }
1689
1690        // sanity check that the positive case works
1691        {
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        //File size must be % 512 to be valid, should produce error and file should not be created
1702        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        // Recreating file should fail without force create flag
1713        let result = test_vmgs_create(&path, Some(4194304), false, None).await;
1714        assert!(result.is_err());
1715        // Should be able to resize the file when force create is passed in
1716        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        // Old key should no longer work
1768        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        // delete the file without decrypting, as the cmdline tool would do
1926        {
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        // make sure the file is not corrupted
1939        {
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}