Skip to content

Commit

Permalink
Add mlock and memfd_secret implementations
Browse files Browse the repository at this point in the history
  • Loading branch information
dani-garcia committed Feb 3, 2025
1 parent c6e08b2 commit c52afa4
Show file tree
Hide file tree
Showing 6 changed files with 998 additions and 2 deletions.
12 changes: 12 additions & 0 deletions Cargo.lock

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

7 changes: 7 additions & 0 deletions crates/bitwarden-crypto/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -50,6 +50,9 @@ wasm-bindgen = { workspace = true, optional = true }
zeroize = { version = ">=1.7.0, <2.0", features = ["derive", "aarch64"] }
zeroizing-alloc = ">=0.1.0, <0.2"

[target.'cfg(all(not(target_arch = "wasm32"), not(windows)))'.dependencies]
memsec = { version = "0.7.0", features = ["alloc_ext"] }

[dev-dependencies]
criterion = "0.5.1"
rand_chacha = "0.3.1"
Expand All @@ -67,3 +70,7 @@ required-features = ["no-memory-hardening"]

[lints]
workspace = true

[package.metadata.cargo-udeps.ignore]
# This is unused when using --all-features, as that disables memory-hardening
normal = ["memsec"]
Original file line number Diff line number Diff line change
@@ -0,0 +1,111 @@
use std::{mem::MaybeUninit, ptr::NonNull, sync::OnceLock};

use super::{KeyId, SliceBackend, SliceLike};

// This is an in-memory key store that is protected by memfd_secret on Linux 5.14+.
// This should be secure against memory dumps from anything except a malicious kernel driver.
// Note that not all 5.14+ systems have support for memfd_secret enabled, so
// LinuxMemfdSecretKeyStore::new returns an Option.
pub(crate) type LinuxMemfdSecretBackend<Key> = SliceBackend<Key, MemfdSecretImplKeyData>;

pub(crate) struct MemfdSecretImplKeyData {
ptr: std::ptr::NonNull<[u8]>,
capacity: usize,
}

// For Send+Sync to be safe, we need to ensure that the memory is only accessed mutably from one
// thread. To do this, we have to make sure that any funcion in `MemfdSecretImplKeyData` that
// accesses the pointer mutably is defined as &mut self, and that the pointer is never copied or
// moved outside the struct.
unsafe impl Send for MemfdSecretImplKeyData {}
unsafe impl Sync for MemfdSecretImplKeyData {}

impl Drop for MemfdSecretImplKeyData {
fn drop(&mut self) {
unsafe {
memsec::free_memfd_secret(self.ptr);
}
}
}

impl<Key: KeyId> SliceLike<Key> for MemfdSecretImplKeyData {
fn is_available() -> bool {
static IS_SUPPORTED: OnceLock<bool> = OnceLock::new();

*IS_SUPPORTED.get_or_init(|| unsafe {
let Some(ptr) = memsec::memfd_secret_sized(1) else {
return false;
};
memsec::free_memfd_secret(ptr);
true
})
}

fn with_capacity(capacity: usize) -> Self {
let entry_size = std::mem::size_of::<Option<(Key, Key::KeyValue)>>();

unsafe {
let ptr: NonNull<[u8]> = memsec::memfd_secret_sized(capacity * entry_size)
.expect("memfd_secret_sized failed");

// Initialize the array with Nones using MaybeUninit
let uninit_slice: &mut [MaybeUninit<_>] = std::slice::from_raw_parts_mut(
ptr.as_ptr() as *mut MaybeUninit<Option<(Key, Key::KeyValue)>>,
capacity,
);
for elem in uninit_slice {
elem.write(None);
}

MemfdSecretImplKeyData { ptr, capacity }
}
}

fn get_key_data(&self) -> &[Option<(Key, Key::KeyValue)>] {
let ptr = self.ptr.as_ptr() as *const Option<(Key, Key::KeyValue)>;
// SAFETY: The pointer is valid and points to a valid slice of the correct size.
// This function is &self so it only takes a immutable *const pointer.
unsafe { std::slice::from_raw_parts(ptr, self.capacity) }
}

fn get_key_data_mut(&mut self) -> &mut [Option<(Key, Key::KeyValue)>] {
let ptr = self.ptr.as_ptr() as *mut Option<(Key, Key::KeyValue)>;
// SAFETY: The pointer is valid and points to a valid slice of the correct size.
// This function is &mut self so it can take a mutable *mut pointer.
unsafe { std::slice::from_raw_parts_mut(ptr, self.capacity) }
}
}

#[cfg(test)]
mod tests {
use super::*;
use crate::store::backend::{
implementation::custom_slice::tests::{TestKey, TestKeyValue},
StoreBackend as _,
};

#[test]
fn test_resize() {
let mut store = LinuxMemfdSecretBackend::<TestKey>::with_capacity(1).unwrap();

for (idx, key) in [
TestKey::A,
TestKey::B(10),
TestKey::C,
TestKey::B(7),
TestKey::A,
TestKey::C,
]
.into_iter()
.enumerate()
{
store.upsert(key, TestKeyValue::new(idx));
}

assert_eq!(store.get(TestKey::A), Some(&TestKeyValue::new(4)));
assert_eq!(store.get(TestKey::B(10)), Some(&TestKeyValue::new(1)));
assert_eq!(store.get(TestKey::C), Some(&TestKeyValue::new(5)));
assert_eq!(store.get(TestKey::B(7)), Some(&TestKeyValue::new(3)));
assert_eq!(store.get(TestKey::B(20)), None);
}
}
Loading

0 comments on commit c52afa4

Please sign in to comment.