Skip to main content

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