#![doc = include_str!("../readme.md")]
#![cfg_attr(not(feature = "use_os"), no_std)]
#[cfg(not(feature = "use_os"))]
extern crate alloc;
pub mod array;
#[cfg(feature = "codec")]
pub mod codec;
pub mod string;
pub mod vec;
#[cfg(feature = "use_os")]
pub mod writer;
pub use array::SecureArray;
pub use string::SecureString;
pub use vec::{SecureBytes, SecureVec};
#[cfg(feature = "use_os")]
pub use writer::SecureBytesWriter;
#[cfg(feature = "serde")]
pub use vec::SeqElement;
#[cfg(feature = "codec")]
pub use codec::{
DecodeError, EncodeError, FORMAT_VERSION, decode, decode_slice, encode, encode_into_vec, encode_to_vec,
encode_to_vec_with_capacity, encode_with_capacity, encoded_len,
};
use core::ptr::NonNull;
pub use zeroize::Zeroize;
#[cfg(feature = "use_os")]
pub use memsec;
#[cfg(feature = "use_os")]
use memsec::Prot;
#[derive(Debug)]
#[cfg_attr(feature = "serde", derive(serde::Deserialize, serde::Serialize))]
pub enum Error {
AllocationFailed,
LengthCannotBeZero,
SizeCannotBeZero,
NullAllocation,
LockFailed,
UnlockFailed,
LengthMismatch,
InvalidUtf8,
AlignmentFailed,
}
impl core::fmt::Display for Error {
fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
match self {
Self::AllocationFailed => write!(f, "Failed to allocate memory"),
Self::LengthCannotBeZero => write!(f, "Length cannot be zero"),
Self::SizeCannotBeZero => write!(f, "Size cannot be zero"),
Self::NullAllocation => write!(f, "Allocated Ptr is null"),
Self::LockFailed => write!(f, "Failed to lock memory"),
Self::UnlockFailed => write!(f, "Failed to unlock memory"),
Self::LengthMismatch => {
write!(
f,
"Source length does not match the fixed size of the destination array"
)
}
Self::InvalidUtf8 => write!(f, "Bytes are not valid UTF-8"),
Self::AlignmentFailed => write!(f, "Failed to satisfy allocation alignment"),
}
}
}
impl core::error::Error for Error {}
#[cfg(all(feature = "use_os", unix))]
const ALLOC_TAG_MALLOC: usize = 0xDEAD_BEEF;
#[cfg(all(feature = "use_os", target_os = "linux"))]
const ALLOC_TAG_MEMFD: usize = 0x5EC0_0000;
#[cfg(all(feature = "use_os", target_os = "linux"))]
use core::sync::atomic::{AtomicU8, Ordering};
#[cfg(all(feature = "use_os", target_os = "linux"))]
static MEMFD_SECRET_SUPPORT: AtomicU8 = AtomicU8::new(MEMFD_UNKNOWN);
#[cfg(all(feature = "use_os", target_os = "linux"))]
const MEMFD_UNKNOWN: u8 = 0;
#[cfg(all(feature = "use_os", target_os = "linux"))]
const MEMFD_NO: u8 = 1;
#[cfg(all(feature = "use_os", target_os = "linux"))]
const MEMFD_YES: u8 = 2;
#[cfg(all(feature = "use_os", unix))]
const fn get_header_offset<T>() -> usize {
let header_size = core::mem::size_of::<usize>();
let align = core::mem::align_of::<T>();
if align > header_size {
align
} else {
header_size
}
}
#[cfg(all(feature = "use_os", unix))]
pub fn supports_memfd_secret() -> bool {
#[cfg(target_os = "linux")]
{
match MEMFD_SECRET_SUPPORT.load(Ordering::Relaxed) {
MEMFD_YES => true,
MEMFD_NO => false,
_ => {
let supported = unsafe {
use libc::{SYS_memfd_secret, close, syscall};
let res = syscall(SYS_memfd_secret as _, 0isize);
if res >= 0 {
close(res as libc::c_int);
true
} else {
false
}
};
MEMFD_SECRET_SUPPORT.store(
if supported { MEMFD_YES } else { MEMFD_NO },
Ordering::Relaxed,
);
supported
}
}
}
#[cfg(not(target_os = "linux"))]
{
false
}
}
pub(crate) unsafe fn alloc<T>(size: usize) -> Result<NonNull<T>, Error> {
#[cfg(feature = "use_os")]
{
if size == 0 {
return Err(Error::SizeCannotBeZero);
}
#[cfg(windows)]
unsafe {
let allocated_ptr = memsec::malloc_sized(size);
let non_null = allocated_ptr.ok_or(Error::AllocationFailed)?;
let ptr = non_null.as_ptr() as *mut T;
NonNull::new(ptr).ok_or(Error::NullAllocation)
}
#[cfg(unix)]
{
let header_offset = get_header_offset::<T>();
let align_req = core::mem::align_of::<usize>().max(core::mem::align_of::<T>());
let raw_size = size
.checked_add(header_offset)
.ok_or(Error::AllocationFailed)?;
let remainder = raw_size % align_req;
let alloc_size = if remainder == 0 {
raw_size
} else {
raw_size
.checked_add(align_req - remainder)
.ok_or(Error::AllocationFailed)?
};
#[cfg(target_os = "linux")]
{
let ptr_opt = if supports_memfd_secret() {
unsafe { memsec::memfd_secret_sized(alloc_size) }
} else {
None
};
if let Some(raw_ptr_nonnull) = ptr_opt {
let raw_ptr = raw_ptr_nonnull.as_ptr() as *mut u8;
debug_assert!(
(raw_ptr as usize).is_multiple_of(core::mem::align_of::<usize>()),
"allocator returned a pointer not aligned for the usize header tag"
);
unsafe { *(raw_ptr as *mut usize) = ALLOC_TAG_MEMFD };
let user_ptr = unsafe { raw_ptr.add(header_offset) as *mut T };
return NonNull::new(user_ptr).ok_or(Error::NullAllocation);
}
}
unsafe {
let allocated_ptr = memsec::malloc_sized(alloc_size);
let non_null = allocated_ptr.ok_or(Error::AllocationFailed)?;
let raw_ptr = non_null.as_ptr() as *mut u8;
debug_assert!(
(raw_ptr as usize).is_multiple_of(core::mem::align_of::<usize>()),
"allocator returned a pointer not aligned for the usize header tag"
);
*(raw_ptr as *mut usize) = ALLOC_TAG_MALLOC;
let user_ptr = raw_ptr.add(header_offset) as *mut T;
NonNull::new(user_ptr).ok_or(Error::NullAllocation)
}
}
}
#[cfg(not(feature = "use_os"))]
{
if size == 0 {
return Err(Error::SizeCannotBeZero);
}
let layout = core::alloc::Layout::from_size_align(size, core::mem::align_of::<T>())
.map_err(|_| Error::AlignmentFailed)?;
let ptr = unsafe { alloc::alloc::alloc(layout) as *mut T };
if ptr.is_null() {
return Err(Error::NullAllocation);
}
unsafe { Ok(NonNull::new_unchecked(ptr)) }
}
}
#[cfg(feature = "use_os")]
pub(crate) fn free<T>(ptr: NonNull<T>) {
#[cfg(windows)]
unsafe {
memsec::free(ptr);
}
#[cfg(unix)]
{
let header_offset = get_header_offset::<T>();
unsafe {
let user_ptr = ptr.as_ptr() as *mut u8;
let raw_ptr = user_ptr.sub(header_offset);
let non_null_raw = NonNull::new_unchecked(raw_ptr);
let tag = *(raw_ptr as *const usize);
match tag {
#[cfg(target_os = "linux")]
ALLOC_TAG_MEMFD => {
memsec::free_memfd_secret(non_null_raw);
}
ALLOC_TAG_MALLOC => {
memsec::free(non_null_raw);
}
_ => {
panic!(
"SecureAllocator: Corrupt header tag found: {:x}",
tag
);
}
}
}
}
}
#[cfg(feature = "use_os")]
pub(crate) fn mprotect<T>(ptr: NonNull<T>, prot: Prot::Ty) -> bool {
#[cfg(unix)]
{
let header_offset = get_header_offset::<T>();
unsafe {
let raw_ptr = (ptr.as_ptr() as *mut u8).sub(header_offset);
let raw_non_null = NonNull::new_unchecked(raw_ptr as *mut T);
memsec::mprotect(raw_non_null, prot)
}
}
#[cfg(windows)]
{
unsafe { memsec::mprotect(ptr, prot) }
}
}