Anforderungen  |   Konzepte  |   Entwurf  |   Entwicklung  |   Qualitätssicherung  |   Lebenszyklus  |   Steuerung
 
 
 
 


Quelle  lib.rs   Sprache: unbekannt

 
Spracherkennung für: .rs vermutete Sprache: Unknown {[0] [0] [0]} [Methode: Schwerpunktbildung, einfache Gewichte, sechs Dimensionen]

//! Custom derive support for `zeroize`

#![crate_type = "proc-macro"]
#![forbid(unsafe_code)]
#![warn(rust_2018_idioms, trivial_casts, unused_qualifications)]

use proc_macro2::{Ident, TokenStream};
use quote::{format_ident, quote};
use syn::{
    parse::{Parse, ParseStream},
    parse_quote,
    punctuated::Punctuated,
    token::Comma,
    visit::Visit,
    Attribute, Data, DeriveInput, Expr, ExprLit, Field, Fields, Lit, Meta, Result, Variant,
    WherePredicate,
};

/// Name of zeroize-related attributes
const ZEROIZE_ATTR: &str = "zeroize";

/// Derive the `Zeroize` trait.
///
/// Supports the following attributes:
///
/// On the item level:
/// - `#[zeroize(drop)]`: *deprecated* use `ZeroizeOnDrop` instead
/// - `#[zeroize(bound = "T: MyTrait")]`: this replaces any trait bounds
///   inferred by zeroize-derive
///
/// On the field level:
/// - `#[zeroize(skip)]`: skips this field or variant when calling `zeroize()`
#[proc_macro_derive(Zeroize, attributes(zeroize))]
pub fn derive_zeroize(input: proc_macro::TokenStream) -> proc_macro::TokenStream {
    derive_zeroize_impl(syn::parse_macro_input!(input as DeriveInput)).into()
}

fn derive_zeroize_impl(input: DeriveInput) -> TokenStream {
    let attributes = ZeroizeAttrs::parse(&input);

    let mut generics = input.generics.clone();

    let extra_bounds = match attributes.bound {
        Some(bounds) => bounds.0,
        None => attributes
            .auto_params
            .iter()
            .map(|type_param| -> WherePredicate {
                parse_quote! {#type_param: Zeroize}
            })
            .collect(),
    };

    generics.make_where_clause().predicates.extend(extra_bounds);

    let ty_name = &input.ident;

    let (impl_gen, type_gen, where_) = generics.split_for_impl();

    let drop_impl = if attributes.drop {
        quote! {
            #[doc(hidden)]
            impl #impl_gen Drop for #ty_name #type_gen #where_ {
                fn drop(&mut self) {
                    self.zeroize()
                }
            }
        }
    } else {
        quote! {}
    };

    let zeroizers = generate_fields(&input, quote! { zeroize });
    let zeroize_impl = quote! {
        impl #impl_gen ::zeroize::Zeroize for #ty_name #type_gen #where_ {
            fn zeroize(&mut self) {
                #zeroizers
            }
        }
    };

    quote! {
        #zeroize_impl
        #drop_impl
    }
}

/// Derive the `ZeroizeOnDrop` trait.
///
/// Supports the following attributes:
///
/// On the field level:
/// - `#[zeroize(skip)]`: skips this field or variant when calling `zeroize()`
#[proc_macro_derive(ZeroizeOnDrop, attributes(zeroize))]
pub fn derive_zeroize_on_drop(input: proc_macro::TokenStream) -> proc_macro::TokenStream {
    derive_zeroize_on_drop_impl(syn::parse_macro_input!(input as DeriveInput)).into()
}

fn derive_zeroize_on_drop_impl(input: DeriveInput) -> TokenStream {
    let zeroizers = generate_fields(&input, quote! { zeroize_or_on_drop });

    let (impl_gen, type_gen, where_) = input.generics.split_for_impl();
    let name = input.ident.clone();

    let drop_impl = quote! {
        impl #impl_gen Drop for #name #type_gen #where_ {
            fn drop(&mut self) {
                use ::zeroize::__internal::AssertZeroize;
                use ::zeroize::__internal::AssertZeroizeOnDrop;
                #zeroizers
            }
        }
    };
    let zeroize_on_drop_impl = impl_zeroize_on_drop(&input);

    quote! {
        #drop_impl
        #zeroize_on_drop_impl
    }
}

