#[cfg(not(feature = "use_os"))]
use alloc::alloc::Layout;
#[cfg(feature = "use_os")]
use std::vec::Vec;
#[cfg(all(feature = "serde", not(feature = "use_os")))]
use alloc::vec::Vec;
use super::{Error, SecureArray, alloc};
use core::{
marker::PhantomData,
mem,
ops::{Bound, RangeBounds},
ptr::{self, NonNull},
};
use zeroize::{DefaultIsZeroes, Zeroize};
#[cfg(feature = "use_os")]
use super::free;
#[cfg(feature = "use_os")]
use memsec::Prot;
pub type SecureBytes = SecureVec<u8>;
pub(crate) struct UnlockGuard<'a, T: Zeroize> {
vec: &'a SecureVec<T>,
}
impl<'a, T: Zeroize> UnlockGuard<'a, T> {
pub(crate) fn new(vec: &'a SecureVec<T>) -> Self {
let ok = vec.unlock_memory();
debug_assert!(ok, "UnlockGuard::new: unlock_memory failed");
UnlockGuard { vec }
}
}
impl<'a, T: Zeroize> Drop for UnlockGuard<'a, T> {
fn drop(&mut self) {
let ok = self.vec.lock_memory();
assert!(ok, "UnlockGuard::drop: lock_memory failed");
}
}
pub struct SecureVec<T>
where
T: Zeroize,
{
ptr: NonNull<T>,
pub(crate) len: usize,
pub(crate) capacity: usize,
_marker: PhantomData<T>,
}
unsafe impl<T: Zeroize + Send> Send for SecureVec<T> {}
impl<T: Zeroize> SecureVec<T> {
pub fn new() -> Result<Self, Error> {
let capacity = 1;
let size = capacity * mem::size_of::<T>();
let ptr = unsafe { alloc::<T>(size)? };
let secure = SecureVec {
ptr,
len: 0,
capacity,
_marker: PhantomData,
};
let _locked = secure.lock_memory();
#[cfg(feature = "use_os")]
if !_locked {
return Err(Error::LockFailed);
}
Ok(secure)
}
pub fn new_with_capacity(mut capacity: usize) -> Result<Self, Error> {
if capacity == 0 {
capacity = 1;
}
let size = capacity
.checked_mul(size_of::<T>())
.ok_or(Error::AllocationFailed)?;
let ptr = unsafe { alloc::<T>(size)? };
let secure = SecureVec {
ptr,
len: 0,
capacity,
_marker: PhantomData,
};
let _locked = secure.lock_memory();
#[cfg(feature = "use_os")]
if !_locked {
return Err(Error::LockFailed);
}
Ok(secure)
}
#[cfg(feature = "use_os")]
pub fn from_vec(mut vec: Vec<T>) -> Result<Self, Error> {
if vec.capacity() == 0 {
vec.reserve(1);
}
let capacity = vec.capacity();
let len = vec.len();
let size = match capacity.checked_mul(size_of::<T>()) {
Some(s) => s,
None => {
vec.zeroize();
return Err(Error::AllocationFailed);
}
};
let ptr = match unsafe { alloc::<T>(size) } {
Ok(ptr) => ptr,
Err(_) => {
vec.zeroize();
return Err(Error::AllocationFailed);
}
};
unsafe {
let src = vec.as_ptr();
let dst = ptr.as_ptr();
for i in 0..len {
let value = core::ptr::read(src.add(i));
core::ptr::write(dst.add(i), value);
}
}
let old_byte_size = capacity * mem::size_of::<T>();
unsafe {
vec.set_len(0);
}
if old_byte_size > 0 {
let bytes =
unsafe { core::slice::from_raw_parts_mut(vec.as_mut_ptr() as *mut u8, old_byte_size) };
bytes.zeroize();
}
let secure = SecureVec {
ptr,
len,
capacity,
_marker: PhantomData,
};
let locked = secure.lock_memory();
if !locked {
return Err(Error::LockFailed);
}
Ok(secure)
}
pub fn from_slice_mut(slice: &mut [T]) -> Result<Self, Error>
where
T: Clone + DefaultIsZeroes,
{
let mut secure_vec = match SecureVec::new_with_capacity(slice.len()) {
Ok(secure_vec) => secure_vec,
Err(e) => {
slice.zeroize();
return Err(e);
}
};
secure_vec.init_from_clone(slice);
slice.zeroize();
Ok(secure_vec)
}
pub fn from_slice(slice: &[T]) -> Result<Self, Error>
where
T: Clone,
{
let mut secure_vec = SecureVec::new_with_capacity(slice.len())?;
secure_vec.init_from_clone(slice);
Ok(secure_vec)
}
pub fn len(&self) -> usize {
self.len
}
pub fn capacity(&self) -> usize {
self.capacity
}
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 one of the unlock methods instead."
)]
pub fn ptr(&self) -> NonNull<T> {
self.ptr
}
#[cfg(not(feature = "use_os"))]
pub(crate) fn allocated_byte_size(&self) -> usize {
self.capacity * mem::size_of::<T>()
}
pub(crate) fn as_mut_ptr(&mut self) -> *mut u8 {
self.ptr.as_ptr() as *mut u8
}
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(&SecureVec<T>) -> R,
{
let _guard = UnlockGuard::new(self);
f(self)
}
pub fn unlock_slice<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.len) };
f(slice)
}
pub fn unlock_slice_mut<F, R>(&mut self, f: F) -> R
where
F: FnOnce(&mut [T]) -> R,
{
unsafe {
let _guard = UnlockGuard::new(self);
let slice = core::slice::from_raw_parts_mut(self.ptr.as_ptr(), self.len);
f(slice)
}
}
pub fn unlock_iter<F, R>(&self, f: F) -> R
where
F: FnOnce(core::slice::Iter<T>) -> R,
{
unsafe {
let _guard = UnlockGuard::new(self);
let slice = core::slice::from_raw_parts(self.ptr.as_ptr(), self.len);
let iter = slice.iter();
f(iter)
}
}
pub fn unlock_iter_mut<F, R>(&mut self, f: F) -> R
where
F: FnOnce(core::slice::IterMut<T>) -> R,
{
unsafe {
let _guard = UnlockGuard::new(self);
let slice = core::slice::from_raw_parts_mut(self.ptr.as_ptr(), self.len);
let iter = slice.iter_mut();
f(iter)
}
}
pub fn erase(&mut self) {
{
let _guard = UnlockGuard::new(self);
unsafe {
let slice = core::slice::from_raw_parts_mut(self.ptr.as_ptr(), self.len);
for elem in slice.iter_mut() {
elem.zeroize();
}
}
}
self.clear();
}
pub fn clear(&mut self) {
self.len = 0;
}
pub fn push(&mut self, value: T) {
self.reserve(1);
let dst = self.ptr.as_ptr();
let write_at = self.len;
{
let _guard = UnlockGuard::new(self);
unsafe {
core::ptr::write(dst.add(write_at), value);
}
}
self.len = write_at + 1;
}
#[cfg(any(feature = "use_os", feature = "codec"))]
pub(crate) fn extend_from_slice(&mut self, src: &[T]) -> Result<(), Error>
where
T: Clone,
{
if src.is_empty() {
return Ok(());
}
self.try_reserve(src.len())?;
let write_at = self.len;
let dst = self.ptr.as_ptr();
{
let _guard = UnlockGuard::new(self);
unsafe {
for (i, item) in src.iter().enumerate() {
core::ptr::write(dst.add(write_at + i), item.clone());
}
}
}
self.len = write_at + src.len();
Ok(())
}
pub fn reserve(&mut self, additional: usize) {
self.try_reserve(additional).unwrap_or_else(|error| {
panic!(
"secure-types: SecureVec::reserve overflow or allocation failed ({error}); SecureVec left unchanged"
)
});
}
pub(crate) fn try_reserve(&mut self, additional: usize) -> Result<(), Error> {
let required_capacity = self
.len
.checked_add(additional)
.ok_or(Error::AllocationFailed)?;
if required_capacity <= self.capacity {
return Ok(());
}
let new_capacity = self
.capacity
.max(1)
.checked_mul(2)
.unwrap_or(required_capacity)
.max(required_capacity);
let new_size = new_capacity
.checked_mul(mem::size_of::<T>())
.ok_or(Error::AllocationFailed)?;
let new_ptr = unsafe { alloc::<T>(new_size)? };
unsafe {
let ok = self.unlock_memory();
debug_assert!(ok, "SecureVec::try_reserve: unlock_memory failed");
let len = self.len();
for i in 0..len {
let val = core::ptr::read(self.ptr.as_ptr().add(i));
core::ptr::write(new_ptr.as_ptr().add(i), val);
}
if self.capacity > 0 {
let old_bytes = self.capacity * mem::size_of::<T>();
let bytes = core::slice::from_raw_parts_mut(self.ptr.as_ptr() as *mut u8, old_bytes);
bytes.zeroize();
}
#[cfg(feature = "use_os")]
free(self.ptr);
#[cfg(not(feature = "use_os"))]
{
let old_size = self.capacity * mem::size_of::<T>();
let old_layout = Layout::from_size_align_unchecked(old_size, mem::align_of::<T>());
alloc::alloc::dealloc(self.ptr.as_ptr() as *mut u8, old_layout);
}
}
self.ptr = new_ptr;
self.capacity = new_capacity;
let ok = self.lock_memory();
assert!(ok, "SecureVec::try_reserve: lock_memory failed");
Ok(())
}
pub fn drain<R>(&mut self, range: R) -> Drain<'_, T>
where
R: RangeBounds<usize>,
{
let original_len = self.len;
let (drain_start_idx, drain_end_idx) = resolve_range_indices(range, original_len);
let tail_len = original_len - drain_end_idx;
self.len = drain_start_idx;
Drain {
vec_ref: self,
drain_start_index: drain_start_idx,
current_drain_iter_index: drain_start_idx,
drain_end_index: drain_end_idx,
original_vec_len: original_len,
tail_len,
_marker: PhantomData,
}
}
pub(crate) fn init_from_clone(&mut self, src: &[T])
where
T: Clone,
{
debug_assert!(src.len() <= self.capacity);
{
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.len = src.len();
}
}
impl SecureVec<u8> {
#[cfg(feature = "codec")]
pub(crate) fn patch_at(&mut self, offset: usize, src: &[u8]) {
let end = offset
.checked_add(src.len())
.expect("SecureVec::patch_at: offset overflow");
assert!(
end <= self.len,
"SecureVec::patch_at: range {offset}..{end} exceeds length {}",
self.len
);
{
let _guard = UnlockGuard::new(self);
unsafe {
core::ptr::copy_nonoverlapping(
src.as_ptr(),
self.ptr.as_ptr().add(offset),
src.len(),
);
}
}
}
}
impl<T: Clone + Zeroize> Clone for SecureVec<T> {
fn clone(&self) -> Self {
let mut new_vec = SecureVec::new_with_capacity(self.capacity).unwrap();
self.unlock_slice(|src_slice| {
new_vec.init_from_clone(src_slice);
});
new_vec
}
}
impl<T: Clone + Zeroize, const LENGTH: usize> From<SecureArray<T, LENGTH>> for SecureVec<T> {
fn from(array: SecureArray<T, LENGTH>) -> Self {
let mut new_vec = SecureVec::new_with_capacity(LENGTH)
.expect("Failed to allocate SecureVec during conversion");
array.unlock(|array_slice| {
new_vec.init_from_clone(array_slice);
});
new_vec
}
}
impl<T: Zeroize> Drop for SecureVec<T> {
fn drop(&mut self) {
unsafe {
let ok = self.unlock_memory();
debug_assert!(ok, "SecureVec::drop: unlock_memory failed");
let slice = core::slice::from_raw_parts_mut(self.ptr.as_ptr(), self.len);
for elem in slice.iter_mut() {
elem.zeroize();
}
}
#[cfg(feature = "use_os")]
free(self.ptr);
#[cfg(not(feature = "use_os"))]
unsafe {
let byte_size = self.allocated_byte_size();
let bytes = core::slice::from_raw_parts_mut(self.ptr.as_ptr() as *mut u8, byte_size);
bytes.zeroize();
let layout = Layout::from_size_align_unchecked(byte_size, mem::align_of::<T>());
alloc::alloc::dealloc(self.ptr.as_ptr() as *mut u8, layout);
}
}
}
#[cfg(feature = "serde")]
const MAX_PREALLOCATION_FROM_SIZE_HINT: usize = 4096;
#[cfg(feature = "serde")]
impl serde::Serialize for SecureVec<u8> {
fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
where
S: serde::Serializer,
{
self.unlock_slice(|slice| serializer.serialize_bytes(slice))
}
}
#[cfg(feature = "serde")]
impl<'de> serde::Deserialize<'de> for SecureVec<u8> {
fn deserialize<D>(deserializer: D) -> Result<SecureVec<u8>, D::Error>
where
D: serde::Deserializer<'de>,
{
struct SecureVecVisitor;
impl<'de> serde::de::Visitor<'de> for SecureVecVisitor {
type Value = SecureVec<u8>;
fn expecting(&self, formatter: &mut ::core::fmt::Formatter) -> ::core::fmt::Result {
write!(formatter, "a sequence or a byte buffer")
}
fn visit_seq<A>(self, mut seq: A) -> Result<Self::Value, A::Error>
where
A: serde::de::SeqAccess<'de>,
{
let capacity = seq
.size_hint()
.unwrap_or(0)
.min(MAX_PREALLOCATION_FROM_SIZE_HINT);
let mut vec =
SecureVec::new_with_capacity(capacity).map_err(serde::de::Error::custom)?;
while let Some(byte) = seq.next_element::<u8>()? {
vec.push(byte);
}
Ok(vec)
}
fn visit_bytes<E>(self, v: &[u8]) -> Result<Self::Value, E>
where
E: serde::de::Error,
{
SecureVec::from_slice(v).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 vec = self.visit_bytes(&v);
v.zeroize();
vec
}
}
deserializer.deserialize_bytes(SecureVecVisitor)
}
}
#[cfg(feature = "serde")]
pub trait SeqElement: Zeroize {}
#[cfg(feature = "serde")]
impl SeqElement for bool {}
#[cfg(feature = "serde")]
impl SeqElement for char {}
#[cfg(feature = "serde")]
impl SeqElement for f32 {}
#[cfg(feature = "serde")]
impl SeqElement for f64 {}
#[cfg(feature = "serde")]
impl SeqElement for i8 {}
#[cfg(feature = "serde")]
impl SeqElement for i16 {}
#[cfg(feature = "serde")]
impl SeqElement for i32 {}
#[cfg(feature = "serde")]
impl SeqElement for i64 {}
#[cfg(feature = "serde")]
impl SeqElement for i128 {}
#[cfg(feature = "serde")]
impl SeqElement for u16 {}
#[cfg(feature = "serde")]
impl SeqElement for u32 {}
#[cfg(feature = "serde")]
impl SeqElement for u64 {}
#[cfg(feature = "serde")]
impl SeqElement for u128 {}
#[cfg(feature = "serde")]
impl<T> serde::Serialize for SecureVec<T>
where
T: SeqElement + serde::Serialize,
{
fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
where
S: serde::Serializer,
{
use serde::ser::SerializeSeq;
let mut seq = serializer.serialize_seq(Some(self.len()))?;
let elements: Result<(), S::Error> = self.unlock_slice(|slice| {
for item in slice {
seq.serialize_element(item)?;
}
Ok(())
});
elements?;
seq.end()
}
}
#[cfg(feature = "serde")]
impl<'de, T> serde::Deserialize<'de> for SecureVec<T>
where
T: SeqElement + serde::Deserialize<'de>,
{
fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
where
D: serde::Deserializer<'de>,
{
struct SecureSeqVisitor<T>(PhantomData<T>);
impl<'de, T> serde::de::Visitor<'de> for SecureSeqVisitor<T>
where
T: SeqElement + serde::Deserialize<'de>,
{
type Value = SecureVec<T>;
fn expecting(&self, formatter: &mut ::core::fmt::Formatter) -> ::core::fmt::Result {
write!(formatter, "a sequence of secure elements")
}
fn visit_seq<A>(self, mut seq: A) -> Result<Self::Value, A::Error>
where
A: serde::de::SeqAccess<'de>,
{
let capacity = seq
.size_hint()
.unwrap_or(0)
.min(MAX_PREALLOCATION_FROM_SIZE_HINT);
let mut vec =
SecureVec::new_with_capacity(capacity).map_err(serde::de::Error::custom)?;
while let Some(item) = seq.next_element::<T>()? {
vec.push(item);
}
Ok(vec)
}
}
deserializer.deserialize_seq(SecureSeqVisitor::<T>(PhantomData))
}
}
pub struct Drain<'a, T: Zeroize + 'a> {
vec_ref: &'a mut SecureVec<T>,
drain_start_index: usize,
current_drain_iter_index: usize,
drain_end_index: usize,
original_vec_len: usize, tail_len: usize,
_marker: PhantomData<&'a T>,
}
impl<'a, T: Zeroize> Iterator for Drain<'a, T> {
type Item = T;
fn next(&mut self) -> Option<T> {
if self.current_drain_iter_index >= self.drain_end_index {
return None;
}
let base = self.vec_ref.ptr.as_ptr();
let _guard = UnlockGuard::new(&*self.vec_ref);
let item = unsafe { ptr::read(base.add(self.current_drain_iter_index)) };
self.current_drain_iter_index += 1;
Some(item)
}
fn size_hint(&self) -> (usize, Option<usize>) {
let remaining = self.drain_end_index - self.current_drain_iter_index;
(remaining, Some(remaining))
}
}
impl<'a, T: Zeroize> ExactSizeIterator for Drain<'a, T> {}
impl<'a, T: Zeroize> Drain<'a, T> {
fn compact(&self) -> usize {
let base = self.vec_ref.ptr.as_ptr();
let _guard = UnlockGuard::new(&*self.vec_ref);
unsafe {
if mem::needs_drop::<T>() {
let mut current_ptr = base.add(self.current_drain_iter_index);
let end_ptr = base.add(self.drain_end_index);
while current_ptr < end_ptr {
ptr::drop_in_place(current_ptr);
current_ptr = current_ptr.add(1);
}
}
let hole_dst_ptr = base.add(self.drain_start_index);
let tail_src_ptr = base.add(self.drain_end_index);
if self.tail_len > 0 {
ptr::copy(tail_src_ptr, hole_dst_ptr, self.tail_len);
}
let new_len = self.drain_start_index + self.tail_len;
let leftover_elems = self.original_vec_len.saturating_sub(new_len);
let leftover_bytes = leftover_elems.saturating_mul(mem::size_of::<T>());
if leftover_bytes > 0 {
let bytes =
core::slice::from_raw_parts_mut(base.add(new_len) as *mut u8, leftover_bytes);
bytes.zeroize();
}
new_len
}
}
}
impl<'a, T: Zeroize> Drop for Drain<'a, T> {
fn drop(&mut self) {
let new_len = self.compact();
self.vec_ref.len = new_len;
}
}
fn resolve_range_indices<R: RangeBounds<usize>>(range: R, len: usize) -> (usize, usize) {
let start_bound = range.start_bound();
let end_bound = range.end_bound();
let start = match start_bound {
Bound::Included(&s) => s,
Bound::Excluded(&s) => s
.checked_add(1)
.unwrap_or_else(|| panic!("attempted to start drain at Excluded(usize::MAX)")),
Bound::Unbounded => 0,
};
let end = match end_bound {
Bound::Included(&e) => e
.checked_add(1)
.unwrap_or_else(|| panic!("attempted to end drain at Included(usize::MAX)")),
Bound::Excluded(&e) => e,
Bound::Unbounded => len,
};
if start > end {
panic!(
"drain range start ({}) must be less than or equal to end ({})",
start, end
);
}
if end > len {
panic!(
"drain range end ({}) out of bounds for slice of length {}",
end, len
);
}
(start, end)
}
#[cfg(test)]
mod tests {
#[cfg(any(feature = "use_os", feature = "codec"))]
use super::*;
#[cfg(feature = "use_os")]
use std::process::{Command, Stdio};
#[cfg(feature = "use_os")]
#[test]
fn lock_unlock_works() {
let secure: SecureVec<u8> = SecureVec::new().unwrap();
let unlocked = secure.unlock_memory();
assert!(unlocked);
let locked = secure.lock_memory();
assert!(locked);
}
#[cfg(feature = "codec")]
#[test]
fn test_patch_at_overwrites_in_place() {
let mut secure = SecureBytes::from_slice(b"abcdefgh").unwrap();
secure.patch_at(2, b"XY");
secure.unlock_slice(|bytes| {
assert_eq!(bytes, b"abXYefgh");
assert_eq!(bytes.len(), 8);
});
}
#[cfg(feature = "codec")]
#[test]
fn test_patch_at_last_bytes_and_whole_buffer() {
let mut secure = SecureBytes::from_slice(b"abcdefgh").unwrap();
secure.patch_at(6, b"XY");
secure.unlock_slice(|bytes| assert_eq!(bytes, b"abcdefXY"));
secure.patch_at(0, b"12345678");
secure.unlock_slice(|bytes| assert_eq!(bytes, b"12345678"));
}
#[cfg(feature = "codec")]
#[test]
fn test_patch_at_empty_source_is_a_noop() {
let mut secure = SecureBytes::from_slice(b"abc").unwrap();
secure.patch_at(0, b"");
secure.patch_at(3, b"");
secure.unlock_slice(|bytes| assert_eq!(bytes, b"abc"));
}
#[cfg(feature = "use_os")]
#[test]
fn test_erase_zeroizes_initialized_slots() {
let mut secure = SecureVec::from_slice(&[1u8, 2, 3]).unwrap();
let capacity = secure.capacity;
secure.erase();
assert_eq!(secure.len, 0);
assert_eq!(secure.capacity, capacity);
let ok = secure.unlock_memory();
assert!(ok);
unsafe {
let slice = core::slice::from_raw_parts(secure.ptr.as_ptr(), 3);
assert_eq!(slice, &[0, 0, 0]);
}
let ok = secure.lock_memory();
assert!(ok);
}
#[cfg(feature = "codec")]
#[test]
fn test_patch_at_leaves_length_and_capacity_alone() {
let mut secure = SecureBytes::new_with_capacity(16).unwrap();
secure.extend_from_slice(b"abc").unwrap();
let capacity_before = secure.unlock(|vec| vec.capacity);
secure.patch_at(0, b"ZY");
secure.unlock(|vec| {
assert_eq!(vec.len, 3);
assert_eq!(vec.capacity, capacity_before);
});
secure.unlock_slice(|bytes| assert_eq!(bytes, b"ZYc"));
}
#[cfg(feature = "codec")]
#[test]
fn test_patch_at_survives_reallocation() {
let mut secure = SecureBytes::new().unwrap();
secure.extend_from_slice(b"first").unwrap();
secure.reserve(4096);
secure.extend_from_slice(b"second").unwrap();
secure.patch_at(0, b"FIRST");
secure.unlock_slice(|bytes| assert_eq!(bytes, b"FIRSTsecond"));
}
#[cfg(feature = "codec")]
#[test]
#[should_panic(expected = "exceeds length")]
fn test_patch_at_straddling_the_end_panics() {
let mut secure = SecureBytes::from_slice(b"abc").unwrap();
secure.patch_at(2, b"XY");
}
#[cfg(feature = "codec")]
#[test]
#[should_panic(expected = "exceeds length")]
fn test_patch_at_past_the_end_panics() {
let mut secure = SecureBytes::from_slice(b"abc").unwrap();
secure.patch_at(4, b"");
}
#[cfg(feature = "use_os")]
#[test]
fn test_forgotten_drain_keeps_memory_locked() {
let arg = "CRASH_TEST_DRAIN_FORGET_LOCKED";
if std::env::args().any(|a| a == arg) {
let vec: Vec<u8> = vec![1, 2, 3, 4, 5];
let mut secure = SecureVec::from_vec(vec).unwrap();
let drain = secure.drain(..3);
core::mem::forget(drain);
let _value = unsafe { core::hint::black_box(*secure.ptr.as_ptr()) };
std::process::exit(1);
}
let child = Command::new(std::env::current_exe().unwrap())
.arg("vec::tests::test_forgotten_drain_keeps_memory_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?}.",
status.code()
);
}
}
#[cfg(feature = "use_os")]
#[test]
fn test_index_should_fail_when_locked() {
let arg = "CRASH_TEST_SECUREVEC_LOCKED";
if std::env::args().any(|a| a == arg) {
let vec: Vec<u8> = vec![1, 2, 3];
let secure = SecureVec::from_vec(vec).unwrap();
let _value = unsafe { core::hint::black_box(*secure.ptr.as_ptr()) };
std::process::exit(1);
}
let child = Command::new(std::env::current_exe().unwrap())
.arg("vec::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.");
}
}
}