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 let nvram_buf = self
159 .storage
160 .restore()
161 .await
162 .map_err(|e| NvramStorageError::Load(e.into()))?
163 .unwrap_or_default();
164
165 if nvram_buf.len() > MAXIMUM_NVRAM_SIZE {
166 return Err(NvramStorageError::Load(
167 format!(
168 "Existing nvram state exceeds MAXIMUM_NVRAM_SIZE ({} > {})",
169 nvram_buf.len(),
170 MAXIMUM_NVRAM_SIZE
171 )
172 .into(),
173 ));
174 }
175
176 self.in_memory.clear();
178 self.nvram_buf = nvram_buf;
179 let mut buf = self.nvram_buf.as_slice();
180 while let Ok((header, _)) = format::NvramHeader::read_from_prefix(buf) {
182 if buf.len() < header.length as usize {
183 return Err(NvramStorageError::Load(
184 format!(
185 "unexpected EOF. expected at least {} more bytes, but only found {}",
186 header.length,
187 buf.len()
188 )
189 .into(),
190 ));
191 }
192
193 let entry_buf = {
194 let (entry_buf, remaining) = buf.split_at(header.length as usize);
195 buf = remaining;
196 entry_buf
197 };
198
199 match header.header_type {
200 format::NvramHeaderType::VARIABLE => {}
201 _ => {
202 return Err(NvramStorageError::Load(
203 format!("unknown header type: {:?}", header.header_type).into(),
204 ));
205 }
206 }
207
208 let (var_header, var_name, var_data) = {
212 let (var_header, var_length_data) =
215 format::NvramVariable::read_from_prefix(entry_buf)
216 .map_err(|_| NvramStorageError::Load("variable entry too short".into()))?;
217
218 if var_length_data.len()
219 != var_header.name_bytes as usize + var_header.data_bytes as usize
220 {
221 return Err(NvramStorageError::Load(
222 "mismatch between header length and variable data size".into(),
223 ));
224 }
225
226 let (var_name, var_data) = var_length_data.split_at(var_header.name_bytes as usize);
227
228 (var_header, var_name, var_data)
229 };
230
231 if var_name.len() > EFI_MAX_VARIABLE_NAME_SIZE {
232 return Err(NvramStorageError::Load(
233 format!(
234 "variable name too big. {} > {}",
235 var_name.len(),
236 EFI_MAX_VARIABLE_NAME_SIZE
237 )
238 .into(),
239 ));
240 }
241
242 if var_data.len() > EFI_MAX_VARIABLE_DATA_SIZE {
243 return Err(NvramStorageError::Load(
244 format!(
245 "variable data too big. {} > {}",
246 var_data.len(),
247 EFI_MAX_VARIABLE_DATA_SIZE
248 )
249 .into(),
250 ));
251 }
252
253 let name = match Ucs2LeSlice::from_slice_with_nul(var_name) {
254 Ok(name) => name,
255 Err(e) => {
256 if self.quirks.skip_corrupt_vars_with_missing_null_term {
257 let var = {
258 let mut var = var_name.to_vec();
259 var.push(0);
260 var.push(0);
261 ucs2::Ucs2LeVec::from_vec_with_nul(var)
262 };
263 tracing::warn!(
264 CVM_ALLOWED,
265 "skipping corrupt nvram var (missing null term)"
266 );
267 tracing::warn!(
268 CVM_CONFIDENTIAL,
269 ?var,
270 "skipping corrupt nvram var (missing null term)"
271 );
272 continue;
273 } else {
274 return Err(NvramStorageError::Load(e.into()));
275 }
276 }
277 };
278
279 self.in_memory
280 .set_variable(
281 name,
282 var_header.vendor,
283 var_header.attributes,
284 var_data.to_vec(),
285 var_header.timestamp,
286 )
287 .await?;
288 }
289
290 if !buf.is_empty() {
291 return Err(NvramStorageError::Load(
292 "existing nvram state contains excess data".into(),
293 ));
294 }
295
296 self.loaded = true;
297 Ok(())
298 }
299
300 async fn flush_storage(&mut self) -> Result<(), NvramStorageError> {
302 self.nvram_buf.clear();
303
304 for in_memory::VariableEntry {
305 vendor,
306 name,
307 data,
308 timestamp,
309 attr,
310 } in self.in_memory.iter()
311 {
312 self.nvram_buf.extend_from_slice(
313 format::NvramVariable {
314 header: format::NvramHeader {
315 header_type: format::NvramHeaderType::VARIABLE,
316 length: (size_of::<format::NvramVariable>()
317 + name.as_bytes().len()
318 + data.len()) as u32,
319 },
320 attributes: attr,
321 timestamp,
322 vendor,
323 name_bytes: name.as_bytes().len() as u16,
324 data_bytes: data.len() as u16,
325 }
326 .as_bytes(),
327 );
328 self.nvram_buf.extend_from_slice(name.as_bytes());
329 self.nvram_buf.extend_from_slice(data);
330 }
331
332 assert!(self.nvram_buf.len() < MAXIMUM_NVRAM_SIZE);
335
336 self.storage
337 .persist(self.nvram_buf.clone())
338 .await
339 .map_err(|e| NvramStorageError::Commit(e.into()))?;
340
341 Ok(())
342 }
343
344 pub async fn iter(
347 &mut self,
348 ) -> Result<impl Iterator<Item = in_memory::VariableEntry<'_>>, NvramStorageError> {
349 self.lazy_load_from_storage().await?;
350 Ok(self.in_memory.iter())
351 }
352}
353
354#[async_trait::async_trait]
355impl<S: StorageBackend> NvramStorage for HclCompatNvram<S> {
356 async fn get_variable(
357 &mut self,
358 name: &Ucs2LeSlice,
359 vendor: Guid,
360 ) -> Result<Option<(u32, Vec<u8>, EFI_TIME)>, NvramStorageError> {
361 self.lazy_load_from_storage().await?;
362
363 if name.as_bytes().len() > EFI_MAX_VARIABLE_NAME_SIZE {
364 return Err(NvramStorageError::VariableNameTooLong);
365 }
366
367 self.in_memory.get_variable(name, vendor).await
368 }
369
370 async fn set_variable(
371 &mut self,
372 name: &Ucs2LeSlice,
373 vendor: Guid,
374 attr: u32,
375 data: Vec<u8>,
376 timestamp: EFI_TIME,
377 ) -> Result<(), NvramStorageError> {
378 self.lazy_load_from_storage().await?;
379
380 if name.as_bytes().len() > EFI_MAX_VARIABLE_NAME_SIZE {
381 return Err(NvramStorageError::VariableNameTooLong);
382 }
383
384 if data.len() > EFI_MAX_VARIABLE_DATA_SIZE {
385 return Err(NvramStorageError::VariableDataTooLong);
386 }
387
388 {
390 let new_file_size = match self.in_memory.get_variable(name, vendor).await? {
391 Some((_, existing_data, _)) => {
392 self.nvram_buf.len() - existing_data.len() + data.len()
393 }
394 None => {
395 self.nvram_buf.len()
396 + name.as_bytes().len()
397 + data.len()
398 + size_of::<format::NvramVariable>()
399 }
400 };
401
402 if new_file_size > MAXIMUM_NVRAM_SIZE {
403 return Err(NvramStorageError::OutOfSpace);
404 }
405 }
406
407 self.in_memory
408 .set_variable(name, vendor, attr, data, timestamp)
409 .await?;
410 self.flush_storage().await?;
411
412 Ok(())
413 }
414
415 async fn append_variable(
416 &mut self,
417 name: &Ucs2LeSlice,
418 vendor: Guid,
419 data: Vec<u8>,
420 timestamp: EFI_TIME,
421 ) -> Result<bool, NvramStorageError> {
422 self.lazy_load_from_storage().await?;
423
424 if name.as_bytes().len() > EFI_MAX_VARIABLE_NAME_SIZE {
425 return Err(NvramStorageError::VariableNameTooLong);
426 }
427
428 if let Some((_, existing_data, _)) = self.in_memory.get_variable(name, vendor).await? {
429 if existing_data.len() + data.len() > EFI_MAX_VARIABLE_DATA_SIZE {
430 return Err(NvramStorageError::VariableDataTooLong);
431 }
432
433 let new_file_size = self.nvram_buf.len() + data.len();
434
435 if new_file_size > MAXIMUM_NVRAM_SIZE {
436 return Err(NvramStorageError::OutOfSpace);
437 }
438 }
439
440 let found = self
441 .in_memory
442 .append_variable(name, vendor, data, timestamp)
443 .await?;
444 self.flush_storage().await?;
445
446 Ok(found)
447 }
448
449 async fn remove_variable(
450 &mut self,
451 name: &Ucs2LeSlice,
452 vendor: Guid,
453 ) -> Result<bool, NvramStorageError> {
454 self.lazy_load_from_storage().await?;
455
456 if name.as_bytes().len() > EFI_MAX_VARIABLE_NAME_SIZE {
457 return Err(NvramStorageError::VariableNameTooLong);
458 }
459
460 let removed = self.in_memory.remove_variable(name, vendor).await?;
461 self.flush_storage().await?;
462
463 Ok(removed)
464 }
465
466 async fn next_variable(
467 &mut self,
468 name_vendor: Option<(&Ucs2LeSlice, Guid)>,
469 ) -> Result<NextVariable, NvramStorageError> {
470 self.lazy_load_from_storage().await?;
471
472 if let Some((name, _)) = name_vendor {
473 if name.as_bytes().len() > EFI_MAX_VARIABLE_NAME_SIZE {
474 return Err(NvramStorageError::VariableNameTooLong);
475 }
476 }
477
478 self.in_memory.next_variable(name_vendor).await
479 }
480}
481
482#[cfg(feature = "save_restore")]
483mod save_restore {
484 use super::*;
485 use vmcore::save_restore::RestoreError;
486 use vmcore::save_restore::SaveError;
487 use vmcore::save_restore::SaveRestore;
488
489 impl<S: StorageBackend> SaveRestore for HclCompatNvram<S> {
490 type SavedState = <in_memory::InMemoryNvram as SaveRestore>::SavedState;
491
492 fn save(&mut self) -> Result<Self::SavedState, SaveError> {
493 self.in_memory.save()
494 }
495
496 fn restore(&mut self, state: Self::SavedState) -> Result<(), RestoreError> {
497 if state.nvram.is_some() {
498 self.in_memory.restore(state)?;
499 self.loaded = true;
500 }
501 Ok(())
502 }
503 }
504}
505
506#[cfg(test)]
507mod test {
508 use super::storage_backend::StorageBackend;
509 use super::storage_backend::StorageBackendError;
510 use super::*;
511 use pal_async::async_test;
512 use ucs2::Ucs2LeVec;
513 use uefi_nvram_storage::in_memory::impl_agnostic_tests;
514 use wchar::wchz;
515
516 #[derive(Default)]
519 pub struct EphemeralStorageBackend(Option<Vec<u8>>);
520
521 #[async_trait::async_trait]
522 impl StorageBackend for EphemeralStorageBackend {
523 async fn persist(&mut self, data: Vec<u8>) -> Result<(), StorageBackendError> {
524 self.0 = Some(data);
525 Ok(())
526 }
527
528 async fn restore(&mut self) -> Result<Option<Vec<u8>>, StorageBackendError> {
529 Ok(self.0.clone())
530 }
531 }
532
533 #[async_test]
534 async fn test_single_variable() {
535 let mut storage = EphemeralStorageBackend::default();
536 let mut nvram = HclCompatNvram::new(&mut storage, None);
537 impl_agnostic_tests::test_single_variable(&mut nvram).await;
538 }
539
540 #[async_test]
541 async fn test_multiple_variable() {
542 let mut storage = EphemeralStorageBackend::default();
543 let mut nvram = HclCompatNvram::new(&mut storage, None);
544 impl_agnostic_tests::test_multiple_variable(&mut nvram).await;
545 }
546
547 #[async_test]
548 async fn test_next() {
549 let mut storage = EphemeralStorageBackend::default();
550 let mut nvram = HclCompatNvram::new(&mut storage, None);
551 impl_agnostic_tests::test_next(&mut nvram).await;
552 }
553
554 #[async_test]
555 async fn boundary_conditions() {
556 let mut storage = EphemeralStorageBackend::default();
557 let mut nvram = HclCompatNvram::new(&mut storage, None);
558
559 let vendor = Guid::new_random();
560 let attr = 0x1234;
561 let data = vec![0x1, 0x2, 0x3, 0x4, 0x5];
562 let timestamp = EFI_TIME::default();
563
564 let name_ok = Ucs2LeVec::from_vec_with_nul(
565 std::iter::repeat_n([0, b'a'], (EFI_MAX_VARIABLE_NAME_SIZE / 2) - 1)
566 .chain(Some([0, 0]))
567 .flat_map(|x| x.into_iter())
568 .collect(),
569 )
570 .unwrap();
571 let name_too_big = Ucs2LeVec::from_vec_with_nul(
572 std::iter::repeat_n([0, b'a'], EFI_MAX_VARIABLE_NAME_SIZE / 2)
573 .chain(Some([0, 0]))
574 .flat_map(|x| x.into_iter())
575 .collect(),
576 )
577 .unwrap();
578
579 nvram
580 .set_variable(&name_ok, vendor, attr, data.clone(), timestamp)
581 .await
582 .unwrap();
583
584 let res = nvram
585 .set_variable(&name_too_big, vendor, attr, data.clone(), timestamp)
586 .await;
587 assert!(matches!(res, Err(NvramStorageError::VariableNameTooLong)));
588
589 nvram
590 .set_variable(
591 &name_ok,
592 vendor,
593 attr,
594 vec![0xff; EFI_MAX_VARIABLE_DATA_SIZE],
595 timestamp,
596 )
597 .await
598 .unwrap();
599
600 let res = nvram
601 .set_variable(
602 &name_ok,
603 vendor,
604 attr,
605 vec![0xff; EFI_MAX_VARIABLE_DATA_SIZE + 1],
606 timestamp,
607 )
608 .await;
609 assert!(matches!(res, Err(NvramStorageError::VariableDataTooLong)));
610
611 loop {
613 let res = nvram
614 .set_variable(
615 &name_ok,
616 Guid::new_random(), attr,
618 vec![0xff; EFI_MAX_VARIABLE_DATA_SIZE],
619 timestamp,
620 )
621 .await;
622
623 match res {
624 Ok(()) => {}
625 Err(NvramStorageError::OutOfSpace) => break,
626 Err(_) => panic!(),
627 }
628 }
629 }
630
631 #[async_test]
632 async fn load_reload() {
633 let mut storage = EphemeralStorageBackend::default();
634
635 let vendor1 = Guid::new_random();
636 let name1 = Ucs2LeSlice::from_slice_with_nul(wchz!(u16, "var1").as_bytes()).unwrap();
637 let vendor2 = Guid::new_random();
638 let name2 = Ucs2LeSlice::from_slice_with_nul(wchz!(u16, "var2").as_bytes()).unwrap();
639 let vendor3 = Guid::new_random();
640 let name3 = Ucs2LeSlice::from_slice_with_nul(wchz!(u16, "var3").as_bytes()).unwrap();
641 let attr = 0x1234;
642 let data = vec![0x1, 0x2, 0x3, 0x4, 0x5];
643 let timestamp = EFI_TIME::default();
644
645 let mut nvram = HclCompatNvram::new(&mut storage, None);
646 nvram
647 .set_variable(name1, vendor1, attr, data.clone(), timestamp)
648 .await
649 .unwrap();
650 nvram
651 .set_variable(name2, vendor2, attr, data.clone(), timestamp)
652 .await
653 .unwrap();
654 nvram
655 .set_variable(name3, vendor3, attr, data.clone(), timestamp)
656 .await
657 .unwrap();
658
659 drop(nvram);
660
661 let mut nvram = HclCompatNvram::new(&mut storage, None);
663
664 let (result_attr, result_data, result_timestamp) =
665 nvram.get_variable(name1, vendor1).await.unwrap().unwrap();
666 assert_eq!(result_attr, attr);
667 assert_eq!(result_data, data);
668 assert_eq!(result_timestamp, timestamp);
669
670 let (result_attr, result_data, result_timestamp) =
671 nvram.get_variable(name2, vendor2).await.unwrap().unwrap();
672 assert_eq!(result_attr, attr);
673 assert_eq!(result_data, data);
674 assert_eq!(result_timestamp, timestamp);
675
676 let (result_attr, result_data, result_timestamp) =
677 nvram.get_variable(name3, vendor3).await.unwrap().unwrap();
678 assert_eq!(result_attr, attr);
679 assert_eq!(result_data, data);
680 assert_eq!(result_timestamp, timestamp);
681 }
682}