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, ¤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 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