Skip to main content

secure_types/
lib.rs

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/// Calculates the offset needed to store a usize header while maintaining
74/// the alignment requirements of T.
75#[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 T needs stronger alignment than usize, we must pad more.
81   // Otherwise, sizeof(usize) is sufficient.
82   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
114/// Allocate memory
115///
116/// For `Windows` it always uses [memsec::malloc_sized]
117///
118/// For `Unix` it uses [memsec::memfd_secret_sized] if `memfd_secret` is supported
119///
120/// If the allocation fails it fallbacks to [memsec::malloc_sized]
121pub(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         // Calculate alignment requirement
143         let align_req = core::mem::align_of::<usize>().max(core::mem::align_of::<T>());
144
145         // Calculate raw size (Header + Data)
146         let raw_size = size
147            .checked_add(header_offset)
148            .ok_or(Error::AllocationFailed)?;
149
150         // Calculate padded size to satisfy alignment
151         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            // Write the MEMFD tag
175            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            // Write the MALLOC tag
193            *(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         // Reconstruct the NonNull pointer to the START of the allocation (header)
230         let non_null_raw = NonNull::new_unchecked(raw_ptr);
231
232         // Read the tag
233         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               // SHOULD NOT HAPPEN
244               // Tag mismatch: Double free or corruption.
245               #[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      // We need to protect the whole block, including the header.
261      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}