firmware_uefi/service/nvram/spec_services/mod.rs
1// Copyright (c) Microsoft Corporation.
2// Licensed under the MIT License.
3
4//! An implementation of UEFI spec 8.2 - Variable Services
5//!
6//! This implementation is a direct implementation / transcription of the UEFI
7//! spec, and does not contain any Hyper-V specific features* (i.e: injecting
8//! various nvram vars related to secure boot, boot order, etc...).
9//!
10//! *that isn't _entirely_ true just yet, as there is one bit of code
11//! that enforce read-only access to certain Hyper-V specific vars, but if the
12//! need arises, those code paths can be refactored.
13
14pub use nvram_services_ext::NvramServicesExt;
15
16use bitfield_struct::bitfield;
17use guid::Guid;
18use inspect::Inspect;
19use mesh::payload::Protobuf;
20use std::borrow::Cow;
21use thiserror::Error;
22use ucs2::Ucs2LeSlice;
23use ucs2::Ucs2ParseError;
24use uefi_nvram_specvars::signature_list;
25use uefi_nvram_specvars::signature_list::ParseSignatureLists;
26use uefi_nvram_storage::NextVariable;
27use uefi_nvram_storage::NvramStorageError;
28use uefi_nvram_storage::VmmNvramStorage;
29use uefi_specs::uefi::common::EfiStatus;
30use uefi_specs::uefi::nvram::EfiVariableAttributes;
31use uefi_specs::uefi::time::EFI_TIME;
32use zerocopy::FromBytes;
33use zerocopy::FromZeros;
34
35#[cfg(feature = "fuzzing")]
36pub mod auth_var_crypto;
37#[cfg(not(feature = "fuzzing"))]
38mod auth_var_crypto;
39mod nvram_services_ext;
40
41#[derive(Debug, Error)]
42pub enum NvramError {
43 #[error("storage backend error")]
44 NvramStorage(#[source] NvramStorageError),
45 #[error("variable name cannot be null/None")]
46 NameNull,
47 #[error("variable data of non-zero len cannot be null")]
48 DataNull,
49 #[error("variable name validation failed")]
50 NameValidation(#[from] Ucs2ParseError),
51 #[error("cannot pass empty string to SetVariable")]
52 NameEmpty,
53 #[error("attributes include non-spec values")]
54 AttributeNonSpec,
55 #[error("invalid runtime access")]
56 InvalidRuntimeAccess,
57 #[error("invalid attr: hardware error records are not supported")]
58 UnsupportedHardwareErrorRecord,
59 #[error("invalid attr: enhanced authenticated access unsupported")]
60 UnsupportedEnhancedAuthAccess,
61 #[error("invalid attr: volatile variables unsupported")]
62 UnsupportedVolatile,
63 #[error("attribute mismatch with existing variable")]
64 AttributeMismatch,
65 #[error("authenticated variable error")]
66 AuthError(#[from] AuthError),
67 #[error("updating SetupMode variable")]
68 UpdateSetupMode(#[source] NvramStorageError),
69 #[error("parsing signature list")]
70 SignatureList(#[from] signature_list::ParseError),
71}
72
73#[derive(Debug, Error)]
74pub enum AuthError {
75 #[error("data too short (cannot extract EFI_VARIABLE_AUTHENTICATION_2 header)")]
76 NotEnoughHdrData,
77 #[error("data too short (cannot extract WIN_CERTIFICATE_UEFI_GUID cert)")]
78 NotEnoughCertData,
79 #[error("invalid WIN_CERTIFICATE Header")]
80 InvalidWinCertHeader,
81 #[error("invalid WIN_CERTIFICATE_UEFI_GUID Header")]
82 InvalidWinCertUefiGuidHeader,
83 #[error("incorrect cert type (must be WIN_CERTIFICATE_UEFI_GUID)")]
84 IncorrectCertType,
85 #[error("incorrect timestamp values")]
86 IncorrectTimestamp,
87 #[error("new timestamp is not later than current timestamp")]
88 OldTimestamp,
89
90 #[error("current implementation cannot authenticate specified var")]
91 UnsupportedAuthVar,
92
93 #[error("could not verify auth var")]
94 CryptoError,
95 #[cfg(feature = "auth-var-verify-crypto")]
96 #[error("error in crypto payload format")]
97 CryptoFormat(#[from] auth_var_crypto::FormatError),
98}
99
100/// `SetVariable` validation is incredibly tricky, since there are a _lot_ of
101/// subtle logic branches that are predicated on the presence (or lack thereof)
102/// of various attribute bits.
103///
104/// In order to make the implementation a bit easier to understand and maintain,
105/// we switch from using the full-featured `EfiVariableAttributes` bitflags type
106/// to a restricted subset of these flags described by `SupportedAttrs` part-way
107/// through SetVariable.
108#[bitfield(u32)]
109#[derive(PartialEq)]
110pub struct SupportedAttrs {
111 pub non_volatile: bool,
112 pub bootservice_access: bool,
113 pub runtime_access: bool,
114 pub hardware_error_record: bool,
115 _reserved: bool,
116 pub time_based_authenticated_write_access: bool,
117 #[bits(26)]
118 _reserved2: u32,
119}
120
121impl SupportedAttrs {
122 pub fn contains_unsupported_bits(&self) -> bool {
123 u32::from(*self)
124 & !u32::from(
125 Self::new()
126 .with_non_volatile(true)
127 .with_bootservice_access(true)
128 .with_runtime_access(true)
129 .with_hardware_error_record(true)
130 .with_time_based_authenticated_write_access(true),
131 )
132 != 0
133 }
134}
135
136/// Helper struct to collect various properties of a parsed authenticated var
137#[allow(dead_code)]
138#[derive(Debug, Clone, Copy)]
139pub struct ParsedAuthVar<'a> {
140 pub name: &'a Ucs2LeSlice,
141 pub vendor: Guid,
142 pub attr: u32,
143 pub timestamp: EFI_TIME,
144 pub pkcs7_data: &'a [u8],
145 pub var_data: &'a [u8],
146}
147
148/// Unlike a typical result type, NvramErrors contain _both_ a payload _and_ an
149/// error code. Depending on the error code, an optional `NvramError` might be
150/// included as well, which provides more context.
151///
152/// Notably, **this result types cannot be propagated via the `?` operator!**
153#[derive(Debug)]
154pub struct NvramResult<T>(pub T, pub EfiStatus, pub Option<NvramError>);
155
156impl<T> NvramResult<T> {
157 pub fn is_success(&self) -> bool {
158 matches!(self.1, EfiStatus::SUCCESS)
159 }
160}
161
162impl<T> std::fmt::Display for NvramResult<T> {
163 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
164 match &self.2 {
165 Some(_) => write!(f, "{:?} (with error context)", self.1),
166 None => write!(f, "{:?}", self.1),
167 }
168 }
169}
170
171impl<T> std::error::Error for NvramResult<T>
172where
173 T: std::fmt::Debug,
174{
175 fn source(&self) -> Option<&(dyn std::error::Error + 'static)> {
176 self.2
177 .as_ref()
178 .map(|s| s as &(dyn std::error::Error + 'static))
179 }
180}
181
182#[derive(Clone, Copy, Debug, Protobuf, Inspect)]
183enum RuntimeState {
184 /// Implementation-specific state, whereby certain read-only and
185 /// authenticated variable checks are bypassed.
186 ///
187 /// Transitions into `Boot` once all pre-boot nvram variables have been
188 /// successfully injected.
189 PreBoot,
190 /// UEFI firmware hasn't called `ExitBootServices`
191 Boot,
192 /// UEFI firmware has called `ExitBootServices`
193 Runtime,
194}
195
196impl RuntimeState {
197 fn is_pre_boot(&self) -> bool {
198 matches!(&self, RuntimeState::PreBoot)
199 }
200
201 fn is_boot(&self) -> bool {
202 matches!(&self, RuntimeState::Boot)
203 }
204
205 fn is_runtime(&self) -> bool {
206 matches!(&self, RuntimeState::Runtime)
207 }
208}
209
210/// An implementation of UEFI spec 8.2 - Variable Services
211///
212/// This API tries to match the API defined by the UEFI spec 1:1, hence why it
213/// doesn't look very "Rust-y".
214///
215/// If you need to interact with `NvramServices` outside the context of the UEFI
216/// device itself, consider importing the [`NvramServicesExt`] trait. This trait
217/// provides various helper methods that make it easier to get/set nvram
218/// variables, without worrying about the nitty-gritty details of UCS-2 string
219/// encoding, pointer sizes/nullness, etc...
220///
221/// Instead of returning a typical `Result` type, these methods all return a
222/// tuple of `(Option<T>, EfiStatus, Option<NvramError>)`, where the `EfiStatus`
223/// field should be unconditionally returned to the guest, while the
224/// `NvramError` type provides additional context as to what error occurred in
225/// OpenVMM (i.e: for logging purposes).
226#[derive(Debug, Inspect)]
227pub struct NvramSpecServices<S: VmmNvramStorage> {
228 storage: S,
229 runtime_state: RuntimeState,
230}
231
232impl<S: VmmNvramStorage> NvramSpecServices<S> {
233 /// Construct a new NvramServices instance from an existing storage backend.
234 pub fn new(storage: S) -> NvramSpecServices<S> {
235 NvramSpecServices {
236 storage,
237 runtime_state: RuntimeState::PreBoot,
238 }
239 }
240
241 /// Check if the nvram store is empty.
242 pub async fn is_empty(&mut self) -> Result<bool, NvramStorageError> {
243 self.storage.is_empty().await
244 }
245
246 /// Update "SetupMode" based on the current value of "PK"
247 ///
248 /// From UEFI spec section 32.3
249 ///
250 /// While no Platform Key is enrolled, the SetupMode variable shall be equal
251 /// to 1. While SetupMode == 1, the platform firmware shall not require
252 /// authentication in order to modify the Platform Key, Key Enrollment Key,
253 /// OsRecoveryOrder, OsRecovery####, and image security databases.
254 ///
255 /// After the Platform Key is enrolled, the SetupMode variable shall be
256 /// equal to 0. While SetupMode == 0, the platform firmware shall require
257 /// authentication in order to modify the Platform Key, Key Enrollment Key,
258 /// OsRecoveryOrder, OsRecovery####, and image security databases.
259 pub async fn update_setup_mode(&mut self) -> Result<(), NvramStorageError> {
260 use uefi_specs::uefi::nvram::vars::PK;
261 use uefi_specs::uefi::nvram::vars::SETUP_MODE;
262
263 let (pk_vendor, pk_name) = PK();
264 let (setup_mode_vendor, setup_mode_name) = SETUP_MODE();
265
266 let attr = EfiVariableAttributes::DEFAULT_ATTRIBUTES;
267 let timestamp = EFI_TIME::new_zeroed();
268 let data = match self.storage.get_variable(pk_name, pk_vendor).await? {
269 Some(_) => [0x00],
270 None => [0x01],
271 };
272
273 self.storage
274 .set_variable(
275 setup_mode_name,
276 setup_mode_vendor,
277 attr.into(),
278 data.to_vec(),
279 timestamp,
280 )
281 .await?;
282
283 Ok(())
284 }
285
286 /// Nvram behavior changes after the guest signals that ExitBootServices has
287 /// been called (e.g: hiding variables that are only accessible at
288 /// boot-time).
289 pub fn exit_boot_services(&mut self) {
290 assert!(self.runtime_state.is_boot());
291 tracing::trace!("NVRAM has entered runtime mode");
292 self.runtime_state = RuntimeState::Runtime;
293 }
294
295 /// Called when the VM resets to return to the preboot state.
296 pub fn reset(&mut self) {
297 self.runtime_state = RuntimeState::PreBoot;
298 }
299
300 /// Called after injecting any pre-boot nvram vars, transitioning the nvram
301 /// store to start accepting calls from guest UEFI.
302 pub fn prepare_for_boot(&mut self) {
303 assert!(self.runtime_state.is_pre_boot());
304 tracing::trace!("NVRAM has entered boot mode");
305 self.runtime_state = RuntimeState::Boot;
306 }
307
308 async fn get_setup_mode(&mut self) -> Result<bool, NvramStorageError> {
309 use uefi_specs::uefi::nvram::vars::SETUP_MODE;
310
311 let (setup_mode_vendor, setup_mode_name) = SETUP_MODE();
312 let in_setup_mode = match self
313 .storage
314 .get_variable(setup_mode_name, setup_mode_vendor)
315 .await?
316 {
317 None => false,
318 Some((_, data, _)) => data.first().map(|b| *b == 0x01).unwrap_or(false),
319 };
320
321 Ok(in_setup_mode)
322 }
323
324 /// Get a variable identified by `name` + `vendor`, returning the variable's
325 /// attributes and data.
326 ///
327 /// - `in_name`
328 /// - (In) Variable name (a null-terminated UTF-16 string, or `None` if
329 /// the guest passed a `nullptr`)
330 /// - `in_vendor`
331 /// - (In) Variable vendor guid
332 /// - `out_attr`
333 /// - (Out) Variable's attributes
334 /// - _Note:_ According to the UEFI spec: `attr` will be populated on
335 /// both EFI_SUCCESS _and_ when EFI_BUFFER_TOO_SMALL is returned.
336 /// - `in_out_data_size`
337 /// - (In) Size of available data buffer (provided by guest)
338 /// - (Out) Size of data to be written into buffer
339 /// - _Note:_ If `data_is_null` is `true`, and `in_out_data_size` is set
340 /// to `0`, `in_out_data_size` will be updated with the size required
341 /// to store the variable.
342 /// - `data_is_null`
343 /// - (In) bool indicating if guest passed `nullptr` as the data addr
344 pub async fn uefi_get_variable(
345 &mut self,
346 name: Option<&[u8]>,
347 in_vendor: Guid,
348 out_attr: &mut u32,
349 in_out_data_size: &mut u32,
350 data_is_null: bool,
351 ) -> NvramResult<Option<Vec<u8>>> {
352 let name = match name {
353 Some(name) => {
354 Ucs2LeSlice::from_slice_with_nul(name).map_err(NvramError::NameValidation)
355 }
356 None => Err(NvramError::NameNull),
357 };
358
359 let name = match name {
360 Ok(name) => name,
361 Err(e) => return NvramResult(None, EfiStatus::INVALID_PARAMETER, Some(e)),
362 };
363
364 tracing::trace!(
365 ?in_vendor,
366 ?name,
367 in_out_data_size,
368 data_is_null,
369 "Get NVRAM variable",
370 );
371
372 let (attr, data) = match self.get_variable_inner(name, in_vendor).await {
373 Ok(Some((attr, data, _))) => (attr, data),
374 Ok(None) => return NvramResult(None, EfiStatus::NOT_FOUND, None),
375 Err((status, err)) => return NvramResult(None, status, err),
376 };
377
378 if self.runtime_state.is_runtime() && !attr.runtime_access() {
379 // From UEFI spec section 8.2:
380 //
381 // If EFI_BOOT_SERVICES.ExitBootServices() has already been
382 // executed, data variables without the EFI_VARIABLE_RUNTIME_ACCESS
383 // attribute set will not be visible to GetVariable() and will
384 // return an EFI_NOT_FOUND error.
385 return NvramResult(
386 None,
387 EfiStatus::NOT_FOUND,
388 Some(NvramError::InvalidRuntimeAccess),
389 );
390 }
391
392 *out_attr = attr.into();
393 match (*in_out_data_size, data_is_null) {
394 (0, true) => *in_out_data_size = data.len() as u32,
395 (_, true) => return NvramResult(None, EfiStatus::INVALID_PARAMETER, None),
396 (_, false) => {
397 let guest_buf_len = *in_out_data_size as usize;
398 *in_out_data_size = data.len() as u32;
399 if guest_buf_len < data.len() {
400 return NvramResult(None, EfiStatus::BUFFER_TOO_SMALL, None);
401 }
402 }
403 }
404
405 NvramResult(Some(data), EfiStatus::SUCCESS, None)
406 }
407
408 async fn get_variable_inner(
409 &mut self,
410 name: &Ucs2LeSlice,
411 vendor: Guid,
412 ) -> Result<Option<(SupportedAttrs, Vec<u8>, EFI_TIME)>, (EfiStatus, Option<NvramError>)> {
413 match self.storage.get_variable(name, vendor).await {
414 Ok(None) => Ok(None),
415 Ok(Some((attr, data, timestamp))) => {
416 let attr = SupportedAttrs::from(attr);
417 assert!(
418 !attr.contains_unsupported_bits(),
419 "underlying storage should only ever contain valid attributes"
420 );
421
422 Ok(Some((attr, data, timestamp)))
423 }
424 Err(e) => {
425 let status = match &e {
426 NvramStorageError::Deserialize => EfiStatus::DEVICE_ERROR,
427 _ => panic!("unexpected NvramStorageError from get_variable"),
428 };
429 Err((status, Some(NvramError::NvramStorage(e))))
430 }
431 }
432 }
433
434 /// Set a variable identified by `name` + `vendor` with the specified `attr`
435 /// and `data`
436 ///
437 /// - `name`
438 /// - (In) Variable name (a null-terminated UTF-16 string, or `None` if
439 /// the guest passed a `nullptr`)
440 /// - _Note:_ `name` must contain one or more character.
441 /// - `in_vendor`
442 /// - (In) Variable vendor guid
443 /// - `in_attr`
444 /// - (In) Variable's attributes
445 /// - `in_data_size`
446 /// - (In) Length of data to be written
447 /// - If len in `0`, and the EFI_VARIABLE_APPEND_WRITE,
448 /// EFI_VARIABLE_AUTHENTICATED_WRITE_ACCESS,
449 /// EFI_VARIABLE_ENHANCED_AUTHENTICATED_ACCESS, or
450 /// EFI_VARIABLE_TIME_BASED_AUTHENTICATED_WRITE_ACCESS are not set,
451 /// the variable will be deleted.
452 /// - `data`
453 /// - (In) Variable data (or `None` if the guest passed a `nullptr`)
454 pub async fn uefi_set_variable(
455 &mut self,
456 name: Option<&[u8]>,
457 in_vendor: Guid,
458 in_attr: u32,
459 in_data_size: u32,
460 data: Option<Vec<u8>>,
461 ) -> NvramResult<()> {
462 let name = match name {
463 Some(name) => {
464 Ucs2LeSlice::from_slice_with_nul(name).map_err(NvramError::NameValidation)
465 }
466 None => Err(NvramError::NameNull),
467 };
468
469 let name = match name {
470 Ok(name) => name,
471 Err(e) => return NvramResult((), EfiStatus::INVALID_PARAMETER, Some(e)),
472 };
473
474 if name.as_bytes() == [0, 0] {
475 return NvramResult(
476 (),
477 EfiStatus::INVALID_PARAMETER,
478 Some(NvramError::NameEmpty),
479 );
480 }
481
482 tracing::trace!(
483 %in_vendor,
484 %name,
485 in_attr,
486 in_data_size,
487 data = if data.is_some() { "Some([..])" } else { "None" },
488 "Set NVRAM variable",
489 );
490
491 // Perform some basic attribute validation
492 let attr = {
493 // Validate that set bits correspond to valid attribute flags
494 let attr = EfiVariableAttributes::from(in_attr);
495 if attr.contains_unsupported_bits() {
496 return NvramResult(
497 (),
498 EfiStatus::INVALID_PARAMETER,
499 Some(NvramError::AttributeNonSpec),
500 );
501 }
502
503 // From UEFI spec section 8.2:
504 //
505 // Runtime access to a data variable implies boot service access.
506 // Attributes that have EFI_VARIABLE_RUNTIME_ACCESS set must also
507 // have EFI_VARIABLE_BOOTSERVICE_ACCESS set. The caller is
508 // responsible for following this rule.
509 if attr.runtime_access() && !attr.bootservice_access() {
510 return NvramResult((), EfiStatus::INVALID_PARAMETER, None);
511 }
512
513 // From UEFI spec section 8.2:
514 //
515 // If both the EFI_VARIABLE_TIME_BASED_AUTHENTICATED_WRITE_ACCESS
516 // and the EFI_VARIABLE_ENHANCED_AUTHENTICATED_ACCESS attribute are
517 // set in a SetVariable() call, then the firmware must return
518 // EFI_INVALID_PARAMETER.
519 if attr.time_based_authenticated_write_access() && attr.enhanced_authenticated_access()
520 {
521 return NvramResult((), EfiStatus::INVALID_PARAMETER, None);
522 }
523
524 attr
525 };
526
527 // Report EFI_UNSUPPORTED for any attributes our implementation doesn't
528 // support
529 {
530 if attr.hardware_error_record() {
531 return NvramResult(
532 (),
533 EfiStatus::UNSUPPORTED,
534 Some(NvramError::UnsupportedHardwareErrorRecord),
535 );
536 }
537
538 if attr.enhanced_authenticated_access() {
539 return NvramResult(
540 (),
541 EfiStatus::UNSUPPORTED,
542 Some(NvramError::UnsupportedEnhancedAuthAccess),
543 );
544 }
545
546 // From UEFI spec section 8.2:
547 //
548 // EFI_VARIABLE_AUTHENTICATED_WRITE_ACCESS is deprecated and should
549 // not be used. Platforms should return EFI_UNSUPPORTED if a caller
550 // to SetVariable() specifies this attribute.
551 if attr.authenticated_write_access() {
552 return NvramResult((), EfiStatus::UNSUPPORTED, None);
553 }
554 }
555
556 // From UEFI spec section 32.3, Figure 32-4
557 //
558 // There are various nvram variables that determine what part of secure
559 // boot flow we are in. These get used later on in validation, but we'll
560 // go ahead and fetch them here...
561 //
562 // TODO: implement logic around AuditMode and DeployedMode
563 let in_setup_mode = match self.get_setup_mode().await {
564 Ok(val) => val,
565 Err(err) => {
566 return NvramResult(
567 (),
568 EfiStatus::DEVICE_ERROR,
569 Some(NvramError::NvramStorage(err)),
570 );
571 }
572 };
573
574 // From UEFI spec section 8.2:
575 //
576 // Once ExitBootServices() is performed, only variables that have
577 // EFI_VARIABLE_RUNTIME_ACCESS and EFI_VARIABLE_NON_VOLATILE set can be
578 // set with SetVariable(). Variables that have runtime access but that
579 // are not nonvolatile are readonly data variables once
580 // ExitBootServices() is performed.
581 if self.runtime_state.is_runtime() {
582 // At first glance, this seems like a pretty straightforward
583 // conditional, but unfortunately, we need to consider the
584 // interaction with this other clause:
585 //
586 // From UEFI spec section 8.2:
587 //
588 // If a preexisting variable is rewritten with no access
589 // attributes specified, the variable will be deleted.
590 //
591 // As such, if neither access attribute is set, we punt this runtime
592 // access check to the implementation of the delete operation,
593 // whereby it will make sure the variable being deleted has the
594 // correct attributes.
595 let missing_access_attrs = !(attr.runtime_access() || attr.bootservice_access());
596
597 if !missing_access_attrs {
598 if !attr.runtime_access() || !attr.non_volatile() {
599 return NvramResult(
600 (),
601 EfiStatus::INVALID_PARAMETER,
602 Some(NvramError::InvalidRuntimeAccess),
603 );
604 }
605 }
606 }
607
608 // Check if variable being set is read-only from the Guest
609 //
610 // Note: these checks are bypassed during pre-boot in order to set the
611 // vars' initial values.
612 if !self.runtime_state.is_pre_boot() {
613 use uefi_specs::hyperv::nvram::vars as hyperv_vars;
614 use uefi_specs::uefi::nvram::vars as spec_vars;
615
616 // In true UEFI spec fashion, there are always exceptions...
617 enum Exception {
618 None,
619 SetupMode,
620 // TODO: add more exception variants as new RO vars are added
621 }
622
623 #[rustfmt::skip]
624 let read_only_vars = [
625 // UEFI Spec - Table 3-1 Global Variables
626 //
627 // NOTE: Does not implement all of the read-only
628 // variables defined by the UEFI spec in section 3.3
629 (spec_vars::SECURE_BOOT(), Exception::None),
630 (spec_vars::SETUP_MODE(), Exception::None),
631 (spec_vars::KEK(), Exception::SetupMode),
632 (spec_vars::PK(), Exception::SetupMode),
633 (spec_vars::DBDEFAULT(), Exception::None),
634 // Hyper-V also uses some read-only vars that aren't specified
635 // in the UEFI spec
636 (hyperv_vars::SECURE_BOOT_ENABLE(), Exception::None),
637 (hyperv_vars::CURRENT_POLICY(), Exception::None),
638 (hyperv_vars::OS_LOADER_INDICATIONS_SUPPORTED(), Exception::None),
639 ];
640
641 let is_readonly = read_only_vars.into_iter().any(|(v, exception)| {
642 let skip_check = match exception {
643 Exception::None => false,
644 Exception::SetupMode => in_setup_mode,
645 };
646
647 // NOTE: The HCL and worker process implementations perform a
648 // case-insensitive comparisons here. A better fix would've
649 // been to make all comparisons case _sensitive_, rather than
650 // introducing bits of case _insensitivity_ around the nvram
651 // implementation. Hindsight is 20-20.
652 //
653 // In OpenVMM, we don't consider nvram variable names as strings
654 // with semantic meaning. Instead, they are akin to a
655 // bag-of-bytes that _just so happen_ to have a convenient debug
656 // representation when printed out at a UCS-2 string.
657 //
658 // Case-sensitive comparisons has been confirmed correct with
659 // the UEFI team, and as such, it may be worthwhile to backport
660 // this change into the C++ implementation as well.
661 if !skip_check {
662 v == (in_vendor, name)
663 } else {
664 false
665 }
666 });
667
668 if is_readonly {
669 return NvramResult((), EfiStatus::WRITE_PROTECTED, None);
670 }
671 }
672
673 // The behavior of various operations changes depending on whether or
674 // not the specified variable already exists, so go ahead and try to
675 // fetch it
676 let existing_var = match self.get_variable_inner(name, in_vendor).await {
677 Ok(v) => v,
678 Err((status, err)) => return NvramResult((), status, err),
679 };
680
681 let (in_data_size, data, timestamp) = {
682 if !attr.time_based_authenticated_write_access() {
683 // nothing fancy here, just some regular 'ol data...
684 let timestamp = EFI_TIME::new_zeroed();
685
686 (in_data_size, data, timestamp)
687 } else {
688 // the payload includes an authenticated variable header
689 //
690 // UEFI spec 8.2.2 - Using the EFI_VARIABLE_AUTHENTICATION_2 descriptor
691 use uefi_specs::uefi::nvram::EFI_VARIABLE_AUTHENTICATION_2;
692 use uefi_specs::uefi::signing::EFI_CERT_TYPE_PKCS7_GUID;
693 use uefi_specs::uefi::signing::WIN_CERT_TYPE_EFI_GUID;
694 use uefi_specs::uefi::signing::WIN_CERTIFICATE_UEFI_GUID;
695
696 tracing::trace!(
697 "variable is attempting to use TIME_BASED_AUTHENTICATED_WRITE_ACCESS"
698 );
699
700 // data cannot be null
701 let data = match data {
702 Some(data) => data,
703 None => {
704 return NvramResult(
705 (),
706 EfiStatus::INVALID_PARAMETER,
707 Some(NvramError::DataNull),
708 );
709 }
710 };
711
712 // extract EFI_VARIABLE_AUTHENTICATION_2 header
713 // TODO: zerocopy: err (https://github.com/microsoft/openvmm/issues/759)
714 let auth_hdr =
715 match EFI_VARIABLE_AUTHENTICATION_2::read_from_prefix(data.as_slice()).ok() {
716 Some((hdr, _)) => hdr,
717 None => {
718 return NvramResult(
719 (),
720 EfiStatus::SECURITY_VIOLATION,
721 Some(NvramError::AuthError(AuthError::NotEnoughHdrData)),
722 );
723 }
724 };
725 let timestamp = auth_hdr.timestamp;
726 let auth_info = auth_hdr.auth_info;
727
728 // split off the variable-length WIN_CERTIFICATE_UEFI_GUID cert
729 // data from the variable length payload
730 let (pkcs7_data, var_data) = {
731 let auth_info_offset = size_of_val(&auth_hdr.timestamp);
732
733 // use the header's length value to extract the
734 // WIN_CERTIFICATE_UEFI_GUID struct + variable length payload
735 if data[auth_info_offset..].len() < (auth_info.header.length as usize) {
736 return NvramResult(
737 (),
738 EfiStatus::SECURITY_VIOLATION,
739 Some(NvramError::AuthError(AuthError::NotEnoughCertData)),
740 );
741 }
742 let (auth_info_hdr_and_cert, var_data) =
743 data[auth_info_offset..].split_at(auth_info.header.length as usize);
744
745 // ...and then strip off the WIN_CERTIFICATE_UEFI_GUID
746 // struct from the variable length payload
747 let pkcs7_data = match auth_info_hdr_and_cert
748 .get(size_of::<WIN_CERTIFICATE_UEFI_GUID>()..)
749 {
750 Some(data) => data,
751 None => {
752 return NvramResult(
753 (),
754 EfiStatus::SECURITY_VIOLATION,
755 Some(NvramError::AuthError(AuthError::NotEnoughCertData)),
756 );
757 }
758 };
759
760 (pkcs7_data, var_data)
761 };
762
763 // validate WIN_CERTIFICATE header construction
764 if auth_info.header.revision != 0x0200 {
765 return NvramResult(
766 (),
767 EfiStatus::SECURITY_VIOLATION,
768 Some(NvramError::AuthError(AuthError::InvalidWinCertHeader)),
769 );
770 }
771
772 // validate correct cert type is being used
773 if auth_info.header.certificate_type != WIN_CERT_TYPE_EFI_GUID
774 || auth_info.cert_type != EFI_CERT_TYPE_PKCS7_GUID
775 {
776 return NvramResult(
777 (),
778 EfiStatus::SECURITY_VIOLATION,
779 Some(NvramError::AuthError(AuthError::IncorrectCertType)),
780 );
781 }
782
783 // validate timestamp according to spec
784 if timestamp.pad1 != 0
785 || timestamp.nanosecond != 0
786 || timestamp.timezone.0 != 0
787 || u8::from(timestamp.daylight) != 0
788 || timestamp.pad2 != 0
789 {
790 return NvramResult(
791 (),
792 EfiStatus::SECURITY_VIOLATION,
793 Some(NvramError::AuthError(AuthError::IncorrectTimestamp)),
794 );
795 }
796
797 // if a variable already exists, make sure the timestamp is
798 // newer (or in the case of Append, clamp the timestamp to the
799 // existing timestamp)
800 let orig_timestamp = timestamp; // original value must be used when performing variable auth
801 let timestamp = {
802 let mut timestamp = timestamp;
803 if let Some((_, _, existing_timestamp)) = existing_var {
804 let is_newer = (
805 timestamp.year,
806 timestamp.month,
807 timestamp.day,
808 timestamp.hour,
809 timestamp.minute,
810 timestamp.second,
811 timestamp.nanosecond,
812 )
813 .cmp(&(
814 existing_timestamp.year,
815 existing_timestamp.month,
816 existing_timestamp.day,
817 existing_timestamp.hour,
818 existing_timestamp.minute,
819 existing_timestamp.second,
820 existing_timestamp.nanosecond,
821 ))
822 .is_gt();
823
824 if !is_newer {
825 if !attr.append_write() {
826 return NvramResult(
827 (),
828 EfiStatus::SECURITY_VIOLATION,
829 Some(NvramError::AuthError(AuthError::OldTimestamp)),
830 );
831 } else {
832 timestamp = existing_timestamp
833 }
834 }
835 }
836 timestamp
837 };
838
839 // If PK is present, then we need to authenticate the payload with KEK or PK.
840 let pk_var = {
841 let (pk_vendor, pk_name) = uefi_specs::uefi::nvram::vars::PK();
842 match self.get_variable_inner(pk_name, pk_vendor).await {
843 Ok(v) => v,
844 Err((status, err)) => return NvramResult((), status, err),
845 }
846 };
847
848 // From UEFI spec section 8.2.2:
849 //
850 // If the variable SetupMode==1, and the variable is a secure
851 // boot policy variable, then the firmware implementation shall
852 // consider the checks in the following steps 4 and 5 to have
853 // passed, and proceed with updating the variable value as
854 // outlined below.
855 //
856 // (our implementation extends this condition to include
857 // "is nvram currently in the pre-boot state")
858 let bypass_auth = self.runtime_state.is_pre_boot()
859 || (in_setup_mode
860 && uefi_specs::uefi::nvram::is_secure_boot_policy_var(in_vendor, name));
861
862 if pk_var.is_some() && !bypass_auth {
863 tracing::trace!("pk exists, attempting to actually authenticate var...");
864
865 let parsed_auth_var = ParsedAuthVar {
866 name,
867 vendor: in_vendor,
868 attr: attr.into(),
869 timestamp: orig_timestamp,
870 pkcs7_data,
871 var_data,
872 };
873
874 // The UEFI spec has several special-cased authenticated vars.
875 // At the moment, our implementation only supports a handful of these cases.
876 enum AuthVarKind {
877 Db,
878 PkKek,
879 Unsupported,
880 }
881
882 let var_kind = match (in_vendor, name) {
883 v if v == uefi_specs::uefi::nvram::vars::DB() => AuthVarKind::Db,
884 v if v == uefi_specs::uefi::nvram::vars::DBX() => AuthVarKind::Db,
885 v if v == uefi_specs::uefi::nvram::vars::PK() => AuthVarKind::PkKek,
886 v if v == uefi_specs::uefi::nvram::vars::KEK() => AuthVarKind::PkKek,
887 // TODO: add support for:
888 // - dbr, dbt
889 // - OsRecoveryOrder, OsRecovery####
890 // - private auth vars
891 _ => AuthVarKind::Unsupported,
892 };
893
894 let auth_res = match var_kind {
895 AuthVarKind::Db => {
896 // UEFI Spec - 8.2.2 Using the EFI_VARIABLE_AUTHENTICATION_2 descriptor
897 //
898 // If the variable is the “db”, “dbt”, “dbr”, or “dbx” variable mentioned
899 // in step 3, verify that the signer’s certificate chains to a certificate
900 // in the Key Exchange Key database (or that the signature was made with
901 // the current Platform Key).
902 match self
903 .authenticate_var(
904 uefi_specs::uefi::nvram::vars::KEK(),
905 parsed_auth_var,
906 )
907 .await
908 {
909 Ok(res) => Ok(res),
910 // If authentication with KEK fails, then try PK authentication.
911 Err(_) => {
912 self.authenticate_var(
913 uefi_specs::uefi::nvram::vars::PK(),
914 parsed_auth_var,
915 )
916 .await
917 }
918 }
919 }
920 AuthVarKind::PkKek => {
921 // UEFI Spec - 8.2.2 Using the EFI_VARIABLE_AUTHENTICATION_2 descriptor
922 //
923 // If the variable is the global PK variable or the global KEK variable,
924 // verify that the signature has been made with the current Platform Key.
925 self.authenticate_var(
926 uefi_specs::uefi::nvram::vars::PK(),
927 parsed_auth_var,
928 )
929 .await
930 }
931 AuthVarKind::Unsupported => {
932 // TODO: the HCL treats this case the same as the `PkKek` case, but that
933 // seems wrong...
934 return NvramResult(
935 (),
936 EfiStatus::SECURITY_VIOLATION,
937 Some(NvramError::AuthError(AuthError::UnsupportedAuthVar)),
938 );
939 }
940 };
941
942 if let Err((status, err)) = auth_res {
943 return NvramResult((), status, err);
944 }
945 }
946
947 // now that everything has been validated, we can strip off the
948 // auth header and go on to actually performing the requested
949 // operation of the remaining payload.
950 let total_auth_hdr_len =
951 size_of_val(&auth_hdr.timestamp) + (auth_info.header.length as usize);
952
953 (
954 in_data_size - total_auth_hdr_len as u32,
955 Some({
956 let mut data = data;
957 data.drain(..total_auth_hdr_len);
958 data
959 }),
960 timestamp,
961 )
962 }
963 };
964
965 // SetVariable is pretty weird, as it overloads a single method to
966 // perform a whole bunch of different variable operations, such as
967 // removing, updating, appending, and setting variables.
968 //
969 // Determining which specific operation is being requested requires
970 // navigating a hodgepodge of various rules and indicators, such as the
971 // length of the data passed in, what attributes are set, etc...
972 #[derive(Debug)]
973 enum VariableOperation {
974 Set,
975 Append,
976 Delete,
977 }
978
979 let op = {
980 let is_doing_append = attr.append_write();
981 let is_doing_delete = {
982 // From UEFI spec section 8.2:
983 //
984 // If a preexisting variable is rewritten with no access attributes
985 // specified, the variable will be deleted.
986 let missing_access_attrs = !(attr.runtime_access() || attr.bootservice_access());
987
988 // From UEFI spec section 8.2:
989 //
990 // Unless the EFI_VARIABLE_APPEND_WRITE,
991 // EFI_VARIABLE_TIME_BASED_AUTHENTICATED_WRITE_ACCESS, or
992 // EFI_VARIABLE_ENHANCED_AUTHENTICATED_ACCESS attribute is set (see
993 // below), using SetVariable() with a DataSize of zero will cause the
994 // entire variable to be deleted
995 let zero_data_size = in_data_size == 0 && !is_doing_append;
996
997 missing_access_attrs || zero_data_size
998 };
999
1000 // append takes precedence over delete/set
1001 if is_doing_append {
1002 VariableOperation::Append
1003 } else if is_doing_delete {
1004 VariableOperation::Delete
1005 } else {
1006 VariableOperation::Set
1007 }
1008 };
1009
1010 tracing::trace!(?op, "SetVariable is performing");
1011
1012 // normalize attr bits (i.e: strip off APPEND_WRITE indicator)
1013 let attr = attr.with_append_write(false);
1014
1015 // Drop down to using `SupportedAttrs` instead of
1016 // `EfiVariableAttributes` to make things easier to follow.
1017 let attr = SupportedAttrs::from(u32::from(attr));
1018
1019 let res = match op {
1020 VariableOperation::Append => {
1021 // This implementation only supports non-volatile variables.
1022 // Volatile variables should be handled within UEFI itself.
1023 if !attr.non_volatile() {
1024 return NvramResult(
1025 (),
1026 EfiStatus::UNSUPPORTED,
1027 Some(NvramError::UnsupportedVolatile),
1028 );
1029 }
1030
1031 // data *might* get modified in the case that it contains an
1032 // EFI_SIGNATURE_LIST, and duplicates need to get filtered out
1033 // (hence the use of `mut`)
1034 let mut data = match (in_data_size, data) {
1035 // Appending with zero data will silently do nothing,
1036 // regardless if a variable already exists
1037 (0, _) => return NvramResult((), EfiStatus::SUCCESS, None),
1038 // If data len is non-zero, data cannot be nullptr
1039 (_, None) => {
1040 return NvramResult((), EfiStatus::SUCCESS, Some(NvramError::DataNull));
1041 }
1042 (_, Some(data)) => data,
1043 };
1044
1045 if let Some((existing_attr, existing_data, _)) = existing_var {
1046 // attempting to fetch a boot-time variable at runtime
1047 if self.runtime_state.is_runtime() && !existing_attr.runtime_access() {
1048 // ...will fail, since the variable "doesn't exist" at runtime
1049 return NvramResult(
1050 (),
1051 EfiStatus::NOT_FOUND,
1052 Some(NvramError::InvalidRuntimeAccess),
1053 );
1054 }
1055
1056 // From UEFI spec section 8.2:
1057 //
1058 // If a preexisting variable is rewritten with different
1059 // attributes, SetVariable() shall not modify the variable
1060 // and shall return EFI_INVALID_PARAMETER.
1061 if attr != existing_attr {
1062 return NvramResult(
1063 (),
1064 EfiStatus::INVALID_PARAMETER,
1065 Some(NvramError::AttributeMismatch),
1066 );
1067 }
1068
1069 // From UEFI spec section 8.2:
1070 //
1071 // For variables with the GUID EFI_IMAGE_SECURITY_DATABASE_GUID
1072 // (i.e. where the data buffer is formatted as EFI_SIGNATURE_LIST),
1073 // the driver shall not perform an append of EFI_SIGNATURE_DATA
1074 // values that are already part of the existing variable value.
1075 //
1076 // Note: This situation is not considered an error, and shall in itself
1077 // not cause a status code other than EFI_SUCCESS to be returned or the
1078 // timestamp associated with the variable not to be updated.
1079 if attr.time_based_authenticated_write_access() {
1080 use signature_list::SignatureDataPayload;
1081
1082 let existing_signatures = ParseSignatureLists::new(&existing_data)
1083 .collect_signature_set()
1084 .expect("existing var must contain valid list of EFI_SIGNATURE_LIST");
1085
1086 // NOTE: the Hyper-V implementation filter signature lists in-place. While
1087 // that *would* be more efficient, it also makes the code a _lot_ harder to
1088 // understand, so in OpenVMM, lets keep things simple and just allocate a new
1089 // buffer for the filtered signatures.
1090 let filtered_signatures = ParseSignatureLists::new(&data)
1091 .collect_signature_lists(|header, sig| {
1092 let sig: &[u8] = match sig {
1093 SignatureDataPayload::X509(buf) => buf,
1094 SignatureDataPayload::Sha256(buf) => buf,
1095 };
1096
1097 !existing_signatures.contains(&(header, Cow::Borrowed(sig)))
1098 });
1099
1100 // it *is* an error if the provided signature list is malformed
1101 let filtered_signatures = match filtered_signatures {
1102 Ok(sigs) => sigs,
1103 Err(e) => {
1104 return NvramResult(
1105 (),
1106 EfiStatus::INVALID_PARAMETER,
1107 Some(NvramError::SignatureList(e)),
1108 );
1109 }
1110 };
1111
1112 let mut new_data = Vec::new();
1113 for list in filtered_signatures {
1114 list.extend_as_spec_signature_list(&mut new_data);
1115 }
1116
1117 // update data to point at the new signature list we just created
1118 data = new_data;
1119 }
1120 }
1121
1122 // All validation checks have passed, so perform the operation
1123 match self
1124 .storage
1125 .append_variable(name, in_vendor, data.to_vec(), timestamp)
1126 .await
1127 {
1128 Ok(true) => NvramResult((), EfiStatus::SUCCESS, None),
1129 Ok(false) => NvramResult((), EfiStatus::NOT_FOUND, None),
1130 Err(e) => {
1131 let status = match &e {
1132 NvramStorageError::Commit(_) => EfiStatus::DEVICE_ERROR,
1133 NvramStorageError::OutOfSpace => EfiStatus::OUT_OF_RESOURCES,
1134 NvramStorageError::VariableNameTooLong => EfiStatus::INVALID_PARAMETER,
1135 NvramStorageError::VariableDataTooLong => EfiStatus::INVALID_PARAMETER,
1136 _ => {
1137 panic!("unexpected NvramStorageError from append_variable")
1138 }
1139 };
1140
1141 NvramResult((), status, Some(NvramError::NvramStorage(e)))
1142 }
1143 }
1144 }
1145 VariableOperation::Delete => {
1146 if let Some((existing_attr, _, _)) = existing_var {
1147 // attempting to delete an existing boot-time variable at runtime
1148 if self.runtime_state.is_runtime() && !existing_attr.runtime_access() {
1149 // ...will fail, since the variable "doesn't exist" at runtime
1150 return NvramResult(
1151 (),
1152 EfiStatus::NOT_FOUND,
1153 Some(NvramError::InvalidRuntimeAccess),
1154 );
1155 }
1156 }
1157
1158 // All validation checks have passed, so perform the operation
1159 match self.storage.remove_variable(name, in_vendor).await {
1160 Ok(true) => NvramResult((), EfiStatus::SUCCESS, None),
1161 Ok(false) => NvramResult((), EfiStatus::NOT_FOUND, None),
1162 Err(e) => {
1163 let status = match &e {
1164 NvramStorageError::Commit(_) => EfiStatus::DEVICE_ERROR,
1165 NvramStorageError::OutOfSpace => EfiStatus::OUT_OF_RESOURCES,
1166 NvramStorageError::VariableNameTooLong => EfiStatus::INVALID_PARAMETER,
1167 NvramStorageError::VariableDataTooLong => EfiStatus::INVALID_PARAMETER,
1168 _ => {
1169 panic!("unexpected NvramStorageError from remove_variable")
1170 }
1171 };
1172
1173 NvramResult((), status, Some(NvramError::NvramStorage(e)))
1174 }
1175 }
1176 }
1177 VariableOperation::Set => {
1178 // This implementation only supports non-volatile variables.
1179 // Volatile variables should be handled within UEFI itself.
1180 //
1181 // The exceptions are variables that are controlled/injected by the loader.
1182 // This includes secure boot enablement (volatile by specification),
1183 // as well as the private Hyper-V OsLoaderIndications and
1184 // OsLoaderIndicationsSupported variables, which are volatile variables
1185 // that are injected via the non-volatile store. The dbDefault variable
1186 // is also an exception.
1187 if !attr.non_volatile() {
1188 use uefi_specs::hyperv::nvram::vars as hyperv_vars;
1189 use uefi_specs::uefi::nvram::vars::DBDEFAULT;
1190 use uefi_specs::uefi::nvram::vars::SECURE_BOOT;
1191 let allowed_volatile = [
1192 hyperv_vars::OS_LOADER_INDICATIONS(),
1193 hyperv_vars::OS_LOADER_INDICATIONS_SUPPORTED(),
1194 DBDEFAULT(),
1195 SECURE_BOOT(),
1196 ];
1197
1198 let is_allowed = allowed_volatile.into_iter().any(|v| v == (in_vendor, name));
1199
1200 if !is_allowed {
1201 return NvramResult(
1202 (),
1203 EfiStatus::UNSUPPORTED,
1204 Some(NvramError::UnsupportedVolatile),
1205 );
1206 }
1207 }
1208
1209 // if we are doing a variable set, then data cannot be a nullptr
1210 let data = match data {
1211 Some(data) => data,
1212 None => {
1213 return NvramResult(
1214 (),
1215 EfiStatus::INVALID_PARAMETER,
1216 Some(NvramError::DataNull),
1217 );
1218 }
1219 };
1220
1221 if let Some((existing_attr, _, _)) = existing_var {
1222 // attempting to overwrite an existing boot-time variable
1223 if self.runtime_state.is_runtime() && !existing_attr.runtime_access() {
1224 // This is a weird case, since calling GetVariable would
1225 // actually return `EFI_NOT_FOUND` (as the variable is
1226 // "hidden" at runtime), implying that it should be
1227 // _fine_ to set the variable.
1228 //
1229 // It seems that unless we have some kind of "runtime
1230 // shadow variable" support, it's possible to use
1231 // `SetVariable` as a way to check if boot-time
1232 // variables _actually_ exist...
1233 //
1234 // The UEFI folks seem to think this gap is _fine_, as
1235 // it doesn't give access to protected data - just the
1236 // fact that that the boot time var exists.
1237 //
1238 // So... while this isn't a _great_ solution, it matches
1239 // all existing implementations (both within and outside
1240 // Hyper-V)
1241 return NvramResult(
1242 (),
1243 EfiStatus::WRITE_PROTECTED,
1244 Some(NvramError::InvalidRuntimeAccess),
1245 );
1246 }
1247
1248 // From UEFI spec section 8.2:
1249 //
1250 // If a preexisting variable is rewritten with different
1251 // attributes, SetVariable() shall not modify the
1252 // variable and shall return EFI_INVALID_PARAMETER.
1253 if attr != existing_attr {
1254 return NvramResult(
1255 (),
1256 EfiStatus::INVALID_PARAMETER,
1257 Some(NvramError::AttributeMismatch),
1258 );
1259 }
1260 }
1261
1262 // All validation checks have passed, so perform the operation
1263 match self
1264 .storage
1265 .set_variable(name, in_vendor, attr.into(), data.to_vec(), timestamp)
1266 .await
1267 {
1268 Ok(_) => NvramResult((), EfiStatus::SUCCESS, None),
1269 Err(e) => {
1270 let status = match &e {
1271 NvramStorageError::Commit(_) => EfiStatus::DEVICE_ERROR,
1272 NvramStorageError::OutOfSpace => EfiStatus::OUT_OF_RESOURCES,
1273 NvramStorageError::VariableNameTooLong => EfiStatus::INVALID_PARAMETER,
1274 NvramStorageError::VariableDataTooLong => EfiStatus::INVALID_PARAMETER,
1275 _ => panic!("unexpected NvramStorageError from set_variable"),
1276 };
1277
1278 NvramResult((), status, Some(NvramError::NvramStorage(e)))
1279 }
1280 }
1281 }
1282 };
1283
1284 // If we modified the PK variable, we need to update the SetupMode
1285 // variable accordingly.
1286 if res.is_success() && (in_vendor, name) == uefi_specs::uefi::nvram::vars::PK() {
1287 if let Err(e) = self.update_setup_mode().await {
1288 return NvramResult(
1289 (),
1290 EfiStatus::DEVICE_ERROR,
1291 Some(NvramError::UpdateSetupMode(e)),
1292 );
1293 }
1294 }
1295
1296 res
1297 }
1298
1299 #[cfg(not(feature = "auth-var-verify-crypto"))]
1300 async fn authenticate_var(
1301 &mut self,
1302 // NOTE: Due to a compiler limitation with async fn, 'static bound was removed here
1303 // https://github.com/rust-lang/rust/issues/63033#issuecomment-521234696
1304 _: (Guid, &Ucs2LeSlice),
1305 _: ParsedAuthVar<'_>,
1306 ) -> Result<(), (EfiStatus, Option<NvramError>)> {
1307 tracing::warn!(
1308 "compiled without 'auth-var-verify-crypto' - unconditionally failing auth var validation!"
1309 );
1310 Err((EfiStatus::SECURITY_VIOLATION, None))
1311 }
1312
1313 /// Authenticate the given variable against the signatures stored in the
1314 /// specified EFI_SIGNATURE_LIST
1315 #[cfg(feature = "auth-var-verify-crypto")]
1316 async fn authenticate_var(
1317 &mut self,
1318 // NOTE: Due to a compiler limitation with async fn, 'static bound was removed here
1319 // https://github.com/rust-lang/rust/issues/63033#issuecomment-521234696
1320 (key_var_name, key_var_vendor): (Guid, &Ucs2LeSlice),
1321 auth_var: ParsedAuthVar<'_>,
1322 ) -> Result<(), (EfiStatus, Option<NvramError>)> {
1323 let signature_lists = match self
1324 .get_variable_inner(key_var_vendor, key_var_name)
1325 .await?
1326 {
1327 Some((_, data, _)) => data,
1328 None => return Err((EfiStatus::SECURITY_VIOLATION, None)),
1329 };
1330
1331 // the nitty-gritty of how authentication works is best left to a separate module...
1332 match auth_var_crypto::authenticate_variable(&signature_lists, auth_var) {
1333 Ok(true) => Ok(()),
1334 Ok(false) => Err((
1335 EfiStatus::SECURITY_VIOLATION,
1336 Some(NvramError::AuthError(AuthError::CryptoError)),
1337 )),
1338 Err(e) if e.key_var_error() => {
1339 panic!("existing signature list must contain valid data: {}", e)
1340 }
1341 // all other errors are due to malformed auth_var data
1342 Err(e) => Err((
1343 EfiStatus::SECURITY_VIOLATION,
1344 Some(NvramError::AuthError(AuthError::CryptoFormat(e))),
1345 )),
1346 }
1347 }
1348
1349 /// Return the variable immediately following the variable identified by
1350 /// `name` + `vendor` `key`.
1351 ///
1352 /// If `name` is an empty string, the first variable is returned.
1353 ///
1354 /// - `name`
1355 /// - (In) Variable name (a null-terminated UTF-16 string, or `None` if
1356 /// the guest passed a `nullptr`)
1357 /// - `in_out_name_size`
1358 /// - (In) Length of the provided `name`
1359 /// - (Out) Length of the next variable name
1360 /// - _Note:_ If there is insufficient space in the name buffer to store
1361 /// the next variable, `in_out_name_size` will be updated with the
1362 /// size required to store the variable.
1363 /// - `vendor`
1364 /// - (In) Variable vendor guid
1365 pub async fn uefi_get_next_variable(
1366 &mut self,
1367 in_out_name_size: &mut u32,
1368 name: Option<&[u8]>,
1369 vendor: Guid,
1370 ) -> NvramResult<Option<(Vec<u8>, Guid)>> {
1371 let name = match name {
1372 Some(name) => {
1373 Ucs2LeSlice::from_slice_with_nul(name).map_err(NvramError::NameValidation)
1374 }
1375 None => Err(NvramError::NameNull),
1376 };
1377
1378 let name = match name {
1379 Ok(name) => name,
1380 Err(e) => return NvramResult(None, EfiStatus::INVALID_PARAMETER, Some(e)),
1381 };
1382
1383 tracing::trace!(?vendor, ?name, in_out_name_size, "Next NVRAM variable",);
1384
1385 // As per UEFI spec: if an empty null-terminated string is passed to
1386 // GetNextVariable, the first variable should be returned
1387 let mut res = if name.as_bytes() == [0, 0] {
1388 self.storage.next_variable(None).await
1389 } else {
1390 self.storage.next_variable(Some((name, vendor))).await
1391 };
1392
1393 loop {
1394 match res {
1395 Ok(NextVariable::EndOfList) => {
1396 return NvramResult(None, EfiStatus::NOT_FOUND, None);
1397 }
1398 Ok(NextVariable::InvalidKey) => {
1399 return NvramResult(None, EfiStatus::INVALID_PARAMETER, None);
1400 }
1401 Ok(NextVariable::Exists { name, vendor, attr }) => {
1402 let attr = EfiVariableAttributes::from(attr);
1403 assert!(
1404 !attr.contains_unsupported_bits(),
1405 "underlying storage should only ever contain valid attributes"
1406 );
1407
1408 // From UEFI spec section 8.2:
1409 //
1410 // Once EFI_BOOT_SERVICES.ExitBootServices() is performed,
1411 // variables that are only visible during boot services will
1412 // no longer be returned.
1413 //
1414 // i.e: continue iterating
1415 if self.runtime_state.is_runtime() && !attr.runtime_access() {
1416 res = self
1417 .storage
1418 .next_variable(Some((name.as_ref(), vendor)))
1419 .await;
1420 continue;
1421 }
1422
1423 let guest_buf_len = *in_out_name_size as usize;
1424 *in_out_name_size = name.as_bytes().len() as u32;
1425 if guest_buf_len < name.as_bytes().len() {
1426 return NvramResult(None, EfiStatus::BUFFER_TOO_SMALL, None);
1427 }
1428
1429 return NvramResult(
1430 Some((name.into_inner(), vendor)),
1431 EfiStatus::SUCCESS,
1432 None,
1433 );
1434 }
1435 Err(e) => {
1436 let status = match &e {
1437 NvramStorageError::Deserialize => EfiStatus::DEVICE_ERROR,
1438 _ => panic!("unexpected NvramStorageError from next_variable"),
1439 };
1440
1441 return NvramResult(None, status, Some(NvramError::NvramStorage(e)));
1442 }
1443 }
1444 }
1445 }
1446}
1447
1448mod save_restore {
1449 use super::*;
1450 use vmcore::save_restore::RestoreError;
1451 use vmcore::save_restore::SaveError;
1452 use vmcore::save_restore::SaveRestore;
1453
1454 mod state {
1455 use mesh::payload::Protobuf;
1456 use uefi_nvram_storage::in_memory::InMemoryNvram;
1457 use vmcore::save_restore::SaveRestore;
1458
1459 #[derive(Protobuf)]
1460 #[mesh(package = "firmware.uefi.nvram.spec")]
1461 pub enum SavedRuntimeState {
1462 #[mesh(1)]
1463 PreBoot,
1464 #[mesh(2)]
1465 Boot,
1466 #[mesh(3)]
1467 Runtime,
1468 }
1469
1470 #[derive(Protobuf)]
1471 #[mesh(package = "firmware.uefi.nvram.spec")]
1472 pub struct SavedState {
1473 #[mesh(1)]
1474 pub runtime_state: SavedRuntimeState,
1475 #[mesh(2)]
1476 pub storage: <InMemoryNvram as SaveRestore>::SavedState,
1477 }
1478 }
1479
1480 impl<S: VmmNvramStorage> SaveRestore for NvramSpecServices<S> {
1481 type SavedState = state::SavedState;
1482
1483 fn save(&mut self) -> Result<Self::SavedState, SaveError> {
1484 Ok(state::SavedState {
1485 runtime_state: match self.runtime_state {
1486 RuntimeState::PreBoot => state::SavedRuntimeState::PreBoot,
1487 RuntimeState::Boot => state::SavedRuntimeState::Boot,
1488 RuntimeState::Runtime => state::SavedRuntimeState::Runtime,
1489 },
1490 storage: self.storage.save()?,
1491 })
1492 }
1493
1494 fn restore(&mut self, state: Self::SavedState) -> Result<(), RestoreError> {
1495 let state::SavedState {
1496 runtime_state,
1497 storage,
1498 } = state;
1499
1500 self.runtime_state = match runtime_state {
1501 state::SavedRuntimeState::PreBoot => RuntimeState::PreBoot,
1502 state::SavedRuntimeState::Boot => RuntimeState::Boot,
1503 state::SavedRuntimeState::Runtime => RuntimeState::Runtime,
1504 };
1505 self.storage.restore(storage)?;
1506
1507 Ok(())
1508 }
1509 }
1510}
1511
1512#[cfg(test)]
1513mod test {
1514 use super::*;
1515 use uefi_nvram_storage::in_memory::InMemoryNvram;
1516 // TODO: wchz returns UTF-16 strings, _not_ UCS-2 strings. This works fine
1517 // when using english variable names, but things will _not_ work as expected
1518 // if one tries to use any particularly "exotic" chars (that cannot be
1519 // represented in UCS-2).
1520 use pal_async::async_test;
1521 use wchar::wchz;
1522
1523 use zerocopy::IntoBytes;
1524
1525 /// Extension trait around `NvramServices` that makes it easier to use the
1526 /// API outside the context of the UEFI device
1527 #[async_trait::async_trait]
1528 trait NvramServicesTestExt {
1529 async fn set_test_var(&mut self, name: &[u8], attr: u32, data: &[u8]) -> NvramResult<()>;
1530 async fn get_test_var(&mut self, name: &[u8]) -> NvramResult<(u32, Option<Vec<u8>>)>;
1531 async fn get_next_test_var(
1532 &mut self,
1533 name: Option<Vec<u8>>,
1534 ) -> NvramResult<Option<Vec<u8>>>;
1535 }
1536
1537 #[async_trait::async_trait]
1538 impl<S: VmmNvramStorage> NvramServicesTestExt for NvramSpecServices<S> {
1539 async fn set_test_var(&mut self, name: &[u8], attr: u32, data: &[u8]) -> NvramResult<()> {
1540 let vendor = Guid::default();
1541
1542 self.uefi_set_variable(
1543 Some(name),
1544 vendor,
1545 attr,
1546 data.len() as u32,
1547 Some(data.to_vec()),
1548 )
1549 .await
1550 }
1551
1552 async fn get_test_var(&mut self, name: &[u8]) -> NvramResult<(u32, Option<Vec<u8>>)> {
1553 let vendor = Guid::default();
1554
1555 let mut attr = 0;
1556 let NvramResult(data, status, err) = self
1557 .uefi_get_variable(Some(name), vendor, &mut attr, &mut 256, false)
1558 .await;
1559
1560 NvramResult((attr, data), status, err)
1561 }
1562
1563 async fn get_next_test_var(
1564 &mut self,
1565 name: Option<Vec<u8>>,
1566 ) -> NvramResult<Option<Vec<u8>>> {
1567 let vendor = Guid::default();
1568
1569 let NvramResult(name_guid, status, err) = self
1570 .uefi_get_next_variable(&mut 256, name.as_deref(), vendor)
1571 .await;
1572
1573 NvramResult(name_guid.map(|(n, _)| n.to_vec()), status, err)
1574 }
1575 }
1576
1577 trait NvramRetTestExt<T> {
1578 fn unwrap_efi_success(self) -> T;
1579 }
1580
1581 impl<T> NvramRetTestExt<T> for NvramResult<T> {
1582 #[track_caller]
1583 fn unwrap_efi_success(self) -> T {
1584 let NvramResult(val, status, err) = self;
1585 if let Some(err) = err {
1586 panic!("{}", err)
1587 }
1588 assert_eq!(status, EfiStatus::SUCCESS);
1589 val
1590 }
1591 }
1592
1593 #[async_test]
1594 async fn runtime_vars() {
1595 let nvram_storage = InMemoryNvram::new();
1596 let mut nvram = NvramSpecServices::new(nvram_storage);
1597
1598 nvram.prepare_for_boot();
1599
1600 let name1 = wchz!(u16, "var1").as_bytes();
1601 let name2 = wchz!(u16, "var2").as_bytes();
1602 let name3 = wchz!(u16, "var3").as_bytes();
1603 let name4 = wchz!(u16, "var4").as_bytes();
1604
1605 let dummy_data = b"dummy data".to_vec();
1606
1607 let runtime_attr = (EfiVariableAttributes::DEFAULT_ATTRIBUTES).into();
1608 let no_runtime_attr = EfiVariableAttributes::DEFAULT_ATTRIBUTES
1609 .with_runtime_access(false)
1610 .into();
1611
1612 // set some vars
1613 nvram
1614 .set_test_var(name1, runtime_attr, &dummy_data)
1615 .await
1616 .unwrap_efi_success();
1617 nvram
1618 .set_test_var(name2, no_runtime_attr, &dummy_data)
1619 .await
1620 .unwrap_efi_success();
1621 nvram
1622 .set_test_var(name3, runtime_attr, &dummy_data)
1623 .await
1624 .unwrap_efi_success();
1625 nvram
1626 .set_test_var(name4, no_runtime_attr, &dummy_data)
1627 .await
1628 .unwrap_efi_success();
1629
1630 // ensure they can all be accessed in pre-runtime environment
1631
1632 // access them individually
1633 let (attr, data) = nvram.get_test_var(name1).await.unwrap_efi_success();
1634 assert_eq!(attr, runtime_attr);
1635 assert_eq!(data, Some(dummy_data.clone()));
1636
1637 let (attr, data) = nvram.get_test_var(name2).await.unwrap_efi_success();
1638 assert_eq!(attr, no_runtime_attr);
1639 assert_eq!(data, Some(dummy_data.clone()));
1640
1641 let (attr, data) = nvram.get_test_var(name3).await.unwrap_efi_success();
1642 assert_eq!(attr, runtime_attr);
1643 assert_eq!(data, Some(dummy_data.clone()));
1644
1645 let (attr, data) = nvram.get_test_var(name4).await.unwrap_efi_success();
1646 assert_eq!(attr, no_runtime_attr);
1647 assert_eq!(data, Some(dummy_data.clone()));
1648
1649 // access them sequentially
1650 let mut name = Some(wchz!(u16, "").as_bytes().into());
1651 name = nvram.get_next_test_var(name).await.unwrap_efi_success();
1652 assert_eq!(name, Some(name1.into()));
1653
1654 name = nvram.get_next_test_var(name).await.unwrap_efi_success();
1655 assert_eq!(name, Some(name2.into()));
1656
1657 name = nvram.get_next_test_var(name).await.unwrap_efi_success();
1658 assert_eq!(name, Some(name3.into()));
1659
1660 name = nvram.get_next_test_var(name).await.unwrap_efi_success();
1661 assert_eq!(name, Some(name4.into()));
1662
1663 let NvramResult(name, status, err) = nvram.get_next_test_var(name).await;
1664 assert!(name.is_none());
1665 assert_eq!(status, EfiStatus::NOT_FOUND);
1666 assert!(err.is_none());
1667
1668 // ensure vars are hidden once runtime toggle is set
1669 nvram.exit_boot_services();
1670
1671 // try to set non-runtime access var
1672 let NvramResult(_, status, err) = nvram
1673 .set_test_var(
1674 wchz!(u16, "non-volatile").as_bytes(),
1675 no_runtime_attr,
1676 &dummy_data,
1677 )
1678 .await;
1679 assert_eq!(status, EfiStatus::INVALID_PARAMETER);
1680 assert!(matches!(err, Some(NvramError::InvalidRuntimeAccess)));
1681
1682 // access them individually
1683 let (attr, data) = nvram.get_test_var(name1).await.unwrap_efi_success();
1684 assert_eq!(attr, runtime_attr);
1685 assert_eq!(data, Some(dummy_data.clone()));
1686
1687 let NvramResult((attr, data), status, err) = nvram.get_test_var(name2).await;
1688 assert_eq!(attr, 0);
1689 assert_eq!(data, None);
1690 assert_eq!(status, EfiStatus::NOT_FOUND);
1691 assert!(matches!(err, Some(NvramError::InvalidRuntimeAccess)));
1692
1693 let (attr, data) = nvram.get_test_var(name3).await.unwrap_efi_success();
1694 assert_eq!(attr, runtime_attr);
1695 assert_eq!(data, Some(dummy_data));
1696
1697 let NvramResult((attr, data), status, err) = nvram.get_test_var(name4).await;
1698 assert_eq!(attr, 0);
1699 assert_eq!(data, None);
1700 assert_eq!(status, EfiStatus::NOT_FOUND);
1701 assert!(matches!(err, Some(NvramError::InvalidRuntimeAccess)));
1702
1703 // access them sequentially
1704 let mut name = Some(wchz!(u16, "").as_bytes().into());
1705 name = nvram.get_next_test_var(name).await.unwrap_efi_success();
1706 assert_eq!(name, Some(name1.into()));
1707
1708 // DON'T read name2
1709
1710 name = nvram.get_next_test_var(name).await.unwrap_efi_success();
1711 assert_eq!(name, Some(name3.into()));
1712
1713 // DON'T read name4
1714
1715 let NvramResult(name, status, err) = nvram.get_next_test_var(name).await;
1716 assert!(name.is_none());
1717 assert_eq!(status, EfiStatus::NOT_FOUND);
1718 assert!(err.is_none());
1719 }
1720}