igvmfilegen/signed_measurement/
snp.rs

1// Copyright (c) Microsoft Corporation.
2// Licensed under the MIT License.
3
4//! Support for creating SNP ID blocks
5
6use super::SHA_384_OUTPUT_SIZE_BYTES;
7use crate::file_loader::DEFAULT_COMPATIBILITY_MASK;
8use igvm::IgvmDirectiveHeader;
9use igvm::IgvmInitializationHeader;
10use igvm_defs::IgvmPageDataType;
11use igvm_defs::PAGE_SIZE_4K;
12use sha2::Digest;
13use sha2::Sha384;
14use std::collections::HashMap;
15use thiserror::Error;
16use x86defs::snp::SnpPageInfo;
17use x86defs::snp::SnpPageType;
18use x86defs::snp::SnpPspIdBlock;
19use zerocopy::IntoBytes;
20
21#[derive(Debug, Error)]
22pub enum Error {
23    #[error("invalid parameter area index")]
24    InvalidParameterAreaIndex,
25}
26
27/// Iterate through all headers, creating a launch digest which is then signed,
28/// returning an [`IgvmDirectiveHeader::SnpIdBlock`]
29pub fn generate_snp_measurement(
30    initialization_headers: &[IgvmInitializationHeader],
31    directive_headers: &[IgvmDirectiveHeader],
32    svn: u32,
33) -> Result<[u8; SHA_384_OUTPUT_SIZE_BYTES], Error> {
34    let mut parameter_area_table = HashMap::new();
35    const PAGE_SIZE_4K_USIZE: usize = PAGE_SIZE_4K as usize;
36    let snp_compatibility_mask = DEFAULT_COMPATIBILITY_MASK;
37
38    let mut launch_digest: [u8; SHA_384_OUTPUT_SIZE_BYTES] = [0; SHA_384_OUTPUT_SIZE_BYTES];
39    let zero_page: [u8; PAGE_SIZE_4K as usize] = [0; PAGE_SIZE_4K as usize];
40    let mut hasher = Sha384::new();
41
42    // Hash the contents of empty 4K page, used when file does not carry data
43    hasher.update(zero_page.as_bytes());
44    let zero_digest = hasher.finalize();
45
46    // Reuse the same vec for padding out data to 4k.
47    let mut padding_vec = vec![0; PAGE_SIZE_4K_USIZE];
48
49    let mut measure_page = |page_type: SnpPageType, gpa: u64, page_data: Option<&[u8]>| {
50        let mut hash = Sha384::new();
51        let hash_contents = match page_data {
52            Some(data) => {
53                match data.len() {
54                    0 => zero_digest,
55                    _ if data.len() < PAGE_SIZE_4K_USIZE => {
56                        padding_vec.fill(0);
57                        padding_vec[..data.len()].copy_from_slice(data);
58                        hash.update(&padding_vec);
59                        hash.finalize()
60                    }
61                    PAGE_SIZE_4K_USIZE => {
62                        hash.update(data);
63                        hash.finalize()
64                    }
65                    _ => {
66                        // TODO SNP: Need to check the PSP spec how to measure 2MB
67                        // pages. Fail for now, as they shouldn't exist.
68                        todo!(
69                            "unable to measure greater than 4k pages, len: {}",
70                            data.len()
71                        )
72                    }
73                }
74            }
75            None => [0; SHA_384_OUTPUT_SIZE_BYTES].into(),
76        };
77
78        let info = SnpPageInfo {
79            digest_current: launch_digest,
80            contents: hash_contents.into(),
81            length: size_of::<SnpPageInfo>() as u16,
82            page_type,
83            imi_page_bit: 0,
84            lower_vmpl_permissions: 0,
85            gpa,
86        };
87
88        let mut hash = Sha384::new();
89        hash.update(info.as_bytes());
90        launch_digest = hash.finalize().into();
91    };
92
93    let mut policy: u64 = 0;
94
95    for header in initialization_headers {
96        if let IgvmInitializationHeader::GuestPolicy {
97            policy: snp_policy,
98            compatibility_mask,
99        } = header
100        {
101            assert_eq!(
102                compatibility_mask & snp_compatibility_mask,
103                snp_compatibility_mask
104            );
105            policy = *snp_policy;
106        }
107    }
108    assert_ne!(policy, 0);
109
110    // Loop over all the page data to build the digest
111    for header in directive_headers {
112        // Skip headers that have compatibility masks that do not match snp.
113        if header
114            .compatibility_mask()
115            .map(|mask| mask & snp_compatibility_mask != snp_compatibility_mask)
116            .unwrap_or(false)
117        {
118            continue;
119        }
120
121        match header {
122            IgvmDirectiveHeader::ErrorRange { .. } => todo!("error range not implemented"),
123            IgvmDirectiveHeader::ParameterArea {
124                number_of_bytes,
125                parameter_area_index,
126                initial_data: _,
127            } => {
128                assert_eq!(
129                    parameter_area_table.contains_key(&parameter_area_index),
130                    false
131                );
132                assert_eq!(number_of_bytes % PAGE_SIZE_4K, 0);
133                parameter_area_table.insert(parameter_area_index, number_of_bytes);
134            }
135            IgvmDirectiveHeader::PageData {
136                gpa,
137                compatibility_mask,
138                flags,
139                data_type,
140                data,
141            } => {
142                assert_eq!(
143                    compatibility_mask & snp_compatibility_mask,
144                    snp_compatibility_mask
145                );
146
147                // Skip shared pages.
148                if flags.shared() {
149                    continue;
150                }
151
152                let (page_type, data) = match *data_type {
153                    IgvmPageDataType::SECRETS => (SnpPageType::SECRETS, None),
154                    IgvmPageDataType::CPUID_DATA | IgvmPageDataType::CPUID_XF => {
155                        (SnpPageType::CPUID, None)
156                    }
157                    _ => {
158                        if flags.unmeasured() {
159                            (SnpPageType::UNMEASURED, None)
160                        } else {
161                            (SnpPageType::NORMAL, Some(data.as_bytes()))
162                        }
163                    }
164                };
165
166                measure_page(page_type, *gpa, data);
167            }
168            IgvmDirectiveHeader::ParameterInsert(param) => {
169                assert_eq!(
170                    param.compatibility_mask & snp_compatibility_mask,
171                    snp_compatibility_mask
172                );
173
174                let parameter_area_size = parameter_area_table
175                    .get(&param.parameter_area_index)
176                    .ok_or(Error::InvalidParameterAreaIndex)?;
177
178                for gpa in (param.gpa..param.gpa + *parameter_area_size).step_by(PAGE_SIZE_4K_USIZE)
179                {
180                    measure_page(SnpPageType::UNMEASURED, gpa, None)
181                }
182            }
183            IgvmDirectiveHeader::SnpVpContext {
184                gpa,
185                compatibility_mask,
186                vp_index: _,
187                vmsa,
188            } => {
189                assert_eq!(
190                    compatibility_mask & snp_compatibility_mask,
191                    snp_compatibility_mask
192                );
193
194                let vmsa_bytes = vmsa.as_ref().as_bytes();
195                measure_page(SnpPageType::VMSA, *gpa, Some(vmsa_bytes));
196            }
197            _ => {}
198        }
199    }
200
201    let family_id = *b"msft\0\0\0\0\0\0\0\0\0\0\0\0";
202    let image_id = *b"underhill\0\0\0\0\0\0\0";
203
204    // Generate the PSP ID block format, hash with SHA-384
205    let psp_id_block = SnpPspIdBlock {
206        ld: launch_digest,
207        version: 0x1,
208        guest_svn: svn,
209        policy,
210        family_id,
211        image_id,
212    };
213    // Print the ID block for reference, not currently used.
214    tracing::info!("SNP ID Block {:x?}", psp_id_block);
215    Ok(psp_id_block.ld)
216}