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")]
17 ReadDescriptor(#[source] std::io::Error),
18 #[error("Failed read TDVF descriptor offset")]
19 ReadDescriptorOffset(#[source] std::io::Error),
20 #[error("Failed read GUID table")]
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")]
29 GuestMemoryWriteHob(#[source] GuestMemoryError),
30 #[error("Failed to create Uuid")]
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
tdvf_descriptor_offset(file: &mut File) -> Result<(SeekFrom, bool), TdvfError>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
parse_tdvf_sections(file: &mut File) -> Result<(Vec<TdvfSection>, bool), TdvfError>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
align_hob(v: u64) -> u64301 fn align_hob(v: u64) -> u64 {
302 v.div_ceil(8) * 8
303 }
304
305 impl TdHob {
update_offset<T>(&mut self)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
start(offset: u64) -> TdHob310 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
finish(&mut self, mem: &GuestMemoryMmap) -> Result<(), TdvfError>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
add_resource( &mut self, mem: &GuestMemoryMmap, physical_start: u64, resource_length: u64, resource_type: u32, resource_attribute: u32, ) -> Result<(), TdvfError>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
add_memory_resource( &mut self, mem: &GuestMemoryMmap, physical_start: u64, resource_length: u64, ram: bool, guid_found: bool, ) -> Result<(), TdvfError>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
add_mmio_resource( &mut self, mem: &GuestMemoryMmap, physical_start: u64, resource_length: u64, ) -> Result<(), TdvfError>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
add_acpi_table( &mut self, mem: &GuestMemoryMmap, table_content: &[u8], ) -> Result<(), TdvfError>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
add_payload( &mut self, mem: &GuestMemoryMmap, payload_info: PayloadInfo, ) -> Result<(), TdvfError>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]
test_parse_tdvf_sections()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