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
99#[cfg_attr(feature = "inspect", derive(inspect::Inspect))]
101pub struct HclCompatNvramQuirks {
102 pub skip_corrupt_vars_with_missing_null_term: bool,
117}
118
119impl<S: StorageBackend> HclCompatNvram<S> {
120 pub async fn new(
122 storage: S,
123 quirks: Option<HclCompatNvramQuirks>,
124 is_restoring: bool,
125 ) -> Result<Self, NvramStorageError> {
126 let mut nvram = Self {
127 quirks: quirks.unwrap_or(HclCompatNvramQuirks {
128 skip_corrupt_vars_with_missing_null_term: false,
129 }),
130
131 storage,
132
133 in_memory: in_memory::InMemoryNvram::new(),
134
135 nvram_buf: Vec::new(),
136 };
137 if !is_restoring {
138 nvram.load_from_storage().await?;
139 }
140 Ok(nvram)
141 }
142
143 async fn load_from_storage(&mut self) -> Result<(), NvramStorageError> {
144 tracing::info!("loading uefi nvram from storage");
145 let res = self.load_from_storage_inner().await;
146 if let Err(e) = &res {
147 tracing::error!(CVM_ALLOWED, "storage contains corrupt nvram state");
148 tracing::error!(
149 CVM_CONFIDENTIAL,
150 error = e as &dyn std::error::Error,
151 "storage contains corrupt nvram state"
152 );
153 }
154 res
155 }
156
157 async fn load_from_storage_inner(&mut self) -> Result<(), NvramStorageError> {
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 Ok(())
297 }
298
299 async fn flush_storage(&mut self) -> Result<(), NvramStorageError> {
301 tracing::info!("flushing uefi nvram to storage");
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 fn iter(&mut self) -> impl Iterator<Item = in_memory::VariableEntry<'_>> {
347 self.in_memory.iter()
348 }
349}
350
351#[async_trait::async_trait]
352impl<S: StorageBackend> NvramStorage for HclCompatNvram<S> {
353 async fn get_variable(
354 &mut self,
355 name: &Ucs2LeSlice,
356 vendor: Guid,
357 ) -> Result<Option<(u32, Vec<u8>, EFI_TIME)>, NvramStorageError> {
358 if name.as_bytes().len() > EFI_MAX_VARIABLE_NAME_SIZE {
359 return Err(NvramStorageError::VariableNameTooLong);
360 }
361
362 self.in_memory.get_variable(name, vendor).await
363 }
364
365 async fn set_variable(
366 &mut self,
367 name: &Ucs2LeSlice,
368 vendor: Guid,
369 attr: u32,
370 data: Vec<u8>,
371 timestamp: EFI_TIME,
372 ) -> Result<(), NvramStorageError> {
373 if name.as_bytes().len() > EFI_MAX_VARIABLE_NAME_SIZE {
374 return Err(NvramStorageError::VariableNameTooLong);
375 }
376
377 if data.len() > EFI_MAX_VARIABLE_DATA_SIZE {
378 return Err(NvramStorageError::VariableDataTooLong);
379 }
380
381 {
383 let new_file_size = match self.in_memory.get_variable(name, vendor).await? {
384 Some((_, existing_data, _)) => {
385 self.nvram_buf.len() - existing_data.len() + data.len()
386 }
387 None => {
388 self.nvram_buf.len()
389 + name.as_bytes().len()
390 + data.len()
391 + size_of::<format::NvramVariable>()
392 }
393 };
394
395 if new_file_size > MAXIMUM_NVRAM_SIZE {
396 return Err(NvramStorageError::OutOfSpace);
397 }
398 }
399
400 self.in_memory
401 .set_variable(name, vendor, attr, data, timestamp)
402 .await?;
403 self.flush_storage().await?;
404
405 Ok(())
406 }
407
408 async fn append_variable(
409 &mut self,
410 name: &Ucs2LeSlice,
411 vendor: Guid,
412 data: Vec<u8>,
413 timestamp: EFI_TIME,
414 ) -> Result<bool, NvramStorageError> {
415 if name.as_bytes().len() > EFI_MAX_VARIABLE_NAME_SIZE {
416 return Err(NvramStorageError::VariableNameTooLong);
417 }
418
419 if let Some((_, existing_data, _)) = self.in_memory.get_variable(name, vendor).await? {
420 if existing_data.len() + data.len() > EFI_MAX_VARIABLE_DATA_SIZE {
421 return Err(NvramStorageError::VariableDataTooLong);
422 }
423
424 let new_file_size = self.nvram_buf.len() + data.len();
425
426 if new_file_size > MAXIMUM_NVRAM_SIZE {
427 return Err(NvramStorageError::OutOfSpace);
428 }
429 }
430
431 let found = self
432 .in_memory
433 .append_variable(name, vendor, data, timestamp)
434 .await?;
435 self.flush_storage().await?;
436
437 Ok(found)
438 }
439
440 async fn remove_variable(
441 &mut self,
442 name: &Ucs2LeSlice,
443 vendor: Guid,
444 ) -> Result<bool, NvramStorageError> {
445 if name.as_bytes().len() > EFI_MAX_VARIABLE_NAME_SIZE {
446 return Err(NvramStorageError::VariableNameTooLong);
447 }
448
449 let removed = self.in_memory.remove_variable(name, vendor).await?;
450 self.flush_storage().await?;
451
452 Ok(removed)
453 }
454
455 async fn next_variable(
456 &mut self,
457 name_vendor: Option<(&Ucs2LeSlice, Guid)>,
458 ) -> Result<NextVariable, NvramStorageError> {
459 if let Some((name, _)) = name_vendor {
460 if name.as_bytes().len() > EFI_MAX_VARIABLE_NAME_SIZE {
461 return Err(NvramStorageError::VariableNameTooLong);
462 }
463 }
464
465 self.in_memory.next_variable(name_vendor).await
466 }
467}
468
469#[cfg(feature = "save_restore")]
470mod save_restore {
471 use super::*;
472 use vmcore::save_restore::RestoreError;
473 use vmcore::save_restore::SaveError;
474 use vmcore::save_restore::SaveRestore;
475
476 impl<S: StorageBackend> SaveRestore for HclCompatNvram<S> {
477 type SavedState = <in_memory::InMemoryNvram as SaveRestore>::SavedState;
478
479 fn save(&mut self) -> Result<Self::SavedState, SaveError> {
480 self.in_memory.save()
481 }
482
483 fn restore(&mut self, state: Self::SavedState) -> Result<(), RestoreError> {
484 self.in_memory.restore(state)
485 }
486 }
487}
488
489#[cfg(test)]
490mod test {
491 use super::storage_backend::StorageBackend;
492 use super::storage_backend::StorageBackendError;
493 use super::*;
494 use pal_async::async_test;
495 use ucs2::Ucs2LeVec;
496 use uefi_nvram_storage::in_memory::impl_agnostic_tests;
497 use wchar::wchz;
498
499 #[derive(Default)]
502 pub struct EphemeralStorageBackend(Option<Vec<u8>>);
503
504 #[async_trait::async_trait]
505 impl StorageBackend for EphemeralStorageBackend {
506 async fn persist(&mut self, data: Vec<u8>) -> Result<(), StorageBackendError> {
507 self.0 = Some(data);
508 Ok(())
509 }
510
511 async fn restore(&mut self) -> Result<Option<Vec<u8>>, StorageBackendError> {
512 Ok(self.0.clone())
513 }
514 }
515
516 #[async_test]
517 async fn test_single_variable() {
518 let mut storage = EphemeralStorageBackend::default();
519 let mut nvram = HclCompatNvram::new(&mut storage, None, false)
520 .await
521 .unwrap();
522 impl_agnostic_tests::test_single_variable(&mut nvram).await;
523 }
524
525 #[async_test]
526 async fn test_multiple_variable() {
527 let mut storage = EphemeralStorageBackend::default();
528 let mut nvram = HclCompatNvram::new(&mut storage, None, false)
529 .await
530 .unwrap();
531 impl_agnostic_tests::test_multiple_variable(&mut nvram).await;
532 }
533
534 #[async_test]
535 async fn test_next() {
536 let mut storage = EphemeralStorageBackend::default();
537 let mut nvram = HclCompatNvram::new(&mut storage, None, false)
538 .await
539 .unwrap();
540 impl_agnostic_tests::test_next(&mut nvram).await;
541 }
542
543 #[async_test]
544 async fn boundary_conditions() {
545 let mut storage = EphemeralStorageBackend::default();
546 let mut nvram = HclCompatNvram::new(&mut storage, None, false)
547 .await
548 .unwrap();
549
550 let vendor = Guid::new_random();
551 let attr = 0x1234;
552 let data = vec![0x1, 0x2, 0x3, 0x4, 0x5];
553 let timestamp = EFI_TIME::default();
554
555 let name_ok = Ucs2LeVec::from_vec_with_nul(
556 std::iter::repeat_n([0, b'a'], (EFI_MAX_VARIABLE_NAME_SIZE / 2) - 1)
557 .chain(Some([0, 0]))
558 .flat_map(|x| x.into_iter())
559 .collect(),
560 )
561 .unwrap();
562 let name_too_big = Ucs2LeVec::from_vec_with_nul(
563 std::iter::repeat_n([0, b'a'], EFI_MAX_VARIABLE_NAME_SIZE / 2)
564 .chain(Some([0, 0]))
565 .flat_map(|x| x.into_iter())
566 .collect(),
567 )
568 .unwrap();
569
570 nvram
571 .set_variable(&name_ok, vendor, attr, data.clone(), timestamp)
572 .await
573 .unwrap();
574
575 let res = nvram
576 .set_variable(&name_too_big, vendor, attr, data.clone(), timestamp)
577 .await;
578 assert!(matches!(res, Err(NvramStorageError::VariableNameTooLong)));
579
580 nvram
581 .set_variable(
582 &name_ok,
583 vendor,
584 attr,
585 vec![0xff; EFI_MAX_VARIABLE_DATA_SIZE],
586 timestamp,
587 )
588 .await
589 .unwrap();
590
591 let res = nvram
592 .set_variable(
593 &name_ok,
594 vendor,
595 attr,
596 vec![0xff; EFI_MAX_VARIABLE_DATA_SIZE + 1],
597 timestamp,
598 )
599 .await;
600 assert!(matches!(res, Err(NvramStorageError::VariableDataTooLong)));
601
602 loop {
604 let res = nvram
605 .set_variable(
606 &name_ok,
607 Guid::new_random(), attr,
609 vec![0xff; EFI_MAX_VARIABLE_DATA_SIZE],
610 timestamp,
611 )
612 .await;
613
614 match res {
615 Ok(()) => {}
616 Err(NvramStorageError::OutOfSpace) => break,
617 Err(_) => panic!(),
618 }
619 }
620 }
621
622 #[async_test]
623 async fn load_reload() {
624 let mut storage = EphemeralStorageBackend::default();
625
626 let vendor1 = Guid::new_random();
627 let name1 = Ucs2LeSlice::from_slice_with_nul(wchz!(u16, "var1").as_bytes()).unwrap();
628 let vendor2 = Guid::new_random();
629 let name2 = Ucs2LeSlice::from_slice_with_nul(wchz!(u16, "var2").as_bytes()).unwrap();
630 let vendor3 = Guid::new_random();
631 let name3 = Ucs2LeSlice::from_slice_with_nul(wchz!(u16, "var3").as_bytes()).unwrap();
632 let attr = 0x1234;
633 let data = vec![0x1, 0x2, 0x3, 0x4, 0x5];
634 let timestamp = EFI_TIME::default();
635
636 let mut nvram = HclCompatNvram::new(&mut storage, None, false)
637 .await
638 .unwrap();
639 nvram
640 .set_variable(name1, vendor1, attr, data.clone(), timestamp)
641 .await
642 .unwrap();
643 nvram
644 .set_variable(name2, vendor2, attr, data.clone(), timestamp)
645 .await
646 .unwrap();
647 nvram
648 .set_variable(name3, vendor3, attr, data.clone(), timestamp)
649 .await
650 .unwrap();
651
652 drop(nvram);
653
654 let mut nvram = HclCompatNvram::new(&mut storage, None, false)
656 .await
657 .unwrap();
658
659 let (result_attr, result_data, result_timestamp) =
660 nvram.get_variable(name1, vendor1).await.unwrap().unwrap();
661 assert_eq!(result_attr, attr);
662 assert_eq!(result_data, data);
663 assert_eq!(result_timestamp, timestamp);
664
665 let (result_attr, result_data, result_timestamp) =
666 nvram.get_variable(name2, vendor2).await.unwrap().unwrap();
667 assert_eq!(result_attr, attr);
668 assert_eq!(result_data, data);
669 assert_eq!(result_timestamp, timestamp);
670
671 let (result_attr, result_data, result_timestamp) =
672 nvram.get_variable(name3, vendor3).await.unwrap().unwrap();
673 assert_eq!(result_attr, attr);
674 assert_eq!(result_data, data);
675 assert_eq!(result_timestamp, timestamp);
676 }
677}