mirror of
https://github.com/rust-embedded/heapless.git
synced 2025-09-26 20:10:24 +00:00
Merge pull request #614 from vishy11/master
Add zeroization support for heapless data structures
This commit is contained in:
commit
bbe988d87f
5
.github/workflows/build.yml
vendored
5
.github/workflows/build.yml
vendored
@ -48,7 +48,7 @@ jobs:
|
||||
components: miri
|
||||
|
||||
- name: Run miri
|
||||
run: MIRIFLAGS=-Zmiri-ignore-leaks cargo miri test --features="alloc,defmt,mpmc_large,portable-atomic-critical-section,serde,ufmt,bytes"
|
||||
run: MIRIFLAGS=-Zmiri-ignore-leaks cargo miri test --features="alloc,defmt,mpmc_large,portable-atomic-critical-section,serde,ufmt,bytes,zeroize"
|
||||
|
||||
# Run cargo test
|
||||
test:
|
||||
@ -84,7 +84,7 @@ jobs:
|
||||
toolchain: stable
|
||||
|
||||
- name: Run cargo test
|
||||
run: cargo test --features="alloc,defmt,mpmc_large,portable-atomic-critical-section,serde,ufmt,bytes"
|
||||
run: cargo test --features="alloc,defmt,mpmc_large,portable-atomic-critical-section,serde,ufmt,bytes,zeroize"
|
||||
|
||||
# Run cargo fmt --check
|
||||
style:
|
||||
@ -176,6 +176,7 @@ jobs:
|
||||
cargo check --target="${target}" --features="portable-atomic-critical-section"
|
||||
cargo check --target="${target}" --features="serde"
|
||||
cargo check --target="${target}" --features="ufmt"
|
||||
cargo check --target="${target}" --features="zeroize"
|
||||
env:
|
||||
target: ${{ matrix.target }}
|
||||
|
||||
|
@ -13,6 +13,7 @@ and this project adheres to [Semantic Versioning](http://semver.org/).
|
||||
- Implement `defmt::Format` for `CapacityError`.
|
||||
- Implement `TryFrom` for `Deque` from array.
|
||||
- Switch from `serde` to `serde_core` for enabling faster compilations.
|
||||
- Implement `Zeroize` trait for all data structures with the `zeroize` feature to securely clear sensitive data from memory.
|
||||
|
||||
## [v0.9.1] - 2025-08-19
|
||||
|
||||
|
@ -47,6 +47,9 @@ ufmt = ["dep:ufmt", "dep:ufmt-write"]
|
||||
# Implement `defmt::Format`.
|
||||
defmt = ["dep:defmt"]
|
||||
|
||||
# Implement `zeroize::Zeroize` trait.
|
||||
zeroize = ["dep:zeroize"]
|
||||
|
||||
# Enable larger MPMC sizes.
|
||||
mpmc_large = []
|
||||
|
||||
@ -63,6 +66,7 @@ serde_core = { version = "1", optional = true, default-features = false }
|
||||
ufmt = { version = "0.2", optional = true }
|
||||
ufmt-write = { version = "0.1", optional = true }
|
||||
defmt = { version = "1.0.1", optional = true }
|
||||
zeroize = { version = "1.8", optional = true, default-features = false, features = ["derive"] }
|
||||
|
||||
# for the pool module
|
||||
[target.'cfg(any(target_arch = "arm", target_pointer_width = "32", target_pointer_width = "64"))'.dependencies]
|
||||
|
@ -17,6 +17,9 @@ use core::{
|
||||
ptr, slice,
|
||||
};
|
||||
|
||||
#[cfg(feature = "zeroize")]
|
||||
use zeroize::Zeroize;
|
||||
|
||||
use crate::vec::{OwnedVecStorage, Vec, VecInner, VecStorage, ViewVecStorage};
|
||||
|
||||
/// Min-heap
|
||||
@ -55,6 +58,11 @@ impl private::Sealed for Min {}
|
||||
///
|
||||
/// In most cases you should use [`BinaryHeap`] or [`BinaryHeapView`] directly. Only use this
|
||||
/// struct if you want to write code that's generic over both.
|
||||
#[cfg_attr(
|
||||
feature = "zeroize",
|
||||
derive(Zeroize),
|
||||
zeroize(bound = "T: Zeroize, S: Zeroize")
|
||||
)]
|
||||
pub struct BinaryHeapInner<T, K, S: VecStorage<T> + ?Sized> {
|
||||
pub(crate) _kind: PhantomData<K>,
|
||||
pub(crate) data: VecInner<T, usize, S>,
|
||||
@ -882,6 +890,24 @@ mod tests {
|
||||
assert_eq!(heap.pop(), None);
|
||||
}
|
||||
|
||||
#[test]
|
||||
#[cfg(feature = "zeroize")]
|
||||
fn test_binary_heap_zeroize() {
|
||||
use zeroize::Zeroize;
|
||||
|
||||
let mut heap = BinaryHeap::<u8, Max, 8>::new();
|
||||
for i in 0..8 {
|
||||
heap.push(i).unwrap();
|
||||
}
|
||||
|
||||
assert_eq!(heap.len(), 8);
|
||||
assert_eq!(heap.peek(), Some(&7));
|
||||
|
||||
// zeroized using Vec's implementation
|
||||
heap.zeroize();
|
||||
assert_eq!(heap.len(), 0);
|
||||
}
|
||||
|
||||
fn _test_variance<'a: 'b, 'b>(x: BinaryHeap<&'a (), Max, 42>) -> BinaryHeap<&'b (), Max, 42> {
|
||||
x
|
||||
}
|
||||
|
@ -10,6 +10,9 @@ use core::{
|
||||
ops::Deref,
|
||||
};
|
||||
|
||||
#[cfg(feature = "zeroize")]
|
||||
use zeroize::Zeroize;
|
||||
|
||||
/// A fixed capacity [`CString`](https://doc.rust-lang.org/std/ffi/struct.CString.html).
|
||||
///
|
||||
/// It stores up to `N - 1` non-nul characters with a trailing nul terminator.
|
||||
@ -18,6 +21,20 @@ pub struct CString<const N: usize, LenT: LenType = usize> {
|
||||
inner: Vec<u8, N, LenT>,
|
||||
}
|
||||
|
||||
#[cfg(feature = "zeroize")]
|
||||
impl<const N: usize, LenT: LenType> Zeroize for CString<N, LenT> {
|
||||
fn zeroize(&mut self) {
|
||||
self.inner.zeroize();
|
||||
|
||||
const {
|
||||
assert!(N > 0);
|
||||
}
|
||||
|
||||
// SAFETY: We just asserted that `N > 0`.
|
||||
unsafe { self.inner.push_unchecked(b'\0') };
|
||||
}
|
||||
}
|
||||
|
||||
impl<const N: usize, LenT: LenType> CString<N, LenT> {
|
||||
/// Creates a new C-compatible string with a terminating nul byte.
|
||||
///
|
||||
@ -483,6 +500,35 @@ mod tests {
|
||||
assert_eq!(Borrow::<CStr>::borrow(&string), c"foo");
|
||||
}
|
||||
|
||||
#[test]
|
||||
#[cfg(feature = "zeroize")]
|
||||
fn test_cstring_zeroize() {
|
||||
use zeroize::Zeroize;
|
||||
|
||||
let mut c_string = CString::<32>::from_bytes_with_nul(b"sensitive_password\0").unwrap();
|
||||
|
||||
assert_eq!(c_string.to_str(), Ok("sensitive_password"));
|
||||
assert!(!c_string.to_bytes().is_empty());
|
||||
let original_length = c_string.to_bytes().len();
|
||||
assert_eq!(original_length, 18);
|
||||
|
||||
let new_string = CString::<32>::from_bytes_with_nul(b"short\0").unwrap();
|
||||
c_string = new_string;
|
||||
|
||||
assert_eq!(c_string.to_str(), Ok("short"));
|
||||
assert_eq!(c_string.to_bytes().len(), 5);
|
||||
|
||||
// zeroized using Vec's implementation
|
||||
c_string.zeroize();
|
||||
|
||||
assert_eq!(c_string.to_bytes().len(), 0);
|
||||
assert_eq!(c_string.to_bytes_with_nul(), &[0]);
|
||||
|
||||
c_string.extend_from_bytes(b"new_data").unwrap();
|
||||
assert_eq!(c_string.to_str(), Ok("new_data"));
|
||||
assert_eq!(c_string.to_bytes().len(), 8);
|
||||
}
|
||||
|
||||
mod equality {
|
||||
use super::*;
|
||||
|
||||
|
29
src/deque.rs
29
src/deque.rs
@ -42,10 +42,14 @@ use core::marker::PhantomData;
|
||||
use core::mem::{ManuallyDrop, MaybeUninit};
|
||||
use core::{ptr, slice};
|
||||
|
||||
#[cfg(feature = "zeroize")]
|
||||
use zeroize::Zeroize;
|
||||
|
||||
/// Base struct for [`Deque`] and [`DequeView`], generic over the [`VecStorage`].
|
||||
///
|
||||
/// In most cases you should use [`Deque`] or [`DequeView`] directly. Only use this
|
||||
/// struct if you want to write code that's generic over both.
|
||||
#[cfg_attr(feature = "zeroize", derive(Zeroize))]
|
||||
pub struct DequeInner<T, S: VecStorage<T> + ?Sized> {
|
||||
// This phantomdata is required because otherwise rustc thinks that `T` is not used
|
||||
phantom: PhantomData<T>,
|
||||
@ -1674,4 +1678,29 @@ mod tests {
|
||||
|
||||
assert_eq!(Droppable::count(), 0);
|
||||
}
|
||||
|
||||
#[test]
|
||||
#[cfg(feature = "zeroize")]
|
||||
fn test_deque_zeroize() {
|
||||
use zeroize::Zeroize;
|
||||
|
||||
let mut deque = Deque::<u8, 16>::new();
|
||||
|
||||
for i in 1..=8 {
|
||||
deque.push_back(i).unwrap();
|
||||
}
|
||||
for i in 9..=16 {
|
||||
deque.push_front(i).unwrap();
|
||||
}
|
||||
|
||||
assert_eq!(deque.len(), 16);
|
||||
assert_eq!(deque.front(), Some(&16));
|
||||
assert_eq!(deque.back(), Some(&8));
|
||||
|
||||
// zeroized using Vec's implementation
|
||||
deque.zeroize();
|
||||
|
||||
assert_eq!(deque.len(), 0);
|
||||
assert!(deque.is_empty());
|
||||
}
|
||||
}
|
||||
|
@ -38,10 +38,14 @@ use core::ops::Deref;
|
||||
use core::ptr;
|
||||
use core::slice;
|
||||
|
||||
mod storage {
|
||||
use core::mem::MaybeUninit;
|
||||
#[cfg(feature = "zeroize")]
|
||||
use zeroize::Zeroize;
|
||||
|
||||
mod storage {
|
||||
use super::{HistoryBufInner, HistoryBufView};
|
||||
use core::mem::MaybeUninit;
|
||||
#[cfg(feature = "zeroize")]
|
||||
use zeroize::Zeroize;
|
||||
|
||||
/// Trait defining how data for a container is stored.
|
||||
///
|
||||
@ -83,6 +87,7 @@ mod storage {
|
||||
}
|
||||
|
||||
// One sealed layer of indirection to hide the internal details (The MaybeUninit).
|
||||
#[cfg_attr(feature = "zeroize", derive(Zeroize))]
|
||||
pub struct HistoryBufStorageInner<T: ?Sized> {
|
||||
pub(crate) buffer: T,
|
||||
}
|
||||
@ -148,6 +153,7 @@ use self::storage::HistoryBufSealedStorage;
|
||||
///
|
||||
/// In most cases you should use [`HistoryBuf`] or [`HistoryBufView`] directly. Only use this
|
||||
/// struct if you want to write code that's generic over both.
|
||||
#[cfg_attr(feature = "zeroize", derive(Zeroize))]
|
||||
pub struct HistoryBufInner<T, S: HistoryBufStorage<T> + ?Sized> {
|
||||
// This phantomdata is required because otherwise rustc thinks that `T` is not used
|
||||
phantom: PhantomData<T>,
|
||||
@ -938,6 +944,39 @@ mod tests {
|
||||
assert_eq!(DROP_COUNT.load(Ordering::SeqCst), 3);
|
||||
}
|
||||
|
||||
#[test]
|
||||
#[cfg(feature = "zeroize")]
|
||||
fn test_history_buf_zeroize() {
|
||||
use zeroize::Zeroize;
|
||||
|
||||
let mut buffer = HistoryBuf::<u8, 8>::new();
|
||||
for i in 0..8 {
|
||||
buffer.write(i);
|
||||
}
|
||||
|
||||
assert_eq!(buffer.len(), 8);
|
||||
assert_eq!(buffer.recent(), Some(&7));
|
||||
|
||||
// Clear to mark formerly used memory as unused, to make sure that it also gets zeroed
|
||||
buffer.clear();
|
||||
|
||||
buffer.write(20);
|
||||
assert_eq!(buffer.len(), 1);
|
||||
assert_eq!(buffer.recent(), Some(&20));
|
||||
|
||||
buffer.zeroize();
|
||||
|
||||
assert_eq!(buffer.len(), 0);
|
||||
assert!(buffer.is_empty());
|
||||
|
||||
// Check that all underlying memory actually got zeroized
|
||||
unsafe {
|
||||
for a in buffer.data.buffer {
|
||||
assert_eq!(a.assume_init(), 0);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fn _test_variance<'a: 'b, 'b>(x: HistoryBuf<&'a (), 42>) -> HistoryBuf<&'b (), 42> {
|
||||
x
|
||||
}
|
||||
|
@ -8,6 +8,9 @@ use core::{
|
||||
ops, slice,
|
||||
};
|
||||
|
||||
#[cfg(feature = "zeroize")]
|
||||
use zeroize::Zeroize;
|
||||
|
||||
use hash32::{BuildHasherDefault, FnvHasher};
|
||||
|
||||
use crate::Vec;
|
||||
@ -66,6 +69,7 @@ use crate::Vec;
|
||||
pub type FnvIndexMap<K, V, const N: usize> = IndexMap<K, V, BuildHasherDefault<FnvHasher>, N>;
|
||||
|
||||
#[derive(Clone, Copy, Eq, PartialEq)]
|
||||
#[cfg_attr(feature = "zeroize", derive(Zeroize))]
|
||||
struct HashValue(u16);
|
||||
|
||||
impl HashValue {
|
||||
@ -80,6 +84,7 @@ impl HashValue {
|
||||
|
||||
#[doc(hidden)]
|
||||
#[derive(Clone)]
|
||||
#[cfg_attr(feature = "zeroize", derive(Zeroize))]
|
||||
pub struct Bucket<K, V> {
|
||||
hash: HashValue,
|
||||
key: K,
|
||||
@ -88,6 +93,7 @@ pub struct Bucket<K, V> {
|
||||
|
||||
#[doc(hidden)]
|
||||
#[derive(Clone, Copy, PartialEq)]
|
||||
#[cfg_attr(feature = "zeroize", derive(Zeroize))]
|
||||
pub struct Pos {
|
||||
// compact representation of `{ hash_value: u16, index: u16 }`
|
||||
// To get the most from `NonZero` we store the *value minus 1*. This way `None::Option<Pos>`
|
||||
@ -138,6 +144,11 @@ macro_rules! probe_loop {
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg_attr(
|
||||
feature = "zeroize",
|
||||
derive(Zeroize),
|
||||
zeroize(bound = "K: Zeroize, V: Zeroize")
|
||||
)]
|
||||
struct CoreMap<K, V, const N: usize> {
|
||||
entries: Vec<Bucket<K, V>, N, usize>,
|
||||
indices: [Option<Pos>; N],
|
||||
@ -722,8 +733,14 @@ where
|
||||
/// println!("{}: \"{}\"", book, review);
|
||||
/// }
|
||||
/// ```
|
||||
#[cfg_attr(
|
||||
feature = "zeroize",
|
||||
derive(Zeroize),
|
||||
zeroize(bound = "K: Zeroize, V: Zeroize")
|
||||
)]
|
||||
pub struct IndexMap<K, V, S, const N: usize> {
|
||||
core: CoreMap<K, V, N>,
|
||||
#[cfg_attr(feature = "zeroize", zeroize(skip))]
|
||||
build_hasher: S,
|
||||
}
|
||||
|
||||
@ -1988,4 +2005,28 @@ mod tests {
|
||||
let map: FnvIndexMap<usize, f32, 4> = Default::default();
|
||||
assert_eq!(map, map);
|
||||
}
|
||||
|
||||
#[test]
|
||||
#[cfg(feature = "zeroize")]
|
||||
fn test_index_map_zeroize() {
|
||||
use zeroize::Zeroize;
|
||||
|
||||
let mut map: FnvIndexMap<u8, u8, 8> = FnvIndexMap::new();
|
||||
for i in 1..=8 {
|
||||
map.insert(i, i * 10).unwrap();
|
||||
}
|
||||
|
||||
assert_eq!(map.len(), 8);
|
||||
assert!(!map.is_empty());
|
||||
|
||||
// zeroized using Vec's implementation
|
||||
map.zeroize();
|
||||
|
||||
assert_eq!(map.len(), 0);
|
||||
assert!(map.is_empty());
|
||||
|
||||
map.insert(1, 10).unwrap();
|
||||
assert_eq!(map.len(), 1);
|
||||
assert_eq!(map.get(&1), Some(&10));
|
||||
}
|
||||
}
|
||||
|
@ -5,6 +5,9 @@ use core::{
|
||||
hash::{BuildHasher, Hash},
|
||||
};
|
||||
|
||||
#[cfg(feature = "zeroize")]
|
||||
use zeroize::Zeroize;
|
||||
|
||||
use hash32::{BuildHasherDefault, FnvHasher};
|
||||
|
||||
use crate::index_map::{self, IndexMap};
|
||||
@ -85,6 +88,7 @@ pub type FnvIndexSet<T, const N: usize> = IndexSet<T, BuildHasherDefault<FnvHash
|
||||
/// println!("{}", book);
|
||||
/// }
|
||||
/// ```
|
||||
#[cfg_attr(feature = "zeroize", derive(Zeroize), zeroize(bound = "T: Zeroize"))]
|
||||
pub struct IndexSet<T, S, const N: usize> {
|
||||
map: IndexMap<T, (), S, N>,
|
||||
}
|
||||
@ -696,4 +700,28 @@ mod tests {
|
||||
|
||||
// Ensure a `IndexSet` containing `!Send` values stays `!Send` itself.
|
||||
assert_not_impl_any!(IndexSet<*const (), BuildHasherDefault<()>, 4>: Send);
|
||||
|
||||
#[test]
|
||||
#[cfg(feature = "zeroize")]
|
||||
fn test_index_set_zeroize() {
|
||||
use zeroize::Zeroize;
|
||||
|
||||
let mut set: IndexSet<u8, BuildHasherDefault<hash32::FnvHasher>, 8> = IndexSet::new();
|
||||
for i in 1..=8 {
|
||||
set.insert(i).unwrap();
|
||||
}
|
||||
|
||||
assert_eq!(set.len(), 8);
|
||||
assert!(set.contains(&8));
|
||||
|
||||
// zeroized using index_map's implementation
|
||||
set.zeroize();
|
||||
|
||||
assert_eq!(set.len(), 0);
|
||||
assert!(set.is_empty());
|
||||
|
||||
set.insert(1).unwrap();
|
||||
assert_eq!(set.len(), 1);
|
||||
assert!(set.contains(&1));
|
||||
}
|
||||
}
|
||||
|
@ -3,6 +3,9 @@ use core::{
|
||||
ops::{Add, AddAssign, Sub, SubAssign},
|
||||
};
|
||||
|
||||
#[cfg(feature = "zeroize")]
|
||||
use zeroize::Zeroize;
|
||||
|
||||
pub trait Sealed:
|
||||
Send
|
||||
+ Sync
|
||||
@ -75,6 +78,15 @@ macro_rules! impl_lentype {
|
||||
/// A sealed trait representing a valid type to use as a length for a container.
|
||||
///
|
||||
/// This cannot be implemented in user code, and is restricted to `u8`, `u16`, `u32`, and `usize`.
|
||||
///
|
||||
/// When the `zeroize` feature is enabled, this trait requires the `Zeroize` trait.
|
||||
#[cfg(feature = "zeroize")]
|
||||
pub trait LenType: Sealed + Zeroize {}
|
||||
|
||||
/// A sealed trait representing a valid type to use as a length for a container.
|
||||
///
|
||||
/// This cannot be implemented in user code, and is restricted to `u8`, `u16`, `u32`, and `usize`.
|
||||
#[cfg(not(feature = "zeroize"))]
|
||||
pub trait LenType: Sealed {}
|
||||
|
||||
impl_lentype!(
|
||||
|
10
src/lib.rs
10
src/lib.rs
@ -106,6 +106,16 @@
|
||||
//! - [`mpmc::MpMcQueue`](mpmc): A lock-free multiple-producer, multiple-consumer queue.
|
||||
//! - [`spsc::Queue`](spsc): A lock-free single-producer, single-consumer queue.
|
||||
//!
|
||||
//! # Zeroize Support
|
||||
//!
|
||||
//! The `zeroize` feature enables secure memory wiping for the data structures via the [`zeroize`](https://crates.io/crates/zeroize)
|
||||
//! crate. Sensitive data can be properly erased from memory when no longer needed.
|
||||
//!
|
||||
//! When zeroizing a container, all underlying memory (including unused portion of the containers)
|
||||
//! is overwritten with zeros, length counters are reset, and the container is left in a valid but
|
||||
//! empty state that can be reused.
|
||||
//!
|
||||
//! Check the [documentation of the zeroize crate](https://docs.rs/zeroize/) for more information.
|
||||
//! # Minimum Supported Rust Version (MSRV)
|
||||
//!
|
||||
//! This crate does *not* have a Minimum Supported Rust Version (MSRV) and may make use of language
|
||||
|
@ -4,6 +4,9 @@
|
||||
|
||||
use core::{borrow::Borrow, fmt, mem, ops, slice};
|
||||
|
||||
#[cfg(feature = "zeroize")]
|
||||
use zeroize::Zeroize;
|
||||
|
||||
use crate::vec::{OwnedVecStorage, Vec, VecInner, ViewVecStorage};
|
||||
|
||||
mod storage {
|
||||
@ -88,6 +91,11 @@ pub type OwnedStorage<K, V, const N: usize> = OwnedVecStorage<(K, V), N>;
|
||||
pub type ViewStorage<K, V> = ViewVecStorage<(K, V)>;
|
||||
|
||||
/// Base struct for [`LinearMap`] and [`LinearMapView`]
|
||||
#[cfg_attr(
|
||||
feature = "zeroize",
|
||||
derive(Zeroize),
|
||||
zeroize(bound = "S: Zeroize, K: Zeroize, V: Zeroize")
|
||||
)]
|
||||
pub struct LinearMapInner<K, V, S: LinearMapStorage<K, V> + ?Sized> {
|
||||
pub(crate) buffer: VecInner<(K, V), usize, S>,
|
||||
}
|
||||
@ -752,4 +760,24 @@ mod test {
|
||||
let map: LinearMap<usize, f32, 4> = Default::default();
|
||||
assert_eq!(map, map);
|
||||
}
|
||||
|
||||
#[test]
|
||||
#[cfg(feature = "zeroize")]
|
||||
fn test_linear_map_zeroize() {
|
||||
use zeroize::Zeroize;
|
||||
|
||||
let mut map: LinearMap<u8, u8, 8> = LinearMap::new();
|
||||
for i in 1..=8 {
|
||||
map.insert(i, i * 10).unwrap();
|
||||
}
|
||||
|
||||
assert_eq!(map.len(), 8);
|
||||
assert_eq!(map.get(&5), Some(&50));
|
||||
|
||||
// zeroized using Vec's implementation
|
||||
map.zeroize();
|
||||
|
||||
assert_eq!(map.len(), 0);
|
||||
assert!(map.is_empty());
|
||||
}
|
||||
}
|
||||
|
@ -33,6 +33,9 @@ use core::mem::MaybeUninit;
|
||||
use core::ops::{Deref, DerefMut};
|
||||
use core::ptr;
|
||||
|
||||
#[cfg(feature = "zeroize")]
|
||||
use zeroize::Zeroize;
|
||||
|
||||
mod storage {
|
||||
use super::{LenType, Node, SortedLinkedListInner, SortedLinkedListView};
|
||||
|
||||
@ -191,6 +194,7 @@ impl private::Sealed for Max {}
|
||||
impl private::Sealed for Min {}
|
||||
|
||||
/// A node in the [`SortedLinkedList`].
|
||||
#[cfg_attr(feature = "zeroize", derive(Zeroize))]
|
||||
pub struct Node<T, Idx> {
|
||||
val: MaybeUninit<T>,
|
||||
next: Idx,
|
||||
@ -845,6 +849,26 @@ where
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(feature = "zeroize")]
|
||||
impl<T, Idx, K, S> Zeroize for SortedLinkedListInner<T, Idx, K, S>
|
||||
where
|
||||
T: Ord + Zeroize,
|
||||
Idx: LenType + Zeroize,
|
||||
K: Kind,
|
||||
S: SortedLinkedListStorage<T, Idx> + ?Sized,
|
||||
{
|
||||
fn zeroize(&mut self) {
|
||||
while let Some(mut item) = self.pop() {
|
||||
item.zeroize();
|
||||
}
|
||||
|
||||
let buffer = self.list.borrow_mut();
|
||||
for elem in buffer {
|
||||
elem.zeroize();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use static_assertions::assert_not_impl_any;
|
||||
@ -974,6 +998,38 @@ mod tests {
|
||||
assert_eq!(ll.peek().unwrap(), &1001);
|
||||
}
|
||||
|
||||
#[test]
|
||||
#[cfg(feature = "zeroize")]
|
||||
fn test_sorted_linked_list_zeroize() {
|
||||
use zeroize::Zeroize;
|
||||
|
||||
let mut list: SortedLinkedList<u8, Max, 8, u8> = SortedLinkedList::new_u8();
|
||||
for i in 1..=8 {
|
||||
list.push(i).unwrap();
|
||||
}
|
||||
|
||||
assert_eq!(list.is_empty(), false);
|
||||
assert!(list.is_full());
|
||||
assert_eq!(list.peek(), Some(&8));
|
||||
|
||||
list.pop();
|
||||
list.pop();
|
||||
list.push(100).unwrap();
|
||||
|
||||
assert_eq!(list.peek(), Some(&100));
|
||||
|
||||
list.zeroize();
|
||||
|
||||
assert_eq!(list.peek(), None);
|
||||
assert!(list.is_empty());
|
||||
|
||||
unsafe {
|
||||
for node in &list.list.buffer {
|
||||
assert_eq!(node.val.assume_init(), 0);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fn _test_variance<'a: 'b, 'b>(
|
||||
x: SortedLinkedList<&'a (), Max, 42, u8>,
|
||||
) -> SortedLinkedList<&'b (), Max, 42, u8> {
|
||||
|
@ -11,6 +11,9 @@ use core::{
|
||||
str::{self, Utf8Error},
|
||||
};
|
||||
|
||||
#[cfg(feature = "zeroize")]
|
||||
use zeroize::Zeroize;
|
||||
|
||||
use crate::CapacityError;
|
||||
use crate::{
|
||||
len_type::LenType,
|
||||
@ -142,6 +145,7 @@ pub type ViewStorage = ViewVecStorage<u8>;
|
||||
///
|
||||
/// In most cases you should use [`String`] or [`StringView`] directly. Only use this
|
||||
/// struct if you want to write code that's generic over both.
|
||||
#[cfg_attr(feature = "zeroize", derive(Zeroize), zeroize(bound = "S: Zeroize"))]
|
||||
pub struct StringInner<LenT: LenType, S: StringStorage + ?Sized> {
|
||||
vec: VecInner<u8, LenT, S>,
|
||||
}
|
||||
@ -1462,4 +1466,26 @@ mod tests {
|
||||
let mut s: String<8> = String::try_from("a").unwrap();
|
||||
_ = s.insert_str(2, "a");
|
||||
}
|
||||
|
||||
#[test]
|
||||
#[cfg(feature = "zeroize")]
|
||||
fn test_string_zeroize() {
|
||||
use zeroize::Zeroize;
|
||||
|
||||
let mut s: String<32> = String::try_from("sensitive_password").unwrap();
|
||||
|
||||
assert_eq!(s.as_str(), "sensitive_password");
|
||||
assert!(!s.is_empty());
|
||||
assert_eq!(s.len(), 18);
|
||||
|
||||
s.truncate(9);
|
||||
assert_eq!(s.as_str(), "sensitive");
|
||||
assert_eq!(s.len(), 9);
|
||||
|
||||
// zeroized using Vec's implementation
|
||||
s.zeroize();
|
||||
|
||||
assert_eq!(s.len(), 0);
|
||||
assert!(s.is_empty());
|
||||
}
|
||||
}
|
||||
|
@ -11,6 +11,9 @@ use core::{
|
||||
slice,
|
||||
};
|
||||
|
||||
#[cfg(feature = "zeroize")]
|
||||
use zeroize::Zeroize;
|
||||
|
||||
use crate::len_type::{check_capacity_fits, LenType};
|
||||
use crate::CapacityError;
|
||||
|
||||
@ -84,7 +87,11 @@ mod storage {
|
||||
Self: VecStorage<T>;
|
||||
}
|
||||
|
||||
#[cfg(feature = "zeroize")]
|
||||
use zeroize::Zeroize;
|
||||
|
||||
// One sealed layer of indirection to hide the internal details (The MaybeUninit).
|
||||
#[cfg_attr(feature = "zeroize", derive(Zeroize))]
|
||||
pub struct VecStorageInner<T: ?Sized> {
|
||||
pub(crate) buffer: T,
|
||||
}
|
||||
@ -208,6 +215,7 @@ pub use drain::Drain;
|
||||
///
|
||||
/// In most cases you should use [`Vec`] or [`VecView`] directly. Only use this
|
||||
/// struct if you want to write code that's generic over both.
|
||||
#[cfg_attr(feature = "zeroize", derive(Zeroize), zeroize(bound = "S: Zeroize"))]
|
||||
pub struct VecInner<T, LenT: LenType, S: VecStorage<T> + ?Sized> {
|
||||
phantom: PhantomData<T>,
|
||||
len: LenT,
|
||||
@ -2247,6 +2255,69 @@ mod tests {
|
||||
<alloc::vec::Vec<u8> as TryInto<Vec<u8, 1>>>::try_into(av.clone()).unwrap_err();
|
||||
}
|
||||
|
||||
#[test]
|
||||
#[cfg(feature = "zeroize")]
|
||||
fn test_vec_zeroize() {
|
||||
use zeroize::Zeroize;
|
||||
|
||||
let mut v: Vec<u8, 8> = Vec::new();
|
||||
for i in 0..8 {
|
||||
v.push(i).unwrap();
|
||||
}
|
||||
|
||||
for i in 0..8 {
|
||||
assert_eq!(v[i], i as u8);
|
||||
}
|
||||
|
||||
v.truncate(4);
|
||||
assert_eq!(v.len(), 4);
|
||||
|
||||
for i in 0..4 {
|
||||
assert_eq!(v[i], i as u8);
|
||||
}
|
||||
|
||||
v.zeroize();
|
||||
|
||||
assert_eq!(v.len(), 0);
|
||||
|
||||
unsafe {
|
||||
v.set_len(8);
|
||||
}
|
||||
|
||||
for i in 0..8 {
|
||||
assert_eq!(v[i], 0);
|
||||
}
|
||||
}
|
||||
|
||||
#[test]
|
||||
#[cfg(feature = "zeroize")]
|
||||
fn test_vecview_zeroize() {
|
||||
use zeroize::Zeroize;
|
||||
|
||||
let mut v: Vec<u8, 8> = Vec::new();
|
||||
for i in 0..8 {
|
||||
v.push(i).unwrap();
|
||||
}
|
||||
|
||||
let view = v.as_mut_view();
|
||||
|
||||
for i in 0..8 {
|
||||
assert_eq!(view[i], i as u8);
|
||||
}
|
||||
|
||||
view.zeroize();
|
||||
|
||||
assert_eq!(view.len(), 0);
|
||||
|
||||
unsafe {
|
||||
view.set_len(8);
|
||||
}
|
||||
|
||||
for i in 0..8 {
|
||||
assert_eq!(view[i], 0);
|
||||
}
|
||||
}
|
||||
|
||||
fn _test_variance<'a: 'b, 'b>(x: Vec<&'a (), 42>) -> Vec<&'b (), 42> {
|
||||
x
|
||||
}
|
||||
|
Loading…
x
Reference in New Issue
Block a user