#[cfg(not(feature = "use_os"))]
use alloc::alloc::Layout;
#[cfg(not(feature = "use_os"))]
use alloc::vec::Vec;
use super::{Error, SecureVec, alloc};
use core::{marker::PhantomData, mem, ptr::NonNull};
use zeroize::Zeroize;
#[cfg(feature = "use_os")]
use super::free;
#[cfg(feature = "use_os")]
use memsec::Prot;
struct UnlockGuard<'a, T: Zeroize, const LENGTH: usize> {
array: &'a SecureArray<T, LENGTH>,
}
impl<'a, T: Zeroize, const LENGTH: usize> UnlockGuard<'a, T, LENGTH> {
fn new(array: &'a SecureArray<T, LENGTH>) -> Self {
let ok = array.unlock_memory();
debug_assert!(ok, "UnlockGuard::new: unlock_memory failed");
UnlockGuard { array }
}
}
impl<'a, T: Zeroize, const LENGTH: usize> Drop for UnlockGuard<'a, T, LENGTH> {
fn drop(&mut self) {
let ok = self.array.lock_memory();
assert!(ok, "UnlockGuard::drop: lock_memory failed");
}
}
pub struct SecureArray<T, const LENGTH: usize>
where
T: Zeroize,
{
ptr: NonNull<T>,
initialized: usize,
_marker: PhantomData<T>,
}
unsafe impl<T: Zeroize + Send, const LENGTH: usize> Send for SecureArray<T, LENGTH> {}
impl<T, const LENGTH: usize> SecureArray<T, LENGTH>
where
T: Zeroize,
{
pub fn empty() -> Result<Self, Error> {
let size = LENGTH
.checked_mul(mem::size_of::<T>())
.ok_or(Error::AllocationFailed)?;
if size == 0 {
return Err(Error::LengthCannotBeZero);
}
let ptr = unsafe { alloc::<T>(size)? };
let secure_array = SecureArray {
ptr,
initialized: 0,
_marker: PhantomData,
};
let _locked = secure_array.lock_memory();
#[cfg(feature = "use_os")]
if !_locked {
return Err(Error::LockFailed);
}
Ok(secure_array)
}
pub fn from_slice_mut(content: &mut [T; LENGTH]) -> Result<Self, Error>
where
T: Clone,
{
let mut secure_array = match Self::empty() {
Ok(secure_array) => secure_array,
Err(e) => {
content.zeroize();
return Err(e);
}
};
{
let _guard = UnlockGuard::new(&secure_array);
unsafe {
let dst = secure_array.ptr.as_ptr();
for (i, item) in content.iter().enumerate() {
core::ptr::write(dst.add(i), item.clone());
}
}
}
secure_array.initialized = LENGTH;
content.zeroize();
Ok(secure_array)
}
pub fn from_slice(content: &[T; LENGTH]) -> Result<Self, Error>
where
T: Clone,
{
let mut secure_array = Self::empty()?;
{
let _guard = UnlockGuard::new(&secure_array);
unsafe {
let dst = secure_array.ptr.as_ptr();
for (i, item) in content.iter().enumerate() {
core::ptr::write(dst.add(i), item.clone());
}
}
}
secure_array.initialized = LENGTH;
Ok(secure_array)
}
pub fn len(&self) -> usize {
LENGTH
}
pub fn is_empty(&self) -> bool {
self.len() == 0
}
#[cfg(feature = "expose-ptr")]
#[deprecated(
since = "0.3.0",
note = "This method is intended only for testing/crash reproduction. Use unlock() or unlock_mut() instead."
)]
pub fn ptr(&self) -> NonNull<T> {
self.ptr
}
pub(crate) fn lock_memory(&self) -> bool {
#[cfg(feature = "use_os")]
{
#[cfg(windows)]
{
super::mprotect(self.ptr, Prot::NoAccess)
}
#[cfg(unix)]
{
super::mprotect(self.ptr, Prot::NoAccess)
}
}
#[cfg(not(feature = "use_os"))]
{
true }
}
pub(crate) fn unlock_memory(&self) -> bool {
#[cfg(feature = "use_os")]
{
#[cfg(windows)]
{
super::mprotect(self.ptr, Prot::ReadWrite)
}
#[cfg(unix)]
{
super::mprotect(self.ptr, Prot::ReadWrite)
}
}
#[cfg(not(feature = "use_os"))]
{
true }
}
pub fn unlock<F, R>(&self, f: F) -> R
where
F: FnOnce(&[T]) -> R,
{
let _guard = UnlockGuard::new(self);
let slice = unsafe { core::slice::from_raw_parts(self.ptr.as_ptr(), self.initialized) };
f(slice)
}
pub fn unlock_mut<F, R>(&mut self, f: F) -> R
where
F: FnOnce(&mut [T]) -> R,
{
self.initialized = LENGTH;
let _guard = UnlockGuard::new(self);
let slice = unsafe { core::slice::from_raw_parts_mut(self.ptr.as_ptr(), LENGTH) };
f(slice)
}
pub fn erase(&mut self) {
let _guard = UnlockGuard::new(self);
unsafe {
let slice = core::slice::from_raw_parts_mut(self.ptr.as_ptr(), self.initialized);
for element in slice.iter_mut() {
element.zeroize();
}
}
}
pub(crate) fn init_from_clone(&mut self, src: &[T])
where
T: Clone,
{
debug_assert_eq!(src.len(), LENGTH);
{
let _guard = UnlockGuard::new(self);
unsafe {
let dst = self.ptr.as_ptr();
for (i, item) in src.iter().enumerate() {
core::ptr::write(dst.add(i), item.clone());
}
}
}
self.initialized = src.len();
}
}
impl<T: Zeroize, const LENGTH: usize> Drop for SecureArray<T, LENGTH> {
fn drop(&mut self) {
let ok = self.unlock_memory();
debug_assert!(ok, "SecureArray::drop: unlock_memory failed");
let slice = unsafe { core::slice::from_raw_parts_mut(self.ptr.as_ptr(), self.initialized) };
for element in slice.iter_mut() {
element.zeroize();
}
let size = LENGTH.checked_mul(mem::size_of::<T>()).unwrap_or(0);
if size == 0 {
return;
}
#[cfg(feature = "use_os")]
free(self.ptr);
#[cfg(not(feature = "use_os"))]
unsafe {
let bytes = core::slice::from_raw_parts_mut(self.ptr.as_ptr() as *mut u8, size);
bytes.zeroize();
let layout = Layout::from_size_align_unchecked(size, mem::align_of::<T>());
alloc::alloc::dealloc(self.ptr.as_ptr() as *mut u8, layout);
}
}
}
impl<T: Clone + Zeroize, const LENGTH: usize> Clone for SecureArray<T, LENGTH> {
fn clone(&self) -> Self {
let mut new_array = Self::empty().unwrap();
self.unlock(|src_slice| {
new_array.init_from_clone(src_slice);
});
new_array
}
}
impl<T: Clone + Zeroize, const LENGTH: usize> TryFrom<SecureVec<T>> for SecureArray<T, LENGTH> {
type Error = Error;
fn try_from(vec: SecureVec<T>) -> Result<Self, Self::Error> {
if vec.len() != LENGTH {
return Err(Error::LengthMismatch);
}
let mut new_array = Self::empty()?;
vec.unlock_slice(|vec_slice| {
new_array.init_from_clone(vec_slice);
});
Ok(new_array)
}
}
impl<T: Clone + Zeroize, const LENGTH: usize> TryFrom<Vec<T>> for SecureArray<T, LENGTH> {
type Error = Error;
fn try_from(mut vec: Vec<T>) -> Result<Self, Self::Error> {
if vec.len() != LENGTH {
vec.zeroize();
return Err(Error::LengthMismatch);
}
let mut new_array = match Self::empty() {
Ok(new_array) => new_array,
Err(e) => {
vec.zeroize();
return Err(e);
}
};
new_array.init_from_clone(&vec);
vec.zeroize();
Ok(new_array)
}
}
#[cfg(feature = "serde")]
impl<const LENGTH: usize> serde::Serialize for SecureArray<u8, LENGTH> {
fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
where
S: serde::Serializer,
{
self.unlock(|slice| serializer.serialize_bytes(slice))
}
}
#[cfg(feature = "serde")]
impl<'de, const LENGTH: usize> serde::Deserialize<'de> for SecureArray<u8, LENGTH> {
fn deserialize<D>(deserializer: D) -> Result<SecureArray<u8, LENGTH>, D::Error>
where
D: serde::Deserializer<'de>,
{
struct SecureArrayVisitor<const L: usize>;
impl<'de, const L: usize> serde::de::Visitor<'de> for SecureArrayVisitor<L> {
type Value = SecureArray<u8, L>;
fn expecting(&self, formatter: &mut ::core::fmt::Formatter) -> ::core::fmt::Result {
write!(formatter, "a byte array of length {}", L)
}
fn visit_seq<A>(self, mut seq: A) -> Result<Self::Value, A::Error>
where
A: serde::de::SeqAccess<'de>,
{
let mut data: SecureVec<u8> =
SecureVec::new_with_capacity(L).map_err(serde::de::Error::custom)?;
while let Some(byte) = seq.next_element::<u8>()? {
if data.len() == L {
return Err(serde::de::Error::invalid_length(
data.len() + 1,
&self,
));
}
data.push(byte);
}
if data.len() != L {
return Err(serde::de::Error::invalid_length(
data.len(),
&self,
));
}
SecureArray::try_from(data).map_err(serde::de::Error::custom)
}
fn visit_bytes<E>(self, v: &[u8]) -> Result<Self::Value, E>
where
E: serde::de::Error,
{
let bytes: &[u8; L] = v
.try_into()
.map_err(|_| serde::de::Error::invalid_length(v.len(), &self))?;
SecureArray::from_slice(bytes).map_err(serde::de::Error::custom)
}
fn visit_byte_buf<E>(self, mut v: Vec<u8>) -> Result<Self::Value, E>
where
E: serde::de::Error,
{
let array = self.visit_bytes(&v);
v.zeroize();
array
}
}
deserializer.deserialize_bytes(SecureArrayVisitor::<LENGTH>)
}
}
#[cfg(feature = "serde")]
impl<const LENGTH: usize, T> serde::Serialize for SecureArray<T, LENGTH>
where
T: crate::vec::SeqElement + serde::Serialize,
{
fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
where
S: serde::Serializer,
{
use serde::ser::SerializeTuple;
let mut tuple = serializer.serialize_tuple(LENGTH)?;
let elements: Result<(), S::Error> = self.unlock(|slice| {
for item in slice {
tuple.serialize_element(item)?;
}
Ok(())
});
elements?;
tuple.end()
}
}
#[cfg(feature = "serde")]
impl<'de, const LENGTH: usize, T> serde::Deserialize<'de> for SecureArray<T, LENGTH>
where
T: crate::vec::SeqElement + Clone + serde::Deserialize<'de>,
{
fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
where
D: serde::Deserializer<'de>,
{
struct SecureArraySeqVisitor<const L: usize, T>(::core::marker::PhantomData<T>);
impl<'de, const L: usize, T> serde::de::Visitor<'de> for SecureArraySeqVisitor<L, T>
where
T: crate::vec::SeqElement + Clone + serde::Deserialize<'de>,
{
type Value = SecureArray<T, L>;
fn expecting(&self, formatter: &mut ::core::fmt::Formatter) -> ::core::fmt::Result {
write!(formatter, "a secure array of length {}", L)
}
fn visit_seq<A>(self, mut seq: A) -> Result<Self::Value, A::Error>
where
A: serde::de::SeqAccess<'de>,
{
let mut data: SecureVec<T> =
SecureVec::new_with_capacity(L).map_err(serde::de::Error::custom)?;
while let Some(element) = seq.next_element::<T>()? {
if data.len() == L {
return Err(serde::de::Error::invalid_length(
data.len() + 1,
&self,
));
}
data.push(element);
}
if data.len() != L {
return Err(serde::de::Error::invalid_length(
data.len(),
&self,
));
}
SecureArray::try_from(data).map_err(serde::de::Error::custom)
}
}
deserializer.deserialize_tuple(
LENGTH,
SecureArraySeqVisitor::<LENGTH, T>(::core::marker::PhantomData),
)
}
}
#[cfg(all(test, feature = "use_os"))]
mod tests {
use super::*;
use std::process::{Command, Stdio};
#[test]
fn lock_unlock() {
let exposed: &mut [u8; 3] = &mut [1, 2, 3];
let secure: SecureArray<u8, 3> = SecureArray::from_slice_mut(exposed).unwrap();
let unlocked = secure.unlock_memory();
assert!(unlocked);
let locked = secure.lock_memory();
assert!(locked);
}
#[test]
fn test_initialized_count_tracking() {
let mut array: SecureArray<u8, 3> = SecureArray::empty().unwrap();
assert_eq!(array.initialized, 0);
array.unlock_mut(|slice| {
slice[0] = 1;
slice[1] = 2;
slice[2] = 3;
});
assert_eq!(array.initialized, 3);
let from_slice: SecureArray<u8, 3> = SecureArray::from_slice(&[1, 2, 3]).unwrap();
assert_eq!(from_slice.initialized, 3);
}
#[test]
fn test_index_should_fail_when_locked() {
let arg = "CRASH_TEST_ARRAY_LOCKED";
if std::env::args().any(|a| a == arg) {
let exposed: &mut [u8; 3] = &mut [1, 2, 3];
let array: SecureArray<u8, 3> = SecureArray::from_slice_mut(exposed).unwrap();
let _value = unsafe { core::hint::black_box(*array.ptr.as_ptr()) };
std::process::exit(1);
}
let child = Command::new(std::env::current_exe().unwrap())
.arg("array::tests::test_index_should_fail_when_locked")
.arg(arg)
.arg("--nocapture")
.stdout(Stdio::piped())
.stderr(Stdio::piped())
.spawn()
.expect("Failed to spawn child process");
let output = child.wait_with_output().expect("Failed to wait on child");
let status = output.status;
assert!(
!status.success(),
"Process exited successfully with code {:?}, but it should have crashed.",
status.code()
);
#[cfg(unix)]
{
use std::os::unix::process::ExitStatusExt;
let signal = status
.signal()
.expect("Process was not terminated by a signal on Unix.");
assert!(
signal == libc::SIGSEGV || signal == libc::SIGBUS,
"Process terminated with unexpected signal: {}",
signal
);
println!(
"Test passed: Process correctly terminated with signal {}.",
signal
);
}
#[cfg(windows)]
{
const STATUS_ACCESS_VIOLATION: i32 = 0xC0000005_u32 as i32;
assert_eq!(
status.code(),
Some(STATUS_ACCESS_VIOLATION),
"Process exited with unexpected code: {:x?}. Expected STATUS_ACCESS_VIOLATION.",
status.code()
);
eprintln!("Test passed: Process correctly terminated with STATUS_ACCESS_VIOLATION.");
}
}
}