61
61
//! impl'd for `Vec<T>` for the above types as well as `String`, where it provides
62
62
//! [`Vec::clear`] / [`String::clear`]-like behavior (truncating to zero-length)
63
63
//! but ensures the backing memory is securely zeroed with some caveats.
64
- //! (NOTE: see "Stack/Heap Zeroing Notes" for important `Vec`/`String` details)
64
+ //!
65
+ //! With the `std` feature enabled (which it is **not** by default), [`Zeroize`]
66
+ //! is also implemented for [`CString`]. After calling `zeroize()` on a `CString`,
67
+ //! it will its internal buffer will contain exactly one nul byte. The backing
68
+ //! memory is zeroed by converting it to a `Vec<u8>` and back into a `CString`.
69
+ //! (NOTE: see "Stack/Heap Zeroing Notes" for important `Vec`/`String`/`CString` details)
70
+ //!
65
71
//!
66
72
//! The [`DefaultIsZeroes`] marker trait can be impl'd on types which also
67
73
//! impl [`Default`], which implements [`Zeroize`] by overwriting a value with
191
197
//! [`Pin`][`core::pin::Pin`] can be leveraged in conjunction with this crate
192
198
//! to ensure data kept on the stack isn't moved.
193
199
//!
194
- //! The `Zeroize` impls for `Vec` and `String ` zeroize the entire capacity of
195
- //! their backing buffer, but cannot guarantee copies of the data were not
196
- //! previously made by buffer reallocation. It's therefore important when
197
- //! attempting to zeroize such buffers to initialize them to the correct
200
+ //! The `Zeroize` impls for `Vec`, `String` and `CString ` zeroize the entire
201
+ //! capacity of their backing buffer, but cannot guarantee copies of the data
202
+ //! were not previously made by buffer reallocation. It's therefore important
203
+ //! when attempting to zeroize such buffers to initialize them to the correct
198
204
//! capacity, and take care to prevent subsequent reallocation.
199
205
//!
200
206
//! The `secrecy` crate provides higher-level abstractions for eliminating
233
239
#[ cfg_attr( test, macro_use) ]
234
240
extern crate alloc;
235
241
242
+ #[ cfg( feature = "std" ) ]
243
+ extern crate std;
244
+
236
245
#[ cfg( feature = "zeroize_derive" ) ]
237
246
#[ cfg_attr( docsrs, doc( cfg( feature = "zeroize_derive" ) ) ) ]
238
247
pub use zeroize_derive:: { Zeroize , ZeroizeOnDrop } ;
@@ -253,6 +262,9 @@ use core::{ops, ptr, slice::IterMut, sync::atomic};
253
262
#[ cfg( feature = "alloc" ) ]
254
263
use alloc:: { boxed:: Box , string:: String , vec:: Vec } ;
255
264
265
+ #[ cfg( feature = "std" ) ]
266
+ use std:: ffi:: CString ;
267
+
256
268
/// Trait for securely erasing types from memory
257
269
pub trait Zeroize {
258
270
/// Zero out this object from memory using Rust intrinsics which ensure the
@@ -511,6 +523,27 @@ impl Zeroize for String {
511
523
}
512
524
}
513
525
526
+ #[ cfg( feature = "std" ) ]
527
+ #[ cfg_attr( docsrs, doc( cfg( feature = "std" ) ) ) ]
528
+ impl Zeroize for CString {
529
+ fn zeroize ( & mut self ) {
530
+ // mem::take uses replace internally to swap the pointer
531
+ // Unfortunately this results in an allocation for a Box::new(&[0]) as CString must
532
+ // contain a trailing zero byte
533
+ let this = std:: mem:: take ( self ) ;
534
+ // - CString::into_bytes calls ::into_vec which takes ownership of the heap pointer
535
+ // as a Vec<u8>
536
+ // - Calling .zeroize() on the resulting vector clears out the bytes
537
+ // From: https://github.com/RustCrypto/utils/pull/759#issuecomment-1087976570
538
+ let mut buf = this. into_bytes ( ) ;
539
+ buf. zeroize ( ) ;
540
+ // expect() should never fail, because zeroize() truncates the Vec
541
+ let zeroed = CString :: new ( buf) . expect ( "buf not truncated" ) ;
542
+ // Replace self by the zeroed CString to maintain the original ptr of the buffer
543
+ let _ = std:: mem:: replace ( self , zeroed) ;
544
+ }
545
+ }
546
+
514
547
/// Fallible trait for representing cases where zeroization may or may not be
515
548
/// possible.
516
549
///
@@ -723,6 +756,9 @@ mod tests {
723
756
#[ cfg( feature = "alloc" ) ]
724
757
use alloc:: vec:: Vec ;
725
758
759
+ #[ cfg( feature = "std" ) ]
760
+ use std:: ffi:: CString ;
761
+
726
762
#[ derive( Clone , Debug , PartialEq ) ]
727
763
struct ZeroizedOnDrop ( u64 ) ;
728
764
@@ -881,6 +917,27 @@ mod tests {
881
917
assert ! ( as_vec. iter( ) . all( |byte| * byte == 0 ) ) ;
882
918
}
883
919
920
+ #[ cfg( feature = "std" ) ]
921
+ #[ test]
922
+ fn zeroize_c_string ( ) {
923
+ let mut cstring = CString :: new ( "Hello, world!" ) . expect ( "CString::new failed" ) ;
924
+ let orig_len = cstring. as_bytes ( ) . len ( ) ;
925
+ let orig_ptr = cstring. as_bytes ( ) . as_ptr ( ) ;
926
+ cstring. zeroize ( ) ;
927
+ // This doesn't quite test that the original memory has been cleared, but only that
928
+ // cstring now owns an empty buffer
929
+ assert ! ( cstring. as_bytes( ) . is_empty( ) ) ;
930
+ for i in 0 ..orig_len {
931
+ unsafe {
932
+ // Using a simple deref, only one iteration of the loop is performed
933
+ // presumably because after zeroize, the internal buffer has a length of one/
934
+ // `read_volatile` seems to "fix" this
935
+ // Note that this is very likely UB
936
+ assert_eq ! ( orig_ptr. add( i) . read_volatile( ) , 0 ) ;
937
+ }
938
+ }
939
+ }
940
+
884
941
#[ cfg( feature = "alloc" ) ]
885
942
#[ test]
886
943
fn zeroize_box ( ) {
0 commit comments