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}