lit_sev_snp_utils/guest/measure/
ovmf.rs

1use 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/// Types of sections declared by OVMF SEV Metadata, as appears in:
22/// https://github.com/tianocore/edk2/blob/edk2-stable202205/OvmfPkg/ResetVector/X64/OvmfSevMetadata.asm
23#[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    // Accessors
177
178    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    // Parsing
230
231    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        // metadata_items
338        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        // table
363        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        // sev_hashes_table_gpa
385        assert_eq!(ovmf.sev_hashes_table_gpa().unwrap(), 8453120);
386
387        // sev_es_reset_eip
388        assert_eq!(ovmf.sev_es_reset_eip().unwrap(), 8433668);
389    }
390}