Expand description
Securely zero memory with a simple trait (Zeroize) built on stable Rust primitives which guarantee the operation will not be “optimized away”.
About
Zeroing memory securely is hard - compilers optimize for performance, and in doing so they love to “optimize away” unnecessary zeroing calls. There are many documented “tricks” to attempt to avoid these optimizations and ensure that a zeroing routine is performed reliably.
This crate isn’t about tricks: it uses core::ptr::write_volatile and core::sync::atomic memory fences to provide easy-to-use, portable zeroing behavior which works on all of Rust’s core number types and slices thereof, implemented in pure Rust with no usage of FFI or assembly.
- No insecure fallbacks!
- No dependencies!
- No FFI or inline assembly! WASM friendly (and tested)!
#![no_std]
i.e. embedded-friendly!- No functionality besides securely zeroing memory!
- (Optional) Custom derive support for zeroing complex structures
Minimum Supported Rust Version
Requires Rust 1.47 or newer.
In the future, we reserve the right to change MSRV (i.e. MSRV is out-of-scope for this crate’s SemVer guarantees), however when we do it will be accompanied by a minor version bump.
Usage
use zeroize::Zeroize;
fn main() {
// Protip: don't embed secrets in your source code.
// This is just an example.
let mut secret = b"Air shield password: 1,2,3,4,5".to_vec();
// [ ... ] open the air shield here
// Now that we're done using the secret, zero it out.
secret.zeroize();
}
The Zeroize trait is impl’d on all of Rust’s core scalar types including
integers, floats, bool
, and char
.
Additionally, it’s implemented on slices and IterMut
s of the above types.
When the alloc
feature is enabled (which it is by default), it’s also
impl’d for Vec<T>
for the above types as well as String
, where it provides
Vec::clear() / String::clear()-like behavior (truncating to zero-length)
but ensures the backing memory is securely zeroed with some caveats.
(NOTE: see “Stack/Heap Zeroing Notes” for important Vec
/String
details)
The DefaultIsZeroes marker trait can be impl’d on types which also impl Default, which implements Zeroize by overwriting a value with the default value.
Custom Derive Support
This crate has custom derive support for the Zeroize
trait,
gated under the zeroize
crate’s zeroize_derive
Cargo feature,
which automatically calls zeroize()
on all members of a struct
or tuple struct.
Additionally it supports the following attribute:
#[zeroize(drop)]
: callzeroize()
when this item is dropped
Example which derives Drop
:
use zeroize::Zeroize;
// This struct will be zeroized on drop
#[derive(Zeroize)]
#[zeroize(drop)]
struct MyStruct([u8; 32]);
Example which does not derive Drop
(useful for e.g. Copy
types)
#[cfg(feature = "derive")]
use zeroize::Zeroize;
// This struct will *NOT* be zeroized on drop
#[derive(Copy, Clone, Zeroize)]
struct MyStruct([u8; 32]);
Zeroizing<Z>
: wrapper for zeroizing arbitrary values on drop
Zeroizing<Z: Zeroize>
is a generic wrapper type that impls Deref
and DerefMut
, allowing access to an inner value of type Z
, and also
impls a Drop
handler which calls zeroize()
on its contents:
use zeroize::Zeroizing;
fn main() {
let mut secret = Zeroizing::new([0u8; 5]);
// Set the air shield password
// Protip (again): don't embed secrets in your source code.
secret.copy_from_slice(&[1, 2, 3, 4, 5]);
assert_eq!(secret.as_ref(), &[1, 2, 3, 4, 5]);
// The contents of `secret` will be automatically zeroized on drop
}
What guarantees does this crate provide?
This crate guarantees the following:
- The zeroing operation can’t be “optimized away” by the compiler.
- All subsequent reads to memory will see “zeroized” values.
LLVM’s volatile semantics ensure #1 is true.
Additionally, thanks to work by the Unsafe Code Guidelines Working Group,
we can now fairly confidently say #2 is true as well. Previously there were
worries that the approach used by this crate (mixing volatile and
non-volatile accesses) was undefined behavior due to language contained
in the documentation for write_volatile
, however after some discussion
these remarks have been removed and the specific usage pattern in this
crate is considered to be well-defined.
Additionally this crate leverages compiler_fence from core::sync::atomic with the strictest ordering (Ordering::SeqCst) as a precaution to help ensure reads are not reordered before memory has been zeroed.
All of that said, there is still potential for microarchitectural attacks (ala Spectre/Meltdown) to leak “zeroized” secrets through covert channels. This crate makes no guarantees that zeroized values cannot be leaked through such channels, as they represent flaws in the underlying hardware.
Stack/Heap Zeroing Notes
This crate can be used to zero values from either the stack or the heap.
However, be aware several operations in Rust can unintentionally leave copies of data in memory. This includes but is not limited to:
- Moves and
Copy
- Heap reallocation when using
Vec
andString
- Borrowers of a reference making copies of the data
Pin
can be leveraged in conjunction with this crate to ensure
data kept on the stack isn’t moved.
The Zeroize
impls for Vec
and String
zeroize the entire capacity of
their backing buffer, but cannot guarantee copies of the data were not
previously made by buffer reallocation. It’s therefore important when
attempting to zeroize such buffers to initialize them to the correct
capacity, and take care to prevent subsequent reallocation.
The secrecy
crate provides higher-level abstractions for eliminating
usage patterns which can cause reallocations:
https://crates.io/crates/secrecy
What about: clearing registers, mlock, mprotect, etc?
This crate is focused on providing simple, unobtrusive support for reliably zeroing memory using the best approach possible on stable Rust.
Clearing registers is a difficult problem that can’t easily be solved by something like a crate, and requires either inline ASM or rustc support. See https://github.com/rust-lang/rust/issues/17046 for background on this particular problem.
Other memory protection mechanisms are interesting and useful, but often overkill (e.g. defending against RAM scraping or attackers with swap access). In as much as there may be merit to these approaches, there are also many other crates that already implement more sophisticated memory protections. Such protections are explicitly out-of-scope for this crate.
Zeroing memory is good cryptographic hygiene and this crate seeks to promote
it in the most unobtrusive manner possible. This includes omitting complex
unsafe
memory protection systems and just trying to make the best memory
zeroing crate available.
Structs
Zeroizing
is a a wrapper for any Z: Zeroize
type which implements a
Drop
handler which zeroizes dropped values.
Traits
Marker trait for types whose Default
is the desired zeroization result
Fallible trait for representing cases where zeroization may or may not be possible.
Trait for securely erasing types from memory
Derive Macros
Derive the Zeroize
trait.