lit_sev_snp_utils/guest/measure/
ovmf.rs1use std::collections::HashMap;
2use std::fs;
3use std::path::Path;
4use bytemuck::{Pod, try_from_bytes, Zeroable};
5
6use libc::{c_uchar, c_uint};
7use uuid::{Bytes, Uuid};
8
9use crate::common::binary::{fmt_slice_vec_to_hex};
10use crate::common::guid::guid_le_to_slice;
11use crate::error::{conversion, io, Result, validation};
12
13const EXPECTED_METADATA_SIG: &[u8] = b"ASEV";
14
15const FOUR_GB: u64 = 0x100000000;
16const OVMF_TABLE_FOOTER_GUID: &'static str = "96b582de-1fb2-45f7-baea-a366c55a082d";
17const SEV_HASH_TABLE_RV_GUID: &'static str = "7255371f-3a3b-4b04-927b-1da6efa8d454";
18const SEV_ES_RESET_BLOCK_GUID: &'static str = "00f771de-1a7e-4fcb-890e-68c77e2fb44e";
19const OVMF_SEV_META_DATA_GUID: &'static str = "dc886566-984a-4798-a75e-5585a7bf67cc";
20
21#[derive(Debug, Clone, PartialEq)]
24pub enum SectionType {
25 SnpSecMem = 1,
26 SnpSecrets = 2,
27 CPUID = 3,
28}
29
30impl TryFrom<u8> for SectionType {
31 type Error = crate::error::Error;
32
33 fn try_from(value: u8) -> Result<Self> {
34 match value {
35 1 => Ok(SectionType::SnpSecMem),
36 2 => Ok(SectionType::SnpSecrets),
37 3 => Ok(SectionType::CPUID),
38 _ => {
39 return Err(conversion(format!("value: '{}' cannot map to SectionType", value), None));
40 }
41 }
42 }
43}
44
45#[repr(C)]
46#[derive(Debug, Clone, Copy)]
47pub struct OvmfSevMetadataSectionDesc {
48 gpa: c_uint,
49 size: c_uint,
50 section_type_id: c_uint,
51}
52
53impl OvmfSevMetadataSectionDesc {
54 pub fn try_from_bytes(value: &[u8], offset: usize) -> Result<&Self> {
55 let value = &value[offset..offset +
56 std::mem::size_of::<OvmfSevMetadataSectionDesc>()];
57
58 try_from_bytes(value)
59 .map_err(|e| conversion(e.to_string(), None))
60 }
61
62 pub fn gpa(&self) -> u32 {
63 self.gpa as u32
64 }
65
66 pub fn size(&self) -> u32 {
67 self.size as u32
68 }
69
70 pub fn section_type_id(&self) -> u32 {
71 self.section_type_id as u32
72 }
73
74 pub fn section_type(&self) -> Result<SectionType> {
75 SectionType::try_from(self.section_type_id as u8)
76 }
77}
78
79unsafe impl Zeroable for OvmfSevMetadataSectionDesc {
80
81}
82
83unsafe impl Pod for OvmfSevMetadataSectionDesc {
84
85}
86
87#[repr(C)]
88#[derive(Debug, Clone, Copy)]
89pub struct OvmfSevMetadataHeader {
90 signature: [c_uchar; 4],
91 size: c_uint,
92 version: c_uint,
93 num_items: c_uint,
94}
95
96impl OvmfSevMetadataHeader {
97 pub fn try_from_bytes(value: &[u8], offset: usize) -> Result<&Self> {
98 let value = &value[offset..offset +
99 std::mem::size_of::<OvmfSevMetadataHeader>()];
100
101 try_from_bytes(value)
102 .map_err(|e| conversion(e.to_string(), None))
103 }
104
105 pub fn signature(&self) -> &[u8; 4] {
106 &self.signature
107 }
108
109 pub fn size(&self) -> u32 {
110 self.size as u32
111 }
112
113 pub fn version(&self) -> u32 {
114 self.version as u32
115 }
116
117 pub fn num_items(&self) -> u32 {
118 self.num_items as u32
119 }
120
121 pub fn verify(&self) -> Result<()> {
122 if !self.signature.eq(EXPECTED_METADATA_SIG) {
123 return match String::from_utf8(self.signature.to_vec()) {
124 Ok(sig) => {
125 Err(validation(format!("Wrong SEV metadata signature: {}", sig),
126 None))
127 }
128 Err(_e) => {
129 Err(validation(format!("Wrong SEV metadata signature: {:?}", self.signature),
130 None))
131 }
132 };
133 }
134 if self.version != 1 {
135 return Err(validation(format!("Wrong SEV metadata version: {:?}", self.version),
136 None));
137 }
138
139 Ok(())
140 }
141}
142
143unsafe impl Zeroable for OvmfSevMetadataHeader {
144
145}
146
147unsafe impl Pod for OvmfSevMetadataHeader {
148
149}
150
151pub struct OVMF {
152 data: Vec<u8>,
153 table: HashMap<String, Vec<u8>>,
154 metadata_header: Option<OvmfSevMetadataHeader>,
155 metadata_items: Vec<OvmfSevMetadataSectionDesc>
156}
157
158impl OVMF {
159 pub fn from_path<P: AsRef<Path>>(path: P) -> Result<Self> {
160 let data = fs::read(&path)
161 .map_err(|e| io(e, None))?;
162
163 let mut ovmf = Self {
164 data,
165 table: HashMap::new(),
166 metadata_header: None,
167 metadata_items: Vec::new()
168 };
169
170 ovmf.parse_footer_table()?;
171 ovmf.parse_sev_metadata()?;
172
173 Ok(ovmf)
174 }
175
176 pub fn data(&self) -> &Vec<u8> {
179 &self.data
180 }
181
182 pub fn gpa(&self) -> u64 {
183 FOUR_GB - (self.data.len() as u64)
184 }
185
186 pub fn table_item(&self, guid: &str) -> Option<&Vec<u8>> {
187 self.table.get(guid)
188 }
189
190 pub fn metadata_items(&self) -> &Vec<OvmfSevMetadataSectionDesc> {
191 &self.metadata_items
192 }
193
194 pub fn sev_hashes_table_gpa(&self) -> Result<i32> {
195 match self.table_item(SEV_HASH_TABLE_RV_GUID) {
196 Some(entry) => {
197 let val: [u8; 4] = entry[..4]
198 .try_into()
199 .map_err(|e| conversion(e, None))?;
200 let val: i32 = i32::from_le_bytes(val);
201
202 Ok(val)
203 }
204 None => {
205 return Err(validation("OVMF SEV metadata: missing table guid 'SEV_HASH_TABLE_RV_GUID'", None));
206 }
207 }
208 }
209
210 pub fn sev_es_reset_eip(&self) -> Result<u64> {
211 match self.table_item(SEV_ES_RESET_BLOCK_GUID) {
212 Some(entry) => {
213 let val: [u8; 4] = entry[..4]
214 .try_into()
215 .map_err(|e| conversion(e, None))?;
216 let val: i32 = i32::from_le_bytes(val);
217 if val < 0 {
218 return Err(validation("sev_es_reset_eip < 0", None));
219 }
220
221 Ok(val as u64)
222 }
223 None => {
224 return Err(validation("OVMF SEV metadata: missing table guid 'SEV_ES_RESET_BLOCK_GUID'", None));
225 }
226 }
227 }
228
229 fn parse_footer_table(&mut self) -> Result<()> {
232 self.table.clear();
233 let len = self.data.len();
234
235 let footer_guid = &self.data[len-48..len-32];
236 let expected_footer_guid = guid_le_to_slice(OVMF_TABLE_FOOTER_GUID)?;
237 if !footer_guid.eq(&expected_footer_guid) {
238 return Err(validation(format!("OVMF table footer GUID does not match ({} vs {})",
239 fmt_slice_vec_to_hex(&expected_footer_guid),
240 fmt_slice_vec_to_hex(footer_guid)), None));
241 }
242
243 let full_table_size: [u8; 2] = self.data[len-50..len-48].try_into()
244 .map_err(|e| conversion(e, None))?;
245 let full_table_size: i16 = i16::from_le_bytes(full_table_size);
246 let table_size = full_table_size - 16 - 2;
247 if table_size < 0 {
248 return Err(validation("OVMF table footer: table size < 0", None));
249 }
250 let table_size: usize = table_size as usize;
251
252 let mut table_bytes = &self.data[len-50-table_size..len-50];
253 while table_bytes.len() >= (16 + 2) {
254 let table_bytes_len = table_bytes.len();
255 let entry_guid = &table_bytes[table_bytes_len-16..];
256 let entry_guid_bytes: Bytes = entry_guid
257 .try_into()
258 .map_err(|e| conversion(e, None))?;
259 let entry_guid_str = Uuid::from_bytes_le(entry_guid_bytes).to_string();
260
261 let entry_size: [u8; 2] = table_bytes[table_bytes_len-18..table_bytes_len-16]
262 .try_into()
263 .map_err(|e| conversion(e, None))?;
264 let entry_size: i16 = i16::from_le_bytes(entry_size);
265 if entry_size < (16 + 2) {
266 return Err(validation("OVMF table footer: invalid entry size", None));
267 }
268 let entry_size: usize = entry_size as usize;
269
270 let entry_data = &table_bytes[table_bytes_len-entry_size..table_bytes_len-18];
271
272 self.table.insert(entry_guid_str, entry_data.to_vec());
273
274 table_bytes = &table_bytes[..table_bytes_len-entry_size];
275 }
276
277 Ok(())
278 }
279
280 fn parse_sev_metadata(&mut self) -> Result<()> {
281 match self.table.get(OVMF_SEV_META_DATA_GUID) {
282 Some(entry) => {
283 let offset_from_end: [u8; 4] = entry[..4]
284 .try_into()
285 .map_err(|e| conversion(e, None))?;
286 let offset_from_end: i32 = i32::from_le_bytes(offset_from_end);
287 let start = self.data.len() - (offset_from_end as usize);
288
289 let header =
290 OvmfSevMetadataHeader::try_from_bytes(self.data.as_slice(), start)?;
291 header.verify()?;
292
293 let items = &self.data[start+std::mem::size_of::<OvmfSevMetadataHeader>()..start+(header.size as usize)];
294
295 for i in 0..header.num_items() {
296 let offset = (i as usize) * std::mem::size_of::<OvmfSevMetadataSectionDesc>();
297
298 let item =
299 OvmfSevMetadataSectionDesc::try_from_bytes(items, offset)?;
300
301 self.metadata_items.push(item.to_owned());
302 }
303
304 self.metadata_header = Some(header.to_owned());
305 }
306 None => {
307 return Err(validation("OVMF SEV metadata: missing metadata GUID", None));
308 }
309 }
310
311 Ok(())
312 }
313}
314
315#[cfg(test)]
316mod tests {
317 use std::path::PathBuf;
318 use crate::common::binary::fmt_slice_vec_to_hex;
319 use crate::guest::measure::ovmf::{OVMF, SectionType};
320
321 #[test]
322 fn ovmf_file_test() {
323 const TEST_OVMF_CODE_FILE: &str = "resources/test/measure/OVMF_CODE.fd";
324
325 let mut test_file = PathBuf::from(env!("CARGO_MANIFEST_DIR"));
326 test_file.push(TEST_OVMF_CODE_FILE);
327
328 let ovmf = OVMF::from_path(&test_file)
329 .expect("failed to load OVMF file");
330
331 let metadata_header = ovmf.metadata_header.unwrap();
332 metadata_header.verify().unwrap();
333
334 assert_eq!(metadata_header.size, 76);
335 assert_eq!(metadata_header.num_items, 5);
336
337 assert_eq!(ovmf.metadata_items().len(), 5);
339
340 for (idx, exp_gpa, exp_size, exp_section_type_id, exp_section_type) in vec![
341 (0, 8388608, 36864, 1, SectionType::SnpSecMem),
342 (1, 8429568, 12288, 1, SectionType::SnpSecMem),
343 (2, 8441856, 4096, 2, SectionType::SnpSecrets),
344 (3, 8445952, 4096, 3, SectionType::CPUID),
345 (4, 8454144, 65536, 1, SectionType::SnpSecMem),
346 ] {
347 println!("Running metadata_items test idx: {}", idx);
348
349 match ovmf.metadata_items().get(idx) {
350 Some(item) => {
351 assert_eq!(exp_gpa, item.gpa());
352 assert_eq!(exp_size, item.size());
353 assert_eq!(exp_section_type_id, item.section_type_id());
354 assert_eq!(exp_section_type, item.section_type().unwrap());
355 }
356 None => {
357 panic!("missing metadata_items idx: {}", idx);
358 }
359 }
360 }
361
362 assert_eq!(ovmf.table.len(), 5);
364
365 for (guid, data) in vec![
366 ("00f771de-1a7e-4fcb-890e-68c77e2fb44e", "04b08000"),
367 ("4c2eb361-7d9b-4cc3-8081-127c90d3d294", "00f08000000c0000"),
368 ("7255371f-3a3b-4b04-927b-1da6efa8d454", "00fc800000040000"),
369 ("dc886566-984a-4798-a75e-5585a7bf67cc", "2c050000"),
370 ("e47a6535-984a-4798-865e-4685a7bf8ec2", "40080000")
371 ] {
372 println!("Running table_item test guid: {}", guid);
373
374 match ovmf.table_item(guid) {
375 Some(entry) => {
376 assert_eq!(data, fmt_slice_vec_to_hex(entry));
377 }
378 None => {
379 panic!("missing table guid: {}", guid);
380 }
381 }
382 }
383
384 assert_eq!(ovmf.sev_hashes_table_gpa().unwrap(), 8453120);
386
387 assert_eq!(ovmf.sev_es_reset_eip().unwrap(), 8433668);
389 }
390}