Skip to main content

vmgstool/
main.rs

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