Skip to main content

secure_types/
lib.rs

1#![doc = include_str!("../readme.md")]
2// no_std is implied whenever `use_os` is not active.
3// `use_os` is the default feature — see Cargo.toml.
4#![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/// Calculates the offset needed to store a usize header while maintaining
81/// the alignment requirements of T.
82#[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 T needs stronger alignment than usize, we must pad more.
88   // Otherwise, sizeof(usize) is sufficient.
89   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
121/// Allocate memory
122///
123/// For `Windows` it always uses [memsec::malloc_sized]
124///
125/// For `Unix` it uses [memsec::memfd_secret_sized] if `memfd_secret` is supported
126///
127/// If the allocation fails it fallbacks to [memsec::malloc_sized]
128pub(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         // Calculate alignment requirement
150         let align_req = core::mem::align_of::<usize>().max(core::mem::align_of::<T>());
151
152         // Calculate raw size (Header + Data)
153         let raw_size = size
154            .checked_add(header_offset)
155            .ok_or(Error::AllocationFailed)?;
156
157         // Calculate padded size to satisfy alignment
158         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            // Write the MEMFD tag
182            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            // Write the MALLOC tag
200            *(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         // Reconstruct the NonNull pointer to the START of the allocation (header)
236         let non_null_raw = NonNull::new_unchecked(raw_ptr);
237
238         // Read the tag
239         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               // SHOULD NOT HAPPEN
250               // Tag mismatch: Double free or corruption.
251               #[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      // We need to protect the whole block, including the header.
267      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}