1#![doc = include_str!("../readme.md")]
2#![cfg_attr(feature = "no_os", no_os)]
3
4#[cfg(feature = "no_os")]
5extern crate alloc;
6
7pub mod array;
8pub mod string;
9pub mod vec;
10
11pub use array::SecureArray;
12pub use string::SecureString;
13pub use vec::{SecureBytes, SecureVec};
14
15use core::ptr::NonNull;
16pub use zeroize::Zeroize;
17
18#[cfg(feature = "use_os")]
19pub use memsec;
20#[cfg(feature = "use_os")]
21use memsec::Prot;
22
23use thiserror::Error as ThisError;
24
25#[cfg(feature = "use_os")]
26#[derive(ThisError, Debug)]
27#[cfg_attr(feature = "serde", derive(serde::Deserialize, serde::Serialize))]
28pub enum Error {
29 #[error("Failed to allocate secure memory")]
30 AllocationFailed,
31 #[error("Length cannot be zero")]
32 LengthCannotBeZero,
33 #[error("Size cannot be zero")]
34 SizeCannotBeZero,
35 #[error("Allocated Ptr is null")]
36 NullAllocation,
37 #[error("Failed to lock memory")]
38 LockFailed,
39 #[error("Failed to unlock memory")]
40 UnlockFailed,
41 #[error("Source length does not match the fixed size of the destination array")]
42 LengthMismatch,
43 #[error("Bytes are not valid UTF-8")]
44 InvalidUtf8,
45}
46
47#[cfg(not(feature = "use_os"))]
48#[derive(Debug)]
49pub enum Error {
50 AlignmentFailed,
51 AllocationFailed,
52 NullAllocation,
53 InvalidUtf8,
54}
55
56#[cfg(all(feature = "use_os", unix))]
57const ALLOC_TAG_MALLOC: usize = 0xDEAD_BEEF;
58#[cfg(all(feature = "use_os", unix))]
59const ALLOC_TAG_MEMFD: usize = 0x5EC0_0000;
60
61#[cfg(all(feature = "use_os", unix))]
62use core::sync::atomic::{AtomicU8, Ordering};
63
64#[cfg(all(feature = "use_os", unix))]
65static MEMFD_SECRET_SUPPORT: AtomicU8 = AtomicU8::new(MEMFD_UNKNOWN);
66#[cfg(all(feature = "use_os", unix))]
67const MEMFD_UNKNOWN: u8 = 0;
68#[cfg(all(feature = "use_os", unix))]
69const MEMFD_NO: u8 = 1;
70#[cfg(all(feature = "use_os", unix))]
71const MEMFD_YES: u8 = 2;
72
73#[cfg(all(feature = "use_os", unix))]
76const fn get_header_offset<T>() -> usize {
77 let header_size = core::mem::size_of::<usize>();
78 let align = core::mem::align_of::<T>();
79
80 if align > header_size {
83 align
84 } else {
85 header_size
86 }
87}
88
89#[cfg(all(feature = "use_os", unix))]
90fn supports_memfd_secret() -> bool {
91 match MEMFD_SECRET_SUPPORT.load(Ordering::Relaxed) {
92 MEMFD_YES => true,
93 MEMFD_NO => false,
94 _ => {
95 let supported = unsafe {
96 use libc::{SYS_memfd_secret, close, syscall};
97 let res = syscall(SYS_memfd_secret as _, 0isize);
98 if res >= 0 {
99 close(res as libc::c_int);
100 true
101 } else {
102 false
103 }
104 };
105 MEMFD_SECRET_SUPPORT.store(
106 if supported { MEMFD_YES } else { MEMFD_NO },
107 Ordering::Relaxed,
108 );
109 supported
110 }
111 }
112}
113
114pub(crate) unsafe fn alloc<T>(size: usize) -> Result<NonNull<T>, Error> {
122 #[cfg(feature = "use_os")]
123 {
124 if size == 0 {
125 return Err(Error::SizeCannotBeZero);
126 }
127
128 #[cfg(windows)]
129 unsafe {
130 let allocated_ptr = memsec::malloc_sized(size);
131 let non_null = allocated_ptr.ok_or(Error::AllocationFailed)?;
132 let ptr = non_null.as_ptr() as *mut T;
133 NonNull::new(ptr).ok_or(Error::NullAllocation)
134 }
135
136 #[cfg(unix)]
137 {
138 let supports_memfd_secret = supports_memfd_secret();
139
140 let header_offset = get_header_offset::<T>();
141
142 let align_req = core::mem::align_of::<usize>().max(core::mem::align_of::<T>());
144
145 let raw_size = size
147 .checked_add(header_offset)
148 .ok_or(Error::AllocationFailed)?;
149
150 let remainder = raw_size % align_req;
152 let alloc_size = if remainder == 0 {
153 raw_size
154 } else {
155 raw_size
156 .checked_add(align_req - remainder)
157 .ok_or(Error::AllocationFailed)?
158 };
159
160 let ptr_opt = if supports_memfd_secret {
161 unsafe { memsec::memfd_secret_sized(alloc_size) }
162 } else {
163 None
164 };
165
166 if let Some(raw_ptr_nonnull) = ptr_opt {
167 let raw_ptr = raw_ptr_nonnull.as_ptr() as *mut u8;
168
169 debug_assert!(
170 (raw_ptr as usize) % core::mem::align_of::<usize>() == 0,
171 "allocator returned a pointer not aligned for the usize header tag"
172 );
173
174 unsafe { *(raw_ptr as *mut usize) = ALLOC_TAG_MEMFD };
176
177 let user_ptr = unsafe { raw_ptr.add(header_offset) as *mut T };
178 return NonNull::new(user_ptr).ok_or(Error::NullAllocation);
179 }
180
181 unsafe {
182 let allocated_ptr = memsec::malloc_sized(alloc_size);
183 let non_null = allocated_ptr.ok_or(Error::AllocationFailed)?;
184
185 let raw_ptr = non_null.as_ptr() as *mut u8;
186
187 debug_assert!(
188 (raw_ptr as usize) % core::mem::align_of::<usize>() == 0,
189 "allocator returned a pointer not aligned for the usize header tag"
190 );
191
192 *(raw_ptr as *mut usize) = ALLOC_TAG_MALLOC;
194
195 let user_ptr = raw_ptr.add(header_offset) as *mut T;
196 NonNull::new(user_ptr).ok_or(Error::NullAllocation)
197 }
198 }
199 }
200
201 #[cfg(not(feature = "use_os"))]
202 {
203 use core::alloc::Layout;
204 let layout =
205 Layout::from_size_align(size, mem::align_of::<T>()).map_err(|_| Error::AlignmentFailed)?;
206 let ptr = unsafe { alloc::alloc(layout) as *mut T };
207 if ptr.is_null() {
208 return Err(Error::NullAllocation);
209 }
210 unsafe { NonNull::new_unchecked(ptr) }
211 }
212}
213
214#[cfg(feature = "use_os")]
215pub(crate) fn free<T>(ptr: NonNull<T>) {
216 #[cfg(windows)]
217 unsafe {
218 memsec::free(ptr);
219 }
220
221 #[cfg(unix)]
222 {
223 let header_offset = get_header_offset::<T>();
224
225 unsafe {
226 let user_ptr = ptr.as_ptr() as *mut u8;
227 let raw_ptr = user_ptr.sub(header_offset);
228
229 let non_null_raw = NonNull::new_unchecked(raw_ptr);
231
232 let tag = *(raw_ptr as *const usize);
234
235 match tag {
236 ALLOC_TAG_MEMFD => {
237 memsec::free_memfd_secret(non_null_raw);
238 }
239 ALLOC_TAG_MALLOC => {
240 memsec::free(non_null_raw);
241 }
242 _ => {
243 #[cfg(debug_assertions)]
246 panic!(
247 "SecureAllocator: Corrupt header tag found: {:x}",
248 tag
249 );
250 }
251 }
252 }
253 }
254}
255
256#[cfg(feature = "use_os")]
257pub(crate) fn mprotect<T>(ptr: NonNull<T>, prot: Prot::Ty) -> bool {
258 #[cfg(unix)]
259 {
260 let header_offset = get_header_offset::<T>();
262 unsafe {
263 let raw_ptr = (ptr.as_ptr() as *mut u8).sub(header_offset);
264 let raw_non_null = NonNull::new_unchecked(raw_ptr as *mut T);
265
266 memsec::mprotect(raw_non_null, prot)
267 }
268 }
269 #[cfg(windows)]
270 {
271 unsafe { memsec::mprotect(ptr, prot) }
272 }
273}
274
275#[cfg(test)]
276mod tests {
277
278 #[cfg(unix)]
279 #[test]
280 fn test_supports_memfd_secret() {
281 use super::*;
282
283 let supports = supports_memfd_secret();
284
285 if supports {
286 print!("memfd_secret is supported");
287 let size = 1 * size_of::<u8>();
288 let ptr = unsafe { memsec::memfd_secret_sized(size) };
289 assert!(ptr.is_some());
290 } else {
291 print!("memfd_secret is not supported");
292 }
293 }
294
295 #[cfg(feature = "serde")]
296 #[test]
297 fn test_array_and_secure_vec_serde_compatibility() {
298 use super::*;
299 let exposed_array: &mut [u8; 3] = &mut [1, 2, 3];
300 let array: SecureArray<u8, 3> = SecureArray::from_slice_mut(exposed_array).unwrap();
301 let vec: SecureVec<u8> = array.clone().into();
302
303 let array_json_string = serde_json::to_string(&array).unwrap();
304 let array_json_bytes = serde_json::to_vec(&array).unwrap();
305 let vec_json_string = serde_json::to_string(&vec).unwrap();
306 let vec_json_bytes = serde_json::to_vec(&vec).unwrap();
307
308 assert_eq!(array_json_string, vec_json_string);
309 assert_eq!(array_json_bytes, vec_json_bytes);
310
311 let deserialized_array_from_string: SecureArray<u8, 3> =
312 serde_json::from_str(&array_json_string).unwrap();
313
314 let deserialized_array_from_bytes: SecureArray<u8, 3> =
315 serde_json::from_slice(&array_json_bytes).unwrap();
316
317 let deserialized_vec_from_string: SecureVec<u8> =
318 serde_json::from_str(&vec_json_string).unwrap();
319
320 let deserialized_vec_from_bytes: SecureVec<u8> =
321 serde_json::from_slice(&vec_json_bytes).unwrap();
322
323 deserialized_array_from_string.unlock(|slice| {
324 deserialized_vec_from_string.unlock_slice(|slice2| {
325 assert_eq!(slice, slice2);
326 });
327 });
328
329 deserialized_array_from_bytes.unlock(|slice| {
330 deserialized_vec_from_bytes.unlock_slice(|slice2| {
331 assert_eq!(slice, slice2);
332 });
333 });
334 }
335}