#[cfg(not(feature = "use_os"))]
use alloc::alloc::Layout;
#[cfg(feature = "use_os")]
use std::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>;
struct UnlockGuard<'a, T: Zeroize> {
vec: &'a SecureVec<T>,
}
impl<'a, T: Zeroize> UnlockGuard<'a, T> {
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();
debug_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> {}
unsafe impl<T: Zeroize + Send + Sync> Sync 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;
}
capacity
.checked_mul(size_of::<T>())
.ok_or(Error::AllocationFailed)?;
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)
}
#[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 {
core::ptr::copy_nonoverlapping(vec.as_ptr(), ptr.as_ptr() as *mut T, len);
}
vec.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 is_empty(&self) -> bool {
self.len() == 0
}
#[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);
let result = f(self);
result
}
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);
let result = f(slice);
result
}
}
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();
let result = f(iter);
result
}
}
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();
let result = f(iter);
result
}
}
pub fn erase(&mut self) {
unsafe {
let ok = self.unlock_memory();
debug_assert!(ok, "SecureVec::erase: unlock_memory failed");
let slice = core::slice::from_raw_parts_mut(self.ptr.as_ptr(), self.capacity);
for elem in slice.iter_mut() {
elem.zeroize();
}
self.clear();
let ok = self.lock_memory();
debug_assert!(ok, "SecureVec::erase: lock_memory failed");
}
}
pub fn clear(&mut self) {
self.len = 0;
}
pub fn push(&mut self, value: T) {
self.reserve(1);
let ok = self.unlock_memory();
debug_assert!(ok, "SecureVec::push: unlock_memory failed");
unsafe {
core::ptr::write(self.ptr.as_ptr().add(self.len), value);
self.len += 1;
}
let ok = self.lock_memory();
debug_assert!(ok, "SecureVec::push: lock_memory failed");
}
pub fn reserve(&mut self, additional: usize) {
if self.len() + additional <= self.capacity {
return;
}
let required_capacity = self.len() + additional;
let new_capacity = (self.capacity.max(1) * 2).max(required_capacity);
let new_size = new_capacity * mem::size_of::<T>();
let new_ptr = unsafe {
alloc::<T>(new_size).unwrap_or_else(|_| {
panic!(
"secure-types: failed to allocate {} bytes of locked memory \
(possibly RLIMIT_MEMLOCK exhausted); SecureVec left unchanged",
new_size
)
})
};
unsafe {
let ok = self.unlock_memory();
debug_assert!(ok, "SecureVec::reserve: unlock_memory failed");
core::ptr::copy_nonoverlapping(
self.ptr.as_ptr(),
new_ptr.as_ptr() as *mut T,
self.len(),
);
if self.capacity > 0 {
let slice = core::slice::from_raw_parts_mut(self.ptr.as_ptr(), self.capacity);
for elem in slice.iter_mut() {
elem.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();
debug_assert!(ok, "SecureVec::reserve: lock_memory failed");
}
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;
let ok = self.unlock_memory();
debug_assert!(ok, "SecureVec::drain: unlock_memory failed");
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 ok = self.unlock_memory();
debug_assert!(
ok,
"SecureVec::init_from_clone: unlock_memory failed"
);
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();
let ok = self.lock_memory();
debug_assert!(
ok,
"SecureVec::init_from_clone: lock_memory failed"
);
}
}
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<const LENGTH: usize> From<SecureArray<u8, LENGTH>> for SecureVec<u8> {
fn from(array: SecureArray<u8, 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) {
self.erase();
let ok = self.unlock_memory();
debug_assert!(ok, "SecureVec::drop: unlock_memory failed");
#[cfg(feature = "use_os")]
free(self.ptr);
#[cfg(not(feature = "use_os"))]
unsafe {
let layout =
Layout::from_size_align_unchecked(self.allocated_byte_size(), mem::align_of::<T>());
alloc::alloc::dealloc(self.ptr.as_ptr() as *mut u8, layout);
}
}
}
impl<T: Zeroize> core::ops::Index<usize> for SecureVec<T> {
type Output = T;
fn index(&self, index: usize) -> &Self::Output {
assert!(index < self.len, "Index out of bounds");
unsafe {
let ptr = self.ptr.as_ptr().add(index);
&*ptr
}
}
}
#[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.collect_seq(slice.iter()))
}
}
#[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 of bytes")
}
fn visit_seq<A>(
self,
mut seq: A,
) -> Result<<Self as serde::de::Visitor<'de>>::Value, A::Error>
where
A: serde::de::SeqAccess<'de>,
{
let mut vec = SecureVec::new().map_err(serde::de::Error::custom)?;
while let Some(byte) = seq.next_element::<u8>()? {
vec.push(byte);
}
Ok(vec)
}
}
deserializer.deserialize_seq(SecureVecVisitor)
}
}
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 {
unsafe {
let item_ptr = self.vec_ref.ptr.as_ptr().add(self.current_drain_iter_index);
let item = ptr::read(item_ptr);
self.current_drain_iter_index += 1;
Some(item)
}
} else {
None
}
}
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> Drop for Drain<'a, T> {
fn drop(&mut self) {
unsafe {
if mem::needs_drop::<T>() {
let mut current_ptr = self.vec_ref.ptr.as_ptr().add(self.current_drain_iter_index);
let end_ptr = self.vec_ref.ptr.as_ptr().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 = self.vec_ref.ptr.as_ptr().add(self.drain_start_index);
let tail_src_ptr = self.vec_ref.ptr.as_ptr().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 mut current_cleanup_ptr = self.vec_ref.ptr.as_ptr().add(new_len);
let end_cleanup_ptr = self.vec_ref.ptr.as_ptr().add(self.original_vec_len);
let original_tail_start_ptr_val = tail_src_ptr as usize;
while current_cleanup_ptr < end_cleanup_ptr {
if mem::needs_drop::<T>() {
let current_ptr_val = current_cleanup_ptr as usize;
let original_tail_end_ptr_val =
original_tail_start_ptr_val + self.tail_len * mem::size_of::<T>();
if current_ptr_val >= original_tail_start_ptr_val
&& current_ptr_val < original_tail_end_ptr_val
{
ptr::drop_in_place(current_cleanup_ptr);
}
}
(*current_cleanup_ptr).zeroize();
current_cleanup_ptr = current_cleanup_ptr.add(1);
}
self.vec_ref.len = new_len;
let ok = self.vec_ref.lock_memory();
debug_assert!(ok, "Drain::drop: lock_memory failed");
}
}
}
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(all(test, feature = "use_os"))]
mod tests {
use super::*;
use std::process::{Command, Stdio};
use std::sync::{Arc, Mutex};
#[test]
fn test_creation() {
let vec: Vec<u8> = vec![1, 2, 3];
let secure_vec = SecureVec::from_vec(vec).unwrap();
secure_vec.unlock_slice(|slice| {
assert_eq!(slice, &[1, 2, 3]);
});
let exposed_slice = &mut [1, 2, 3];
let secure_slice = SecureVec::from_slice_mut(exposed_slice).unwrap();
assert_eq!(exposed_slice, &[0u8; 3]);
secure_slice.unlock_slice(|slice| {
assert_eq!(slice, &[1, 2, 3]);
});
let exposed_slice = [1, 2, 3];
let secure_slice = SecureVec::from_slice(&exposed_slice).unwrap();
secure_slice.unlock_slice(|slice| {
assert_eq!(slice, exposed_slice);
});
}
#[test]
fn test_from_secure_array() {
let exposed: &mut [u8; 3] = &mut [1, 2, 3];
let array: SecureArray<u8, 3> = SecureArray::from_slice_mut(exposed).unwrap();
let vec: SecureVec<u8> = array.into();
assert_eq!(vec.len(), 3);
vec.unlock_slice(|slice| {
assert_eq!(slice, &[1, 2, 3]);
});
}
#[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);
}
#[test]
fn test_thread_safety() {
let vec: Vec<u8> = vec![];
let secure = SecureVec::from_vec(vec).unwrap();
let secure = Arc::new(Mutex::new(secure));
let mut handles = Vec::new();
for i in 0..5u8 {
let secure_clone = secure.clone();
let handle = std::thread::spawn(move || {
let mut secure = secure_clone.lock().unwrap();
secure.push(i);
});
handles.push(handle);
}
for handle in handles {
handle.join().unwrap();
}
let mut sec = secure.lock().unwrap();
sec.unlock_slice_mut(|slice| {
slice.sort();
assert_eq!(slice.len(), 5);
assert_eq!(slice, &[0, 1, 2, 3, 4]);
});
}
#[test]
fn test_clone() {
let vec: Vec<u8> = vec![1, 2, 3];
let secure1 = SecureVec::from_vec(vec).unwrap();
let secure2 = secure1.clone();
secure1.unlock_slice(|slice| {
secure2.unlock_slice(|slice2| {
assert_eq!(slice, slice2);
});
});
}
#[test]
fn test_do_not_call_forget_on_drain() {
let vec: Vec<u8> = vec![1, 2, 3, 4, 5, 6, 7, 8, 9, 10];
let mut secure = SecureVec::from_vec(vec).unwrap();
let drain = secure.drain(..3);
core::mem::forget(drain);
secure.unlock_slice(|secure| {
assert_eq!(secure.len(), 0);
});
}
#[test]
fn test_drain() {
let vec: Vec<u8> = vec![1, 2, 3, 4, 5, 6, 7, 8, 9, 10];
let mut secure = SecureVec::from_vec(vec).unwrap();
let mut drain = secure.drain(..3);
assert_eq!(drain.next(), Some(1));
assert_eq!(drain.next(), Some(2));
assert_eq!(drain.next(), Some(3));
assert_eq!(drain.next(), None);
drop(drain);
secure.unlock_slice(|secure| {
assert_eq!(secure.len(), 7);
assert_eq!(secure, &[4, 5, 6, 7, 8, 9, 10]);
});
}
#[cfg(feature = "serde")]
#[test]
fn test_secure_vec_serde() {
let vec: Vec<u8> = vec![1, 2, 3];
let secure = SecureVec::from_vec(vec).unwrap();
let json = serde_json::to_vec(&secure).expect("Serialization failed");
let deserialized: SecureVec<u8> =
serde_json::from_slice(&json).expect("Deserialization failed");
deserialized.unlock_slice(|slice| {
assert_eq!(slice, &[1, 2, 3]);
});
}
#[test]
fn test_erase() {
let mut secure = SecureVec::new_with_capacity(10).unwrap();
for i in 0..9 {
secure.push(i);
}
secure.erase();
secure.unlock(|secure| {
assert_eq!(secure.len, 0);
assert_eq!(secure.capacity, 10);
});
secure.unlock_iter(|iter| {
for elem in iter {
assert_eq!(elem, &0);
}
});
}
#[test]
fn test_push() {
let vec: Vec<u8> = Vec::new();
let mut secure = SecureVec::from_vec(vec).unwrap();
for i in 0..10 {
secure.push(i);
}
assert_eq!(secure.len(), 10);
secure.unlock_slice(|slice| {
assert_eq!(slice, &[0, 1, 2, 3, 4, 5, 6, 7, 8, 9]);
});
}
#[test]
fn test_reserve() {
let mut secure: SecureVec<u8> = SecureVec::new().unwrap();
secure.reserve(10);
assert_eq!(secure.capacity, 10);
}
#[test]
fn test_reserve_doubling() {
let mut secure: SecureVec<u8> = SecureVec::new().unwrap();
secure.reserve(10);
for i in 0..9 {
secure.push(i);
}
secure.push(9);
assert_eq!(secure.capacity, 10);
assert_eq!(secure.len(), 10);
secure.push(10);
assert_eq!(secure.capacity, 20);
assert_eq!(secure.len(), 11);
}
#[test]
fn test_index() {
let vec: Vec<u8> = vec![1, 2, 3];
let secure = SecureVec::from_vec(vec).unwrap();
secure.unlock(|secure| {
assert_eq!(secure[0], 1);
assert_eq!(secure[1], 2);
assert_eq!(secure[2], 3);
});
}
#[test]
fn test_unlock_slice() {
let vec: Vec<u8> = vec![1, 2, 3];
let secure = SecureVec::from_vec(vec).unwrap();
secure.unlock_slice(|slice| {
assert_eq!(slice, &[1, 2, 3]);
});
}
#[test]
fn test_unlock_slice_mut() {
let vec: Vec<u8> = vec![1, 2, 3];
let mut secure = SecureVec::from_vec(vec).unwrap();
secure.unlock_slice_mut(|slice| {
slice[0] = 4;
assert_eq!(slice, &mut [4, 2, 3]);
});
}
#[test]
fn test_unlock_iter() {
let vec: Vec<u8> = vec![1, 2, 3];
let secure = SecureVec::from_vec(vec).unwrap();
let sum: u8 = secure.unlock_iter(|iter| iter.map(|&x| x).sum());
assert_eq!(sum, 6);
let secure: SecureVec<u8> = SecureVec::new_with_capacity(3).unwrap();
let sum: u8 = secure.unlock_iter(|iter| iter.map(|&x| x).sum());
assert_eq!(sum, 0);
}
#[test]
fn test_unlock_iter_mut() {
let vec: Vec<u8> = vec![1, 2, 3];
let mut secure = SecureVec::from_vec(vec).unwrap();
secure.unlock_iter_mut(|iter| {
for elem in iter {
*elem += 1;
}
});
secure.unlock_slice(|slice| {
assert_eq!(slice, &[2, 3, 4]);
});
}
#[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 = core::hint::black_box(secure[0]);
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.");
}
}
}