Skip to content

Commit c52afa4

Browse files
committed
Add mlock and memfd_secret implementations
1 parent c6e08b2 commit c52afa4

File tree

6 files changed

+998
-2
lines changed

6 files changed

+998
-2
lines changed

Cargo.lock

+12
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

crates/bitwarden-crypto/Cargo.toml

+7
Original file line numberDiff line numberDiff line change
@@ -50,6 +50,9 @@ wasm-bindgen = { workspace = true, optional = true }
5050
zeroize = { version = ">=1.7.0, <2.0", features = ["derive", "aarch64"] }
5151
zeroizing-alloc = ">=0.1.0, <0.2"
5252

53+
[target.'cfg(all(not(target_arch = "wasm32"), not(windows)))'.dependencies]
54+
memsec = { version = "0.7.0", features = ["alloc_ext"] }
55+
5356
[dev-dependencies]
5457
criterion = "0.5.1"
5558
rand_chacha = "0.3.1"
@@ -67,3 +70,7 @@ required-features = ["no-memory-hardening"]
6770

6871
[lints]
6972
workspace = true
73+
74+
[package.metadata.cargo-udeps.ignore]
75+
# This is unused when using --all-features, as that disables memory-hardening
76+
normal = ["memsec"]
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,111 @@
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

Comments
 (0)