|
| 1 | +use std::{mem::MaybeUninit, ptr::NonNull, sync::OnceLock}; |
| 2 | + |
| 3 | +use super::{KeyId, SliceBackend, SliceLike}; |
| 4 | + |
| 5 | +// This is an in-memory key store that is protected by memfd_secret on Linux 5.14+. |
| 6 | +// This should be secure against memory dumps from anything except a malicious kernel driver. |
| 7 | +// Note that not all 5.14+ systems have support for memfd_secret enabled, so |
| 8 | +// LinuxMemfdSecretKeyStore::new returns an Option. |
| 9 | +pub(crate) type LinuxMemfdSecretBackend<Key> = SliceBackend<Key, MemfdSecretImplKeyData>; |
| 10 | + |
| 11 | +pub(crate) struct MemfdSecretImplKeyData { |
| 12 | + ptr: std::ptr::NonNull<[u8]>, |
| 13 | + capacity: usize, |
| 14 | +} |
| 15 | + |
| 16 | +// For Send+Sync to be safe, we need to ensure that the memory is only accessed mutably from one |
| 17 | +// thread. To do this, we have to make sure that any funcion in `MemfdSecretImplKeyData` that |
| 18 | +// accesses the pointer mutably is defined as &mut self, and that the pointer is never copied or |
| 19 | +// moved outside the struct. |
| 20 | +unsafe impl Send for MemfdSecretImplKeyData {} |
| 21 | +unsafe impl Sync for MemfdSecretImplKeyData {} |
| 22 | + |
| 23 | +impl Drop for MemfdSecretImplKeyData { |
| 24 | + fn drop(&mut self) { |
| 25 | + unsafe { |
| 26 | + memsec::free_memfd_secret(self.ptr); |
| 27 | + } |
| 28 | + } |
| 29 | +} |
| 30 | + |
| 31 | +impl<Key: KeyId> SliceLike<Key> for MemfdSecretImplKeyData { |
| 32 | + fn is_available() -> bool { |
| 33 | + static IS_SUPPORTED: OnceLock<bool> = OnceLock::new(); |
| 34 | + |
| 35 | + *IS_SUPPORTED.get_or_init(|| unsafe { |
| 36 | + let Some(ptr) = memsec::memfd_secret_sized(1) else { |
| 37 | + return false; |
| 38 | + }; |
| 39 | + memsec::free_memfd_secret(ptr); |
| 40 | + true |
| 41 | + }) |
| 42 | + } |
| 43 | + |
| 44 | + fn with_capacity(capacity: usize) -> Self { |
| 45 | + let entry_size = std::mem::size_of::<Option<(Key, Key::KeyValue)>>(); |
| 46 | + |
| 47 | + unsafe { |
| 48 | + let ptr: NonNull<[u8]> = memsec::memfd_secret_sized(capacity * entry_size) |
| 49 | + .expect("memfd_secret_sized failed"); |
| 50 | + |
| 51 | + // Initialize the array with Nones using MaybeUninit |
| 52 | + let uninit_slice: &mut [MaybeUninit<_>] = std::slice::from_raw_parts_mut( |
| 53 | + ptr.as_ptr() as *mut MaybeUninit<Option<(Key, Key::KeyValue)>>, |
| 54 | + capacity, |
| 55 | + ); |
| 56 | + for elem in uninit_slice { |
| 57 | + elem.write(None); |
| 58 | + } |
| 59 | + |
| 60 | + MemfdSecretImplKeyData { ptr, capacity } |
| 61 | + } |
| 62 | + } |
| 63 | + |
| 64 | + fn get_key_data(&self) -> &[Option<(Key, Key::KeyValue)>] { |
| 65 | + let ptr = self.ptr.as_ptr() as *const Option<(Key, Key::KeyValue)>; |
| 66 | + // SAFETY: The pointer is valid and points to a valid slice of the correct size. |
| 67 | + // This function is &self so it only takes a immutable *const pointer. |
| 68 | + unsafe { std::slice::from_raw_parts(ptr, self.capacity) } |
| 69 | + } |
| 70 | + |
| 71 | + fn get_key_data_mut(&mut self) -> &mut [Option<(Key, Key::KeyValue)>] { |
| 72 | + let ptr = self.ptr.as_ptr() as *mut Option<(Key, Key::KeyValue)>; |
| 73 | + // SAFETY: The pointer is valid and points to a valid slice of the correct size. |
| 74 | + // This function is &mut self so it can take a mutable *mut pointer. |
| 75 | + unsafe { std::slice::from_raw_parts_mut(ptr, self.capacity) } |
| 76 | + } |
| 77 | +} |
| 78 | + |
| 79 | +#[cfg(test)] |
| 80 | +mod tests { |
| 81 | + use super::*; |
| 82 | + use crate::store::backend::{ |
| 83 | + implementation::custom_slice::tests::{TestKey, TestKeyValue}, |
| 84 | + StoreBackend as _, |
| 85 | + }; |
| 86 | + |
| 87 | + #[test] |
| 88 | + fn test_resize() { |
| 89 | + let mut store = LinuxMemfdSecretBackend::<TestKey>::with_capacity(1).unwrap(); |
| 90 | + |
| 91 | + for (idx, key) in [ |
| 92 | + TestKey::A, |
| 93 | + TestKey::B(10), |
| 94 | + TestKey::C, |
| 95 | + TestKey::B(7), |
| 96 | + TestKey::A, |
| 97 | + TestKey::C, |
| 98 | + ] |
| 99 | + .into_iter() |
| 100 | + .enumerate() |
| 101 | + { |
| 102 | + store.upsert(key, TestKeyValue::new(idx)); |
| 103 | + } |
| 104 | + |
| 105 | + assert_eq!(store.get(TestKey::A), Some(&TestKeyValue::new(4))); |
| 106 | + assert_eq!(store.get(TestKey::B(10)), Some(&TestKeyValue::new(1))); |
| 107 | + assert_eq!(store.get(TestKey::C), Some(&TestKeyValue::new(5))); |
| 108 | + assert_eq!(store.get(TestKey::B(7)), Some(&TestKeyValue::new(3))); |
| 109 | + assert_eq!(store.get(TestKey::B(20)), None); |
| 110 | + } |
| 111 | +} |
0 commit comments