From 9d8d620c6e1b144b2a9374aaf8971149a726386e Mon Sep 17 00:00:00 2001 From: daxpedda Date: Tue, 4 Jan 2022 18:57:18 +0100 Subject: [PATCH] Implement `ZeroizeOnDrop` --- zeroize/derive/src/lib.rs | 125 ++++++++++++++++++++++++++++---- zeroize/src/lib.rs | 35 +++++++-- zeroize/tests/zeroize_derive.rs | 45 ++++++++---- 3 files changed, 168 insertions(+), 37 deletions(-) diff --git a/zeroize/derive/src/lib.rs b/zeroize/derive/src/lib.rs index 4566ac15..549cda07 100644 --- a/zeroize/derive/src/lib.rs +++ b/zeroize/derive/src/lib.rs @@ -22,7 +22,7 @@ decl_derive!( /// Supports the following attributes: /// /// On the item level: - /// - `#[zeroize(drop)]`: call `zeroize()` when this item is dropped + /// - `#[zeroize(drop)]`: *deprecated* use `ZeroizeOnDrop` instead /// - `#[zeroize(bound = "T: MyTrait")]`: this replaces any trait bounds /// inferred by zeroize-derive /// @@ -31,6 +31,18 @@ decl_derive!( derive_zeroize ); +decl_derive!( + [ZeroizeOnDrop, attributes(zeroize)] => + + /// Derive the `ZeroizeOnDrop` trait. + /// + /// Supports the following attributes: + /// + /// On the field level: + /// - `#[zeroize(skip)]`: skips this field or variant when calling `zeroize()` + derive_zeroize_on_drop +); + /// Name of zeroize-related attributes const ZEROIZE_ATTR: &str = "zeroize"; @@ -55,6 +67,29 @@ fn derive_zeroize(mut s: synstructure::Structure<'_>) -> TokenStream { } } +/// Custom derive for `ZeroizeOnDrop` +fn derive_zeroize_on_drop(mut s: synstructure::Structure<'_>) -> TokenStream { + let zeroizers = generate_fields(&mut s); + + let drop_impl = s.gen_impl(quote! { + gen impl Drop for @Self { + fn drop(&mut self) { + match self { + #zeroizers + } + } + } + }); + + let zeroize_on_drop_impl = impl_zeroize_on_drop(&s); + + quote! { + #drop_impl + + #zeroize_on_drop_impl + } +} + /// Custom derive attributes for `Zeroize` #[derive(Default)] struct ZeroizeAttrs { @@ -216,6 +251,23 @@ impl ZeroizeAttrs { } } +fn generate_fields(s: &mut synstructure::Structure<'_>) -> TokenStream { + s.bind_with(|_| BindStyle::RefMut); + + s.filter_variants(|vi| { + let result = filter_skip(vi.ast().attrs, true); + + // check for duplicate `#[zeroize(skip)]` attributes in nested variants + for field in vi.ast().fields { + filter_skip(&field.attrs, result); + } + + result + }) + .filter(|bi| filter_skip(&bi.ast().attrs, true)) + .each(|bi| quote! { #bi.zeroize(); }) +} + fn filter_skip(attrs: &[Attribute], start: bool) -> bool { let mut result = start; @@ -239,21 +291,7 @@ fn filter_skip(attrs: &[Attribute], start: bool) -> bool { /// Custom derive for `Zeroize` (without `Drop`) fn derive_zeroize_without_drop(mut s: synstructure::Structure<'_>) -> TokenStream { - s.bind_with(|_| BindStyle::RefMut); - - let zeroizers = s - .filter_variants(|vi| { - let result = filter_skip(vi.ast().attrs, true); - - // check for duplicate `#[zeroize(skip)]` attributes in nested variants - for field in vi.ast().fields { - filter_skip(&field.attrs, result); - } - - result - }) - .filter(|bi| filter_skip(&bi.ast().attrs, true)) - .each(|bi| quote! { #bi.zeroize(); }); + let zeroizers = generate_fields(&mut s); s.bound_impl( quote!(zeroize::Zeroize), @@ -277,6 +315,8 @@ fn derive_zeroize_with_drop(s: synstructure::Structure<'_>) -> TokenStream { } }); + let zeroize_on_drop_impl = impl_zeroize_on_drop(&s); + let zeroize_impl = derive_zeroize_without_drop(s); quote! { @@ -284,9 +324,16 @@ fn derive_zeroize_with_drop(s: synstructure::Structure<'_>) -> TokenStream { #[doc(hidden)] #drop_impl + + #zeroize_on_drop_impl } } +fn impl_zeroize_on_drop(s: &synstructure::Structure<'_>) -> TokenStream { + #[allow(unused_qualifications)] + s.bound_impl(quote!(zeroize::ZeroizeOnDrop), Option::::None) +} + #[cfg(test)] mod tests { use super::*; @@ -369,6 +416,12 @@ mod tests { } } }; + #[allow(non_upper_case_globals)] + #[doc(hidden)] + const _DERIVE_zeroize_ZeroizeOnDrop_FOR_Z: () = { + extern crate zeroize; + impl zeroize::ZeroizeOnDrop for Z {} + }; } no_build // tests the code compiles are in the `zeroize` crate } @@ -439,6 +492,46 @@ mod tests { } } + #[test] + fn zeroize_only_drop() { + test_derive! { + derive_zeroize_on_drop { + struct Z { + a: String, + b: Vec, + c: [u8; 3], + } + } + expands to { + #[allow(non_upper_case_globals)] + const _DERIVE_Drop_FOR_Z: () = { + impl Drop for Z { + fn drop(&mut self) { + match self { + Z { + a: ref mut __binding_0, + b: ref mut __binding_1, + c: ref mut __binding_2, + } => { + { __binding_0.zeroize(); } + { __binding_1.zeroize(); } + { __binding_2.zeroize(); } + } + } + } + } + }; + #[allow(non_upper_case_globals)] + #[doc(hidden)] + const _DERIVE_zeroize_ZeroizeOnDrop_FOR_Z: () = { + extern crate zeroize; + impl zeroize::ZeroizeOnDrop for Z {} + }; + } + no_build // tests the code compiles are in the `zeroize` crate + } + } + #[test] fn zeroize_on_struct() { parse_zeroize_test(stringify!( diff --git a/zeroize/src/lib.rs b/zeroize/src/lib.rs index 17ad75ca..05c29d9c 100644 --- a/zeroize/src/lib.rs +++ b/zeroize/src/lib.rs @@ -66,26 +66,30 @@ //! which automatically calls `zeroize()` on all members of a struct //! or tuple struct. //! -//! Additionally it supports the following attributes: +//! Attributes supported for `Zeroize`: //! //! On the item level: -//! - `#[zeroize(drop)]`: call `zeroize()` when this item is dropped +//! - `#[zeroize(drop)]`: *deprecated* use `ZeroizeOnDrop` instead //! - `#[zeroize(bound = "T: MyTrait")]`: this replaces any trait bounds //! inferred by zeroize //! //! On the field level: //! - `#[zeroize(skip)]`: skips this field or variant when calling `zeroize()` //! +//! Attributes supported for `ZeroizeOnDrop`: +//! +//! On the field level: +//! - `#[zeroize(skip)]`: skips this field or variant when calling `zeroize()` +//! //! Example which derives `Drop`: //! //! ``` //! # #[cfg(feature = "derive")] //! # { -//! use zeroize::Zeroize; +//! use zeroize::{Zeroize, ZeroizeDrop}; //! //! // This struct will be zeroized on drop -//! #[derive(Zeroize)] -//! #[zeroize(drop)] +//! #[derive(Zeroize, ZeroizeDrop)] //! struct MyStruct([u8; 32]); //! # } //! ``` @@ -103,6 +107,19 @@ //! # } //! ``` //! +//! Example which only derives `Drop`: +//! +//! ``` +//! # #[cfg(feature = "derive")] +//! # { +//! use zeroize::ZeroizeDrop; +//! +//! // This struct will be zeroized on drop +//! #[derive(ZeroizeDrop)] +//! struct MyStruct([u8; 32]); +//! # } +//! ``` +//! //! ## `Zeroizing`: wrapper for zeroizing arbitrary values on drop //! //! `Zeroizing` is a generic wrapper type that impls `Deref` @@ -219,7 +236,7 @@ extern crate alloc; #[cfg(feature = "zeroize_derive")] #[cfg_attr(docsrs, doc(cfg(feature = "zeroize_derive")))] -pub use zeroize_derive::Zeroize; +pub use zeroize_derive::{Zeroize, ZeroizeOnDrop}; #[cfg(any(target_arch = "x86", target_arch = "x86_64"))] mod x86; @@ -242,6 +259,10 @@ pub trait Zeroize { fn zeroize(&mut self); } +/// Marker trait signifying that this type will [`zeroize`](Zeroize::zeroize) itself on [`Drop`]. +#[allow(drop_bounds)] +pub trait ZeroizeOnDrop: Drop {} + /// Marker trait for types whose `Default` is the desired zeroization result pub trait DefaultIsZeroes: Copy + Default + Sized {} @@ -531,6 +552,8 @@ where } } +impl ZeroizeOnDrop for Zeroizing where Z: Zeroize {} + impl Drop for Zeroizing where Z: Zeroize, diff --git a/zeroize/tests/zeroize_derive.rs b/zeroize/tests/zeroize_derive.rs index 523fcb69..8ef60a87 100644 --- a/zeroize/tests/zeroize_derive.rs +++ b/zeroize/tests/zeroize_derive.rs @@ -2,12 +2,11 @@ #[cfg(feature = "zeroize_derive")] mod custom_derive_tests { - use zeroize::Zeroize; + use zeroize::{Zeroize, ZeroizeOnDrop}; #[test] fn derive_tuple_struct_test() { - #[derive(Zeroize)] - #[zeroize(drop)] + #[derive(Zeroize, ZeroizeOnDrop)] struct Z([u8; 3]); let mut value = Z([1, 2, 3]); @@ -17,8 +16,7 @@ mod custom_derive_tests { #[test] fn derive_struct_test() { - #[derive(Zeroize)] - #[zeroize(drop)] + #[derive(Zeroize, ZeroizeOnDrop)] struct Z { string: String, vec: Vec, @@ -46,8 +44,7 @@ mod custom_derive_tests { #[test] fn derive_enum_test() { - #[derive(Zeroize)] - #[zeroize(drop)] + #[derive(Zeroize, ZeroizeOnDrop)] enum Z { #[allow(dead_code)] Variant1, @@ -64,8 +61,7 @@ mod custom_derive_tests { /// Test that the custom macro actually derived `Drop` for `Z` #[test] fn derive_struct_drop() { - #[derive(Zeroize)] - #[zeroize(drop)] + #[derive(Zeroize, ZeroizeOnDrop)] struct Z([u8; 3]); assert!(std::mem::needs_drop::()); @@ -75,8 +71,29 @@ mod custom_derive_tests { #[test] fn derive_enum_drop() { #[allow(dead_code)] - #[derive(Zeroize)] - #[zeroize(drop)] + #[derive(Zeroize, ZeroizeOnDrop)] + enum Z { + Variant1, + Variant2(usize), + } + + assert!(std::mem::needs_drop::()); + } + + /// Test that the custom macro actually derived `Drop` for `Z` + #[test] + fn derive_struct_only_drop() { + #[derive(ZeroizeOnDrop)] + struct Z([u8; 3]); + + assert!(std::mem::needs_drop::()); + } + + /// Test that the custom macro actually derived `Drop` for `Z` + #[test] + fn derive_enum_only_drop() { + #[allow(dead_code)] + #[derive(ZeroizeOnDrop)] enum Z { Variant1, Variant2(usize), @@ -107,8 +124,7 @@ mod custom_derive_tests { #[test] fn derive_struct_skip() { - #[derive(Zeroize)] - #[zeroize(drop)] + #[derive(Zeroize, ZeroizeOnDrop)] struct Z { string: String, vec: Vec, @@ -137,8 +153,7 @@ mod custom_derive_tests { #[test] fn derive_enum_skip() { - #[derive(Zeroize)] - #[zeroize(drop)] + #[derive(Zeroize, ZeroizeOnDrop)] enum Z { #[allow(dead_code)] Variant1,