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