firmware_uefi/service/nvram/
mod.rs

1// Copyright (c) Microsoft Corporation.
2// Licensed under the MIT License.
3
4//! UEFI Nvram variable services subsystem.
5//!
6//! Special care has been taken to keep all Hyper-V specific interfaces and
7//! extensions in a separate layer from the underlying UEFI spec mandated
8//! functionality.
9//!
10//! e.g: things like injecting various nvram vars related to secure boot, boot
11//! order, etc... are not part of the UEFI spec, and are therefore implemented
12//! _outside_ of the [`spec_services`] module.
13
14pub use spec_services::NvramError;
15pub use spec_services::NvramResult;
16pub use spec_services::NvramServicesExt;
17pub use spec_services::NvramSpecServices;
18
19use crate::UefiDevice;
20use crate::platform::nvram::VsmConfig;
21use firmware_uefi_custom_vars::CustomVars;
22use guestmem::GuestMemoryError;
23use inspect::Inspect;
24use std::borrow::Cow;
25use std::fmt::Debug;
26use thiserror::Error;
27use uefi_nvram_storage::InspectableNvramStorage;
28use uefi_specs::uefi::common::EfiStatus;
29use uefi_specs::uefi::nvram::EfiVariableAttributes;
30use zerocopy::IntoBytes;
31
32#[cfg(feature = "fuzzing")]
33pub mod spec_services;
34#[cfg(not(feature = "fuzzing"))]
35mod spec_services;
36
37#[derive(Debug, Error)]
38pub enum NvramSetupError {
39    #[error("could not query backing nvram storage")]
40    BadNvramStorage(#[source] crate::platform::nvram::NvramStorageError),
41    #[error("could not inject pre-boot var '{0}': {1:?}")]
42    InjectPreBootVar(
43        Cow<'static, ucs2::Ucs2LeSlice>,
44        EfiStatus,
45        #[source] Option<NvramError>,
46    ),
47    #[error("could not inject signature var '{0}': {1:?}")]
48    InjectSigVar(
49        Cow<'static, ucs2::Ucs2LeSlice>,
50        EfiStatus,
51        #[source] Option<NvramError>,
52    ),
53    #[error("could not inject custom var '{0}': {1:?}")]
54    InjectCustomVar(String, EfiStatus, #[source] Option<NvramError>),
55    #[error("custom variable name is not valid UCS-2")]
56    CustomVarNotUcs2,
57}
58
59/// Implements Hyper-V specific nvram service interfaces, extensions, and
60/// functionality, deferring to the underlying [`NvramSpecServices`] object to
61/// implement any UEFI spec mandated nvram service functionality.
62#[derive(Inspect)]
63pub struct NvramServices {
64    // Runtime glue
65    #[inspect(skip)]
66    vsm_config: Option<Box<dyn VsmConfig>>,
67
68    // Sub-emulators
69    #[inspect(flatten)]
70    services: NvramSpecServices<Box<dyn InspectableNvramStorage>>,
71}
72
73impl NvramServices {
74    pub async fn new(
75        nvram_storage: Box<dyn InspectableNvramStorage>,
76        custom_vars: CustomVars,
77        secure_boot_enabled: bool,
78        vsm_config: Option<Box<dyn VsmConfig>>,
79        is_restoring: bool,
80    ) -> Result<NvramServices, NvramSetupError> {
81        let mut nvram = NvramServices {
82            services: NvramSpecServices::new(nvram_storage),
83            vsm_config,
84        };
85
86        if !is_restoring {
87            nvram.inject_vars_on_first_boot(custom_vars).await?;
88            nvram.inject_hyperv_vars().await?;
89            nvram.setup_secure_boot(secure_boot_enabled).await?;
90        }
91
92        nvram.services.prepare_for_boot();
93
94        Ok(nvram)
95    }
96
97    pub fn reset(&mut self) {
98        self.services.reset();
99        self.services.prepare_for_boot();
100    }
101
102    /// Check if this is the VM's first boot, and if so, inject various
103    /// hard-coded and custom UEFI vars.
104    async fn inject_vars_on_first_boot(
105        &mut self,
106        custom_vars: CustomVars,
107    ) -> Result<(), NvramSetupError> {
108        // "First boot" is marked by having no variables in nvram storage
109        if !self
110            .services
111            .is_empty()
112            .await
113            .map_err(NvramSetupError::BadNvramStorage)?
114        {
115            return Ok(());
116        }
117
118        tracing::info!("No NVRAM variables (first boot). Loading in initial NVRAM values.");
119
120        // Windows uses CurrentPolicy to protect secure boot policy
121        tracing::trace!("Injecting 'CurrentPolicy'");
122        {
123            use uefi_specs::hyperv::nvram::vars::CURRENT_POLICY;
124
125            let (vendor, name) = CURRENT_POLICY();
126            const CURRENT_POLICY_AUTHENTICATED_MARKER: u8 = 0x02;
127            let data = [CURRENT_POLICY_AUTHENTICATED_MARKER];
128            let attr = EfiVariableAttributes::DEFAULT_ATTRIBUTES_TIME_BASED_AUTH;
129
130            // because this variable is set with time based auth, it needs a
131            // `EFI_VARIABLE_AUTHENTICATION_2`. fortunately, we are still in
132            // pre-boot, which means it suffices to use a dummy header.
133            let data = {
134                let mut v = Vec::new();
135                v.extend(uefi_specs::uefi::nvram::EFI_VARIABLE_AUTHENTICATION_2::DUMMY.as_bytes());
136                v.extend(data);
137                v
138            };
139
140            self.services
141                .set_variable_ucs2(vendor, name, attr.into(), data.to_vec())
142                .await
143                .map_err(|(status, err)| {
144                    NvramSetupError::InjectPreBootVar(name.into(), status, err)
145                })?;
146        }
147
148        tracing::trace!("Updating 'SetupMode'");
149        {
150            use uefi_specs::uefi::nvram::vars::SETUP_MODE;
151            let (_, name) = SETUP_MODE();
152
153            self.services.update_setup_mode().await.map_err(|e| {
154                NvramSetupError::InjectPreBootVar(
155                    name.into(),
156                    EfiStatus::DEVICE_ERROR,
157                    Some(NvramError::NvramStorage(e)),
158                )
159            })?
160        }
161
162        self.inject_custom_vars(custom_vars).await?;
163
164        Ok(())
165    }
166
167    async fn inject_hyperv_vars(&mut self) -> Result<(), NvramSetupError> {
168        // Always inject these, in case the vmgs file was first booted RS1.86
169        tracing::trace!("Injecting 'OsLoaderIndications'");
170        {
171            use uefi_specs::hyperv::nvram::vars::OS_LOADER_INDICATIONS;
172
173            let (vendor, name) = OS_LOADER_INDICATIONS();
174            let data = 0u32.as_bytes();
175            let attr = EfiVariableAttributes::new().with_bootservice_access(true);
176
177            self.services
178                .set_variable_ucs2(vendor, name, attr.into(), data.to_vec())
179                .await
180                .map_err(|(status, err)| {
181                    NvramSetupError::InjectPreBootVar(name.into(), status, err)
182                })?;
183        }
184
185        tracing::trace!("Injecting 'OsLoaderIndicationsSupported'");
186        {
187            use uefi_specs::hyperv::nvram::vars::OS_LOADER_INDICATIONS_SUPPORTED;
188
189            let (vendor, name) = OS_LOADER_INDICATIONS_SUPPORTED();
190            // All VM versions capable of running the HCL support VSM
191            let data = 1u32.as_bytes();
192            let attr = EfiVariableAttributes::new().with_bootservice_access(true);
193
194            self.services
195                .set_variable_ucs2(vendor, name, attr.into(), data.to_vec())
196                .await
197                .map_err(|(status, err)| {
198                    NvramSetupError::InjectPreBootVar(name.into(), status, err)
199                })?;
200        }
201
202        Ok(())
203    }
204
205    async fn inject_custom_vars(&mut self, custom_vars: CustomVars) -> Result<(), NvramSetupError> {
206        use firmware_uefi_custom_vars::CustomVar;
207        use firmware_uefi_custom_vars::Sha256Digest;
208        use firmware_uefi_custom_vars::Signature;
209        use firmware_uefi_custom_vars::X509Cert;
210        use uefi_nvram_specvars::signature_list::SignatureData;
211        use uefi_nvram_specvars::signature_list::SignatureList;
212        use uefi_specs::hyperv::nvram::vars::MSFT_SECURE_BOOT_PRODUCTION_GUID;
213        use uefi_specs::uefi::nvram::EFI_VARIABLE_AUTHENTICATION_2;
214
215        tracing::trace!(custom_vars = ?custom_vars.custom_vars, "custom uefi vars");
216
217        // inject freeform custom vars first, as some may require an auth bypass
218        for (name, CustomVar { guid, attr, value }) in custom_vars.custom_vars {
219            tracing::trace!(%name, "Injecting custom var");
220
221            // the value might need to be prepended with an auth header,
222            // depending on what auth mode the variable is using.
223            let value = {
224                let attr = EfiVariableAttributes::from(attr);
225                if attr.contains_unsupported_bits() {
226                    return Err(NvramSetupError::InjectCustomVar(
227                        name,
228                        EfiStatus::INVALID_PARAMETER,
229                        Some(NvramError::AttributeNonSpec),
230                    ));
231                }
232
233                if attr.time_based_authenticated_write_access() {
234                    let mut new_value = Vec::new();
235                    // a dummy header needs to be present, even through no
236                    // actual validation will be performed while nvram is still
237                    // in SetupMode (i.e: until `pk` is injected).
238                    new_value.extend(EFI_VARIABLE_AUTHENTICATION_2::DUMMY.as_bytes());
239                    new_value.extend(value);
240                    new_value
241                } else {
242                    value
243                }
244            };
245
246            self.services
247                .set_variable(guid, &name, attr, value)
248                .await
249                .map_err(|(status, err)| NvramSetupError::InjectCustomVar(name, status, err))?;
250        }
251
252        // inject structured signature vars
253        if let Some(sigs) = custom_vars.signatures {
254            use uefi_specs::linux::nvram::vars as linux_vars;
255            use uefi_specs::uefi::nvram::vars as uefi_vars;
256
257            // `dbDefault` is a read-only copy of the initial `db`
258            let dbdefault_sig = sigs.db.clone();
259
260            // for each of the signatures, construct the variable payload
261            // (in the form of a signature list), and inject it into nvram.
262            #[rustfmt::skip]
263            let sigs_loop = [
264                (uefi_vars::KEK(),        sigs.kek,      EfiVariableAttributes::DEFAULT_ATTRIBUTES_TIME_BASED_AUTH),
265                (uefi_vars::DB(),         sigs.db,       EfiVariableAttributes::DEFAULT_ATTRIBUTES_TIME_BASED_AUTH),
266                (uefi_vars::DBX(),        sigs.dbx,      EfiVariableAttributes::DEFAULT_ATTRIBUTES_TIME_BASED_AUTH),
267                // Two notes:
268                //
269                // 1. Why the `vec![]`? Well, while there can only ever be a
270                //    single PK, it still ends up getting stored in a signature
271                //    _list_, so we may as well reuse the existing logic (rather
272                //    than having a special cased block just for PK).
273                //
274                // 2. pk _must_ be injected after kek, db, and dbx, as once pk
275                //    is injected, nvram switches out of SetupMode, and requires
276                //    non-dummy auth var headers to update those vars.
277                (uefi_vars::PK(),         vec![sigs.pk], EfiVariableAttributes::DEFAULT_ATTRIBUTES_TIME_BASED_AUTH),
278                (uefi_vars::DBDEFAULT(),  dbdefault_sig, EfiVariableAttributes::DEFAULT_ATTRIBUTES_VOLATILE),
279                (linux_vars::MOK_LIST(),  sigs.moklist,  EfiVariableAttributes::DEFAULT_ATTRIBUTES),
280                (linux_vars::MOK_LISTX(), sigs.moklistx, EfiVariableAttributes::DEFAULT_ATTRIBUTES),
281            ];
282
283            for ((vendor, name), sigs, attr) in sigs_loop {
284                tracing::trace!(?name, "Injecting");
285
286                let mut var_data: Vec<u8> = Vec::new();
287
288                if attr.time_based_authenticated_write_access() {
289                    // a dummy header needs to be present, even through no
290                    // actual validation will be performed while nvram is still
291                    // in SetupMode (i.e: until `pk` is injected).
292                    var_data.extend(EFI_VARIABLE_AUTHENTICATION_2::DUMMY.as_bytes());
293                }
294
295                for sig in sigs {
296                    match sig {
297                        Signature::X509(certs) => {
298                            // x509 is weird, since every cert in the array
299                            // actually ends up as a _separate_ signature list!
300                            for X509Cert(data) in certs {
301                                let sig_list = SignatureList::X509(SignatureData::new_x509(
302                                    MSFT_SECURE_BOOT_PRODUCTION_GUID,
303                                    Cow::Owned(data),
304                                ));
305                                sig_list.extend_as_spec_signature_list(&mut var_data);
306                            }
307                        }
308                        Signature::Sha256(digests) => {
309                            let sig_list = SignatureList::Sha256(
310                                digests
311                                    .into_iter()
312                                    .map(|Sha256Digest(data)| {
313                                        SignatureData::new_sha256(
314                                            MSFT_SECURE_BOOT_PRODUCTION_GUID,
315                                            Cow::Owned(data),
316                                        )
317                                    })
318                                    .collect(),
319                            );
320                            sig_list.extend_as_spec_signature_list(&mut var_data);
321                        }
322                    }
323                }
324
325                if var_data.is_empty() {
326                    continue;
327                }
328
329                self.services
330                    .set_variable_ucs2(vendor, name, attr.into(), var_data)
331                    .await
332                    .map_err(|(status, err)| {
333                        NvramSetupError::InjectSigVar(name.into(), status, err)
334                    })?;
335            }
336        }
337
338        Ok(())
339    }
340
341    /// Inject secure boot configuration vars.
342    async fn setup_secure_boot(&mut self, enabled: bool) -> Result<(), NvramSetupError> {
343        tracing::info!(enabled, "configuring secure boot");
344
345        let data = if enabled { [0x01] } else { [0x00] };
346
347        tracing::trace!("Injecting 'SecureBoot'");
348        {
349            use uefi_specs::uefi::nvram::vars::SECURE_BOOT;
350
351            let (vendor, name) = SECURE_BOOT();
352
353            // Older versions of OpenHCL (and Hyper-V, closed-source HCL, etc. ) may have created
354            // a SecureBoot variable with the NV attribute, which doesn't match the UEFI spec.
355            // Delete this variable (if it exists).
356            let delete_attr = EfiVariableAttributes::new();
357            let _ = self
358                .services
359                .set_variable_ucs2(vendor, name, delete_attr.into(), data.to_vec())
360                .await;
361
362            // TODO: For compatibility with older OpenHCL images that cannot handle a volatile
363            // variable, we still need to create with NV for now.  Once the above variable
364            // deletion code is deployed everywhere, replace with:
365            // let attr = EfiVariableAttributes::DEFAULT_ATTRIBUTES_VOLATILE;
366            let attr = EfiVariableAttributes::DEFAULT_ATTRIBUTES;
367            self.services
368                .set_variable_ucs2(vendor, name, attr.into(), data.to_vec())
369                .await
370                .map_err(|(status, err)| {
371                    NvramSetupError::InjectPreBootVar(name.into(), status, err)
372                })?;
373        }
374
375        tracing::trace!("Injecting 'SecureBootEnabled'");
376        {
377            use uefi_specs::hyperv::nvram::vars::SECURE_BOOT_ENABLE;
378
379            let (vendor, name) = SECURE_BOOT_ENABLE();
380            let attr = EfiVariableAttributes::DEFAULT_ATTRIBUTES;
381
382            self.services
383                .set_variable_ucs2(vendor, name, attr.into(), data.to_vec())
384                .await
385                .map_err(|(status, err)| {
386                    NvramSetupError::InjectPreBootVar(name.into(), status, err)
387                })?;
388        }
389
390        Ok(())
391    }
392}
393
394impl UefiDevice {
395    pub(crate) async fn nvram_handle_command(&mut self, desc_addr: u64) {
396        use uefi_specs::hyperv::nvram::NvramCommandDescriptor;
397
398        let mut desc: NvramCommandDescriptor = match self.gm.read_plain(desc_addr) {
399            Ok(desc) => desc,
400            Err(err) => {
401                tracelimit::warn_ratelimited!(
402                    error = &err as &dyn std::error::Error,
403                    "Could not read NvramCommandDescriptor from guest memory",
404                );
405                return;
406            }
407        };
408
409        let status = match self.handle_nvram_command_inner(desc_addr, desc).await {
410            Ok(status) => status,
411            Err(err) => {
412                tracelimit::warn_ratelimited!(
413                    error = &err as &dyn std::error::Error,
414                    "Guest memory error while handling nvram command"
415                );
416                EfiStatus::DEVICE_ERROR
417            }
418        };
419
420        // write back status into guest memory
421        desc.status = status.into();
422
423        if let Err(err) = self.gm.write_plain(desc_addr, &desc) {
424            tracelimit::warn_ratelimited!(
425                error = &err as &dyn std::error::Error,
426                "Could not write NvramCommandDescriptor into guest memory",
427            );
428        }
429    }
430
431    async fn handle_nvram_command_inner(
432        &mut self,
433        desc_addr: u64,
434        desc: uefi_specs::hyperv::nvram::NvramCommandDescriptor,
435    ) -> Result<EfiStatus, GuestMemoryError> {
436        use uefi_specs::hyperv::nvram::NvramCommand;
437        use uefi_specs::hyperv::nvram::NvramVariableCommand;
438
439        let command_addr = desc_addr + size_of_val(&desc) as u64;
440
441        let (status, err) = match desc.command {
442            NvramCommand::GET_VARIABLE => {
443                let mut command: NvramVariableCommand = self.gm.read_plain(command_addr)?;
444
445                let name = if command.name_address.get() != 0 {
446                    let mut buf = vec![0; command.name_bytes as usize];
447                    self.gm
448                        .read_at(command.name_address.into(), buf.as_mut_slice())?;
449                    Some(buf)
450                } else {
451                    None
452                };
453
454                let NvramResult(data, status, err) = self
455                    .service
456                    .nvram
457                    .services
458                    .uefi_get_variable(
459                        name.as_deref(),
460                        command.vendor_guid,
461                        &mut command.attributes,
462                        &mut command.data_bytes,
463                        command.data_address.get() == 0,
464                    )
465                    .await;
466
467                // writeback updated command struct
468                self.gm.write_plain(command_addr, &command)?;
469
470                // write any data to provided guest memory location
471                // (bounds checking is performed within `nvram.get_variable`)
472                if let Some(data) = data {
473                    self.gm
474                        .write_at(command.data_address.get(), data.as_bytes())?;
475                }
476
477                (status, err)
478            }
479            NvramCommand::SET_VARIABLE => {
480                let command: NvramVariableCommand = self.gm.read_plain(command_addr)?;
481
482                let name = if command.name_address.get() != 0 {
483                    let mut buf = vec![0; command.name_bytes as usize];
484                    self.gm
485                        .read_at(command.name_address.into(), buf.as_mut_slice())?;
486                    Some(buf)
487                } else {
488                    None
489                };
490
491                let data = if command.data_address.get() != 0 {
492                    let mut buf = vec![0; command.data_bytes as usize];
493                    self.gm
494                        .read_at(command.data_address.into(), buf.as_mut_slice())?;
495                    Some(buf)
496                } else {
497                    None
498                };
499
500                let NvramResult((), status, err) = self
501                    .service
502                    .nvram
503                    .services
504                    .uefi_set_variable(
505                        name.as_deref(),
506                        command.vendor_guid,
507                        command.attributes,
508                        command.data_bytes,
509                        data,
510                    )
511                    .await;
512
513                (status, err)
514            }
515            NvramCommand::GET_FIRST_VARIABLE_NAME | NvramCommand::GET_NEXT_VARIABLE_NAME => {
516                let mut command: NvramVariableCommand = self.gm.read_plain(command_addr)?;
517
518                let name = if desc.command == NvramCommand::GET_NEXT_VARIABLE_NAME {
519                    if command.name_address.get() != 0 {
520                        let mut buf = vec![0; command.name_bytes as usize];
521                        self.gm
522                            .read_at(command.name_address.into(), buf.as_mut_slice())?;
523                        Some(buf)
524                    } else {
525                        None
526                    }
527                } else {
528                    // If the command is GET_FIRST_VARIABLE_NAME, then we should
529                    // ignore the name provided in the NvramVariableCommand
530                    // struct, and just pass along a empty UTF-16 string to
531                    // `get_next_variable`, which will fetch the first variable
532                    // name (as specified by the official UEFI spec)
533                    Some(vec![0, 0])
534                };
535
536                let NvramResult(data, status, err) = self
537                    .service
538                    .nvram
539                    .services
540                    .uefi_get_next_variable(
541                        &mut command.name_bytes,
542                        name.as_deref(),
543                        command.vendor_guid,
544                    )
545                    .await;
546
547                // write new name data to provided guest memory location
548                // (bounds checking is performed within `nvram.get_next_variable`)
549                if let Some((name, vendor)) = data {
550                    command.vendor_guid = vendor;
551
552                    self.gm
553                        .write_at(command.name_address.get(), name.as_bytes())?;
554                }
555
556                // writeback updated command struct
557                self.gm.write_at(command_addr, command.as_bytes())?;
558
559                (status, err)
560            }
561            NvramCommand::QUERY_INFO => (EfiStatus::UNSUPPORTED, None),
562            NvramCommand::SIGNAL_RUNTIME => {
563                use uefi_specs::hyperv::nvram::NvramSignalRuntimeCommand;
564                let command: NvramSignalRuntimeCommand = self.gm.read_plain(command_addr)?;
565
566                if !command.flags.vsm_aware() {
567                    if let Some(vsm) = &self.service.nvram.vsm_config {
568                        tracelimit::info_ratelimited!("Revoking guest vsm");
569                        vsm.revoke_guest_vsm()
570                    }
571                }
572                self.service.nvram.services.exit_boot_services();
573
574                (EfiStatus::SUCCESS, None)
575            }
576            NvramCommand::DEBUG_STRING => {
577                let command: uefi_specs::hyperv::nvram::NvramDebugStringCommand =
578                    self.gm.read_plain(command_addr)?;
579
580                let mut data = vec![0u16; command.len as usize / 2];
581                self.gm
582                    .read_at(command.address.into(), data.as_mut_bytes())?;
583
584                tracing::trace!(
585                    target: "uefi-nvram-guest-debug",
586                    data = %String::from_utf16_lossy(&data),
587                    "nvram guest debug",
588                );
589                (EfiStatus::SUCCESS, None)
590            }
591            command => {
592                tracelimit::warn_ratelimited!(?command, "unknown nvram command");
593                (EfiStatus::UNSUPPORTED, None)
594            }
595        };
596
597        // log any errors which may have occurred
598        if let Some(err) = err {
599            let err: &(dyn std::error::Error + 'static) = &err;
600            tracelimit::warn_ratelimited!(
601                command = ?desc.command,
602                ?status,
603                error = err,
604                "nvram error"
605            )
606        }
607
608        if status != EfiStatus::SUCCESS {
609            tracing::trace!(?status, "nvram status");
610        }
611
612        Ok(status)
613    }
614}
615
616mod save_restore {
617    use super::*;
618    use vmcore::save_restore::RestoreError;
619    use vmcore::save_restore::SaveError;
620    use vmcore::save_restore::SaveRestore;
621
622    mod state {
623        use crate::service::nvram::NvramSpecServices;
624        use mesh::payload::Protobuf;
625        use uefi_nvram_storage::InspectableNvramStorage;
626        use vmcore::save_restore::SaveRestore;
627
628        #[derive(Protobuf)]
629        #[mesh(package = "firmware.uefi.nvram")]
630        pub struct SavedState {
631            #[mesh(1)]
632            pub services:
633                <NvramSpecServices<Box<dyn InspectableNvramStorage>> as SaveRestore>::SavedState,
634        }
635    }
636
637    impl SaveRestore for NvramServices {
638        type SavedState = state::SavedState;
639
640        fn save(&mut self) -> Result<Self::SavedState, SaveError> {
641            let NvramServices {
642                vsm_config: _,
643                services,
644            } = self;
645
646            let saved_state = state::SavedState {
647                services: services.save()?,
648            };
649
650            Ok(saved_state)
651        }
652
653        fn restore(&mut self, state: Self::SavedState) -> Result<(), RestoreError> {
654            let state::SavedState { services } = state;
655
656            self.services.restore(services)?;
657
658            Ok(())
659        }
660    }
661}