igvmfilegen/signed_measurement/
snp.rs1use 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
27pub 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 hasher.update(zero_page.as_bytes());
44 let zero_digest = hasher.finalize();
45
46 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!(
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 for header in directive_headers {
112 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(¶meter_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 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(¶m.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 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 tracing::info!("SNP ID Block {:x?}", psp_id_block);
215 Ok(psp_id_block.ld)
216}