/// Custom derive attributes for `Zeroize`
#[derive(Default)]
struct ZeroizeAttrs {
    /// Derive a `Drop` impl which calls zeroize on this type
    drop: bool,
    /// Custom bounds as defined by the user
    bound: Option<Bounds>,
    /// Type parameters in use by fields
    auto_params: Vec<Ident>,
}

/// Parsing helper for custom bounds
struct Bounds(Punctuated<WherePredicate, Comma>);

impl Parse for Bounds {
    fn parse(input: ParseStream<'_>) -> Result<Self> {
        Ok(Self(Punctuated::parse_terminated(input)?))
    }
}

struct BoundAccumulator<'a> {
    generics: &'a syn::Generics,
    params: Vec<Ident>,
}

impl<'ast> Visit<'ast> for BoundAccumulator<'ast> {
    fn visit_path(&mut self, path: &'ast syn::Path) {
        if path.segments.len() != 1 {
            return;
        }

        if let Some(segment) = path.segments.first() {
            for param in &self.generics.params {
                if let syn::GenericParam::Type(type_param) = param {
                    if type_param.ident == segment.ident && !self.params.contains(&segment.ident) {
                        self.params.push(type_param.ident.clone());
                    }
                }
            }
        }
    }
}

impl ZeroizeAttrs {
    /// Parse attributes from the incoming AST
    fn parse(input: &DeriveInput) -> Self {
        let mut result = Self::default();
        let mut bound_accumulator = BoundAccumulator {
            generics: &input.generics,
            params: Vec::new(),
        };

        for attr in &input.attrs {
            result.parse_attr(attr, None, None);
        }

        match &input.data {
            syn::Data::Enum(enum_) => {
                for variant in &enum_.variants {
                    for attr in &variant.attrs {
                        result.parse_attr(attr, Some(variant), None);
                    }
                    for field in &variant.fields {
                        for attr in &field.attrs {
                            result.parse_attr(attr, Some(variant), Some(field));
                        }
                        if !attr_skip(&field.attrs) {
                            bound_accumulator.visit_type(&field.ty);
                        }
                    }
                }
            }
            syn::Data::Struct(struct_) => {
                for field in &struct_.fields {
                    for attr in &field.attrs {
                        result.parse_attr(attr, None, Some(field));
                    }
                    if !attr_skip(&field.attrs) {
                        bound_accumulator.visit_type(&field.ty);
                    }
                }
            }
            syn::Data::Union(union_) => panic!("Unsupported untagged union {:?}", union_),
        }

        result.auto_params = bound_accumulator.params;

        result
    }

    /// Parse attribute and handle `#[zeroize(...)]` attributes
    fn parse_attr(&mut self, attr: &Attribute, variant: Option<&Variant>, binding: Option<&Field>) {
        let meta_list = match &attr.meta {
            Meta::List(list) => list,
            _ => return,
        };

        // Ignore any non-zeroize attributes
        if !meta_list.path.is_ident(ZEROIZE_ATTR) {
            return;
        }

        for meta in attr
            .parse_args_with(Punctuated::<Meta, Comma>::parse_terminated)
            .unwrap_or_else(|e| panic!("error parsing attribute: {:?} ({})", attr, e))
        {
            self.parse_meta(&meta, variant, binding);
        }
    }

