Skip to content

Commit

Permalink
start, support the zeroize crate for data security
Browse files Browse the repository at this point in the history
* implement a fn zeroize method on Repr that's derived from the zeroize crate
* add proptest and fuzz coverage for the new feature
  • Loading branch information
ParkMyCar committed Jan 24, 2025
1 parent c9f31cd commit f7b3318
Show file tree
Hide file tree
Showing 6 changed files with 116 additions and 2 deletions.
2 changes: 2 additions & 0 deletions compact_str/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -29,6 +29,7 @@ sqlx = ["dep:sqlx", "std"]
sqlx-mysql = ["sqlx", "sqlx/mysql"]
sqlx-postgres = ["sqlx", "sqlx/postgres"]
sqlx-sqlite = ["sqlx", "sqlx/sqlite"]
zeroize = ["dep:zeroize"]

[dependencies]
arbitrary = { version = "1", optional = true, default-features = false }
Expand All @@ -42,6 +43,7 @@ rkyv = { version = "0.8", optional = true, default-features = false }
serde = { version = "1", optional = true, default-features = false, features = ["derive", "alloc"] }
smallvec = { version = "1", optional = true, features = ["union"] }
sqlx = { version = "0.8", optional = true, default-features = false }
zeroize = { version = "1", optional = true, default-features = false }

castaway = { version = "0.2.3", default-features = false, features = ["alloc"] }
cfg-if = "1"
Expand Down
2 changes: 2 additions & 0 deletions compact_str/src/features/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -22,3 +22,5 @@ mod serde;
mod smallvec;
#[cfg(feature = "sqlx")]
mod sqlx;
#[cfg(feature = "zeroize")]
mod zeroize;
44 changes: 44 additions & 0 deletions compact_str/src/features/zeroize.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,44 @@
//! Implements the [`zeroize::Zeroize`] trait for [`CompactString`]
use crate::CompactString;
use zeroize::Zeroize;

#[cfg_attr(docsrs, doc(cfg(feature = "zeroize")))]
impl Zeroize for CompactString {
fn zeroize(&mut self) {
self.0.zeroize();
}
}

#[cfg(test)]
mod tests {
use alloc::string::String;
use test_strategy::proptest;

use super::*;
use crate::tests::rand_unicode;

#[test]
fn smoketest_zeroize() {
let mut short = CompactString::from("hello");
short.zeroize();
assert_eq!(short, "\0\0\0\0\0");

let mut long = CompactString::from("I am a long string that will be on the heap");
long.zeroize();
assert_eq!(long, "\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0");
assert!(long.is_heap_allocated());
}

#[proptest]
#[cfg_attr(miri, ignore)]
fn proptest_zeroize(#[strategy(rand_unicode())] s: String) {
let mut compact = CompactString::new(s.clone());
let mut control = s.clone();

compact.zeroize();
control.zeroize();

assert_eq!(compact, control);
}
}
58 changes: 58 additions & 0 deletions compact_str/src/repr/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -533,6 +533,64 @@ impl Repr {
}
}

/// Zero out the memory backing this [`Repr`].
#[cfg(feature = "zeroize")]
pub(crate) fn zeroize(&mut self) {
// We can't zero out static memory so we just replace ourselves with
// the EMPTY variant.
if self.is_static_str() {
*self = EMPTY;
return;
}

/// Performs a volatile `memset` operation which fills a slice with a value.
///
/// # SAFETY:
///
/// * The memory pointed to by `dst` must be valid for `count` contiguous bytes.
/// * `count` must not be larger than an isize
/// * `dst` + `count` must not wrap around the address space.
///
/// Derived from: <https://github.com/RustCrypto/utils/blob/c68a5204b2e66b0f60832d845e048fca96a81211/zeroize/src/lib.rs#L766-L791>.
///
/// TODO(parkmycar): use `volatile_set_memory` when stabilized
#[inline(always)]
unsafe fn volatile_zero(dst: *mut u8, count: usize) {
for i in 0..count {
let dst = dst.add(i);
ptr::write_volatile(dst, 0);
}
}

/// Uses fences to prevent the compiler from re-ordering memory accesses.
#[inline(always)]
fn atomic_fence() {
use core::sync::atomic;
atomic::compiler_fence(atomic::Ordering::SeqCst);
}

// The last byte stores our discriminant and stack length.
let last_byte = self.last_byte();

let (ptr, cap) = if last_byte == HEAP_MASK {
// SAFETY: We just checked the discriminant to make sure we're heap allocated
let heap_buffer = unsafe { self.as_mut_heap() };
let ptr = heap_buffer.ptr.as_ptr();
let cap = heap_buffer.capacity();
(ptr, cap)
} else {
let ptr = self as *mut Self as *mut u8;
let cap = MAX_SIZE - 1;
(ptr, cap)
};

// SAFTEY: We know our pointer is valid for `cap` bytes because the capacity came
// from an already existing CompactString. Also we don't allow allocations larger
// then an isize.
unsafe { volatile_zero(ptr, cap) };
atomic_fence()
}

/// Returns the last byte that's on the stack.
///
/// The last byte stores the discriminant that indicates whether the string is on the stack or
Expand Down
3 changes: 2 additions & 1 deletion fuzz/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -11,9 +11,10 @@ cargo-fuzz = true
[dependencies]
arbitrary = { version = "1", features = ["derive"] }
bytes = "1"
compact_str = { path = "../compact_str", features = ["bytes", "smallvec"] }
compact_str = { path = "../compact_str", features = ["bytes", "smallvec", "zeroize"] }
rand = { version = "0.8", features = ["small_rng"] }
rand_distr = "0.4"
zeroize = "1"

# Fuzz with both AFL++ and libFuzzer
afl = { version = "0.14.2", optional = true }
Expand Down
9 changes: 8 additions & 1 deletion fuzz/src/actions.rs
Original file line number Diff line number Diff line change
Expand Up @@ -46,8 +46,10 @@ pub enum Action<'a> {
CloneAndDrop,
/// Calls into_bytes, validates equality, and converts back into strings
RoundTripIntoBytes,
// Repeat the string to form a new string.
/// Repeat the string to form a new string.
Repeat(usize),
/// Zero out the data backing the string.
Zeroize,
}

impl Action<'_> {
Expand Down Expand Up @@ -405,6 +407,11 @@ impl Action<'_> {
*compact = new_compact;
*control = new_control;
}
Zeroize => {
use zeroize::Zeroize;
control.zeroize();
compact.zeroize();
}
}
}
}
Expand Down

0 comments on commit f7b3318

Please sign in to comment.