1 // Copyright © 2021 Intel Corporation
2 //
3 // SPDX-License-Identifier: Apache-2.0
4
5 extern crate log;
6
7 use std::collections::btree_map::BTreeMap;
8 use std::fs::File;
9 use std::io::{self, Read, Seek, SeekFrom, Write};
10 use std::mem::size_of;
11
12 use byteorder::{ByteOrder, LittleEndian, ReadBytesExt};
13 use remain::sorted;
14 use thiserror::Error;
15 use uuid::Uuid;
16
17 const VHDX_SIGN: u64 = 0x656C_6966_7864_6876; // "vhdxfile"
18 const HEADER_SIGN: u32 = 0x6461_6568; // "head"
19 const REGION_SIGN: u32 = 0x6967_6572; // "regi"
20
21 const FILE_START: u64 = 0; // The first element
22 const HEADER_1_START: u64 = 64 * 1024; // Header 1 start in Bytes
23 const HEADER_2_START: u64 = 128 * 1024; // Header 2 start in Bytes
24 pub const REGION_TABLE_1_START: u64 = 192 * 1024; // Region 1 start in Bytes
25 const REGION_TABLE_2_START: u64 = 256 * 1024; // Region 2 start in Bytes
26
27 const HEADER_SIZE: u64 = 4 * 1024; // Each header is 64 KiB, but only first 4 kiB contains info
28 const REGION_SIZE: u64 = 64 * 1024; // Each region size is 64 KiB
29
30 const REGION_ENTRY_REQUIRED: u32 = 1;
31
32 const BAT_GUID: &str = "2DC27766-F623-4200-9D64-115E9BFD4A08"; // BAT GUID
33 const MDR_GUID: &str = "8B7CA206-4790-4B9A-B8FE-575F050F886E"; // Metadata GUID
34
35 #[sorted]
36 #[derive(Error, Debug)]
37 pub enum VhdxHeaderError {
38 #[error("Failed to calculate checksum")]
39 CalculateChecksum,
40 #[error("BAT entry is not unique")]
41 DuplicateBATEntry,
42 #[error("Metadata region entry is not unique")]
43 DuplicateMDREntry,
44 #[error("Checksum doesn't match for")]
45 InvalidChecksum(String),
46 #[error("Invalid entry count")]
47 InvalidEntryCount,
48 #[error("Not a valid VHDx header")]
49 InvalidHeaderSign,
50 #[error("Not a valid VHDx region")]
51 InvalidRegionSign,
52 #[error("Couldn't parse Uuid for region entry {0}")]
53 InvalidUuid(#[source] uuid::Error),
54 #[error("Not a VHDx file")]
55 InvalidVHDXSign,
56 #[error("No valid header found")]
57 NoValidHeader,
58 #[error("Cannot read checksum")]
59 ReadChecksum,
60 #[error("Failed to read File Type Identifier {0}")]
61 ReadFileTypeIdentifier(#[source] io::Error),
62 #[error("Failed to read headers {0}")]
63 ReadHeader(#[source] io::Error),
64 #[error("Failed to read metadata {0}")]
65 ReadMetadata(#[source] std::io::Error),
66 #[error("Failed to read region table entries {0}")]
67 ReadRegionTableEntries(#[source] io::Error),
68 #[error("Failed to read region table header {0}")]
69 ReadRegionTableHeader(#[source] io::Error),
70 #[error("Failed to read region entries")]
71 RegionEntryCollectionFailed,
72 #[error("Overlapping regions found")]
73 RegionOverlap,
74 #[error("Reserved region has non-zero value")]
75 ReservedIsNonZero,
76 #[error("Failed to seek in File Type Identifier {0}")]
77 SeekFileTypeIdentifier(#[source] io::Error),
78 #[error("Failed to seek in headers {0}")]
79 SeekHeader(#[source] io::Error),
80 #[error("Failed to seek in region table entries {0}")]
81 SeekRegionTableEntries(#[source] io::Error),
82 #[error("Failed to seek in region table header {0}")]
83 SeekRegionTableHeader(#[source] io::Error),
84 #[error("We do not recognize this entry")]
85 UnrecognizedRegionEntry,
86 #[error("Failed to write header {0}")]
87 WriteHeader(#[source] io::Error),
88 }
89
90 pub type Result<T> = std::result::Result<T, VhdxHeaderError>;
91
92 #[derive(Clone, Debug)]
93 pub struct FileTypeIdentifier {
94 pub _signature: u64,
95 }
96
97 impl FileTypeIdentifier {
98 /// Reads the File Type Identifier structure from a reference VHDx file
new(f: &mut File) -> Result<FileTypeIdentifier>99 pub fn new(f: &mut File) -> Result<FileTypeIdentifier> {
100 f.seek(SeekFrom::Start(FILE_START))
101 .map_err(VhdxHeaderError::SeekFileTypeIdentifier)?;
102 let _signature = f
103 .read_u64::<LittleEndian>()
104 .map_err(VhdxHeaderError::ReadFileTypeIdentifier)?;
105 if _signature != VHDX_SIGN {
106 return Err(VhdxHeaderError::InvalidVHDXSign);
107 }
108
109 Ok(FileTypeIdentifier { _signature })
110 }
111 }
112
113 #[repr(C, packed)]
114 #[derive(Clone, Copy, Debug)]
115 pub struct Header {
116 pub signature: u32,
117 pub checksum: u32,
118 pub sequence_number: u64,
119 pub file_write_guid: u128,
120 pub data_write_guid: u128,
121 pub log_guid: u128,
122 pub log_version: u16,
123 pub version: u16,
124 pub log_length: u32,
125 pub log_offset: u64,
126 }
127
128 impl Header {
129 /// Reads the Header structure from a reference VHDx file
new(f: &mut File, start: u64) -> Result<Header>130 pub fn new(f: &mut File, start: u64) -> Result<Header> {
131 // Read the whole header into a buffer. We will need it for
132 // calculating checksum.
133 let mut buffer = [0; HEADER_SIZE as usize];
134 f.seek(SeekFrom::Start(start))
135 .map_err(VhdxHeaderError::SeekHeader)?;
136 f.read_exact(&mut buffer)
137 .map_err(VhdxHeaderError::ReadHeader)?;
138
139 // SAFETY: buffer is of correct size and has been successfully filled.
140 let header = unsafe { *(buffer.as_ptr() as *mut Header) };
141 if header.signature != HEADER_SIGN {
142 return Err(VhdxHeaderError::InvalidHeaderSign);
143 }
144
145 let new_checksum = calculate_checksum(&mut buffer, size_of::<u32>());
146 if header.checksum != new_checksum {
147 return Err(VhdxHeaderError::InvalidChecksum(String::from("Header")));
148 }
149
150 Ok(header)
151 }
152
153 /// Converts the header structure into a buffer
write_to_buffer(&self, buffer: &mut [u8; HEADER_SIZE as usize])154 fn write_to_buffer(&self, buffer: &mut [u8; HEADER_SIZE as usize]) {
155 // SAFETY: self is a valid header.
156 let reference = unsafe {
157 std::slice::from_raw_parts(self as *const Header as *const u8, HEADER_SIZE as usize)
158 };
159 *buffer = reference.try_into().unwrap();
160 }
161
162 /// Creates and returns new updated header from the provided current header
update_header( f: &mut File, current_header: &Header, change_data_guid: bool, mut file_write_guid: u128, start: u64, ) -> Result<Header>163 fn update_header(
164 f: &mut File,
165 current_header: &Header,
166 change_data_guid: bool,
167 mut file_write_guid: u128,
168 start: u64,
169 ) -> Result<Header> {
170 let mut buffer = [0u8; HEADER_SIZE as usize];
171 let mut data_write_guid = current_header.data_write_guid;
172
173 if change_data_guid {
174 data_write_guid = Uuid::new_v4().as_u128();
175 }
176
177 if file_write_guid == 0 {
178 file_write_guid = current_header.file_write_guid;
179 }
180
181 let mut new_header = Header {
182 signature: current_header.signature,
183 checksum: 0,
184 sequence_number: current_header.sequence_number + 1,
185 file_write_guid,
186 data_write_guid,
187 log_guid: current_header.log_guid,
188 log_version: current_header.log_version,
189 version: current_header.version,
190 log_length: current_header.log_length,
191 log_offset: current_header.log_offset,
192 };
193
194 new_header.write_to_buffer(&mut buffer);
195 new_header.checksum = calculate_checksum(&mut buffer, size_of::<u32>());
196 new_header.write_to_buffer(&mut buffer);
197
198 f.seek(SeekFrom::Start(start))
199 .map_err(VhdxHeaderError::SeekHeader)?;
200 f.write(&buffer).map_err(VhdxHeaderError::WriteHeader)?;
201
202 Ok(new_header)
203 }
204 }
205
206 #[repr(C, packed)]
207 #[derive(Clone, Copy, Debug)]
208 struct RegionTableHeader {
209 pub signature: u32,
210 pub checksum: u32,
211 pub entry_count: u32,
212 pub reserved: u32,
213 }
214
215 impl RegionTableHeader {
216 /// Reads the Region Table Header structure from a reference VHDx file
new(f: &mut File, start: u64) -> Result<RegionTableHeader>217 pub fn new(f: &mut File, start: u64) -> Result<RegionTableHeader> {
218 // Read the whole header into a buffer. We will need it for calculating
219 // checksum.
220 let mut buffer = [0u8; REGION_SIZE as usize];
221 f.seek(SeekFrom::Start(start))
222 .map_err(VhdxHeaderError::SeekRegionTableHeader)?;
223 f.read_exact(&mut buffer)
224 .map_err(VhdxHeaderError::ReadRegionTableHeader)?;
225
226 // SAFETY: buffer is of correct size and has been successfully filled.
227 let region_table_header = unsafe { *(buffer.as_ptr() as *mut RegionTableHeader) };
228 if region_table_header.signature != REGION_SIGN {
229 return Err(VhdxHeaderError::InvalidRegionSign);
230 }
231
232 let new_checksum = calculate_checksum(&mut buffer, size_of::<u32>());
233 if region_table_header.checksum != new_checksum {
234 return Err(VhdxHeaderError::InvalidChecksum(String::from("Region")));
235 }
236
237 if region_table_header.entry_count > 2047 {
238 return Err(VhdxHeaderError::InvalidEntryCount);
239 }
240
241 if region_table_header.reserved != 0 {
242 return Err(VhdxHeaderError::ReservedIsNonZero);
243 }
244
245 Ok(region_table_header)
246 }
247 }
248
249 pub struct RegionInfo {
250 pub bat_entry: RegionTableEntry,
251 pub mdr_entry: RegionTableEntry,
252 pub region_entries: BTreeMap<u64, u64>,
253 }
254
255 impl RegionInfo {
256 /// Collect all entries in a BTreeMap from the Region Table and identifies
257 /// BAT and metadata regions
new(f: &mut File, region_start: u64, entry_count: u32) -> Result<RegionInfo>258 pub fn new(f: &mut File, region_start: u64, entry_count: u32) -> Result<RegionInfo> {
259 let mut bat_entry: Option<RegionTableEntry> = None;
260 let mut mdr_entry: Option<RegionTableEntry> = None;
261
262 let mut offset = 0;
263 let mut region_entries = BTreeMap::new();
264
265 let mut buffer = [0; REGION_SIZE as usize];
266 // Seek after the Region Table Header
267 f.seek(SeekFrom::Start(
268 region_start + size_of::<RegionTableHeader>() as u64,
269 ))
270 .map_err(VhdxHeaderError::SeekRegionTableEntries)?;
271 f.read_exact(&mut buffer)
272 .map_err(VhdxHeaderError::ReadRegionTableEntries)?;
273
274 for _ in 0..entry_count {
275 let entry =
276 RegionTableEntry::new(&buffer[offset..offset + size_of::<RegionTableEntry>()])?;
277
278 offset += size_of::<RegionTableEntry>();
279 let start = entry.file_offset;
280 let end = start + entry.length as u64;
281
282 for (region_ent_start, region_ent_end) in region_entries.iter() {
283 if !((start >= *region_ent_start) || (end <= *region_ent_end)) {
284 return Err(VhdxHeaderError::RegionOverlap);
285 }
286 }
287
288 region_entries.insert(entry.file_offset, entry.file_offset + entry.length as u64);
289
290 if entry.guid == Uuid::parse_str(BAT_GUID).map_err(VhdxHeaderError::InvalidUuid)? {
291 if bat_entry.is_none() {
292 bat_entry = Some(entry);
293 continue;
294 }
295 return Err(VhdxHeaderError::DuplicateBATEntry);
296 }
297
298 if entry.guid == Uuid::parse_str(MDR_GUID).map_err(VhdxHeaderError::InvalidUuid)? {
299 if mdr_entry.is_none() {
300 mdr_entry = Some(entry);
301 continue;
302 }
303 return Err(VhdxHeaderError::DuplicateMDREntry);
304 }
305
306 if (entry.required & REGION_ENTRY_REQUIRED) == 1 {
307 // This implementation doesn't recognize this field.
308 // Therefore, according to the spec, we are throwing an error.
309 return Err(VhdxHeaderError::UnrecognizedRegionEntry);
310 }
311 }
312
313 if bat_entry.is_none() || mdr_entry.is_none() {
314 region_entries.clear();
315 return Err(VhdxHeaderError::RegionEntryCollectionFailed);
316 }
317
318 // It's safe to unwrap as we checked both entries have been filled.
319 // Otherwise, an error is already returned.
320 let bat_entry = bat_entry.unwrap();
321 let mdr_entry = mdr_entry.unwrap();
322
323 Ok(RegionInfo {
324 bat_entry,
325 mdr_entry,
326 region_entries,
327 })
328 }
329 }
330
331 #[repr(C, packed)]
332 #[derive(Clone, Copy, Debug)]
333 pub struct RegionTableEntry {
334 pub guid: Uuid,
335 pub file_offset: u64,
336 pub length: u32,
337 pub required: u32,
338 }
339
340 impl RegionTableEntry {
341 /// Reads one Region Entry from a Region Table index that starts from 0
new(buffer: &[u8]) -> Result<RegionTableEntry>342 pub fn new(buffer: &[u8]) -> Result<RegionTableEntry> {
343 assert!(buffer.len() == std::mem::size_of::<RegionTableEntry>());
344 // SAFETY: the assertion above makes sure the buffer size is correct.
345 let mut region_table_entry = unsafe { *(buffer.as_ptr() as *mut RegionTableEntry) };
346
347 let uuid = crate::vhdx::uuid_from_guid(buffer);
348 region_table_entry.guid = uuid;
349
350 Ok(region_table_entry)
351 }
352 }
353
354 enum HeaderNo {
355 First,
356 Second,
357 }
358
359 /// Contains the information from the header of a VHDx file
360 #[derive(Clone, Debug)]
361 pub struct VhdxHeader {
362 _file_type_identifier: FileTypeIdentifier,
363 header_1: Header,
364 header_2: Header,
365 region_table_1: RegionTableHeader,
366 _region_table_2: RegionTableHeader,
367 }
368
369 impl VhdxHeader {
370 /// Creates a VhdxHeader from a reference to a file
new(f: &mut File) -> Result<VhdxHeader>371 pub fn new(f: &mut File) -> Result<VhdxHeader> {
372 Ok(VhdxHeader {
373 _file_type_identifier: FileTypeIdentifier::new(f)?,
374 header_1: Header::new(f, HEADER_1_START)?,
375 header_2: Header::new(f, HEADER_2_START)?,
376 region_table_1: RegionTableHeader::new(f, REGION_TABLE_1_START)?,
377 _region_table_2: RegionTableHeader::new(f, REGION_TABLE_2_START)?,
378 })
379 }
380
381 /// Identify the current header and return both headers along with an
382 /// integer indicating the current header.
current_header( header_1: Result<Header>, header_2: Result<Header>, ) -> Result<(HeaderNo, Header)>383 fn current_header(
384 header_1: Result<Header>,
385 header_2: Result<Header>,
386 ) -> Result<(HeaderNo, Header)> {
387 let header_1 = header_1.ok();
388 let header_2 = header_2.ok();
389
390 match (header_1, header_2) {
391 (None, None) => Err(VhdxHeaderError::NoValidHeader),
392 (Some(header_1), None) => Ok((HeaderNo::First, header_1)),
393 (None, Some(header_2)) => Ok((HeaderNo::Second, header_2)),
394 (Some(header_1), Some(header_2)) => {
395 if header_1.sequence_number >= header_2.sequence_number {
396 Ok((HeaderNo::First, header_1))
397 } else {
398 Ok((HeaderNo::Second, header_2))
399 }
400 }
401 }
402 }
403
404 /// This takes two headers and update the noncurrent header with the
405 /// current one. Returns both headers as a tuple sequenced the way it was
406 /// received from the parameter list.
update_header( f: &mut File, header_1: Result<Header>, header_2: Result<Header>, guid: u128, ) -> Result<(Header, Header)>407 fn update_header(
408 f: &mut File,
409 header_1: Result<Header>,
410 header_2: Result<Header>,
411 guid: u128,
412 ) -> Result<(Header, Header)> {
413 let (header_no, current_header) = VhdxHeader::current_header(header_1, header_2)?;
414
415 match header_no {
416 HeaderNo::First => {
417 let other_header =
418 Header::update_header(f, ¤t_header, true, guid, HEADER_2_START)?;
419 Ok((current_header, other_header))
420 }
421 HeaderNo::Second => {
422 let other_header =
423 Header::update_header(f, ¤t_header, true, guid, HEADER_1_START)?;
424 Ok((other_header, current_header))
425 }
426 }
427 }
428
429 // Update the provided headers according to the spec
update_headers( f: &mut File, header_1: Result<Header>, header_2: Result<Header>, guid: u128, ) -> Result<(Header, Header)>430 fn update_headers(
431 f: &mut File,
432 header_1: Result<Header>,
433 header_2: Result<Header>,
434 guid: u128,
435 ) -> Result<(Header, Header)> {
436 // According to the spec, update twice
437 let (header_1, header_2) = VhdxHeader::update_header(f, header_1, header_2, guid)?;
438 VhdxHeader::update_header(f, Ok(header_1), Ok(header_2), guid)
439 }
440
update(&mut self, f: &mut File) -> Result<()>441 pub fn update(&mut self, f: &mut File) -> Result<()> {
442 let headers = VhdxHeader::update_headers(f, Ok(self.header_1), Ok(self.header_2), 0)?;
443 self.header_1 = headers.0;
444 self.header_2 = headers.1;
445 Ok(())
446 }
447
region_entry_count(&self) -> u32448 pub fn region_entry_count(&self) -> u32 {
449 self.region_table_1.entry_count
450 }
451 }
452
453 /// Calculates the checksum of a buffer that itself contains its checksum
454 /// Therefore, before calculating, the existing checksum is retrieved and the
455 /// corresponding field is made zero. After the calculation, the existing checksum
456 /// is put back to the buffer.
calculate_checksum(buffer: &mut [u8], csum_offset: usize) -> u32457 fn calculate_checksum(buffer: &mut [u8], csum_offset: usize) -> u32 {
458 // Read the original checksum from the buffer
459 let orig_csum = LittleEndian::read_u32(&buffer[csum_offset..csum_offset + 4]);
460 // Zero the checksum in the buffer
461 LittleEndian::write_u32(&mut buffer[csum_offset..csum_offset + 4], 0);
462 // Calculate the checksum on the resulting buffer
463 let mut crc = crc_any::CRC::crc32c();
464 crc.digest(&buffer);
465 let new_csum = crc.get_crc() as u32;
466
467 // Put back the original checksum in the buffer
468 LittleEndian::write_u32(&mut buffer[csum_offset..csum_offset + 4], orig_csum);
469
470 new_csum
471 }
472