    /// Parse `#[zeroize(...)]` attribute metadata (e.g. `drop`)
    fn parse_meta(&mut self, meta: &Meta, variant: Option<&Variant>, binding: Option<&Field>) {
        if meta.path().is_ident("drop") {
            assert!(!self.drop, "duplicate #[zeroize] drop flags");

            match (variant, binding) {
                (_variant, Some(_binding)) => {
                    // structs don't have a variant prefix, and only structs have bindings outside of a variant
                    let item_kind = match variant {
                        Some(_) => "enum",
                        None => "struct",
                    };
                    panic!(
                        concat!(
                            "The #[zeroize(drop)] attribute is not allowed on {} fields. ",
                            "Use it on the containing {} instead.",
                        ),
                        item_kind, item_kind,
                    )
                }
                (Some(_variant), None) => panic!(concat!(
                    "The #[zeroize(drop)] attribute is not allowed on enum variants. ",
                    "Use it on the containing enum instead.",
                )),
                (None, None) => (),
            };

            self.drop = true;
        } else if meta.path().is_ident("bound") {
            assert!(self.bound.is_none(), "duplicate #[zeroize] bound flags");

            match (variant, binding) {
                (_variant, Some(_binding)) => {
                    // structs don't have a variant prefix, and only structs have bindings outside of a variant
                    let item_kind = match variant {
                        Some(_) => "enum",
                        None => "struct",
                    };
                    panic!(
                        concat!(
                            "The #[zeroize(bound)] attribute is not allowed on {} fields. ",
                            "Use it on the containing {} instead.",
                        ),
                        item_kind, item_kind,
                    )
                }
                (Some(_variant), None) => panic!(concat!(
                    "The #[zeroize(bound)] attribute is not allowed on enum variants. ",
                    "Use it on the containing enum instead.",
                )),
                (None, None) => {
                    if let Meta::NameValue(meta_name_value) = meta {
                        if let Expr::Lit(ExprLit {
                            lit: Lit::Str(lit), ..
                        }) = &meta_name_value.value
                        {
                            if lit.value().is_empty() {
                                self.bound = Some(Bounds(Punctuated::new()));
                            } else {
                                self.bound = Some(lit.parse().unwrap_or_else(|e| {
                                    panic!("error parsing bounds: {:?} ({})", lit, e)
                                }));
                            }

                            return;
                        }
                    }

                    panic!(concat!(
                        "The #[zeroize(bound)] attribute expects a name-value syntax with a string literal value.",
                        "E.g. #[zeroize(bound = \"T: MyTrait\")]."
                    ))
                }
            }
        } else if meta.path().is_ident("skip") {
            if variant.is_none() && binding.is_none() {
                panic!(concat!(
                    "The #[zeroize(skip)] attribute is not allowed on a `struct` or `enum`. ",
                    "Use it on a field or variant instead.",
                ))
            }
        } else {
            panic!("unknown #[zeroize] attribute type: {:?}", meta.path());
        }
    }
}

fn field_ident(n: usize, field: &Field) -> Ident {
    if let Some(ref name) = field.ident {
        name.clone()
    } else {
        format_ident!("__zeroize_field_{}", n)
    }
}

