From 47c8901ab596c01d05a041ec6ff89683ebb68201 Mon Sep 17 00:00:00 2001 From: Gauravpawar101 Date: Thu, 8 Oct 2026 11:06:11 +0530 Subject: [PATCH 1/2] Add a zeroize feature to erase SmallVec contents --- Cargo.lock | 7 ++++++ Cargo.toml | 6 ++++++ README.md | 1 + src/lib.rs | 19 ++++++++++++++++ tests/zeroize.rs | 56 ++++++++++++++++++++++++++++++++++++++++++++++++ 5 files changed, 89 insertions(+) create mode 100644 tests/zeroize.rs diff --git a/Cargo.lock b/Cargo.lock index bb58235d..7954adc7 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -813,6 +813,7 @@ dependencies = [ "rayon", "serde_core", "serde_test", + "zeroize", ] [[package]] @@ -1058,6 +1059,12 @@ dependencies = [ "syn 2.0.119", ] +[[package]] +name = "zeroize" +version = "1.9.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e13084392c5e4bc371903e2935a5eaeed24905a7511356b883835e18a78f6879" + [[package]] name = "zmij" version = "1.0.23" diff --git a/Cargo.toml b/Cargo.toml index 6c38b5f6..c00fa40a 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -36,6 +36,7 @@ rayon = ["std", "dep:rayon"] serde = ["dep:serde_core"] specialization = [] std = [] +zeroize = ["dep:zeroize"] [dependencies] allocator-api2 = { version = "0.4", optional = true, features = ["alloc"], default-features = false } @@ -46,6 +47,7 @@ defmt = { version = "1.1", optional = true, default-features = false } encase = { version = "0.13", optional = true, default-features = false } rayon = { version = "1.12", optional = true, default-features = false } serde_core = { version = "1.0", optional = true, default-features = false } +zeroize = { version = "1", optional = true, default-features = false } malloc_size_of = { version = "0.1", optional = true, default-features = false } [dev-dependencies] @@ -81,6 +83,10 @@ required-features = ["serde"] name = "std" required-features = ["std"] +[[test]] +name = "zeroize" +required-features = ["zeroize"] + [[bench]] name = "bench" path = "benches/bench.rs" diff --git a/README.md b/README.md index 7bdabf5d..153476b4 100644 --- a/README.md +++ b/README.md @@ -65,6 +65,7 @@ v.sort(); - `specialization` (nightly): enables specialization, improving performance on some cases - `std`: implements the `std::io::Write` type for `SmallVec` +- `zeroize`: implements `zeroize::Zeroize`, erasing elements and storage > [!NOTE] > diff --git a/src/lib.rs b/src/lib.rs index 9f391826..895f7dd2 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -55,6 +55,8 @@ use defmt::{ pub use errors::SmallVecError; #[cfg(feature = "std")] use std::io; +#[cfg(feature = "zeroize")] +use zeroize::Zeroize; use { allocator::{ Allocator, @@ -1796,6 +1798,23 @@ impl Debug for SmallVec { } } +#[cfg(feature = "zeroize")] +#[cfg_attr(docsrs, doc(cfg(feature = "zeroize")))] +impl Zeroize for SmallVec { + fn zeroize(&mut self) { + // Wipe the spare capacity before `clear` resets the length to zero, + // otherwise it would cover the whole allocation and wipe the elements + // a second time. + self.spare_capacity_mut().zeroize(); + + // Zeroize the elements in place so nested allocations, such as a + // `String`'s heap buffer, are wiped before they are dropped. + self.iter_mut().zeroize(); + + self.clear(); + } +} + #[cfg(feature = "arbitrary")] #[cfg_attr(docsrs, doc(cfg(feature = "arbitrary")))] impl<'a, T, const N: usize> arbitrary::Arbitrary<'a> for SmallVec diff --git a/tests/zeroize.rs b/tests/zeroize.rs new file mode 100644 index 00000000..85cd76f6 --- /dev/null +++ b/tests/zeroize.rs @@ -0,0 +1,56 @@ +use { + smallvec::SmallVec, + zeroize::Zeroize +}; + +fn assert_fully_zeroed(v: &mut SmallVec) { + assert!(v.is_empty()); + assert!( + v.spare_capacity_mut() + .iter() + .all(|slot| unsafe { slot.assume_init() } == 0) + ); +} + +#[test] +fn zeroize_inline() { + let mut v: SmallVec = SmallVec::from([1, 2, 3, 4]); + v.push(5); + assert!(!v.spilled()); + + v.zeroize(); + + assert_eq!(v.capacity(), 8); + assert_fully_zeroed(&mut v); + + // It is still usable. + v.extend_from_slice(&[1, 2, 3]); + assert_eq!(v.as_slice(), &[1, 2, 3]); +} + +#[test] +fn zeroize_spilled() { + let mut v: SmallVec = SmallVec::from([1, 2, 3, 4, 5, 6, 7, 8]); + v.push(9); + assert!(v.spilled()); + let capacity = v.capacity(); + + v.zeroize(); + + assert!(v.spilled()); + assert_eq!(v.capacity(), capacity); + assert_fully_zeroed(&mut v); + + v.extend_from_slice(&[10, 11]); + assert_eq!(v.as_slice(), &[10, 11]); +} + +#[test] +fn zeroize_empty() { + let mut v: SmallVec = SmallVec::new(); + + v.zeroize(); + + assert_eq!(v.capacity(), 4); + assert_fully_zeroed(&mut v); +} From a69b87629dc5a25d4c6e8183e7db5e7b50651654 Mon Sep 17 00:00:00 2001 From: Gauravpawar101 Date: Thu, 8 Oct 2026 18:06:21 +0530 Subject: [PATCH 2/2] Pin zeroize to MAJOR.MINOR and drop the docsrs attr --- Cargo.toml | 2 +- src/lib.rs | 1 - 2 files changed, 1 insertion(+), 2 deletions(-) diff --git a/Cargo.toml b/Cargo.toml index c00fa40a..99285125 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -47,7 +47,7 @@ defmt = { version = "1.1", optional = true, default-features = false } encase = { version = "0.13", optional = true, default-features = false } rayon = { version = "1.12", optional = true, default-features = false } serde_core = { version = "1.0", optional = true, default-features = false } -zeroize = { version = "1", optional = true, default-features = false } +zeroize = { version = "1.9", optional = true, default-features = false } malloc_size_of = { version = "0.1", optional = true, default-features = false } [dev-dependencies] diff --git a/src/lib.rs b/src/lib.rs index 895f7dd2..f70aa017 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -1799,7 +1799,6 @@ impl Debug for SmallVec { } #[cfg(feature = "zeroize")] -#[cfg_attr(docsrs, doc(cfg(feature = "zeroize")))] impl Zeroize for SmallVec { fn zeroize(&mut self) { // Wipe the spare capacity before `clear` resets the length to zero,