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