fn generate_fields(input: &DeriveInput, method: TokenStream) -> TokenStream {
    let input_id = &input.ident;
    let fields: Vec<_> = match input.data {
        Data::Enum(ref enum_) => enum_
            .variants
            .iter()
            .filter_map(|variant| {
                if attr_skip(&variant.attrs) {
                    if variant.fields.iter().any(|field| attr_skip(&field.attrs)) {
                        panic!("duplicate #[zeroize] skip flags")
                    }
                    None
                } else {
                    let variant_id = &variant.ident;
                    Some((quote! { #input_id :: #variant_id }, &variant.fields))
                }
            })
            .collect(),
        Data::Struct(ref struct_) => vec![(quote! { #input_id }, &struct_.fields)],
        Data::Union(ref union_) => panic!("Cannot generate fields for untagged union {:?}", union_),
    };

    let arms = fields.into_iter().map(|(name, fields)| {
        let method_field = fields.iter().enumerate().filter_map(|(n, field)| {
            if attr_skip(&field.attrs) {
                None
            } else {
                let name = field_ident(n, field);
                Some(quote! { #name.#method() })
            }
        });

        let field_bindings = fields
            .iter()
            .enumerate()
            .map(|(n, field)| field_ident(n, field));

        let binding = match fields {
            Fields::Named(_) => quote! {
                #name { #(#field_bindings),* }
            },
            Fields::Unnamed(_) => quote! {
                #name ( #(#field_bindings),* )
            },
            Fields::Unit => quote! {
                #name
            },
        };

        quote! {
            #[allow(unused_variables)]
            #binding => {
                #(#method_field);*
            }
        }
    });

    quote! {
        match self {
            #(#arms),*
            _ => {}
        }
    }
}

fn attr_skip(attrs: &[Attribute]) -> bool {
    let mut result = false;
    for attr in attrs.iter().map(|attr| &attr.meta) {
        if let Meta::List(list) = attr {
            if list.path.is_ident(ZEROIZE_ATTR) {
                for meta in list
                    .parse_args_with(Punctuated::<Meta, Comma>::parse_terminated)
                    .unwrap_or_else(|e| panic!("error parsing attribute: {:?} ({})", list, e))
                {
                    if let Meta::Path(path) = meta {
                        if path.is_ident("skip") {
                            assert!(!result, "duplicate #[zeroize] skip flags");
                            result = true;
                        }
                    }
                }
            }
        }
    }
    result
}

fn impl_zeroize_on_drop(input: &DeriveInput) -> TokenStream {
    let name = input.ident.clone();
    let (impl_gen, type_gen, where_) = input.generics.split_for_impl();
    quote! {
        #[doc(hidden)]
        impl #impl_gen ::zeroize::ZeroizeOnDrop for #name #type_gen #where_ {}
    }
}

#[cfg(test)]
mod tests {
    use super::*;

    #[track_caller]
    fn test_derive(
        f: impl Fn(DeriveInput) -> TokenStream,
        input: TokenStream,
        expected_output: TokenStream,
    ) {
        let output = f(syn::parse2(input).unwrap());
        assert_eq!(format!("{output}"), format!("{expected_output}"));
    }

    #[track_caller]
    fn parse_zeroize_test(unparsed: &str) -> TokenStream {
        derive_zeroize_impl(syn::parse_str(unparsed).expect("Failed to parse test input"))
    }

    #[test]
    fn zeroize_without_drop() {
        test_derive(
            derive_zeroize_impl,
            quote! {
                struct Z {
                    a: String,
                    b: Vec<u8>,
                    c: [u8; 3],
                }
            },
            quote! {
                impl ::zeroize::Zeroize for Z {
                    fn zeroize(&mut self) {
                        match self {
                            #[allow(unused_variables)]
                            Z { a, b, c } => {
                                a.zeroize();
                                b.zeroize();
                                c.zeroize()
                            }
                            _ => {}
                        }
                    }
                }
            },
        )
    }

    #[test]
    fn zeroize_with_drop() {
        test_derive(
            derive_zeroize_impl,
            quote! {
                #[zeroize(drop)]
                struct Z {
                    a: String,
                    b: Vec<u8>,
                    c: [u8; 3],
                }
            },
            quote! {
                impl ::zeroize::Zeroize for Z {
                    fn zeroize(&mut self) {
                        match self {
                            #[allow(unused_variables)]
                            Z { a, b, c } => {
                                a.zeroize();
                                b.zeroize();
                                c.zeroize()
                            }
                            _ => {}
                        }
                    }
                }
                #[doc(hidden)]
                impl Drop for Z {
                    fn drop(&mut self) {
                        self.zeroize()
                    }
                }
            },
        )
    }

    #[test]
    fn zeroize_with_skip() {
        test_derive(
            derive_zeroize_impl,
            quote! {
                struct Z {
                    a: String,
                    b: Vec<u8>,
                    #[zeroize(skip)]
                    c: [u8; 3],
                }
            },
            quote! {
                impl ::zeroize::Zeroize for Z {
                    fn zeroize(&mut self) {
                        match self {
                            #[allow(unused_variables)]
                            Z { a, b, c } => {
                                a.zeroize();
                                b.zeroize()
                            }
                            _ => {}
                        }
                    }
                }
            },
        )
    }

    #[test]
    fn zeroize_with_bound() {
        test_derive(
            derive_zeroize_impl,
            quote! {
                #[zeroize(bound = "T: MyTrait")]
                struct Z<T>(T);
            },
            quote! {
                impl<T> ::zeroize::Zeroize for Z<T> where T: MyTrait {
                    fn zeroize(&mut self) {
                        match self {
                            #[allow(unused_variables)]
                            Z(__zeroize_field_0) => {
                                __zeroize_field_0.zeroize()
                            }
                            _ => {}
                        }
                    }
                }
            },
        )
    }

    #[test]
    fn zeroize_only_drop() {
        test_derive(
            derive_zeroize_on_drop_impl,
            quote! {
                struct Z {
                    a: String,
                    b: Vec<u8>,
                    c: [u8; 3],
                }
            },
            quote! {
                impl Drop for Z {
                    fn drop(&mut self) {
                        use ::zeroize::__internal::AssertZeroize;
                        use ::zeroize::__internal::AssertZeroizeOnDrop;
                        match self {
                            #[allow(unused_variables)]
                            Z { a, b, c } => {
                                a.zeroize_or_on_drop();
                                b.zeroize_or_on_drop();
                                c.zeroize_or_on_drop()
                            }
                            _ => {}
                        }
                    }
                }
                #[doc(hidden)]
                impl ::zeroize::ZeroizeOnDrop for Z {}
            },
        )
    }

    #[test]
    fn zeroize_on_struct() {
        parse_zeroize_test(stringify!(
            #[zeroize(drop)]
            struct Z {
                a: String,
                b: Vec<u8>,
                c: [u8; 3],
            }
        ));
    }

    #[test]
    fn zeroize_on_enum() {
        parse_zeroize_test(stringify!(
            #[zeroize(drop)]
            enum Z {
                Variant1 { a: String, b: Vec<u8>, c: [u8; 3] },
            }
        ));
    }

    #[test]
    #[should_panic(expected = "#[zeroize(drop)] attribute is not allowed on struct fields")]
    fn zeroize_on_struct_field() {
        parse_zeroize_test(stringify!(
            struct Z {
                #[zeroize(drop)]
                a: String,
                b: Vec<u8>,
                c: [u8; 3],
            }
        ));
    }

    #[test]
    #[should_panic(expected = "#[zeroize(drop)] attribute is not allowed on struct fields")]
    fn zeroize_on_tuple_struct_field() {
        parse_zeroize_test(stringify!(
            struct Z(#[zeroize(drop)] String);
        ));
    }

    #[test]
    #[should_panic(expected = "#[zeroize(drop)] attribute is not allowed on struct fields")]
    fn zeroize_on_second_field() {
        parse_zeroize_test(stringify!(
            struct Z {
                a: String,
                #[zeroize(drop)]
                b: Vec<u8>,
                c: [u8; 3],
            }
        ));
    }

    #[test]
    #[should_panic(expected = "#[zeroize(drop)] attribute is not allowed on enum fields")]
    fn zeroize_on_tuple_enum_variant_field() {
        parse_zeroize_test(stringify!(
            enum Z {
                Variant(#[zeroize(drop)] String),
            }
        ));
    }

    #[test]
    #[should_panic(expected = "#[zeroize(drop)] attribute is not allowed on enum fields")]
    fn zeroize_on_enum_variant_field() {
        parse_zeroize_test(stringify!(
            enum Z {
                Variant {
                    #[zeroize(drop)]
                    a: String,
                    b: Vec<u8>,
                    c: [u8; 3],
                },
            }
        ));
    }

    #[test]
    #[should_panic(expected = "#[zeroize(drop)] attribute is not allowed on enum fields")]
    fn zeroize_on_enum_second_variant_field() {
        parse_zeroize_test(stringify!(
            enum Z {
                Variant1 {
                    a: String,
                    b: Vec<u8>,
                    c: [u8; 3],
                },
                Variant2 {
                    #[zeroize(drop)]
                    a: String,
                    b: Vec<u8>,
                    c: [u8; 3],
                },
            }
        ));
    }

    #[test]
    #[should_panic(expected = "#[zeroize(drop)] attribute is not allowed on enum variants")]
    fn zeroize_on_enum_variant() {
        parse_zeroize_test(stringify!(
            enum Z {
                #[zeroize(drop)]
                Variant,
            }
        ));
    }

    #[test]
    #[should_panic(expected = "#[zeroize(drop)] attribute is not allowed on enum variants")]
    fn zeroize_on_enum_second_variant() {
        parse_zeroize_test(stringify!(
            enum Z {
                Variant1,
                #[zeroize(drop)]
                Variant2,
            }
        ));
    }

    #[test]
    #[should_panic(
        expected = "The #[zeroize(skip)] attribute is not allowed on a `struct` or `enum`. Use it on a field or variant instead."
    )]
    fn zeroize_skip_on_struct() {
        parse_zeroize_test(stringify!(
            #[zeroize(skip)]
            struct Z {
                a: String,
                b: Vec<u8>,
                c: [u8; 3],
            }
        ));
    }

    #[test]
    #[should_panic(
        expected = "The #[zeroize(skip)] attribute is not allowed on a `struct` or `enum`. Use it on a field or variant instead."
    )]
    fn zeroize_skip_on_enum() {
        parse_zeroize_test(stringify!(
            #[zeroize(skip)]
            enum Z {
                Variant1,
                Variant2,
            }
        ));
    }

    #[test]
    #[should_panic(expected = "duplicate #[zeroize] skip flags")]
    fn zeroize_duplicate_skip() {
        parse_zeroize_test(stringify!(
            struct Z {
                a: String,
                #[zeroize(skip)]
                #[zeroize(skip)]
                b: Vec<u8>,
                c: [u8; 3],
            }
        ));
    }

    #[test]
    #[should_panic(expected = "duplicate #[zeroize] skip flags")]
    fn zeroize_duplicate_skip_list() {
        parse_zeroize_test(stringify!(
            struct Z {
                a: String,
                #[zeroize(skip, skip)]
                b: Vec<u8>,
                c: [u8; 3],
            }
        ));
    }

    #[test]
    #[should_panic(expected = "duplicate #[zeroize] skip flags")]
    fn zeroize_duplicate_skip_enum() {
        parse_zeroize_test(stringify!(
            enum Z {
                #[zeroize(skip)]
                Variant {
                    a: String,
                    #[zeroize(skip)]
                    b: Vec<u8>,
                    c: [u8; 3],
                },
            }
        ));
    }

    #[test]
    #[should_panic(expected = "duplicate #[zeroize] bound flags")]
    fn zeroize_duplicate_bound() {
        parse_zeroize_test(stringify!(
            #[zeroize(bound = "T: MyTrait")]
            #[zeroize(bound = "")]
            struct Z<T>(T);
        ));
    }

    #[test]
    #[should_panic(expected = "duplicate #[zeroize] bound flags")]
    fn zeroize_duplicate_bound_list() {
        parse_zeroize_test(stringify!(
            #[zeroize(bound = "T: MyTrait", bound = "")]
            struct Z<T>(T);
        ));
    }

    #[test]
    #[should_panic(
        expected = "The #[zeroize(bound)] attribute is not allowed on struct fields. Use it on the containing struct instead."
    )]
    fn zeroize_bound_struct() {
        parse_zeroize_test(stringify!(
            struct Z<T> {
                #[zeroize(bound = "T: MyTrait")]
                a: T,
            }
        ));
    }

    #[test]
    #[should_panic(
        expected = "The #[zeroize(bound)] attribute is not allowed on enum variants. Use it on the containing enum instead."
    )]
    fn zeroize_bound_enum() {
        parse_zeroize_test(stringify!(
            enum Z<T> {
                #[zeroize(bound = "T: MyTrait")]
                A(T),
            }
        ));
    }

    #[test]
    #[should_panic(
        expected = "The #[zeroize(bound)] attribute is not allowed on enum fields. Use it on the containing enum instead."
    )]
    fn zeroize_bound_enum_variant_field() {
        parse_zeroize_test(stringify!(
            enum Z<T> {
                A {
                    #[zeroize(bound = "T: MyTrait")]
                    a: T,
                },
            }
        ));
    }

    #[test]
    #[should_panic(
        expected = "The #[zeroize(bound)] attribute expects a name-value syntax with a string literal value.E.g. #[zeroize(bound = \"T: MyTrait\")]."
    )]
    fn zeroize_bound_no_value() {
        parse_zeroize_test(stringify!(
            #[zeroize(bound)]
            struct Z<T>(T);
        ));
    }

    #[test]
    #[should_panic(expected = "error parsing bounds: LitStr { token: \"T\" } (expected `:`)")]
    fn zeroize_bound_no_where_predicate() {
        parse_zeroize_test(stringify!(
            #[zeroize(bound = "T")]
            struct Z<T>(T);
        ));
    }
}

[ Dauer der Verarbeitung: 0.38 Sekunden  ]

                                                                                                                                                                                                                                                                                                                                                                                                     


Neuigkeiten

     Aktuelles
     Motto des Tages

Software

     Produkte
     Quellcodebibliothek

Aktivitäten

     Artikel über Sicherheit
     Anleitung zur Aktivierung von SSL

Muße

     Gedichte
     Musik
     Bilder

Jenseits des Üblichen ....

Besucherstatistik

Besucherstatistik

Monitoring

Montastic status badge