vmgs_format/
lib.rs

1// Copyright (c) Microsoft Corporation.
2// Licensed under the MIT License.
3
4//! VMGS format definitions
5
6#![expect(missing_docs)]
7#![forbid(unsafe_code)]
8#![no_std]
9
10use bitfield_struct::bitfield;
11use core::ops::Index;
12use core::ops::IndexMut;
13#[cfg(feature = "inspect")]
14use inspect::Inspect;
15use open_enum::open_enum;
16use static_assertions::const_assert;
17use zerocopy::FromBytes;
18use zerocopy::Immutable;
19use zerocopy::IntoBytes;
20use zerocopy::KnownLayout;
21
22/// The suggested default capacity of a VMGS disk in bytes, 4MB.
23///
24/// In some sense, this is not part of the VMGS format, but all known
25/// implementations default to this capacity (with an optional user-provided
26/// override), so it is useful to have it here. But an implementation is not
27/// _required_ to use this capacity, and the VMGS parser cannot assume that the
28/// disk is this size.
29pub const VMGS_DEFAULT_CAPACITY: u64 = 0x400000;
30
31open_enum! {
32    /// VMGS fixed file IDs
33    #[cfg_attr(feature = "inspect", derive(Inspect))]
34    #[cfg_attr(feature = "inspect", inspect(debug))]
35    pub enum FileId: u32 {
36        FILE_TABLE     = 0,
37        BIOS_NVRAM     = 1,
38        TPM_PPI        = 2,
39        TPM_NVRAM      = 3,
40        RTC_SKEW       = 4,
41        ATTEST         = 5,
42        KEY_PROTECTOR  = 6,
43        VM_UNIQUE_ID   = 7,
44        GUEST_FIRMWARE = 8,
45        CUSTOM_UEFI    = 9,
46        GUEST_WATCHDOG = 10,
47        HW_KEY_PROTECTOR = 11,
48        GUEST_SECRET_KEY = 13,
49        HIBERNATION_FIRMWARE = 14,
50
51        EXTENDED_FILE_TABLE = 63,
52    }
53}
54
55pub const VMGS_VERSION_2_0: u32 = 0x00020000;
56pub const VMGS_VERSION_3_0: u32 = 0x00030000;
57
58pub const VMGS_SIGNATURE: u64 = u64::from_le_bytes(*b"GUESTRTS"); // identical to the V1 format signature
59
60pub const VMGS_BYTES_PER_BLOCK: u32 = 4096;
61
62const VMGS_MAX_CAPACITY_BLOCKS: u64 = 0x100000000;
63pub const VMGS_MAX_CAPACITY_BYTES: u64 = VMGS_MAX_CAPACITY_BLOCKS * VMGS_BYTES_PER_BLOCK as u64;
64
65pub const VMGS_MIN_FILE_BLOCK_OFFSET: u32 = 2;
66pub const VMGS_FILE_COUNT: usize = 64;
67pub const VMGS_MAX_FILE_SIZE_BLOCKS: u64 = 0xFFFFFFFF;
68pub const VMGS_MAX_FILE_SIZE_BYTES: u64 = VMGS_MAX_FILE_SIZE_BLOCKS * VMGS_BYTES_PER_BLOCK as u64;
69
70pub const VMGS_NONCE_SIZE: usize = 12; // Each nonce includes a 4-byte random seed and a 8-byte counter.
71pub const VMGS_NONCE_RANDOM_SEED_SIZE: usize = 4;
72pub const VMGS_AUTHENTICATION_TAG_SIZE: usize = 16;
73pub const VMGS_ENCRYPTION_KEY_SIZE: usize = 32;
74
75pub type VmgsNonce = [u8; VMGS_NONCE_SIZE];
76pub type VmgsAuthTag = [u8; VMGS_AUTHENTICATION_TAG_SIZE];
77pub type VmgsDatastoreKey = [u8; VMGS_ENCRYPTION_KEY_SIZE];
78
79#[repr(C)]
80#[derive(Copy, Clone, Debug, IntoBytes, Immutable, KnownLayout, FromBytes)]
81pub struct VmgsFileEntry {
82    // V2 fields
83    pub offset: u32,
84    pub allocation_size: u32,
85    pub valid_data_size: u64,
86
87    // V3 fields
88    pub nonce: VmgsNonce,
89    pub authentication_tag: VmgsAuthTag,
90
91    pub reserved: [u8; 20],
92}
93
94const_assert!(size_of::<VmgsFileEntry>() == 64);
95
96impl Index<FileId> for [VmgsFileEntry] {
97    type Output = VmgsFileEntry;
98
99    fn index(&self, file_id: FileId) -> &Self::Output {
100        &self[file_id.0 as usize]
101    }
102}
103
104impl IndexMut<FileId> for [VmgsFileEntry] {
105    fn index_mut(&mut self, file_id: FileId) -> &mut Self::Output {
106        &mut self[file_id.0 as usize]
107    }
108}
109
110#[repr(C)]
111#[derive(Copy, Clone, Debug, IntoBytes, Immutable, KnownLayout, FromBytes)]
112pub struct VmgsExtendedFileEntry {
113    pub attributes: FileAttribute,
114    pub encryption_key: VmgsDatastoreKey,
115
116    pub reserved: [u8; 28],
117}
118
119const_assert!(size_of::<VmgsExtendedFileEntry>() == 64);
120
121impl Index<FileId> for [VmgsExtendedFileEntry] {
122    type Output = VmgsExtendedFileEntry;
123
124    fn index(&self, file_id: FileId) -> &Self::Output {
125        &self[file_id.0 as usize]
126    }
127}
128
129impl IndexMut<FileId> for [VmgsExtendedFileEntry] {
130    fn index_mut(&mut self, file_id: FileId) -> &mut Self::Output {
131        &mut self[file_id.0 as usize]
132    }
133}
134
135#[repr(C)]
136#[derive(Copy, Clone, Debug, IntoBytes, Immutable, KnownLayout, FromBytes)]
137#[cfg_attr(feature = "inspect", derive(Inspect))]
138pub struct VmgsEncryptionKey {
139    pub nonce: VmgsNonce,
140    pub reserved: u32,
141    pub authentication_tag: VmgsAuthTag,
142    pub encryption_key: VmgsDatastoreKey,
143}
144
145const_assert!(size_of::<VmgsEncryptionKey>() == 64);
146
147#[repr(C)]
148#[derive(Copy, Clone, Debug, IntoBytes, Immutable, KnownLayout, FromBytes)]
149pub struct VmgsHeader {
150    // V1 compatible fields
151    pub signature: u64,
152    pub version: u32,
153    pub checksum: u32,
154    pub sequence: u32,
155    pub header_size: u32,
156
157    // V2 fields
158    pub file_table_offset: u32,
159    pub file_table_size: u32,
160
161    // V3 fields
162    pub encryption_algorithm: EncryptionAlgorithm,
163    pub markers: VmgsMarkers,
164    pub metadata_keys: [VmgsEncryptionKey; 2],
165    pub reserved_1: u32,
166}
167
168const_assert!(size_of::<VmgsHeader>() == 168);
169
170#[repr(C)]
171#[derive(Copy, Clone, IntoBytes, Immutable, KnownLayout, FromBytes, Debug)]
172pub struct VmgsFileTable {
173    pub entries: [VmgsFileEntry; VMGS_FILE_COUNT],
174}
175
176const_assert!(size_of::<VmgsFileTable>() == 4096);
177const_assert!((size_of::<VmgsFileTable>() as u32).is_multiple_of(VMGS_BYTES_PER_BLOCK));
178pub const VMGS_FILE_TABLE_BLOCK_SIZE: u32 =
179    size_of::<VmgsFileTable>() as u32 / VMGS_BYTES_PER_BLOCK;
180
181#[repr(C)]
182#[derive(Copy, Clone, IntoBytes, Immutable, KnownLayout, FromBytes)]
183pub struct VmgsExtendedFileTable {
184    pub entries: [VmgsExtendedFileEntry; VMGS_FILE_COUNT],
185}
186
187const_assert!(size_of::<VmgsExtendedFileTable>() == 4096);
188const_assert!((size_of::<VmgsExtendedFileTable>() as u32).is_multiple_of(VMGS_BYTES_PER_BLOCK));
189pub const VMGS_EXTENDED_FILE_TABLE_BLOCK_SIZE: u32 =
190    size_of::<VmgsExtendedFileTable>() as u32 / VMGS_BYTES_PER_BLOCK;
191
192/// File attribute for VMGS files
193#[cfg_attr(feature = "inspect", derive(Inspect))]
194#[bitfield(u32)]
195#[derive(IntoBytes, Immutable, KnownLayout, FromBytes, PartialEq, Eq)]
196pub struct FileAttribute {
197    pub encrypted: bool,
198    pub authenticated: bool,
199    #[bits(30)]
200    _reserved: u32,
201}
202
203open_enum! {
204    /// Encryption algorithm used to encrypt VMGS file
205    #[cfg_attr(feature = "inspect", derive(Inspect))]
206    #[cfg_attr(feature = "inspect", inspect(debug))]
207    #[derive(IntoBytes, Immutable, KnownLayout, FromBytes)]
208    pub enum EncryptionAlgorithm: u16 {
209        /// No encryption algorithm
210        NONE = 0,
211        /// AES 256 GCM encryption
212        AES_GCM = 1,
213    }
214}
215
216/// Markers used internally to indicate how the VMGS should be treated
217#[cfg_attr(feature = "inspect", derive(Inspect))]
218#[bitfield(u16)]
219#[derive(IntoBytes, Immutable, KnownLayout, FromBytes, PartialEq, Eq)]
220pub struct VmgsMarkers {
221    pub reprovisioned: bool,
222    #[bits(15)]
223    _reserved: u32,
224}