1#![forbid(unsafe_code)]
18
19pub mod storage_backend;
20
21use cvm_tracing::CVM_ALLOWED;
22use cvm_tracing::CVM_CONFIDENTIAL;
23use guid::Guid;
24use std::fmt::Debug;
25use storage_backend::StorageBackend;
26use ucs2::Ucs2LeSlice;
27use uefi_nvram_storage::EFI_TIME;
28use uefi_nvram_storage::NextVariable;
29use uefi_nvram_storage::NvramStorage;
30use uefi_nvram_storage::NvramStorageError;
31use uefi_nvram_storage::in_memory;
32use zerocopy::FromBytes;
33use zerocopy::Immutable;
34use zerocopy::IntoBytes;
35use zerocopy::KnownLayout;
36
37const EFI_MAX_VARIABLE_NAME_SIZE: usize = 2 * 1024;
38const EFI_MAX_VARIABLE_DATA_SIZE: usize = 32 * 1024;
39
40const INITIAL_NVRAM_SIZE: usize = 32768;
43const MAXIMUM_NVRAM_SIZE: usize = INITIAL_NVRAM_SIZE * 4;
44
45mod format {
46 use super::*;
47 use open_enum::open_enum;
48 use static_assertions::const_assert_eq;
49
50 open_enum! {
51 #[derive(IntoBytes, Immutable, KnownLayout, FromBytes)]
52 pub enum NvramHeaderType: u32 {
53 VARIABLE = 0,
54 }
55 }
56
57 #[repr(C)]
58 #[derive(Copy, Clone, Debug, IntoBytes, Immutable, KnownLayout, FromBytes)]
59 pub struct NvramHeader {
60 pub header_type: NvramHeaderType,
61 pub length: u32, }
63
64 const_assert_eq!(8, size_of::<NvramHeader>());
65
66 #[repr(C)]
67 #[derive(Copy, Clone, Debug, IntoBytes, Immutable, KnownLayout, FromBytes)]
68 pub struct NvramVariable {
69 pub header: NvramHeader, pub attributes: u32,
71 pub timestamp: EFI_TIME, pub vendor: Guid,
73 pub name_bytes: u16, pub data_bytes: u16, }
78 const_assert_eq!(48, size_of::<NvramVariable>());
79}
80
81#[cfg_attr(feature = "inspect", derive(inspect::Inspect))]
84pub struct HclCompatNvram<S> {
85 quirks: HclCompatNvramQuirks,
86
87 #[cfg_attr(feature = "inspect", inspect(skip))]
88 storage: S,
89
90 in_memory: in_memory::InMemoryNvram,
91
92 #[cfg_attr(feature = "inspect", inspect(skip))] nvram_buf: Vec<u8>,
97
98 loaded: bool,
100}
101
102#[cfg_attr(feature = "inspect", derive(inspect::Inspect))]
104pub struct HclCompatNvramQuirks {
105 pub skip_corrupt_vars_with_missing_null_term: bool,
120}
121
122impl<S: StorageBackend> HclCompatNvram<S> {
123 pub fn new(storage: S, quirks: Option<HclCompatNvramQuirks>) -> Self {
125 Self {
126 quirks: quirks.unwrap_or(HclCompatNvramQuirks {
127 skip_corrupt_vars_with_missing_null_term: false,
128 }),
129
130 storage,
131
132 in_memory: in_memory::InMemoryNvram::new(),
133
134 nvram_buf: Vec::new(),
135
136 loaded: false,
137 }
138 }
139
140 async fn lazy_load_from_storage(&mut self) -> Result<(), NvramStorageError> {
141 let res = self.lazy_load_from_storage_inner().await;
142 if let Err(e) = &res {
143 tracing::error!(CVM_ALLOWED, "storage contains corrupt nvram state");
144 tracing::error!(
145 CVM_CONFIDENTIAL,
146 error = e as &dyn std::error::Error,
147 "storage contains corrupt nvram state"
148 );
149 }
150 res
151 }
152
153 async fn lazy_load_from_storage_inner(&mut self) -> Result<(), NvramStorageError> {
154 if self.loaded {
155 return Ok(());
156 }
157
158 tracing::info!("loading uefi nvram from storage");
159
160 let nvram_buf = self
161 .storage
162 .restore()
163 .await
164 .map_err(|e| NvramStorageError::Load(e.into()))?
165 .unwrap_or_default();
166
167 if nvram_buf.len() > MAXIMUM_NVRAM_SIZE {
168 return Err(NvramStorageError::Load(
169 format!(
170 "Existing nvram state exceeds MAXIMUM_NVRAM_SIZE ({} > {})",
171 nvram_buf.len(),
172 MAXIMUM_NVRAM_SIZE
173 )
174 .into(),
175 ));
176 }
177
178 self.in_memory.clear();
180 self.nvram_buf = nvram_buf;
181 let mut buf = self.nvram_buf.as_slice();
182 while let Ok((header, _)) = format::NvramHeader::read_from_prefix(buf) {
184 if buf.len() < header.length as usize {
185 return Err(NvramStorageError::Load(
186 format!(
187 "unexpected EOF. expected at least {} more bytes, but only found {}",
188 header.length,
189 buf.len()
190 )
191 .into(),
192 ));
193 }
194
195 let entry_buf = {
196 let (entry_buf, remaining) = buf.split_at(header.length as usize);
197 buf = remaining;
198 entry_buf
199 };
200
201 match header.header_type {
202 format::NvramHeaderType::VARIABLE => {}
203 _ => {
204 return Err(NvramStorageError::Load(
205 format!("unknown header type: {:?}", header.header_type).into(),
206 ));
207 }
208 }
209
210 let (var_header, var_name, var_data) = {
214 let (var_header, var_length_data) =
217 format::NvramVariable::read_from_prefix(entry_buf)
218 .map_err(|_| NvramStorageError::Load("variable entry too short".into()))?;
219
220 if var_length_data.len()
221 != var_header.name_bytes as usize + var_header.data_bytes as usize
222 {
223 return Err(NvramStorageError::Load(
224 "mismatch between header length and variable data size".into(),
225 ));
226 }
227
228 let (var_name, var_data) = var_length_data.split_at(var_header.name_bytes as usize);
229
230 (var_header, var_name, var_data)
231 };
232
233 if var_name.len() > EFI_MAX_VARIABLE_NAME_SIZE {
234 return Err(NvramStorageError::Load(
235 format!(
236 "variable name too big. {} > {}",
237 var_name.len(),
238 EFI_MAX_VARIABLE_NAME_SIZE
239 )
240 .into(),
241 ));
242 }
243
244 if var_data.len() > EFI_MAX_VARIABLE_DATA_SIZE {
245 return Err(NvramStorageError::Load(
246 format!(
247 "variable data too big. {} > {}",
248 var_data.len(),
249 EFI_MAX_VARIABLE_DATA_SIZE
250 )
251 .into(),
252 ));
253 }
254
255 let name = match Ucs2LeSlice::from_slice_with_nul(var_name) {
256 Ok(name) => name,
257 Err(e) => {
258 if self.quirks.skip_corrupt_vars_with_missing_null_term {
259 let var = {
260 let mut var = var_name.to_vec();
261 var.push(0);
262 var.push(0);
263 ucs2::Ucs2LeVec::from_vec_with_nul(var)
264 };
265 tracing::warn!(
266 CVM_ALLOWED,
267 "skipping corrupt nvram var (missing null term)"
268 );
269 tracing::warn!(
270 CVM_CONFIDENTIAL,
271 ?var,
272 "skipping corrupt nvram var (missing null term)"
273 );
274 continue;
275 } else {
276 return Err(NvramStorageError::Load(e.into()));
277 }
278 }
279 };
280
281 self.in_memory
282 .set_variable(
283 name,
284 var_header.vendor,
285 var_header.attributes,
286 var_data.to_vec(),
287 var_header.timestamp,
288 )
289 .await?;
290 }
291
292 if !buf.is_empty() {
293 return Err(NvramStorageError::Load(
294 "existing nvram state contains excess data".into(),
295 ));
296 }
297
298 self.loaded = true;
299 Ok(())
300 }
301
302 async fn flush_storage(&mut self) -> Result<(), NvramStorageError> {
304 tracing::info!("flushing uefi nvram to storage");
305 self.nvram_buf.clear();
306
307 for in_memory::VariableEntry {
308 vendor,
309 name,
310 data,
311 timestamp,
312 attr,
313 } in self.in_memory.iter()
314 {
315 self.nvram_buf.extend_from_slice(
316 format::NvramVariable {
317 header: format::NvramHeader {
318 header_type: format::NvramHeaderType::VARIABLE,
319 length: (size_of::<format::NvramVariable>()
320 + name.as_bytes().len()
321 + data.len()) as u32,
322 },
323 attributes: attr,
324 timestamp,
325 vendor,
326 name_bytes: name.as_bytes().len() as u16,
327 data_bytes: data.len() as u16,
328 }
329 .as_bytes(),
330 );
331 self.nvram_buf.extend_from_slice(name.as_bytes());
332 self.nvram_buf.extend_from_slice(data);
333 }
334
335 assert!(self.nvram_buf.len() < MAXIMUM_NVRAM_SIZE);
338
339 self.storage
340 .persist(self.nvram_buf.clone())
341 .await
342 .map_err(|e| NvramStorageError::Commit(e.into()))?;
343
344 Ok(())
345 }
346
347 pub async fn iter(
350 &mut self,
351 ) -> Result<impl Iterator<Item = in_memory::VariableEntry<'_>>, NvramStorageError> {
352 self.lazy_load_from_storage().await?;
353 Ok(self.in_memory.iter())
354 }
355}
356
357#[async_trait::async_trait]
358impl<S: StorageBackend> NvramStorage for HclCompatNvram<S> {
359 async fn get_variable(
360 &mut self,
361 name: &Ucs2LeSlice,
362 vendor: Guid,
363 ) -> Result<Option<(u32, Vec<u8>, EFI_TIME)>, NvramStorageError> {
364 self.lazy_load_from_storage().await?;
365
366 if name.as_bytes().len() > EFI_MAX_VARIABLE_NAME_SIZE {
367 return Err(NvramStorageError::VariableNameTooLong);
368 }
369
370 self.in_memory.get_variable(name, vendor).await
371 }
372
373 async fn set_variable(
374 &mut self,
375 name: &Ucs2LeSlice,
376 vendor: Guid,
377 attr: u32,
378 data: Vec<u8>,
379 timestamp: EFI_TIME,
380 ) -> Result<(), NvramStorageError> {
381 self.lazy_load_from_storage().await?;
382
383 if name.as_bytes().len() > EFI_MAX_VARIABLE_NAME_SIZE {
384 return Err(NvramStorageError::VariableNameTooLong);
385 }
386
387 if data.len() > EFI_MAX_VARIABLE_DATA_SIZE {
388 return Err(NvramStorageError::VariableDataTooLong);
389 }
390
391 {
393 let new_file_size = match self.in_memory.get_variable(name, vendor).await? {
394 Some((_, existing_data, _)) => {
395 self.nvram_buf.len() - existing_data.len() + data.len()
396 }
397 None => {
398 self.nvram_buf.len()
399 + name.as_bytes().len()
400 + data.len()
401 + size_of::<format::NvramVariable>()
402 }
403 };
404
405 if new_file_size > MAXIMUM_NVRAM_SIZE {
406 return Err(NvramStorageError::OutOfSpace);
407 }
408 }
409
410 self.in_memory
411 .set_variable(name, vendor, attr, data, timestamp)
412 .await?;
413 self.flush_storage().await?;
414
415 Ok(())
416 }
417
418 async fn append_variable(
419 &mut self,
420 name: &Ucs2LeSlice,
421 vendor: Guid,
422 data: Vec<u8>,
423 timestamp: EFI_TIME,
424 ) -> Result<bool, NvramStorageError> {
425 self.lazy_load_from_storage().await?;
426
427 if name.as_bytes().len() > EFI_MAX_VARIABLE_NAME_SIZE {
428 return Err(NvramStorageError::VariableNameTooLong);
429 }
430
431 if let Some((_, existing_data, _)) = self.in_memory.get_variable(name, vendor).await? {
432 if existing_data.len() + data.len() > EFI_MAX_VARIABLE_DATA_SIZE {
433 return Err(NvramStorageError::VariableDataTooLong);
434 }
435
436 let new_file_size = self.nvram_buf.len() + data.len();
437
438 if new_file_size > MAXIMUM_NVRAM_SIZE {
439 return Err(NvramStorageError::OutOfSpace);
440 }
441 }
442
443 let found = self
444 .in_memory
445 .append_variable(name, vendor, data, timestamp)
446 .await?;
447 self.flush_storage().await?;
448
449 Ok(found)
450 }
451
452 async fn remove_variable(
453 &mut self,
454 name: &Ucs2LeSlice,
455 vendor: Guid,
456 ) -> Result<bool, NvramStorageError> {
457 self.lazy_load_from_storage().await?;
458
459 if name.as_bytes().len() > EFI_MAX_VARIABLE_NAME_SIZE {
460 return Err(NvramStorageError::VariableNameTooLong);
461 }
462
463 let removed = self.in_memory.remove_variable(name, vendor).await?;
464 self.flush_storage().await?;
465
466 Ok(removed)
467 }
468
469 async fn next_variable(
470 &mut self,
471 name_vendor: Option<(&Ucs2LeSlice, Guid)>,
472 ) -> Result<NextVariable, NvramStorageError> {
473 self.lazy_load_from_storage().await?;
474
475 if let Some((name, _)) = name_vendor {
476 if name.as_bytes().len() > EFI_MAX_VARIABLE_NAME_SIZE {
477 return Err(NvramStorageError::VariableNameTooLong);
478 }
479 }
480
481 self.in_memory.next_variable(name_vendor).await
482 }
483}
484
485#[cfg(feature = "save_restore")]
486mod save_restore {
487 use super::*;
488 use vmcore::save_restore::RestoreError;
489 use vmcore::save_restore::SaveError;
490 use vmcore::save_restore::SaveRestore;
491
492 impl<S: StorageBackend> SaveRestore for HclCompatNvram<S> {
493 type SavedState = <in_memory::InMemoryNvram as SaveRestore>::SavedState;
494
495 fn save(&mut self) -> Result<Self::SavedState, SaveError> {
496 self.in_memory.save()
497 }
498
499 fn restore(&mut self, state: Self::SavedState) -> Result<(), RestoreError> {
500 if state.nvram.is_some() {
501 self.in_memory.restore(state)?;
502 self.loaded = true;
503 }
504 Ok(())
505 }
506 }
507}
508
509#[cfg(test)]
510mod test {
511 use super::storage_backend::StorageBackend;
512 use super::storage_backend::StorageBackendError;
513 use super::*;
514 use pal_async::async_test;
515 use ucs2::Ucs2LeVec;
516 use uefi_nvram_storage::in_memory::impl_agnostic_tests;
517 use wchar::wchz;
518
519 #[derive(Default)]
522 pub struct EphemeralStorageBackend(Option<Vec<u8>>);
523
524 #[async_trait::async_trait]
525 impl StorageBackend for EphemeralStorageBackend {
526 async fn persist(&mut self, data: Vec<u8>) -> Result<(), StorageBackendError> {
527 self.0 = Some(data);
528 Ok(())
529 }
530
531 async fn restore(&mut self) -> Result<Option<Vec<u8>>, StorageBackendError> {
532 Ok(self.0.clone())
533 }
534 }
535
536 #[async_test]
537 async fn test_single_variable() {
538 let mut storage = EphemeralStorageBackend::default();
539 let mut nvram = HclCompatNvram::new(&mut storage, None);
540 impl_agnostic_tests::test_single_variable(&mut nvram).await;
541 }
542
543 #[async_test]
544 async fn test_multiple_variable() {
545 let mut storage = EphemeralStorageBackend::default();
546 let mut nvram = HclCompatNvram::new(&mut storage, None);
547 impl_agnostic_tests::test_multiple_variable(&mut nvram).await;
548 }
549
550 #[async_test]
551 async fn test_next() {
552 let mut storage = EphemeralStorageBackend::default();
553 let mut nvram = HclCompatNvram::new(&mut storage, None);
554 impl_agnostic_tests::test_next(&mut nvram).await;
555 }
556
557 #[async_test]
558 async fn boundary_conditions() {
559 let mut storage = EphemeralStorageBackend::default();
560 let mut nvram = HclCompatNvram::new(&mut storage, None);
561
562 let vendor = Guid::new_random();
563 let attr = 0x1234;
564 let data = vec![0x1, 0x2, 0x3, 0x4, 0x5];
565 let timestamp = EFI_TIME::default();
566
567 let name_ok = Ucs2LeVec::from_vec_with_nul(
568 std::iter::repeat_n([0, b'a'], (EFI_MAX_VARIABLE_NAME_SIZE / 2) - 1)
569 .chain(Some([0, 0]))
570 .flat_map(|x| x.into_iter())
571 .collect(),
572 )
573 .unwrap();
574 let name_too_big = Ucs2LeVec::from_vec_with_nul(
575 std::iter::repeat_n([0, b'a'], EFI_MAX_VARIABLE_NAME_SIZE / 2)
576 .chain(Some([0, 0]))
577 .flat_map(|x| x.into_iter())
578 .collect(),
579 )
580 .unwrap();
581
582 nvram
583 .set_variable(&name_ok, vendor, attr, data.clone(), timestamp)
584 .await
585 .unwrap();
586
587 let res = nvram
588 .set_variable(&name_too_big, vendor, attr, data.clone(), timestamp)
589 .await;
590 assert!(matches!(res, Err(NvramStorageError::VariableNameTooLong)));
591
592 nvram
593 .set_variable(
594 &name_ok,
595 vendor,
596 attr,
597 vec![0xff; EFI_MAX_VARIABLE_DATA_SIZE],
598 timestamp,
599 )
600 .await
601 .unwrap();
602
603 let res = nvram
604 .set_variable(
605 &name_ok,
606 vendor,
607 attr,
608 vec![0xff; EFI_MAX_VARIABLE_DATA_SIZE + 1],
609 timestamp,
610 )
611 .await;
612 assert!(matches!(res, Err(NvramStorageError::VariableDataTooLong)));
613
614 loop {
616 let res = nvram
617 .set_variable(
618 &name_ok,
619 Guid::new_random(), attr,
621 vec![0xff; EFI_MAX_VARIABLE_DATA_SIZE],
622 timestamp,
623 )
624 .await;
625
626 match res {
627 Ok(()) => {}
628 Err(NvramStorageError::OutOfSpace) => break,
629 Err(_) => panic!(),
630 }
631 }
632 }
633
634 #[async_test]
635 async fn load_reload() {
636 let mut storage = EphemeralStorageBackend::default();
637
638 let vendor1 = Guid::new_random();
639 let name1 = Ucs2LeSlice::from_slice_with_nul(wchz!(u16, "var1").as_bytes()).unwrap();
640 let vendor2 = Guid::new_random();
641 let name2 = Ucs2LeSlice::from_slice_with_nul(wchz!(u16, "var2").as_bytes()).unwrap();
642 let vendor3 = Guid::new_random();
643 let name3 = Ucs2LeSlice::from_slice_with_nul(wchz!(u16, "var3").as_bytes()).unwrap();
644 let attr = 0x1234;
645 let data = vec![0x1, 0x2, 0x3, 0x4, 0x5];
646 let timestamp = EFI_TIME::default();
647
648 let mut nvram = HclCompatNvram::new(&mut storage, None);
649 nvram
650 .set_variable(name1, vendor1, attr, data.clone(), timestamp)
651 .await
652 .unwrap();
653 nvram
654 .set_variable(name2, vendor2, attr, data.clone(), timestamp)
655 .await
656 .unwrap();
657 nvram
658 .set_variable(name3, vendor3, attr, data.clone(), timestamp)
659 .await
660 .unwrap();
661
662 drop(nvram);
663
664 let mut nvram = HclCompatNvram::new(&mut storage, None);
666
667 let (result_attr, result_data, result_timestamp) =
668 nvram.get_variable(name1, vendor1).await.unwrap().unwrap();
669 assert_eq!(result_attr, attr);
670 assert_eq!(result_data, data);
671 assert_eq!(result_timestamp, timestamp);
672
673 let (result_attr, result_data, result_timestamp) =
674 nvram.get_variable(name2, vendor2).await.unwrap().unwrap();
675 assert_eq!(result_attr, attr);
676 assert_eq!(result_data, data);
677 assert_eq!(result_timestamp, timestamp);
678
679 let (result_attr, result_data, result_timestamp) =
680 nvram.get_variable(name3, vendor3).await.unwrap().unwrap();
681 assert_eq!(result_attr, attr);
682 assert_eq!(result_data, data);
683 assert_eq!(result_timestamp, timestamp);
684 }
685}