1pub use spec_services::NvramError;
15pub use spec_services::NvramResult;
16pub use spec_services::NvramServicesExt;
17pub use spec_services::NvramSpecServices;
18
19use crate::UefiDevice;
20use cvm_tracing::CVM_ALLOWED;
21use cvm_tracing::CVM_CONFIDENTIAL;
22use firmware_uefi_custom_vars::BaseTemplate;
23use firmware_uefi_custom_vars::BaseTemplateVars;
24use firmware_uefi_custom_vars::FinalVars;
25use firmware_uefi_custom_vars::Signature;
26use firmware_uefi_custom_vars::UefiVarsDeltaJson;
27use firmware_uefi_resources::platform::VsmConfig;
28use guestmem::GuestMemoryError;
29use guid::Guid;
30use inspect::Inspect;
31use std::borrow::Cow;
32use std::collections::BTreeSet;
33use std::fmt::Debug;
34use std::mem::size_of;
35use std::mem::size_of_val;
36use thiserror::Error;
37use uefi_nvram_specvars::signature_list::ParseError as SignatureListParseError;
38use uefi_nvram_specvars::signature_list::ParseSignatureList;
39use uefi_nvram_specvars::signature_list::ParseSignatureLists;
40use uefi_nvram_storage::VmmNvramStorage;
41use uefi_specs::uefi::common::EfiStatus;
42use uefi_specs::uefi::nvram::EFI_VARIABLE_AUTHENTICATION_2;
43use uefi_specs::uefi::nvram::EfiVariableAttributes;
44use uefi_specs::uefi::nvram::signature_list::EFI_SIGNATURE_DATA;
45use uefi_specs::uefi::nvram::vars;
46use uefi_specs::uefi::signing::EFI_CERT_TYPE_PKCS7_GUID;
47use uefi_specs::uefi::signing::WIN_CERT_TYPE_EFI_GUID;
48use uefi_specs::uefi::signing::WIN_CERTIFICATE_UEFI_GUID;
49use zerocopy::FromBytes;
50use zerocopy::IntoBytes;
51
52#[cfg(feature = "fuzzing")]
53pub mod spec_services;
54#[cfg(not(feature = "fuzzing"))]
55mod spec_services;
56
57const WIN_CERT_REVISION_2_0: u16 = 0x0200;
58
59#[derive(Clone, Debug, PartialEq, Eq, PartialOrd, Ord)]
61enum SignatureValue {
62 X509(EFI_SIGNATURE_DATA, Vec<u8>),
63 Sha256(EFI_SIGNATURE_DATA, Vec<u8>),
64}
65
66type SignatureSet = BTreeSet<SignatureValue>;
68
69struct SecureBootVariableReport<'a> {
71 name: &'a ucs2::Ucs2LeSlice,
72 in_nvram: bool,
73 has_custom_uefi: bool,
74 loaded_bytes: usize,
75 loaded_entries: usize,
76 template_entries: usize,
77 missing_entries: usize,
78}
79
80impl Debug for SecureBootVariableReport<'_> {
81 fn fmt(&self, formatter: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
82 let Self {
83 name,
84 in_nvram,
85 has_custom_uefi,
86 loaded_bytes,
87 loaded_entries,
88 template_entries,
89 missing_entries,
90 } = self;
91
92 formatter
93 .debug_struct("SecureBootVariable")
94 .field("name", name)
95 .field("in_nvram", in_nvram)
96 .field("has_custom_uefi", has_custom_uefi)
97 .field("loaded_bytes", &format_args!("{loaded_bytes:#x}"))
98 .field("loaded_entries", &format_args!("{loaded_entries:#x}"))
99 .field("template_entries", &format_args!("{template_entries:#x}"))
100 .field("missing_entries", &format_args!("{missing_entries:#x}"))
101 .finish()
102 }
103}
104
105#[derive(Debug, Error)]
106pub enum NvramSetupError {
107 #[error("could not query backing nvram storage")]
108 BadNvramStorage(#[source] uefi_nvram_storage::NvramStorageError),
109 #[error("failed to apply custom UEFI template variables")]
110 ApplyCustomTemplate(#[source] firmware_uefi_custom_vars::ApplyDeltaError),
111 #[error("failed to load built-in UEFI template")]
112 LoadBaseTemplate(#[source] hyperv_uefi_custom_vars_json::ParseJsonError),
113 #[error("failed to load custom UEFI variable JSON")]
114 LoadCustomUefiJson(#[source] hyperv_uefi_custom_vars_json::ParseJsonError),
115 #[error("could not inject pre-boot var '{0}': {1:?}")]
116 InjectPreBootVar(
117 Cow<'static, ucs2::Ucs2LeSlice>,
118 EfiStatus,
119 #[source] Option<NvramError>,
120 ),
121 #[error("could not inject signature var '{0}': {1:?}")]
122 InjectSigVar(
123 Cow<'static, ucs2::Ucs2LeSlice>,
124 EfiStatus,
125 #[source] Option<NvramError>,
126 ),
127 #[error("could not inject UEFI var '{0}': {1:?}")]
128 InjectUefiVar(String, EfiStatus, #[source] Option<NvramError>),
129 #[error("UEFI variable name is not valid UCS-2")]
130 UefiVarNotUcs2,
131}
132
133#[derive(Inspect)]
137pub struct NvramServices {
138 #[inspect(skip)]
140 vsm_config: Option<Box<dyn VsmConfig>>,
141
142 #[inspect(flatten)]
144 services: NvramSpecServices<Box<dyn VmmNvramStorage>>,
145}
146
147impl NvramServices {
148 pub async fn new(
149 nvram_storage: Box<dyn VmmNvramStorage>,
150 base_template: Option<BaseTemplate>,
151 custom_uefi_json: Option<UefiVarsDeltaJson>,
152 secure_boot_enabled: bool,
153 vsm_config: Option<Box<dyn VsmConfig>>,
154 is_restoring: bool,
155 ) -> Result<NvramServices, NvramSetupError> {
156 let mut nvram = NvramServices {
157 services: NvramSpecServices::new(nvram_storage),
158 vsm_config,
159 };
160
161 if !is_restoring {
162 let is_nvram_empty = nvram
163 .services
164 .is_empty()
165 .await
166 .map_err(NvramSetupError::BadNvramStorage)?;
167 let base_template = match base_template
168 .filter(|_| is_nvram_empty || secure_boot_enabled)
169 .map(|template| {
170 hyperv_uefi_custom_vars_json::parse_template_json(template.json.as_bytes())
171 })
172 .transpose()
173 {
174 Ok(vars) => vars,
175 Err(error) if !is_nvram_empty => {
176 tracing::warn!(
177 CVM_ALLOWED,
178 error = &error as &dyn std::error::Error,
179 "secure boot configuration report skipped: failed to parse baseline template"
180 );
181 None
182 }
183 Err(error) => return Err(NvramSetupError::LoadBaseTemplate(error)),
184 };
185
186 let has_custom_uefi = custom_uefi_json
190 .as_ref()
191 .is_some_and(|json| !json.as_bytes().is_empty());
192
193 if is_nvram_empty {
194 nvram
195 .inject_initial_vars(base_template.as_ref(), custom_uefi_json)
196 .await?;
197 }
198 nvram.inject_hyperv_vars().await?;
199 nvram.setup_secure_boot(secure_boot_enabled).await?;
200
201 if secure_boot_enabled {
202 nvram
203 .report_secure_boot_configuration(base_template.as_ref(), has_custom_uefi)
204 .await;
205 }
206 }
207
208 nvram.services.prepare_for_boot();
209
210 Ok(nvram)
211 }
212
213 pub fn reset(&mut self) {
214 self.services.reset();
215 self.services.prepare_for_boot();
216 }
217
218 async fn inject_initial_vars(
220 &mut self,
221 base_template_vars: Option<&BaseTemplateVars>,
222 custom_uefi_json: Option<UefiVarsDeltaJson>,
223 ) -> Result<(), NvramSetupError> {
224 let custom_template_delta = custom_uefi_json
225 .map(|json| hyperv_uefi_custom_vars_json::parse_delta_json(json.as_bytes()))
226 .transpose()
227 .map_err(NvramSetupError::LoadCustomUefiJson)?;
228 let final_vars = FinalVars::resolve(base_template_vars.cloned(), custom_template_delta)
229 .map_err(NvramSetupError::ApplyCustomTemplate)?;
230
231 tracing::info!("No NVRAM variables (first boot). Loading in initial NVRAM values.");
232
233 tracing::trace!("Injecting 'CurrentPolicy'");
235 {
236 use uefi_specs::hyperv::nvram::vars::CURRENT_POLICY;
237
238 let (vendor, name) = CURRENT_POLICY();
239 const CURRENT_POLICY_AUTHENTICATED_MARKER: u8 = 0x02;
240 let data = [CURRENT_POLICY_AUTHENTICATED_MARKER];
241 let attr = EfiVariableAttributes::DEFAULT_ATTRIBUTES_TIME_BASED_AUTH;
242
243 let data = {
247 let mut v = Vec::new();
248 v.extend(EFI_VARIABLE_AUTHENTICATION_2::DUMMY.as_bytes());
249 v.extend(data);
250 v
251 };
252
253 self.services
254 .set_variable_ucs2(vendor, name, attr.into(), data.clone())
255 .await
256 .map_err(|(status, err)| {
257 NvramSetupError::InjectPreBootVar(name.into(), status, err)
258 })?;
259 }
260
261 tracing::trace!("Updating 'SetupMode'");
262 {
263 use uefi_specs::uefi::nvram::vars::SETUP_MODE;
264 let (_, name) = SETUP_MODE();
265
266 self.services.update_setup_mode().await.map_err(|e| {
267 NvramSetupError::InjectPreBootVar(
268 name.into(),
269 EfiStatus::DEVICE_ERROR,
270 Some(NvramError::NvramStorage(e)),
271 )
272 })?
273 }
274
275 self.inject_final_vars(final_vars).await?;
276
277 Ok(())
278 }
279
280 async fn get_secure_boot_variable_report<'a>(
282 &mut self,
283 (vendor, name): (Guid, &'a ucs2::Ucs2LeSlice),
284 template_signatures: &[Signature],
285 has_custom_uefi: bool,
286 ) -> Option<SecureBootVariableReport<'a>> {
287 let base_signatures = base_signature_set(template_signatures);
288 if base_signatures.is_empty() {
289 tracing::warn!(
290 CVM_ALLOWED,
291 %name,
292 "secure boot variable omitted from configuration report: baseline contains no signatures"
293 );
294 return None;
295 }
296
297 match self.services.get_variable_ucs2(vendor, name).await {
298 Ok((_, data)) if data.is_empty() => Some(SecureBootVariableReport {
300 name,
301 in_nvram: true,
302 has_custom_uefi,
303 loaded_bytes: 0,
304 loaded_entries: 0,
305 template_entries: base_signatures.len(),
306 missing_entries: base_signatures.len(),
307 }),
308 Ok((_, data)) => match collect_signature_set(&data) {
310 Ok(loaded_signatures) => Some(SecureBootVariableReport {
312 name,
313 in_nvram: true,
314 has_custom_uefi,
315 loaded_bytes: data.len(),
316 loaded_entries: loaded_signatures.len(),
317 template_entries: base_signatures.len(),
318 missing_entries: base_signatures.difference(&loaded_signatures).count(),
319 }),
320 Err(error) => {
322 tracing::warn!(
323 CVM_CONFIDENTIAL,
324 %name,
325 error = &error as &dyn std::error::Error,
326 "secure boot variable omitted from configuration report: failed to parse variable"
327 );
328 None
329 }
330 },
331 Err((EfiStatus::NOT_FOUND, _)) => Some(SecureBootVariableReport {
333 name,
334 in_nvram: false,
335 has_custom_uefi,
336 loaded_bytes: 0,
337 loaded_entries: 0,
338 template_entries: base_signatures.len(),
339 missing_entries: base_signatures.len(),
340 }),
341 Err((status, error)) => {
343 tracing::warn!(
344 CVM_CONFIDENTIAL,
345 %name,
346 ?status,
347 ?error,
348 "secure boot variable omitted from configuration report: failed to read variable"
349 );
350 None
351 }
352 }
353 }
354
355 async fn report_secure_boot_configuration(
357 &mut self,
358 base_template: Option<&BaseTemplateVars>,
359 has_custom_uefi: bool,
360 ) {
361 let Some(signatures) = base_template.and_then(BaseTemplateVars::signatures) else {
362 tracing::warn!(
363 CVM_ALLOWED,
364 "secure boot configuration report skipped: baseline signatures are unavailable"
365 );
366 return;
367 };
368
369 for (variable, template_signatures) in [
371 (vars::PK(), std::slice::from_ref(&signatures.pk)),
372 (vars::KEK(), signatures.kek.as_slice()),
373 (vars::DB(), signatures.db.as_slice()),
374 (vars::DBX(), signatures.dbx.as_slice()),
375 ] {
376 if let Some(variable_report) = self
377 .get_secure_boot_variable_report(variable, template_signatures, has_custom_uefi)
378 .await
379 {
380 tracing::info!(CVM_ALLOWED, "{variable_report:?}");
381 }
382 }
383 }
384
385 async fn inject_hyperv_vars(&mut self) -> Result<(), NvramSetupError> {
386 tracing::trace!("Injecting 'OsLoaderIndications'");
388 {
389 use uefi_specs::hyperv::nvram::vars::OS_LOADER_INDICATIONS;
390
391 let (vendor, name) = OS_LOADER_INDICATIONS();
392 let data = 0u32.as_bytes();
393 let attr = EfiVariableAttributes::new().with_bootservice_access(true);
394
395 self.services
396 .set_variable_ucs2(vendor, name, attr.into(), data.to_vec())
397 .await
398 .map_err(|(status, err)| {
399 NvramSetupError::InjectPreBootVar(name.into(), status, err)
400 })?;
401 }
402
403 tracing::trace!("Injecting 'OsLoaderIndicationsSupported'");
404 {
405 use uefi_specs::hyperv::nvram::vars::OS_LOADER_INDICATIONS_SUPPORTED;
406
407 let (vendor, name) = OS_LOADER_INDICATIONS_SUPPORTED();
408 let data = 1u32.as_bytes();
410 let attr = EfiVariableAttributes::new().with_bootservice_access(true);
411
412 self.services
413 .set_variable_ucs2(vendor, name, attr.into(), data.to_vec())
414 .await
415 .map_err(|(status, err)| {
416 NvramSetupError::InjectPreBootVar(name.into(), status, err)
417 })?;
418 }
419
420 Ok(())
421 }
422
423 async fn inject_final_vars(&mut self, final_vars: FinalVars) -> Result<(), NvramSetupError> {
424 use firmware_uefi_custom_vars::Sha256Digest;
425 use firmware_uefi_custom_vars::Signature;
426 use firmware_uefi_custom_vars::UefiVar;
427 use firmware_uefi_custom_vars::X509Cert;
428 use uefi_nvram_specvars::signature_list::SignatureData;
429 use uefi_nvram_specvars::signature_list::SignatureList;
430 use uefi_specs::hyperv::nvram::vars::MSFT_SECURE_BOOT_PRODUCTION_GUID;
431 use uefi_specs::uefi::nvram::EFI_VARIABLE_AUTHENTICATION_2;
432
433 let firmware_uefi_custom_vars::UefiVars {
434 signatures,
435 non_signature_vars,
436 } = final_vars.into_uefi_vars();
437
438 for (name, UefiVar { guid, attr, value }) in non_signature_vars {
440 tracing::trace!(%name, "Injecting UEFI var");
441
442 let value = {
445 let attr = EfiVariableAttributes::from(attr);
446 if attr.contains_unsupported_bits() {
447 return Err(NvramSetupError::InjectUefiVar(
448 name,
449 EfiStatus::INVALID_PARAMETER,
450 Some(NvramError::AttributeNonSpec),
451 ));
452 }
453
454 if attr.time_based_authenticated_write_access() {
455 let mut new_value = Vec::new();
456 new_value.extend(EFI_VARIABLE_AUTHENTICATION_2::DUMMY.as_bytes());
460 new_value.extend(value);
461 new_value
462 } else {
463 value
464 }
465 };
466
467 self.services
468 .set_variable(guid, &name, attr, value)
469 .await
470 .map_err(|(status, err)| NvramSetupError::InjectUefiVar(name, status, err))?;
471 }
472
473 if let Some(sigs) = signatures {
475 use uefi_specs::linux::nvram::vars as linux_vars;
476 use uefi_specs::uefi::nvram::vars as uefi_vars;
477
478 let dbdefault_sig = sigs.db.clone();
480
481 #[rustfmt::skip]
484 let sigs_loop = [
485 (uefi_vars::KEK(), sigs.kek, EfiVariableAttributes::DEFAULT_ATTRIBUTES_TIME_BASED_AUTH),
486 (uefi_vars::DB(), sigs.db, EfiVariableAttributes::DEFAULT_ATTRIBUTES_TIME_BASED_AUTH),
487 (uefi_vars::DBX(), sigs.dbx, EfiVariableAttributes::DEFAULT_ATTRIBUTES_TIME_BASED_AUTH),
488 (uefi_vars::PK(), vec![sigs.pk], EfiVariableAttributes::DEFAULT_ATTRIBUTES_TIME_BASED_AUTH),
499 (uefi_vars::DBDEFAULT(), dbdefault_sig, EfiVariableAttributes::DEFAULT_ATTRIBUTES_VOLATILE),
500 (linux_vars::MOK_LIST(), sigs.moklist, EfiVariableAttributes::DEFAULT_ATTRIBUTES),
501 (linux_vars::MOK_LISTX(), sigs.moklistx, EfiVariableAttributes::DEFAULT_ATTRIBUTES),
502 ];
503
504 for ((vendor, name), sigs, attr) in sigs_loop {
505 tracing::trace!(?name, "Injecting");
506
507 let mut var_data: Vec<u8> = Vec::new();
508
509 for sig in sigs {
510 match sig {
511 Signature::X509(certs) => {
512 for X509Cert(data) in certs {
515 let sig_list = SignatureList::X509(SignatureData::new_x509(
516 MSFT_SECURE_BOOT_PRODUCTION_GUID,
517 Cow::Owned(data),
518 ));
519 sig_list.extend_as_spec_signature_list(&mut var_data);
520 }
521 }
522 Signature::Sha256(digests) => {
523 let sig_list = SignatureList::Sha256(
524 digests
525 .into_iter()
526 .map(|Sha256Digest(data)| {
527 SignatureData::new_sha256(
528 MSFT_SECURE_BOOT_PRODUCTION_GUID,
529 Cow::Owned(data),
530 )
531 })
532 .collect(),
533 );
534 sig_list.extend_as_spec_signature_list(&mut var_data);
535 }
536 }
537 }
538
539 if var_data.is_empty() {
540 continue;
541 }
542
543 if attr.time_based_authenticated_write_access() {
544 let mut authenticated_var_data =
548 EFI_VARIABLE_AUTHENTICATION_2::DUMMY.as_bytes().to_vec();
549 authenticated_var_data.extend(var_data);
550 var_data = authenticated_var_data;
551 }
552
553 self.services
554 .set_variable_ucs2(vendor, name, attr.into(), var_data)
555 .await
556 .map_err(|(status, err)| {
557 NvramSetupError::InjectSigVar(name.into(), status, err)
558 })?;
559 }
560 }
561
562 Ok(())
563 }
564
565 async fn setup_secure_boot(&mut self, enabled: bool) -> Result<(), NvramSetupError> {
567 tracing::info!(enabled, "configuring secure boot");
568
569 let data = if enabled { [0x01] } else { [0x00] };
570
571 tracing::trace!("Injecting 'SecureBoot'");
572 {
573 use uefi_specs::uefi::nvram::vars::SECURE_BOOT;
574
575 let (vendor, name) = SECURE_BOOT();
576
577 let delete_attr = EfiVariableAttributes::new();
581 let _ = self
582 .services
583 .set_variable_ucs2(vendor, name, delete_attr.into(), data.to_vec())
584 .await;
585
586 let attr = EfiVariableAttributes::DEFAULT_ATTRIBUTES;
591 self.services
592 .set_variable_ucs2(vendor, name, attr.into(), data.to_vec())
593 .await
594 .map_err(|(status, err)| {
595 NvramSetupError::InjectPreBootVar(name.into(), status, err)
596 })?;
597 }
598
599 tracing::trace!("Injecting 'SecureBootEnabled'");
600 {
601 use uefi_specs::hyperv::nvram::vars::SECURE_BOOT_ENABLE;
602
603 let (vendor, name) = SECURE_BOOT_ENABLE();
604 let attr = EfiVariableAttributes::DEFAULT_ATTRIBUTES;
605
606 self.services
607 .set_variable_ucs2(vendor, name, attr.into(), data.to_vec())
608 .await
609 .map_err(|(status, err)| {
610 NvramSetupError::InjectPreBootVar(name.into(), status, err)
611 })?;
612 }
613
614 Ok(())
615 }
616}
617
618fn base_signature_set<'a>(signatures: impl IntoIterator<Item = &'a Signature>) -> SignatureSet {
620 use uefi_specs::hyperv::nvram::vars::MSFT_SECURE_BOOT_PRODUCTION_GUID;
621
622 let header = EFI_SIGNATURE_DATA {
623 signature_owner: MSFT_SECURE_BOOT_PRODUCTION_GUID,
624 };
625 let mut values = SignatureSet::new();
626 for signature in signatures {
627 match signature {
628 Signature::X509(certs) => values.extend(
629 certs
630 .iter()
631 .map(|cert| SignatureValue::X509(header, cert.0.clone())),
632 ),
633 Signature::Sha256(digests) => values.extend(
634 digests
635 .iter()
636 .map(|digest| SignatureValue::Sha256(header, digest.0.to_vec())),
637 ),
638 }
639 }
640 values
641}
642
643fn collect_signature_set(data: &[u8]) -> Result<SignatureSet, SignatureListParseError> {
645 let mut signatures = SignatureSet::new();
646
647 for list in ParseSignatureLists::new(signature_list_payload(data)) {
648 match list? {
649 ParseSignatureList::X509(certs) => {
650 for cert in certs {
651 let cert = cert?;
652 signatures.insert(SignatureValue::X509(
653 cert.header,
654 cert.data.0.as_ref().to_vec(),
655 ));
656 }
657 }
658 ParseSignatureList::Sha256(digests) => {
659 for digest in digests {
660 let digest = digest?;
661 signatures.insert(SignatureValue::Sha256(
662 digest.header,
663 digest.data.0.as_ref().to_vec(),
664 ));
665 }
666 }
667 }
668 }
669
670 Ok(signatures)
671}
672
673fn signature_list_payload(data: &[u8]) -> &[u8] {
678 let Ok((auth, _)) = EFI_VARIABLE_AUTHENTICATION_2::read_from_prefix(data) else {
679 return data;
680 };
681
682 if auth.auth_info.header.revision != WIN_CERT_REVISION_2_0
683 || auth.auth_info.header.certificate_type != WIN_CERT_TYPE_EFI_GUID
684 || auth.auth_info.cert_type != EFI_CERT_TYPE_PKCS7_GUID
685 {
686 return data;
687 }
688
689 let cert_len = auth.auth_info.header.length as usize;
690 if cert_len < size_of::<WIN_CERTIFICATE_UEFI_GUID>() {
691 return data;
692 }
693
694 let Some(auth_len) = size_of_val(&auth.timestamp).checked_add(cert_len) else {
695 return data;
696 };
697 data.get(auth_len..).unwrap_or(data)
698}
699
700#[cfg(test)]
701mod tests {
702 use super::*;
703 use firmware_uefi_custom_vars::ApplyDeltaError;
704 use pal_async::async_test;
705 use ucs2::Ucs2LeSlice;
706 use uefi_nvram_specvars::signature_list::SignatureData;
707 use uefi_nvram_specvars::signature_list::SignatureList;
708 use uefi_nvram_storage::EFI_TIME;
709 use uefi_nvram_storage::NvramStorage;
710 use uefi_nvram_storage::in_memory::InMemoryNvram;
711 use wchar::wchz;
712
713 const TEST_SIGNATURE_OWNER: Guid = guid::guid!("00000000-0000-0000-0000-000000000001");
714
715 fn x509_variable(certs: &[&'static [u8]]) -> Vec<u8> {
716 let mut data = Vec::new();
717 for cert in certs {
718 SignatureList::X509(SignatureData::new_x509(
719 TEST_SIGNATURE_OWNER,
720 Cow::Borrowed(*cert),
721 ))
722 .extend_as_spec_signature_list(&mut data);
723 }
724 data
725 }
726
727 fn append_without_base_json() -> Vec<u8> {
728 br#"{
729 "type": "Microsoft.Compute/disks",
730 "properties": {
731 "uefiSettings": {
732 "signatureMode": "Append",
733 "signatures": {}
734 }
735 }
736 }"#
737 .to_vec()
738 }
739
740 fn invalid_base_template() -> BaseTemplate {
741 BaseTemplate {
742 json: b"not json".to_vec().into(),
743 }
744 }
745
746 fn nvram_services(storage: InMemoryNvram) -> NvramServices {
747 let storage: Box<dyn VmmNvramStorage> = Box::new(storage);
748 NvramServices {
749 vsm_config: None,
750 services: NvramSpecServices::new(storage),
751 }
752 }
753
754 #[test]
755 fn authenticated_and_raw_signature_payloads_parse_identically() {
756 let raw = x509_variable(&[b"cert1"]);
757 let mut authenticated = EFI_VARIABLE_AUTHENTICATION_2::DUMMY.as_bytes().to_vec();
758 authenticated.extend_from_slice(&raw);
759
760 assert_eq!(
761 collect_signature_set(&authenticated).unwrap(),
762 collect_signature_set(&raw).unwrap()
763 );
764 }
765
766 #[test]
767 fn signature_set_difference_ignores_extra_loaded_signatures() {
768 let base = collect_signature_set(&x509_variable(&[b"cert1", b"cert2"])).unwrap();
769 let loaded = collect_signature_set(&x509_variable(&[b"cert1", b"cert3"])).unwrap();
770
771 assert_eq!(base.difference(&loaded).count(), 1);
772 }
773
774 #[test]
775 fn secure_boot_variable_report_fits_get_trace_limit() {
776 const GET_TRACE_MESSAGE_MAX_BYTES: usize = 256;
777
778 let report = SecureBootVariableReport {
779 name: vars::KEK().1,
780 in_nvram: false,
781 has_custom_uefi: false,
782 loaded_bytes: usize::MAX,
783 loaded_entries: usize::MAX,
784 template_entries: usize::MAX,
785 missing_entries: usize::MAX,
786 };
787 let message = format!("{report:?}");
788
789 assert!(
790 message.len() <= GET_TRACE_MESSAGE_MAX_BYTES,
791 "Secure Boot variable report is {} bytes: {message}",
792 message.len()
793 );
794 }
795
796 #[async_test]
797 async fn missing_secure_boot_variable_is_reported_as_not_present() {
798 let mut nvram = nvram_services(InMemoryNvram::new());
799 let template_signatures = [Signature::X509(vec![firmware_uefi_custom_vars::X509Cert(
800 b"cert1".to_vec(),
801 )])];
802
803 let report = nvram
804 .get_secure_boot_variable_report(vars::DB(), &template_signatures, true)
805 .await
806 .unwrap();
807
808 assert!(!report.in_nvram);
809 assert!(report.has_custom_uefi);
810 assert_eq!(report.loaded_bytes, 0);
811 assert_eq!(report.loaded_entries, 0);
812 assert_eq!(report.template_entries, 1);
813 assert_eq!(report.missing_entries, 1);
814 }
815
816 #[async_test]
817 async fn empty_secure_boot_variable_reports_all_entries_missing() {
818 let (vendor, name) = vars::DB();
819 let mut storage = InMemoryNvram::new();
820 storage
821 .set_variable(name, vendor, 0, Vec::new(), EFI_TIME::default())
822 .await
823 .unwrap();
824 let mut nvram = nvram_services(storage);
825 let template_signatures = [Signature::X509(vec![
826 firmware_uefi_custom_vars::X509Cert(b"cert1".to_vec()),
827 firmware_uefi_custom_vars::X509Cert(b"cert2".to_vec()),
828 ])];
829
830 let report = nvram
831 .get_secure_boot_variable_report((vendor, name), &template_signatures, false)
832 .await
833 .unwrap();
834
835 assert!(report.in_nvram);
836 assert!(!report.has_custom_uefi);
837 assert_eq!(report.loaded_bytes, 0);
838 assert_eq!(report.loaded_entries, 0);
839 assert_eq!(report.template_entries, 2);
840 assert_eq!(report.missing_entries, 2);
841 }
842
843 #[async_test]
844 async fn populated_secure_boot_variable_reports_signature_counts() {
845 use uefi_specs::hyperv::nvram::vars::MSFT_SECURE_BOOT_PRODUCTION_GUID;
846
847 let (vendor, name) = vars::DB();
848 let mut data = Vec::new();
849 for cert in [b"cert1".as_slice(), b"cert3".as_slice()] {
850 SignatureList::X509(SignatureData::new_x509(
851 MSFT_SECURE_BOOT_PRODUCTION_GUID,
852 Cow::Borrowed(cert),
853 ))
854 .extend_as_spec_signature_list(&mut data);
855 }
856 let loaded_bytes = data.len();
857 let mut storage = InMemoryNvram::new();
858 storage
859 .set_variable(name, vendor, 0, data, EFI_TIME::default())
860 .await
861 .unwrap();
862 let mut nvram = nvram_services(storage);
863 let template_signatures = [Signature::X509(vec![
864 firmware_uefi_custom_vars::X509Cert(b"cert1".to_vec()),
865 firmware_uefi_custom_vars::X509Cert(b"cert2".to_vec()),
866 ])];
867
868 let report = nvram
869 .get_secure_boot_variable_report((vendor, name), &template_signatures, false)
870 .await
871 .unwrap();
872
873 assert_eq!(report.loaded_bytes, loaded_bytes);
874 assert_eq!(report.loaded_entries, 2);
875 assert_eq!(report.template_entries, 2);
876 assert_eq!(report.missing_entries, 1);
877 }
878
879 #[async_test]
880 async fn invalid_templates_are_ignored_after_first_boot_without_secure_boot() {
881 let mut storage = InMemoryNvram::new();
882 let name = Ucs2LeSlice::from_slice_with_nul(wchz!(u16, "existing").as_bytes()).unwrap();
883 storage
884 .set_variable(name, Guid::default(), 0, vec![1], EFI_TIME::default())
885 .await
886 .unwrap();
887
888 NvramServices::new(
889 Box::new(storage),
890 Some(invalid_base_template()),
891 Some(b"not json".to_vec().into()),
892 false,
893 None,
894 false,
895 )
896 .await
897 .unwrap();
898 }
899
900 #[async_test]
901 async fn invalid_base_template_is_ignored_after_first_boot_with_secure_boot() {
902 let mut storage = InMemoryNvram::new();
903 let name = Ucs2LeSlice::from_slice_with_nul(wchz!(u16, "existing").as_bytes()).unwrap();
904 storage
905 .set_variable(name, Guid::default(), 0, vec![1], EFI_TIME::default())
906 .await
907 .unwrap();
908
909 NvramServices::new(
910 Box::new(storage),
911 Some(invalid_base_template()),
912 None,
913 true,
914 None,
915 false,
916 )
917 .await
918 .unwrap();
919 }
920
921 #[async_test]
922 async fn invalid_base_template_fails_on_first_boot() {
923 assert!(matches!(
924 NvramServices::new(
925 Box::new(InMemoryNvram::new()),
926 Some(invalid_base_template()),
927 None,
928 false,
929 None,
930 false,
931 )
932 .await,
933 Err(NvramSetupError::LoadBaseTemplate(_))
934 ));
935 }
936
937 #[async_test]
938 async fn invalid_delta_fails_on_first_boot() {
939 let mut nvram = nvram_services(InMemoryNvram::new());
940
941 assert!(matches!(
942 nvram
943 .inject_initial_vars(None, Some(append_without_base_json().into()))
944 .await,
945 Err(NvramSetupError::ApplyCustomTemplate(
946 ApplyDeltaError::AppendWithoutBase
947 ))
948 ));
949 }
950
951 #[async_test]
952 async fn malformed_json_fails_on_first_boot() {
953 let mut nvram = nvram_services(InMemoryNvram::new());
954
955 assert!(matches!(
956 nvram
957 .inject_initial_vars(None, Some(b"not json".to_vec().into()))
958 .await,
959 Err(NvramSetupError::LoadCustomUefiJson(_))
960 ));
961 }
962
963 #[async_test]
964 async fn deferred_base_template_parses_on_first_boot() {
965 let mut nvram = nvram_services(InMemoryNvram::new());
966 let base_template = firmware_uefi_resources::x64_secure_boot_templates::microsoft_windows();
967 let base_template_vars =
968 hyperv_uefi_custom_vars_json::parse_template_json(base_template.json.as_bytes())
969 .unwrap();
970
971 nvram
972 .inject_initial_vars(Some(&base_template_vars), None)
973 .await
974 .unwrap();
975
976 let (vendor, name) = vars::PK();
977 assert!(nvram.services.get_variable_ucs2(vendor, name).await.is_ok());
978 }
979
980 #[async_test]
981 async fn omitted_dbx_is_not_injected_on_first_boot() {
982 let custom_vars = br#"{
983 "type": "Microsoft.Compute/disks",
984 "properties": {
985 "uefiSettings": {
986 "signatureMode": "Replace",
987 "signatures": {
988 "PK": { "type": "x509", "value": ["Jw=="] },
989 "KEK": [{ "type": "x509", "value": ["Jw=="] }],
990 "db": [{ "type": "x509", "value": ["Jw=="] }]
991 }
992 }
993 }
994 }"#;
995 let mut nvram = nvram_services(InMemoryNvram::new());
996
997 nvram
998 .inject_initial_vars(None, Some(custom_vars.to_vec().into()))
999 .await
1000 .unwrap();
1001
1002 let (pk_vendor, pk_name) = vars::PK();
1003 assert!(
1004 nvram
1005 .services
1006 .get_variable_ucs2(pk_vendor, pk_name)
1007 .await
1008 .is_ok()
1009 );
1010
1011 let (dbx_vendor, dbx_name) = vars::DBX();
1012 assert!(matches!(
1013 nvram.services.get_variable_ucs2(dbx_vendor, dbx_name).await,
1014 Err((EfiStatus::NOT_FOUND, _))
1015 ));
1016 }
1017}
1018
1019impl UefiDevice {
1020 pub(crate) async fn nvram_handle_command(&mut self, desc_addr: u64) {
1021 use uefi_specs::hyperv::nvram::NvramCommandDescriptor;
1022
1023 let mut desc: NvramCommandDescriptor = match self.gm.read_plain(desc_addr) {
1024 Ok(desc) => desc,
1025 Err(err) => {
1026 tracelimit::warn_ratelimited!(
1027 error = &err as &dyn std::error::Error,
1028 "Could not read NvramCommandDescriptor from guest memory",
1029 );
1030 return;
1031 }
1032 };
1033
1034 let status = match self.handle_nvram_command_inner(desc_addr, desc).await {
1035 Ok(status) => status,
1036 Err(err) => {
1037 tracelimit::warn_ratelimited!(
1038 error = &err as &dyn std::error::Error,
1039 "Guest memory error while handling nvram command"
1040 );
1041 EfiStatus::DEVICE_ERROR
1042 }
1043 };
1044
1045 desc.status = status.into();
1047
1048 if let Err(err) = self.gm.write_plain(desc_addr, &desc) {
1049 tracelimit::warn_ratelimited!(
1050 error = &err as &dyn std::error::Error,
1051 "Could not write NvramCommandDescriptor into guest memory",
1052 );
1053 }
1054 }
1055
1056 async fn handle_nvram_command_inner(
1057 &mut self,
1058 desc_addr: u64,
1059 desc: uefi_specs::hyperv::nvram::NvramCommandDescriptor,
1060 ) -> Result<EfiStatus, GuestMemoryError> {
1061 use uefi_specs::hyperv::nvram::NvramCommand;
1062 use uefi_specs::hyperv::nvram::NvramVariableCommand;
1063
1064 let command_addr = desc_addr + size_of_val(&desc) as u64;
1065
1066 let (status, err) = match desc.command {
1067 NvramCommand::GET_VARIABLE => {
1068 let mut command: NvramVariableCommand = self.gm.read_plain(command_addr)?;
1069
1070 let name = if command.name_address.get() != 0 {
1071 let mut buf = vec![0; command.name_bytes as usize];
1072 self.gm
1073 .read_at(command.name_address.into(), buf.as_mut_slice())?;
1074 Some(buf)
1075 } else {
1076 None
1077 };
1078
1079 let NvramResult(data, status, err) = self
1080 .service
1081 .nvram
1082 .services
1083 .uefi_get_variable(
1084 name.as_deref(),
1085 command.vendor_guid,
1086 &mut command.attributes,
1087 &mut command.data_bytes,
1088 command.data_address.get() == 0,
1089 )
1090 .await;
1091
1092 self.gm.write_plain(command_addr, &command)?;
1094
1095 if let Some(data) = data {
1098 self.gm
1099 .write_at(command.data_address.get(), data.as_bytes())?;
1100 }
1101
1102 (status, err)
1103 }
1104 NvramCommand::SET_VARIABLE => {
1105 let command: NvramVariableCommand = self.gm.read_plain(command_addr)?;
1106
1107 let name = if command.name_address.get() != 0 {
1108 let mut buf = vec![0; command.name_bytes as usize];
1109 self.gm
1110 .read_at(command.name_address.into(), buf.as_mut_slice())?;
1111 Some(buf)
1112 } else {
1113 None
1114 };
1115
1116 let data = if command.data_address.get() != 0 {
1117 let mut buf = vec![0; command.data_bytes as usize];
1118 self.gm
1119 .read_at(command.data_address.into(), buf.as_mut_slice())?;
1120 Some(buf)
1121 } else {
1122 None
1123 };
1124
1125 let NvramResult((), status, err) = self
1126 .service
1127 .nvram
1128 .services
1129 .uefi_set_variable(
1130 name.as_deref(),
1131 command.vendor_guid,
1132 command.attributes,
1133 command.data_bytes,
1134 data,
1135 )
1136 .await;
1137
1138 (status, err)
1139 }
1140 NvramCommand::GET_FIRST_VARIABLE_NAME | NvramCommand::GET_NEXT_VARIABLE_NAME => {
1141 let mut command: NvramVariableCommand = self.gm.read_plain(command_addr)?;
1142
1143 let name = if desc.command == NvramCommand::GET_NEXT_VARIABLE_NAME {
1144 if command.name_address.get() != 0 {
1145 let mut buf = vec![0; command.name_bytes as usize];
1146 self.gm
1147 .read_at(command.name_address.into(), buf.as_mut_slice())?;
1148 Some(buf)
1149 } else {
1150 None
1151 }
1152 } else {
1153 Some(vec![0, 0])
1159 };
1160
1161 let NvramResult(data, status, err) = self
1162 .service
1163 .nvram
1164 .services
1165 .uefi_get_next_variable(
1166 &mut command.name_bytes,
1167 name.as_deref(),
1168 command.vendor_guid,
1169 )
1170 .await;
1171
1172 if let Some((name, vendor)) = data {
1175 command.vendor_guid = vendor;
1176
1177 self.gm
1178 .write_at(command.name_address.get(), name.as_bytes())?;
1179 }
1180
1181 self.gm.write_at(command_addr, command.as_bytes())?;
1183
1184 (status, err)
1185 }
1186 NvramCommand::QUERY_INFO => (EfiStatus::UNSUPPORTED, None),
1187 NvramCommand::SIGNAL_RUNTIME => {
1188 use uefi_specs::hyperv::nvram::NvramSignalRuntimeCommand;
1189 let command: NvramSignalRuntimeCommand = self.gm.read_plain(command_addr)?;
1190
1191 if !command.flags.vsm_aware() {
1192 if let Some(vsm) = &self.service.nvram.vsm_config {
1193 tracelimit::info_ratelimited!("Revoking guest vsm");
1194 vsm.revoke_guest_vsm()
1195 }
1196 }
1197 self.service.nvram.services.exit_boot_services();
1198
1199 (EfiStatus::SUCCESS, None)
1200 }
1201 NvramCommand::DEBUG_STRING => {
1202 let command: uefi_specs::hyperv::nvram::NvramDebugStringCommand =
1203 self.gm.read_plain(command_addr)?;
1204
1205 let mut data = vec![0u16; command.len as usize / 2];
1206 self.gm
1207 .read_at(command.address.into(), data.as_mut_bytes())?;
1208
1209 tracing::trace!(
1210 target: "uefi-nvram-guest-debug",
1211 data = %String::from_utf16_lossy(&data),
1212 "nvram guest debug",
1213 );
1214 (EfiStatus::SUCCESS, None)
1215 }
1216 command => {
1217 tracelimit::warn_ratelimited!(?command, "unknown nvram command");
1218 (EfiStatus::UNSUPPORTED, None)
1219 }
1220 };
1221
1222 if let Some(err) = err {
1224 let err: &(dyn std::error::Error + 'static) = &err;
1225 tracelimit::warn_ratelimited!(
1226 command = ?desc.command,
1227 ?status,
1228 error = err,
1229 "nvram error"
1230 )
1231 }
1232
1233 if status != EfiStatus::SUCCESS {
1234 tracing::trace!(?status, "nvram status");
1235 }
1236
1237 Ok(status)
1238 }
1239}
1240
1241mod save_restore {
1242 use super::*;
1243 use vmcore::save_restore::RestoreError;
1244 use vmcore::save_restore::SaveError;
1245 use vmcore::save_restore::SaveRestore;
1246
1247 mod state {
1248 use crate::service::nvram::NvramSpecServices;
1249 use mesh::payload::Protobuf;
1250 use uefi_nvram_storage::VmmNvramStorage;
1251 use vmcore::save_restore::SaveRestore;
1252
1253 #[derive(Protobuf)]
1254 #[mesh(package = "firmware.uefi.nvram")]
1255 pub struct SavedState {
1256 #[mesh(1)]
1257 pub services: <NvramSpecServices<Box<dyn VmmNvramStorage>> as SaveRestore>::SavedState,
1258 }
1259 }
1260
1261 impl SaveRestore for NvramServices {
1262 type SavedState = state::SavedState;
1263
1264 fn save(&mut self) -> Result<Self::SavedState, SaveError> {
1265 let NvramServices {
1266 vsm_config: _,
1267 services,
1268 } = self;
1269
1270 let saved_state = state::SavedState {
1271 services: services.save()?,
1272 };
1273
1274 Ok(saved_state)
1275 }
1276
1277 fn restore(&mut self, state: Self::SavedState) -> Result<(), RestoreError> {
1278 let state::SavedState { services } = state;
1279
1280 self.services.restore(services)?;
1281
1282 Ok(())
1283 }
1284 }
1285}