1pub use spec_services::NvramError;
15pub use spec_services::NvramResult;
16pub use spec_services::NvramServicesExt;
17pub use spec_services::NvramSpecServices;
18
19use crate::UefiDevice;
20use 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#[derive(Inspect)]
63pub struct NvramServices {
64 #[inspect(skip)]
66 vsm_config: Option<Box<dyn VsmConfig>>,
67
68 #[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 async fn inject_vars_on_first_boot(
105 &mut self,
106 custom_vars: CustomVars,
107 ) -> Result<(), NvramSetupError> {
108 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 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 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 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 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 for (name, CustomVar { guid, attr, value }) in custom_vars.custom_vars {
219 tracing::trace!(%name, "Injecting custom var");
220
221 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 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 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 let dbdefault_sig = sigs.db.clone();
259
260 #[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 (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 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 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 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 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 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 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 self.gm.write_plain(command_addr, &command)?;
469
470 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 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 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 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 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}