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