1 // Copyright © 2021 Intel Corporation 2 // 3 // SPDX-License-Identifier: Apache-2.0 4 use std::fs::File; 5 use std::io::{Read, Seek, SeekFrom}; 6 use std::str::FromStr; 7 8 use thiserror::Error; 9 use uuid::Uuid; 10 use vm_memory::{ByteValued, Bytes, GuestAddress, GuestMemoryError}; 11 12 use crate::GuestMemoryMmap; 13 14 #[derive(Error, Debug)] 15 pub enum TdvfError { 16 #[error("Failed read TDVF descriptor: {0}")] 17 ReadDescriptor(#[source] std::io::Error), 18 #[error("Failed read TDVF descriptor offset: {0}")] 19 ReadDescriptorOffset(#[source] std::io::Error), 20 #[error("Failed read GUID table: {0}")] 21 ReadGuidTable(#[source] std::io::Error), 22 #[error("Invalid descriptor signature")] 23 InvalidDescriptorSignature, 24 #[error("Invalid descriptor size")] 25 InvalidDescriptorSize, 26 #[error("Invalid descriptor version")] 27 InvalidDescriptorVersion, 28 #[error("Failed to write HOB details to guest memory: {0}")] 29 GuestMemoryWriteHob(#[source] GuestMemoryError), 30 #[error("Failed to create Uuid: {0}")] 31 UuidCreation(#[source] uuid::Error), 32 } 33 34 const TABLE_FOOTER_GUID: &str = "96b582de-1fb2-45f7-baea-a366c55a082d"; 35 const TDVF_METADATA_OFFSET_GUID: &str = "e47a6535-984a-4798-865e-4685a7bf8ec2"; 36 37 // TDVF_DESCRIPTOR 38 #[repr(C, packed)] 39 #[derive(Default)] 40 pub struct TdvfDescriptor { 41 signature: [u8; 4], 42 length: u32, 43 version: u32, 44 num_sections: u32, // NumberOfSectionEntry 45 } 46 47 // TDVF_SECTION 48 #[repr(C, packed)] 49 #[derive(Clone, Copy, Default, Debug)] 50 pub struct TdvfSection { 51 pub data_offset: u32, 52 pub data_size: u32, // RawDataSize 53 pub address: u64, // MemoryAddress 54 pub size: u64, // MemoryDataSize 55 pub r#type: TdvfSectionType, 56 pub attributes: u32, 57 } 58 59 #[repr(u32)] 60 #[derive(Clone, Copy, Debug, Default)] 61 pub enum TdvfSectionType { 62 Bfv, 63 Cfv, 64 TdHob, 65 TempMem, 66 PermMem, 67 Payload, 68 PayloadParam, 69 #[default] 70 Reserved = 0xffffffff, 71 } 72 73 fn tdvf_descriptor_offset(file: &mut File) -> Result<(SeekFrom, bool), TdvfError> { 74 // Let's first try to identify the presence of the table footer GUID 75 file.seek(SeekFrom::End(-0x30)) 76 .map_err(TdvfError::ReadGuidTable)?; 77 let mut table_footer_guid: [u8; 16] = [0; 16]; 78 file.read_exact(&mut table_footer_guid) 79 .map_err(TdvfError::ReadGuidTable)?; 80 let uuid = 81 Uuid::from_slice_le(table_footer_guid.as_slice()).map_err(TdvfError::UuidCreation)?; 82 let expected_uuid = Uuid::from_str(TABLE_FOOTER_GUID).map_err(TdvfError::UuidCreation)?; 83 if uuid == expected_uuid { 84 // Retrieve the table size 85 file.seek(SeekFrom::End(-0x32)) 86 .map_err(TdvfError::ReadGuidTable)?; 87 let mut table_size: [u8; 2] = [0; 2]; 88 file.read_exact(&mut table_size) 89 .map_err(TdvfError::ReadGuidTable)?; 90 let table_size = u16::from_le_bytes(table_size) as usize; 91 let mut table: Vec<u8> = vec![0; table_size]; 92 93 // Read the entire table 94 file.seek(SeekFrom::End(-(table_size as i64 + 0x20))) 95 .map_err(TdvfError::ReadGuidTable)?; 96 file.read_exact(table.as_mut_slice()) 97 .map_err(TdvfError::ReadGuidTable)?; 98 99 // Let's start from the top and go backward down the table. 100 // We start after the footer GUID and the table length. 101 let mut offset = table_size - 18; 102 103 debug!("Parsing GUID structure"); 104 while offset >= 18 { 105 let entry_uuid = Uuid::from_slice_le(&table[offset - 16..offset]) 106 .map_err(TdvfError::UuidCreation)?; 107 let entry_size = 108 u16::from_le_bytes(table[offset - 18..offset - 16].try_into().unwrap()) as usize; 109 debug!( 110 "Entry GUID = {}, size = {}", 111 entry_uuid.hyphenated().to_string(), 112 entry_size 113 ); 114 115 // Avoid going through an infinite loop if the entry size is 0 116 if entry_size == 0 { 117 break; 118 } 119 120 offset -= entry_size; 121 122 let expected_uuid = 123 Uuid::from_str(TDVF_METADATA_OFFSET_GUID).map_err(TdvfError::UuidCreation)?; 124 if entry_uuid == expected_uuid && entry_size == 22 { 125 return Ok(( 126 SeekFrom::End( 127 -(u32::from_le_bytes(table[offset..offset + 4].try_into().unwrap()) as i64), 128 ), 129 true, 130 )); 131 } 132 } 133 } 134 135 // If we end up here, this means the firmware doesn't support the new way 136 // of exposing the TDVF descriptor offset through the table of GUIDs. 137 // That's why we fallback onto the deprecated method. 138 139 // The 32-bit offset to the TDVF metadata is located 32 bytes from 140 // the end of the file. 141 // See "TDVF Metadata Pointer" in "TDX Virtual Firmware Design Guide 142 file.seek(SeekFrom::End(-0x20)) 143 .map_err(TdvfError::ReadDescriptorOffset)?; 144 145 let mut descriptor_offset: [u8; 4] = [0; 4]; 146 file.read_exact(&mut descriptor_offset) 147 .map_err(TdvfError::ReadDescriptorOffset)?; 148 149 Ok(( 150 SeekFrom::Start(u32::from_le_bytes(descriptor_offset) as u64), 151 false, 152 )) 153 } 154 155 pub fn parse_tdvf_sections(file: &mut File) -> Result<(Vec<TdvfSection>, bool), TdvfError> { 156 let (descriptor_offset, guid_found) = tdvf_descriptor_offset(file)?; 157 158 file.seek(descriptor_offset) 159 .map_err(TdvfError::ReadDescriptor)?; 160 161 let mut descriptor: TdvfDescriptor = Default::default(); 162 // SAFETY: we read exactly the size of the descriptor header 163 file.read_exact(unsafe { 164 std::slice::from_raw_parts_mut( 165 &mut descriptor as *mut _ as *mut u8, 166 std::mem::size_of::<TdvfDescriptor>(), 167 ) 168 }) 169 .map_err(TdvfError::ReadDescriptor)?; 170 171 if &descriptor.signature != b"TDVF" { 172 return Err(TdvfError::InvalidDescriptorSignature); 173 } 174 175 if descriptor.length as usize 176 != std::mem::size_of::<TdvfDescriptor>() 177 + std::mem::size_of::<TdvfSection>() * descriptor.num_sections as usize 178 { 179 return Err(TdvfError::InvalidDescriptorSize); 180 } 181 182 if descriptor.version != 1 { 183 return Err(TdvfError::InvalidDescriptorVersion); 184 } 185 186 let mut sections = Vec::new(); 187 sections.resize_with(descriptor.num_sections as usize, TdvfSection::default); 188 189 // SAFETY: we read exactly the advertised sections 190 file.read_exact(unsafe { 191 std::slice::from_raw_parts_mut( 192 sections.as_mut_ptr() as *mut u8, 193 descriptor.num_sections as usize * std::mem::size_of::<TdvfSection>(), 194 ) 195 }) 196 .map_err(TdvfError::ReadDescriptor)?; 197 198 Ok((sections, guid_found)) 199 } 200 201 #[repr(u16)] 202 #[derive(Copy, Clone, Debug, Default)] 203 enum HobType { 204 Handoff = 0x1, 205 ResourceDescriptor = 0x3, 206 GuidExtension = 0x4, 207 #[default] 208 Unused = 0xfffe, 209 EndOfHobList = 0xffff, 210 } 211 212 #[repr(C, packed)] 213 #[derive(Copy, Clone, Default, Debug)] 214 struct HobHeader { 215 r#type: HobType, 216 length: u16, 217 reserved: u32, 218 } 219 220 #[repr(C, packed)] 221 #[derive(Copy, Clone, Default, Debug)] 222 struct HobHandoffInfoTable { 223 header: HobHeader, 224 version: u32, 225 boot_mode: u32, 226 efi_memory_top: u64, 227 efi_memory_bottom: u64, 228 efi_free_memory_top: u64, 229 efi_free_memory_bottom: u64, 230 efi_end_of_hob_list: u64, 231 } 232 233 #[repr(C, packed)] 234 #[derive(Copy, Clone, Default, Debug)] 235 struct EfiGuid { 236 data1: u32, 237 data2: u16, 238 data3: u16, 239 data4: [u8; 8], 240 } 241 242 #[repr(C, packed)] 243 #[derive(Copy, Clone, Default, Debug)] 244 struct HobResourceDescriptor { 245 header: HobHeader, 246 owner: EfiGuid, 247 resource_type: u32, 248 resource_attribute: u32, 249 physical_start: u64, 250 resource_length: u64, 251 } 252 253 #[repr(C, packed)] 254 #[derive(Copy, Clone, Default, Debug)] 255 struct HobGuidType { 256 header: HobHeader, 257 name: EfiGuid, 258 } 259 260 #[repr(u32)] 261 #[derive(Clone, Copy, Debug, Default)] 262 pub enum PayloadImageType { 263 #[default] 264 ExecutablePayload, 265 BzImage, 266 RawVmLinux, 267 } 268 269 #[repr(C, packed)] 270 #[derive(Copy, Clone, Default, Debug)] 271 pub struct PayloadInfo { 272 pub image_type: PayloadImageType, 273 pub entry_point: u64, 274 } 275 276 #[repr(C, packed)] 277 #[derive(Copy, Clone, Default, Debug)] 278 struct TdPayload { 279 guid_type: HobGuidType, 280 payload_info: PayloadInfo, 281 } 282 283 // SAFETY: data structure only contain a series of integers 284 unsafe impl ByteValued for HobHeader {} 285 // SAFETY: data structure only contain a series of integers 286 unsafe impl ByteValued for HobHandoffInfoTable {} 287 // SAFETY: data structure only contain a series of integers 288 unsafe impl ByteValued for HobResourceDescriptor {} 289 // SAFETY: data structure only contain a series of integers 290 unsafe impl ByteValued for HobGuidType {} 291 // SAFETY: data structure only contain a series of integers 292 unsafe impl ByteValued for PayloadInfo {} 293 // SAFETY: data structure only contain a series of integers 294 unsafe impl ByteValued for TdPayload {} 295 296 pub struct TdHob { 297 start_offset: u64, 298 current_offset: u64, 299 } 300 301 fn align_hob(v: u64) -> u64 { 302 (v + 7) / 8 * 8 303 } 304 305 impl TdHob { 306 fn update_offset<T>(&mut self) { 307 self.current_offset = align_hob(self.current_offset + std::mem::size_of::<T>() as u64) 308 } 309 310 pub fn start(offset: u64) -> TdHob { 311 // Leave a gap to place the HandoffTable at the start as it can only be filled in later 312 let mut hob = TdHob { 313 start_offset: offset, 314 current_offset: offset, 315 }; 316 hob.update_offset::<HobHandoffInfoTable>(); 317 hob 318 } 319 320 pub fn finish(&mut self, mem: &GuestMemoryMmap) -> Result<(), TdvfError> { 321 // Write end 322 let end = HobHeader { 323 r#type: HobType::EndOfHobList, 324 length: std::mem::size_of::<HobHeader>() as u16, 325 reserved: 0, 326 }; 327 info!("Writing HOB end {:x} {:x?}", self.current_offset, end); 328 mem.write_obj(end, GuestAddress(self.current_offset)) 329 .map_err(TdvfError::GuestMemoryWriteHob)?; 330 self.update_offset::<HobHeader>(); 331 332 // Write handoff, delayed as it needs end of HOB list 333 let efi_end_of_hob_list = self.current_offset; 334 let handoff = HobHandoffInfoTable { 335 header: HobHeader { 336 r#type: HobType::Handoff, 337 length: std::mem::size_of::<HobHandoffInfoTable>() as u16, 338 reserved: 0, 339 }, 340 version: 0x9, 341 boot_mode: 0, 342 efi_memory_top: 0, 343 efi_memory_bottom: 0, 344 efi_free_memory_top: 0, 345 efi_free_memory_bottom: 0, 346 efi_end_of_hob_list, 347 }; 348 info!("Writing HOB start {:x} {:x?}", self.start_offset, handoff); 349 mem.write_obj(handoff, GuestAddress(self.start_offset)) 350 .map_err(TdvfError::GuestMemoryWriteHob) 351 } 352 353 pub fn add_resource( 354 &mut self, 355 mem: &GuestMemoryMmap, 356 physical_start: u64, 357 resource_length: u64, 358 resource_type: u32, 359 resource_attribute: u32, 360 ) -> Result<(), TdvfError> { 361 let resource_descriptor = HobResourceDescriptor { 362 header: HobHeader { 363 r#type: HobType::ResourceDescriptor, 364 length: std::mem::size_of::<HobResourceDescriptor>() as u16, 365 reserved: 0, 366 }, 367 owner: EfiGuid::default(), 368 resource_type, 369 resource_attribute, 370 physical_start, 371 resource_length, 372 }; 373 info!( 374 "Writing HOB resource {:x} {:x?}", 375 self.current_offset, resource_descriptor 376 ); 377 mem.write_obj(resource_descriptor, GuestAddress(self.current_offset)) 378 .map_err(TdvfError::GuestMemoryWriteHob)?; 379 self.update_offset::<HobResourceDescriptor>(); 380 Ok(()) 381 } 382 383 pub fn add_memory_resource( 384 &mut self, 385 mem: &GuestMemoryMmap, 386 physical_start: u64, 387 resource_length: u64, 388 ram: bool, 389 guid_found: bool, 390 ) -> Result<(), TdvfError> { 391 self.add_resource( 392 mem, 393 physical_start, 394 resource_length, 395 if ram { 396 if guid_found { 397 0x7 /* EFI_RESOURCE_MEMORY_UNACCEPTED */ 398 } else { 399 0 /* EFI_RESOURCE_SYSTEM_MEMORY */ 400 } 401 } else if guid_found { 402 0 /* EFI_RESOURCE_SYSTEM_MEMORY */ 403 } else { 404 0x5 /*EFI_RESOURCE_MEMORY_RESERVED */ 405 }, 406 /* TODO: 407 * QEMU currently fills it in like this: 408 * EFI_RESOURCE_ATTRIBUTE_PRESENT | EFI_RESOURCE_ATTRIBUTE_INITIALIZED | EFI_RESOURCE_ATTRIBUTE_TESTED 409 * which differs from the spec (due to TDVF implementation issue?) 410 */ 411 0x7, 412 ) 413 } 414 415 pub fn add_mmio_resource( 416 &mut self, 417 mem: &GuestMemoryMmap, 418 physical_start: u64, 419 resource_length: u64, 420 ) -> Result<(), TdvfError> { 421 self.add_resource( 422 mem, 423 physical_start, 424 resource_length, 425 0x1, /* EFI_RESOURCE_MEMORY_MAPPED_IO */ 426 /* 427 * EFI_RESOURCE_ATTRIBUTE_PRESENT | EFI_RESOURCE_ATTRIBUTE_INITIALIZED | EFI_RESOURCE_ATTRIBUTE_UNCACHEABLE 428 */ 429 0x403, 430 ) 431 } 432 433 pub fn add_acpi_table( 434 &mut self, 435 mem: &GuestMemoryMmap, 436 table_content: &[u8], 437 ) -> Result<(), TdvfError> { 438 // We already know the HobGuidType size is 8 bytes multiple, but we 439 // need the total size to be 8 bytes multiple. That is why the ACPI 440 // table size must be 8 bytes multiple as well. 441 let length = std::mem::size_of::<HobGuidType>() as u16 442 + align_hob(table_content.len() as u64) as u16; 443 let hob_guid_type = HobGuidType { 444 header: HobHeader { 445 r#type: HobType::GuidExtension, 446 length, 447 reserved: 0, 448 }, 449 // ACPI_TABLE_HOB_GUID 450 // 0x6a0c5870, 0xd4ed, 0x44f4, {0xa1, 0x35, 0xdd, 0x23, 0x8b, 0x6f, 0xc, 0x8d } 451 name: EfiGuid { 452 data1: 0x6a0c_5870, 453 data2: 0xd4ed, 454 data3: 0x44f4, 455 data4: [0xa1, 0x35, 0xdd, 0x23, 0x8b, 0x6f, 0xc, 0x8d], 456 }, 457 }; 458 info!( 459 "Writing HOB ACPI table {:x} {:x?} {:x?}", 460 self.current_offset, hob_guid_type, table_content 461 ); 462 mem.write_obj(hob_guid_type, GuestAddress(self.current_offset)) 463 .map_err(TdvfError::GuestMemoryWriteHob)?; 464 let current_offset = self.current_offset + std::mem::size_of::<HobGuidType>() as u64; 465 466 // In case the table is quite large, let's make sure we can handle 467 // retrying until everything has been correctly copied. 468 let mut offset: usize = 0; 469 loop { 470 let bytes_written = mem 471 .write( 472 &table_content[offset..], 473 GuestAddress(current_offset + offset as u64), 474 ) 475 .map_err(TdvfError::GuestMemoryWriteHob)?; 476 offset += bytes_written; 477 if offset >= table_content.len() { 478 break; 479 } 480 } 481 self.current_offset += length as u64; 482 483 Ok(()) 484 } 485 486 pub fn add_payload( 487 &mut self, 488 mem: &GuestMemoryMmap, 489 payload_info: PayloadInfo, 490 ) -> Result<(), TdvfError> { 491 let payload = TdPayload { 492 guid_type: HobGuidType { 493 header: HobHeader { 494 r#type: HobType::GuidExtension, 495 length: std::mem::size_of::<TdPayload>() as u16, 496 reserved: 0, 497 }, 498 // HOB_PAYLOAD_INFO_GUID 499 // 0xb96fa412, 0x461f, 0x4be3, {0x8c, 0xd, 0xad, 0x80, 0x5a, 0x49, 0x7a, 0xc0 500 name: EfiGuid { 501 data1: 0xb96f_a412, 502 data2: 0x461f, 503 data3: 0x4be3, 504 data4: [0x8c, 0xd, 0xad, 0x80, 0x5a, 0x49, 0x7a, 0xc0], 505 }, 506 }, 507 payload_info, 508 }; 509 info!( 510 "Writing HOB TD_PAYLOAD {:x} {:x?}", 511 self.current_offset, payload 512 ); 513 mem.write_obj(payload, GuestAddress(self.current_offset)) 514 .map_err(TdvfError::GuestMemoryWriteHob)?; 515 self.update_offset::<TdPayload>(); 516 517 Ok(()) 518 } 519 } 520 521 #[cfg(test)] 522 mod tests { 523 use super::*; 524 525 #[test] 526 #[ignore] 527 fn test_parse_tdvf_sections() { 528 let mut f = std::fs::File::open("tdvf.fd").unwrap(); 529 let (sections, _) = parse_tdvf_sections(&mut f).unwrap(); 530 for section in sections { 531 eprintln!("{section:x?}") 532 } 533 } 534 } 535