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