igvmfilegen/signed_measurement/
vbs.rs1use super::SHA_256_OUTPUT_SIZE_BYTES;
7use crate::file_loader::DEFAULT_COMPATIBILITY_MASK;
8use igvm::IgvmDirectiveHeader;
9use igvm_defs::IgvmPageDataType;
10use igvm_defs::PAGE_SIZE_4K;
11use igvm_defs::VbsDigestAlgorithm;
12use igvm_defs::VbsSigningAlgorithm;
13use igvm_defs::VbsVpContextRegister;
14use sha2::Digest;
15use sha2::Sha256;
16use std::collections::HashMap;
17use thiserror::Error;
18use vbs_defs::BootMeasurementType;
19use vbs_defs::VBS_POLICY_FLAGS;
20use vbs_defs::VBS_VM_BOOT_MEASUREMENT_SIGNED_DATA;
21use vbs_defs::VBS_VM_GPA_PAGE_BOOT_METADATA;
22use vbs_defs::VBS_VP_CHUNK_SIZE_BYTES;
23use vbs_defs::VM_GPA_PAGE_READABLE;
24use vbs_defs::VM_GPA_PAGE_WRITABLE;
25use vbs_defs::VbsChunkHeader;
26use vbs_defs::VbsRegisterChunk;
27use vbs_defs::VpGpaPageChunk;
28use zerocopy::IntoBytes;
29
30#[derive(Debug, Error)]
31pub enum Error {
32 #[error("invalid parameter area index")]
33 InvalidParameterAreaIndex,
34}
35
36pub fn generate_vbs_measurement(
39 directive_headers: &[IgvmDirectiveHeader],
40 enable_debug: bool,
41 svn: u32,
42) -> Result<[u8; SHA_256_OUTPUT_SIZE_BYTES], Error> {
43 const VBS_COMPATIBILITY_MASK: u32 = DEFAULT_COMPATIBILITY_MASK;
44
45 let mut digest = VbsDigestor::new()?;
46 let mut parameter_area_table = HashMap::new();
47 let mut bsp_regs = Vec::new();
48
49 for header in directive_headers {
50 if header
52 .compatibility_mask()
53 .map(|mask| mask & VBS_COMPATIBILITY_MASK != VBS_COMPATIBILITY_MASK)
54 .unwrap_or(false)
55 {
56 continue;
57 }
58
59 match header {
60 IgvmDirectiveHeader::PageData {
61 gpa,
62 compatibility_mask,
63 flags,
64 data_type,
65 data,
66 } => {
67 assert_eq!(
68 compatibility_mask & VBS_COMPATIBILITY_MASK,
69 VBS_COMPATIBILITY_MASK
70 );
71
72 assert_eq!(*data_type, IgvmPageDataType::NORMAL);
73
74 if flags.shared() {
76 continue;
77 }
78
79 let boot_metadata = VBS_VM_GPA_PAGE_BOOT_METADATA::new()
80 .with_acceptance(0)
81 .with_data_unmeasured(flags.unmeasured());
82 digest.record_gpa_page(gpa / PAGE_SIZE_4K, 1, boot_metadata, data)?;
83 }
84 IgvmDirectiveHeader::ParameterInsert(param) => {
85 let page_metadata = VBS_VM_GPA_PAGE_BOOT_METADATA::new()
86 .with_acceptance(0)
87 .with_data_unmeasured(true);
88 let parameter_area_size = parameter_area_table
89 .get(¶m.parameter_area_index)
90 .ok_or(Error::InvalidParameterAreaIndex)?;
91 digest.record_gpa_page(
92 param.gpa / PAGE_SIZE_4K,
93 parameter_area_size / PAGE_SIZE_4K,
94 page_metadata,
95 &[],
96 )?;
97 }
98 IgvmDirectiveHeader::X64VbsVpContext {
99 vtl,
100 registers,
101 compatibility_mask,
102 } => {
103 assert_eq!(
104 compatibility_mask & VBS_COMPATIBILITY_MASK,
105 VBS_COMPATIBILITY_MASK
106 );
107 let vtl_registers: Vec<VbsVpContextRegister> = registers
109 .iter()
110 .map(|r| r.into_vbs_vp_context_reg(*vtl))
111 .collect();
112 bsp_regs.push(vtl_registers);
113 }
114 IgvmDirectiveHeader::AArch64VbsVpContext {
115 vtl,
116 registers,
117 compatibility_mask,
118 } => {
119 assert_eq!(
120 compatibility_mask & VBS_COMPATIBILITY_MASK,
121 VBS_COMPATIBILITY_MASK
122 );
123 let vtl_registers: Vec<VbsVpContextRegister> = registers
125 .iter()
126 .map(|r| r.into_vbs_vp_context_reg(*vtl))
127 .collect();
128 bsp_regs.push(vtl_registers);
129 }
130 IgvmDirectiveHeader::ErrorRange {
131 gpa,
132 compatibility_mask,
133 size_bytes,
134 } => {
135 assert_eq!(
136 compatibility_mask & VBS_COMPATIBILITY_MASK,
137 VBS_COMPATIBILITY_MASK
138 );
139 let page_metadata = VBS_VM_GPA_PAGE_BOOT_METADATA::new()
140 .with_acceptance(VM_GPA_PAGE_READABLE | VM_GPA_PAGE_WRITABLE)
141 .with_data_unmeasured(true);
142 digest.record_gpa_page(
143 *gpa / PAGE_SIZE_4K,
144 (*size_bytes as u64).div_ceil(PAGE_SIZE_4K),
145 page_metadata,
146 &[],
147 )?;
148 }
149 IgvmDirectiveHeader::ParameterArea {
150 number_of_bytes,
151 parameter_area_index,
152 initial_data: _,
153 } => {
154 if parameter_area_table.contains_key(parameter_area_index) {
155 return Err(Error::InvalidParameterAreaIndex);
156 }
157 parameter_area_table.insert(parameter_area_index, *number_of_bytes);
158 }
159 _ => {}
160 }
161 }
162
163 for set in bsp_regs {
165 for reg in set {
166 digest.record_vp_register(reg)?;
167 }
168 }
169
170 const MSFT_PRODUCT_ID: u32 = u32::from_le_bytes(*b"msft");
172 const VBS_MODULE_ID: u32 = u32::from_le_bytes(*b"vbs\0");
173 const VBS_VM_BOOT_MEASUREMENT_VERSION_CURRENT: u32 = 0x1;
174
175 let boot_measurement = VBS_VM_BOOT_MEASUREMENT_SIGNED_DATA {
176 version: VBS_VM_BOOT_MEASUREMENT_VERSION_CURRENT,
177 product_id: MSFT_PRODUCT_ID,
178 module_id: VBS_MODULE_ID,
179 security_version: svn,
180 security_policy: VBS_POLICY_FLAGS::new().with_debug(enable_debug),
181 boot_digest_algo: VbsDigestAlgorithm::SHA256.0,
182 signing_algo: VbsSigningAlgorithm::ECDSA_P384.0,
183 boot_measurement_digest: digest.finish_digest(),
184 };
185 tracing::info!("Boot Measurement {:x?}", boot_measurement);
187 Ok(boot_measurement.boot_measurement_digest)
188}
189
190struct VbsDigestor {
191 digest: [u8; SHA_256_OUTPUT_SIZE_BYTES],
192}
193
194impl VbsDigestor {
195 fn new() -> Result<VbsDigestor, Error> {
196 Ok(VbsDigestor {
197 digest: [0; SHA_256_OUTPUT_SIZE_BYTES],
198 })
199 }
200
201 fn record_gpa_page(
202 &mut self,
203 gpa_page_base: u64,
204 page_count: u64,
205 page_metadata: VBS_VM_GPA_PAGE_BOOT_METADATA,
206 mut data: &[u8],
207 ) -> Result<(), Error> {
208 for page in 0..page_count {
209 let import_data_len: usize = match page_metadata.data_unmeasured() {
210 true => 0,
211 false => std::cmp::min(PAGE_SIZE_4K as usize, data.len()),
212 };
213 let (import_data, data_remaining) = data.split_at(import_data_len);
214 data = data_remaining;
215
216 let padding = vec![0; PAGE_SIZE_4K as usize - import_data.len()];
218 let page_number = gpa_page_base + page;
219 let chunk = VpGpaPageChunk {
220 header: VbsChunkHeader {
221 byte_count: VBS_VP_CHUNK_SIZE_BYTES as u32,
222 chunk_type: BootMeasurementType::VP_GPA_PAGE,
223 reserved: 0,
224 },
225 metadata: page_metadata.into(),
226 page_number,
227 };
228 self.create_record_entry(&[chunk.as_bytes(), import_data, &padding])?;
229 }
230 Ok(())
231 }
232
233 fn record_vp_register(&mut self, reg: VbsVpContextRegister) -> Result<(), Error> {
234 let chunk = VbsRegisterChunk {
235 header: VbsChunkHeader {
236 byte_count: size_of::<VbsRegisterChunk>() as u32,
237 chunk_type: BootMeasurementType::VP_REGISTER,
238 reserved: 0,
239 },
240 reserved: 0,
241 vtl: reg.vtl,
242 reserved2: 0,
243 reserved3: 0,
244 reserved4: 0,
245 name: reg.register_name.into(),
246 value: reg.register_value,
247 };
248 self.create_record_entry(&[chunk.as_bytes()])?;
249 Ok(())
250 }
251
252 fn create_record_entry(&mut self, chunks: &[&[u8]]) -> Result<(), Error> {
253 let mut hasher = Sha256::new();
254 hasher.update(self.digest.as_bytes());
255 for chunk in chunks {
256 hasher.update(chunk);
257 }
258 self.digest = hasher.finalize().into();
259 Ok(())
260 }
261
262 fn finish_digest(&self) -> [u8; SHA_256_OUTPUT_SIZE_BYTES] {
263 self.digest
264 }
265}