xref: /cloud-hypervisor/block/src/vhdx/vhdx_header.rs (revision eeae63b4595fbf0cc69f62b6e9d9a79c543c4ac7)
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
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(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
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
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
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(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
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
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(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
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
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.
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.
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, &current_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, &current_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
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 
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 
448     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.
457 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