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;
10#[cfg(feature = "codec")]
11pub mod codec;
12pub mod string;
13pub mod vec;
14#[cfg(feature = "use_os")]
15pub mod writer;
16
17pub use array::SecureArray;
18pub use string::SecureString;
19pub use vec::{SecureBytes, SecureVec};
20#[cfg(feature = "use_os")]
21pub use writer::SecureBytesWriter;
22
23#[cfg(feature = "serde")]
24pub use vec::SeqElement;
25
26#[cfg(feature = "codec")]
27pub use codec::{
28 DecodeError, EncodeError, FORMAT_VERSION, decode, decode_slice, encode, encode_into_vec, encode_to_vec,
29 encode_to_vec_with_capacity, encode_with_capacity, encoded_len,
30};
31
32use core::ptr::NonNull;
33pub use zeroize::Zeroize;
34
35#[cfg(feature = "use_os")]
36pub use memsec;
37#[cfg(feature = "use_os")]
38use memsec::Prot;
39
40#[derive(Debug)]
41#[cfg_attr(feature = "serde", derive(serde::Deserialize, serde::Serialize))]
42pub enum Error {
43 AllocationFailed,
44 LengthCannotBeZero,
45 SizeCannotBeZero,
46 NullAllocation,
47 LockFailed,
48 UnlockFailed,
49 LengthMismatch,
50 InvalidUtf8,
51 AlignmentFailed,
52}
53
54impl core::fmt::Display for Error {
55 fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
56 match self {
57 Self::AllocationFailed => write!(f, "Failed to allocate memory"),
58 Self::LengthCannotBeZero => write!(f, "Length cannot be zero"),
59 Self::SizeCannotBeZero => write!(f, "Size cannot be zero"),
60 Self::NullAllocation => write!(f, "Allocated Ptr is null"),
61 Self::LockFailed => write!(f, "Failed to lock memory"),
62 Self::UnlockFailed => write!(f, "Failed to unlock memory"),
63 Self::LengthMismatch => {
64 write!(
65 f,
66 "Source length does not match the fixed size of the destination array"
67 )
68 }
69 Self::InvalidUtf8 => write!(f, "Bytes are not valid UTF-8"),
70 Self::AlignmentFailed => write!(f, "Failed to satisfy allocation alignment"),
71 }
72 }
73}
74
75impl core::error::Error for Error {}
76
77#[cfg(all(feature = "use_os", unix))]
78const ALLOC_TAG_MALLOC: usize = 0xDEAD_BEEF;
79#[cfg(all(feature = "use_os", target_os = "linux"))]
82const ALLOC_TAG_MEMFD: usize = 0x5EC0_0000;
83
84#[cfg(all(feature = "use_os", target_os = "linux"))]
85use core::sync::atomic::{AtomicU8, Ordering};
86
87#[cfg(all(feature = "use_os", target_os = "linux"))]
88static MEMFD_SECRET_SUPPORT: AtomicU8 = AtomicU8::new(MEMFD_UNKNOWN);
89#[cfg(all(feature = "use_os", target_os = "linux"))]
90const MEMFD_UNKNOWN: u8 = 0;
91#[cfg(all(feature = "use_os", target_os = "linux"))]
92const MEMFD_NO: u8 = 1;
93#[cfg(all(feature = "use_os", target_os = "linux"))]
94const MEMFD_YES: u8 = 2;
95
96#[cfg(all(feature = "use_os", unix))]
99const fn get_header_offset<T>() -> usize {
100 let header_size = core::mem::size_of::<usize>();
101 let align = core::mem::align_of::<T>();
102
103 if align > header_size {
106 align
107 } else {
108 header_size
109 }
110}
111
112#[cfg(all(feature = "use_os", unix))]
118pub fn supports_memfd_secret() -> bool {
119 #[cfg(target_os = "linux")]
120 {
121 match MEMFD_SECRET_SUPPORT.load(Ordering::Relaxed) {
122 MEMFD_YES => true,
123 MEMFD_NO => false,
124 _ => {
125 let supported = unsafe {
128 use libc::{SYS_memfd_secret, close, syscall};
129 let res = syscall(SYS_memfd_secret as _, 0isize);
130 if res >= 0 {
131 close(res as libc::c_int);
132 true
133 } else {
134 false
135 }
136 };
137 MEMFD_SECRET_SUPPORT.store(
138 if supported { MEMFD_YES } else { MEMFD_NO },
139 Ordering::Relaxed,
140 );
141 supported
142 }
143 }
144 }
145
146 #[cfg(not(target_os = "linux"))]
147 {
148 false
149 }
150}
151
152pub(crate) unsafe fn alloc<T>(size: usize) -> Result<NonNull<T>, Error> {
163 #[cfg(feature = "use_os")]
164 {
165 if size == 0 {
166 return Err(Error::SizeCannotBeZero);
167 }
168
169 #[cfg(windows)]
170 unsafe {
173 let allocated_ptr = memsec::malloc_sized(size);
174 let non_null = allocated_ptr.ok_or(Error::AllocationFailed)?;
175 let ptr = non_null.as_ptr() as *mut T;
176 NonNull::new(ptr).ok_or(Error::NullAllocation)
177 }
178
179 #[cfg(unix)]
180 {
181 let header_offset = get_header_offset::<T>();
182
183 let align_req = core::mem::align_of::<usize>().max(core::mem::align_of::<T>());
185
186 let raw_size = size
188 .checked_add(header_offset)
189 .ok_or(Error::AllocationFailed)?;
190
191 let remainder = raw_size % align_req;
193 let alloc_size = if remainder == 0 {
194 raw_size
195 } else {
196 raw_size
197 .checked_add(align_req - remainder)
198 .ok_or(Error::AllocationFailed)?
199 };
200
201 #[cfg(target_os = "linux")]
204 {
205 let ptr_opt = if supports_memfd_secret() {
206 unsafe { memsec::memfd_secret_sized(alloc_size) }
209 } else {
210 None
211 };
212
213 if let Some(raw_ptr_nonnull) = ptr_opt {
214 let raw_ptr = raw_ptr_nonnull.as_ptr() as *mut u8;
215
216 debug_assert!(
217 (raw_ptr as usize).is_multiple_of(core::mem::align_of::<usize>()),
218 "allocator returned a pointer not aligned for the usize header tag"
219 );
220
221 unsafe { *(raw_ptr as *mut usize) = ALLOC_TAG_MEMFD };
226
227 let user_ptr = unsafe { raw_ptr.add(header_offset) as *mut T };
230 return NonNull::new(user_ptr).ok_or(Error::NullAllocation);
231 }
232 }
233
234 unsafe {
238 let allocated_ptr = memsec::malloc_sized(alloc_size);
239 let non_null = allocated_ptr.ok_or(Error::AllocationFailed)?;
240
241 let raw_ptr = non_null.as_ptr() as *mut u8;
242
243 debug_assert!(
244 (raw_ptr as usize).is_multiple_of(core::mem::align_of::<usize>()),
245 "allocator returned a pointer not aligned for the usize header tag"
246 );
247
248 *(raw_ptr as *mut usize) = ALLOC_TAG_MALLOC;
250
251 let user_ptr = raw_ptr.add(header_offset) as *mut T;
252 NonNull::new(user_ptr).ok_or(Error::NullAllocation)
253 }
254 }
255 }
256
257 #[cfg(not(feature = "use_os"))]
258 {
259 if size == 0 {
263 return Err(Error::SizeCannotBeZero);
264 }
265
266 let layout = core::alloc::Layout::from_size_align(size, core::mem::align_of::<T>())
267 .map_err(|_| Error::AlignmentFailed)?;
268 let ptr = unsafe { alloc::alloc::alloc(layout) as *mut T };
271 if ptr.is_null() {
272 return Err(Error::NullAllocation);
273 }
274 unsafe { Ok(NonNull::new_unchecked(ptr)) }
276 }
277}
278
279#[cfg(feature = "use_os")]
280pub(crate) fn free<T>(ptr: NonNull<T>) {
281 #[cfg(windows)]
282 unsafe {
285 memsec::free(ptr);
286 }
287
288 #[cfg(unix)]
289 {
290 let header_offset = get_header_offset::<T>();
291
292 unsafe {
297 let user_ptr = ptr.as_ptr() as *mut u8;
298 let raw_ptr = user_ptr.sub(header_offset);
299
300 let non_null_raw = NonNull::new_unchecked(raw_ptr);
302
303 let tag = *(raw_ptr as *const usize);
305
306 match tag {
307 #[cfg(target_os = "linux")]
308 ALLOC_TAG_MEMFD => {
309 memsec::free_memfd_secret(non_null_raw);
310 }
311 ALLOC_TAG_MALLOC => {
312 memsec::free(non_null_raw);
313 }
314 _ => {
315 panic!(
320 "SecureAllocator: Corrupt header tag found: {:x}",
321 tag
322 );
323 }
324 }
325 }
326 }
327}
328
329#[cfg(feature = "use_os")]
330pub(crate) fn mprotect<T>(ptr: NonNull<T>, prot: Prot::Ty) -> bool {
331 #[cfg(unix)]
332 {
333 let header_offset = get_header_offset::<T>();
335 unsafe {
339 let raw_ptr = (ptr.as_ptr() as *mut u8).sub(header_offset);
340 let raw_non_null = NonNull::new_unchecked(raw_ptr as *mut T);
341
342 memsec::mprotect(raw_non_null, prot)
343 }
344 }
345 #[cfg(windows)]
346 {
347 unsafe { memsec::mprotect(ptr, prot) }
349 }
350}