xref: /cloud-hypervisor/arch/src/x86_64/tdx/mod.rs (revision 2fe7f54ece2a8f0461ed29aaeab41614f1f2da75)
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