diff options
author | Hasini Gunasinghe <hasinitg@google.com> | 2022-10-05 19:53:14 +0000 |
---|---|---|
committer | Automerger Merge Worker <android-build-automerger-merge-worker@system.gserviceaccount.com> | 2022-10-05 19:53:14 +0000 |
commit | 210b6d53c92743225b0e09301669a970b8d2918a (patch) | |
tree | 140df4509f0d6856d3b28f765a53fac390f7fc2e | |
parent | e4331436e0d8bc779897ddd6859e438b88148979 (diff) | |
parent | a6bb55d980e54753f3791ccc963a9ca193bdfc9d (diff) | |
download | zeroize_derive-210b6d53c92743225b0e09301669a970b8d2918a.tar.gz |
Import platform/external/rust/crates/zeroize_derive am: f8b8d0ff3b am: 0ec6ce0e1b am: a6bb55d980
Original change: https://android-review.googlesource.com/c/platform/external/rust/crates/zeroize_derive/+/2240635
Change-Id: Id9fb3b3e91de1a77ec8142b0a4af137c5c32b7fe
Signed-off-by: Automerger Merge Worker <android-build-automerger-merge-worker@system.gserviceaccount.com>
-rw-r--r-- | .cargo_vcs_info.json | 6 | ||||
-rw-r--r-- | Android.bp | 20 | ||||
-rw-r--r-- | CHANGELOG.md | 57 | ||||
-rw-r--r-- | Cargo.toml | 38 | ||||
-rw-r--r-- | Cargo.toml.orig | 23 | ||||
l--------- | LICENSE | 1 | ||||
-rw-r--r-- | LICENSE-APACHE | 202 | ||||
-rw-r--r-- | METADATA | 20 | ||||
-rw-r--r-- | MODULE_LICENSE_APACHE2 | 0 | ||||
-rw-r--r-- | OWNERS | 1 | ||||
-rw-r--r-- | README.md | 49 | ||||
-rw-r--r-- | cargo2android.json | 4 | ||||
-rw-r--r-- | src/lib.rs | 810 |
13 files changed, 1231 insertions, 0 deletions
diff --git a/.cargo_vcs_info.json b/.cargo_vcs_info.json new file mode 100644 index 0000000..68eac09 --- /dev/null +++ b/.cargo_vcs_info.json @@ -0,0 +1,6 @@ +{ + "git": { + "sha1": "25167758af1fcbb8f5ee00c8d8097a50269635f5" + }, + "path_in_vcs": "zeroize/derive" +}
\ No newline at end of file diff --git a/Android.bp b/Android.bp new file mode 100644 index 0000000..6ffcf50 --- /dev/null +++ b/Android.bp @@ -0,0 +1,20 @@ +// This file is generated by cargo2android.py --config cargo2android.json. +// Do not modify this file as changes will be overridden on upgrade. + + + +rust_proc_macro { + name: "libzeroize_derive", + crate_name: "zeroize_derive", + cargo_env_compat: true, + cargo_pkg_version: "1.3.2", + srcs: ["src/lib.rs"], + edition: "2018", + rustlibs: [ + "libproc_macro2", + "libquote", + "libsyn", + "libsynstructure", + ], + compile_multilib: "first", +} diff --git a/CHANGELOG.md b/CHANGELOG.md new file mode 100644 index 0000000..1ecaef2 --- /dev/null +++ b/CHANGELOG.md @@ -0,0 +1,57 @@ +# Changelog +All notable changes to this project will be documented in this file. + +The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/), +and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html). + +## 1.3.2 (2022-02-18) +### Fixed +- Min versions build ([#732]) + +[#732]: https://github.com/RustCrypto/utils/pull/732 + +## 1.3.1 (2021-01-14) [YANKED] +### Removed +- `ZeroizeOnDrop` implementation for `#[zeroize(drop)]` ([#715]) + +[#715]: https://github.com/RustCrypto/utils/pull/715 + +## 1.3.0 (2021-01-14) [YANKED] +### Added +- `#[zeroize(bound = "T: MyTrait")]` ([#663]) +- Custom derive for `ZeroizeOnDrop` ([#699], [#700]) + +[#663]: https://github.com/RustCrypto/utils/pull/663 +[#699]: https://github.com/RustCrypto/utils/pull/699 +[#700]: https://github.com/RustCrypto/utils/pull/700 + +## 1.2.2 (2021-11-04) +### Added +- `#[zeroize(skip)]` attribute ([#654]) + +[#654]: https://github.com/RustCrypto/utils/pull/654 + +## 1.2.1 (2021-11-04) +### Changed +- Moved to `RustCrypto/utils` repository + +## 1.2.0 (2021-09-21) +### Changed +- Bump MSRV to 1.51+ +- Reject `#[zeroize(drop)]` on struct/enum fields, enum variants + +## 1.1.1 (2021-10-09) +### Changed +- Backport 1.2.0 `#[zeroize(drop)]` fixes but with a 1.47+ MSRV. + +## 1.1.0 (2021-04-19) +### Changed +- Bump MSRV to 1.47+ + +## 1.0.1 (2019-09-15) +### Added +- Add docs for the `Zeroize` proc macro + +## 1.0.0 (2019-10-13) + +- Initial 1.0 release diff --git a/Cargo.toml b/Cargo.toml new file mode 100644 index 0000000..2cb23a0 --- /dev/null +++ b/Cargo.toml @@ -0,0 +1,38 @@ +# THIS FILE IS AUTOMATICALLY GENERATED BY CARGO +# +# When uploading crates to the registry Cargo will automatically +# "normalize" Cargo.toml files for maximal compatibility +# with all versions of Cargo and also rewrite `path` dependencies +# to registry (e.g., crates.io) dependencies. +# +# If you are reading this file be aware that the original Cargo.toml +# will likely look very different (and much more reasonable). +# See Cargo.toml.orig for the original contents. + +[package] +edition = "2018" +name = "zeroize_derive" +version = "1.3.2" +authors = ["The RustCrypto Project Developers"] +description = "Custom derive support for zeroize" +readme = "README.md" +keywords = ["memory", "memset", "secure", "volatile", "zero"] +categories = ["cryptography", "memory-management", "no-std", "os"] +license = "Apache-2.0 OR MIT" +repository = "https://github.com/RustCrypto/utils/tree/master/zeroize/derive" +[package.metadata.docs.rs] +rustdoc-args = ["--document-private-items"] + +[lib] +proc-macro = true +[dependencies.proc-macro2] +version = "1" + +[dependencies.quote] +version = "1" + +[dependencies.syn] +version = "1" + +[dependencies.synstructure] +version = "0.12.2" diff --git a/Cargo.toml.orig b/Cargo.toml.orig new file mode 100644 index 0000000..4bac91a --- /dev/null +++ b/Cargo.toml.orig @@ -0,0 +1,23 @@ +[package] +name = "zeroize_derive" +description = "Custom derive support for zeroize" +version = "1.3.2" +authors = ["The RustCrypto Project Developers"] +license = "Apache-2.0 OR MIT" +repository = "https://github.com/RustCrypto/utils/tree/master/zeroize/derive" +readme = "README.md" +categories = ["cryptography", "memory-management", "no-std", "os"] +keywords = ["memory", "memset", "secure", "volatile", "zero"] +edition = "2018" + +[lib] +proc-macro = true + +[dependencies] +proc-macro2 = "1" +quote = "1" +syn = "1" +synstructure = "0.12.2" + +[package.metadata.docs.rs] +rustdoc-args = ["--document-private-items"] @@ -0,0 +1 @@ +LICENSE-APACHE
\ No newline at end of file diff --git a/LICENSE-APACHE b/LICENSE-APACHE new file mode 100644 index 0000000..d645695 --- /dev/null +++ b/LICENSE-APACHE @@ -0,0 +1,202 @@ + + Apache License + Version 2.0, January 2004 + http://www.apache.org/licenses/ + + TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION + + 1. Definitions. + + "License" shall mean the terms and conditions for use, reproduction, + and distribution as defined by Sections 1 through 9 of this document. + + "Licensor" shall mean the copyright owner or entity authorized by + the copyright owner that is granting the License. + + "Legal Entity" shall mean the union of the acting entity and all + other entities that control, are controlled by, or are under common + control with that entity. For the purposes of this definition, + "control" means (i) the power, direct or indirect, to cause the + direction or management of such entity, whether by contract or + otherwise, or (ii) ownership of fifty percent (50%) or more of the + outstanding shares, or (iii) beneficial ownership of such entity. + + "You" (or "Your") shall mean an individual or Legal Entity + exercising permissions granted by this License. + + "Source" form shall mean the preferred form for making modifications, + including but not limited to software source code, documentation + source, and configuration files. + + "Object" form shall mean any form resulting from mechanical + transformation or translation of a Source form, including but + not limited to compiled object code, generated documentation, + and conversions to other media types. + + "Work" shall mean the work of authorship, whether in Source or + Object form, made available under the License, as indicated by a + copyright notice that is included in or attached to the work + (an example is provided in the Appendix below). + + "Derivative Works" shall mean any work, whether in Source or Object + form, that is based on (or derived from) the Work and for which the + editorial revisions, annotations, elaborations, or other modifications + represent, as a whole, an original work of authorship. For the purposes + of this License, Derivative Works shall not include works that remain + separable from, or merely link (or bind by name) to the interfaces of, + the Work and Derivative Works thereof. + + "Contribution" shall mean any work of authorship, including + the original version of the Work and any modifications or additions + to that Work or Derivative Works thereof, that is intentionally + submitted to Licensor for inclusion in the Work by the copyright owner + or by an individual or Legal Entity authorized to submit on behalf of + the copyright owner. For the purposes of this definition, "submitted" + means any form of electronic, verbal, or written communication sent + to the Licensor or its representatives, including but not limited to + communication on electronic mailing lists, source code control systems, + and issue tracking systems that are managed by, or on behalf of, the + Licensor for the purpose of discussing and improving the Work, but + excluding communication that is conspicuously marked or otherwise + designated in writing by the copyright owner as "Not a Contribution." + + "Contributor" shall mean Licensor and any individual or Legal Entity + on behalf of whom a Contribution has been received by Licensor and + subsequently incorporated within the Work. + + 2. Grant of Copyright License. Subject to the terms and conditions of + this License, each Contributor hereby grants to You a perpetual, + worldwide, non-exclusive, no-charge, royalty-free, irrevocable + copyright license to reproduce, prepare Derivative Works of, + publicly display, publicly perform, sublicense, and distribute the + Work and such Derivative Works in Source or Object form. + + 3. Grant of Patent License. Subject to the terms and conditions of + this License, each Contributor hereby grants to You a perpetual, + worldwide, non-exclusive, no-charge, royalty-free, irrevocable + (except as stated in this section) patent license to make, have made, + use, offer to sell, sell, import, and otherwise transfer the Work, + where such license applies only to those patent claims licensable + by such Contributor that are necessarily infringed by their + Contribution(s) alone or by combination of their Contribution(s) + with the Work to which such Contribution(s) was submitted. If You + institute patent litigation against any entity (including a + cross-claim or counterclaim in a lawsuit) alleging that the Work + or a Contribution incorporated within the Work constitutes direct + or contributory patent infringement, then any patent licenses + granted to You under this License for that Work shall terminate + as of the date such litigation is filed. + + 4. Redistribution. You may reproduce and distribute copies of the + Work or Derivative Works thereof in any medium, with or without + modifications, and in Source or Object form, provided that You + meet the following conditions: + + (a) You must give any other recipients of the Work or + Derivative Works a copy of this License; and + + (b) You must cause any modified files to carry prominent notices + stating that You changed the files; and + + (c) You must retain, in the Source form of any Derivative Works + that You distribute, all copyright, patent, trademark, and + attribution notices from the Source form of the Work, + excluding those notices that do not pertain to any part of + the Derivative Works; and + + (d) If the Work includes a "NOTICE" text file as part of its + distribution, then any Derivative Works that You distribute must + include a readable copy of the attribution notices contained + within such NOTICE file, excluding those notices that do not + pertain to any part of the Derivative Works, in at least one + of the following places: within a NOTICE text file distributed + as part of the Derivative Works; within the Source form or + documentation, if provided along with the Derivative Works; or, + within a display generated by the Derivative Works, if and + wherever such third-party notices normally appear. The contents + of the NOTICE file are for informational purposes only and + do not modify the License. You may add Your own attribution + notices within Derivative Works that You distribute, alongside + or as an addendum to the NOTICE text from the Work, provided + that such additional attribution notices cannot be construed + as modifying the License. + + You may add Your own copyright statement to Your modifications and + may provide additional or different license terms and conditions + for use, reproduction, or distribution of Your modifications, or + for any such Derivative Works as a whole, provided Your use, + reproduction, and distribution of the Work otherwise complies with + the conditions stated in this License. + + 5. Submission of Contributions. Unless You explicitly state otherwise, + any Contribution intentionally submitted for inclusion in the Work + by You to the Licensor shall be under the terms and conditions of + this License, without any additional terms or conditions. + Notwithstanding the above, nothing herein shall supersede or modify + the terms of any separate license agreement you may have executed + with Licensor regarding such Contributions. + + 6. Trademarks. This License does not grant permission to use the trade + names, trademarks, service marks, or product names of the Licensor, + except as required for reasonable and customary use in describing the + origin of the Work and reproducing the content of the NOTICE file. + + 7. Disclaimer of Warranty. Unless required by applicable law or + agreed to in writing, Licensor provides the Work (and each + Contributor provides its Contributions) on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or + implied, including, without limitation, any warranties or conditions + of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A + PARTICULAR PURPOSE. You are solely responsible for determining the + appropriateness of using or redistributing the Work and assume any + risks associated with Your exercise of permissions under this License. + + 8. Limitation of Liability. In no event and under no legal theory, + whether in tort (including negligence), contract, or otherwise, + unless required by applicable law (such as deliberate and grossly + negligent acts) or agreed to in writing, shall any Contributor be + liable to You for damages, including any direct, indirect, special, + incidental, or consequential damages of any character arising as a + result of this License or out of the use or inability to use the + Work (including but not limited to damages for loss of goodwill, + work stoppage, computer failure or malfunction, or any and all + other commercial damages or losses), even if such Contributor + has been advised of the possibility of such damages. + + 9. Accepting Warranty or Additional Liability. While redistributing + the Work or Derivative Works thereof, You may choose to offer, + and charge a fee for, acceptance of support, warranty, indemnity, + or other liability obligations and/or rights consistent with this + License. However, in accepting such obligations, You may act only + on Your own behalf and on Your sole responsibility, not on behalf + of any other Contributor, and only if You agree to indemnify, + defend, and hold each Contributor harmless for any liability + incurred by, or claims asserted against, such Contributor by reason + of your accepting any such warranty or additional liability. + + END OF TERMS AND CONDITIONS + + APPENDIX: How to apply the Apache License to your work. + + To apply the Apache License to your work, attach the following + boilerplate notice, with the fields enclosed by brackets "[]" + replaced with your own identifying information. (Don't include + the brackets!) The text should be enclosed in the appropriate + comment syntax for the file format. We also recommend that a + file or class name and description of purpose be included on the + same "printed page" as the copyright notice for easier + identification within third-party archives. + + Copyright [yyyy] [name of copyright owner] + + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. diff --git a/METADATA b/METADATA new file mode 100644 index 0000000..3ec5f4c --- /dev/null +++ b/METADATA @@ -0,0 +1,20 @@ +name: "zeroize_derive" +description: "Custom derive support for zeroize" +third_party { + url { + type: HOMEPAGE + value: "https://crates.io/crates/zeroize_derive" + } + url { + type: ARCHIVE + value: "https://static.crates.io/crates/zeroize_derive/zeroize_derive-1.3.2.crate" + } + version: "1.3.2" + # Dual-licensed, using the least restrictive per go/thirdpartylicenses#same. + license_type: NOTICE + last_upgrade_date { + year: 2022 + month: 9 + day: 6 + } +} diff --git a/MODULE_LICENSE_APACHE2 b/MODULE_LICENSE_APACHE2 new file mode 100644 index 0000000..e69de29 --- /dev/null +++ b/MODULE_LICENSE_APACHE2 @@ -0,0 +1 @@ +include platform/prebuilts/rust:master:/OWNERS diff --git a/README.md b/README.md new file mode 100644 index 0000000..d5b16e3 --- /dev/null +++ b/README.md @@ -0,0 +1,49 @@ +# [RustCrypto]: `zeroize_derive` + +[![Crate][crate-image]][crate-link] +![Apache 2.0 Licensed/MIT][license-image] +![MSRV][rustc-image] +[![Build Status][build-image]][build-link] + +Custom derive support for [zeroize]: a crate for securely zeroing memory +while avoiding compiler optimizations. + +This crate isn't intended to be used directly. +See [zeroize] crate for documentation. + +## Minimum Supported Rust Version + +Rust **1.51** 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. + +## License + +Licensed under either of: + +* [Apache License, Version 2.0](http://www.apache.org/licenses/LICENSE-2.0) +* [MIT license](http://opensource.org/licenses/MIT) + +at your option. + +### Contribution + +Unless you explicitly state otherwise, any contribution intentionally submitted +for inclusion in the work by you, as defined in the Apache-2.0 license, shall be +dual licensed as above, without any additional terms or conditions. + +[//]: # (badges) + +[crate-image]: https://img.shields.io/crates/v/zeroize_derive.svg +[crate-link]: https://crates.io/crates/zeroize_derive +[license-image]: https://img.shields.io/badge/license-Apache2.0/MIT-blue.svg +[rustc-image]: https://img.shields.io/badge/rustc-1.51+-blue.svg +[build-image]: https://github.com/RustCrypto/utils/actions/workflows/zeroize.yml/badge.svg +[build-link]: https://github.com/RustCrypto/utils/actions/workflows/zeroize.yml + +[//]: # (general links) + +[RustCrypto]: https://github.com/RustCrypto +[zeroize]: https://github.com/RustCrypto/utils/tree/master/zeroize diff --git a/cargo2android.json b/cargo2android.json new file mode 100644 index 0000000..58e46ee --- /dev/null +++ b/cargo2android.json @@ -0,0 +1,4 @@ +{ + "host-first-multilib": true, + "run": true +} diff --git a/src/lib.rs b/src/lib.rs new file mode 100644 index 0000000..1efc3b4 --- /dev/null +++ b/src/lib.rs @@ -0,0 +1,810 @@ +//! Custom derive support for `zeroize` + +#![crate_type = "proc-macro"] +#![forbid(unsafe_code)] +#![warn(rust_2018_idioms, trivial_casts, unused_qualifications)] + +use proc_macro2::TokenStream; +use quote::quote; +use syn::{ + parse::{Parse, ParseStream}, + punctuated::Punctuated, + token::Comma, + Attribute, Lit, Meta, NestedMeta, Result, WherePredicate, +}; +use synstructure::{decl_derive, AddBounds, BindStyle, BindingInfo, VariantInfo}; + +decl_derive!( + [Zeroize, attributes(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()` + 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"; + +/// Custom derive for `Zeroize` +fn derive_zeroize(mut s: synstructure::Structure<'_>) -> TokenStream { + let attributes = ZeroizeAttrs::parse(&s); + + if let Some(bounds) = attributes.bound { + s.add_bounds(AddBounds::None); + + for bound in bounds.0 { + s.add_where_predicate(bound); + } + } + + // NOTE: These are split into named functions to simplify testing with + // synstructure's `test_derive!` macro. + if attributes.drop { + derive_zeroize_with_drop(s) + } else { + derive_zeroize_without_drop(s) + } +} + +/// Custom derive for `ZeroizeOnDrop` +fn derive_zeroize_on_drop(mut s: synstructure::Structure<'_>) -> TokenStream { + let zeroizers = generate_fields(&mut s, quote! { zeroize_or_on_drop }); + + let drop_impl = s.gen_impl(quote! { + gen impl Drop for @Self { + fn drop(&mut self) { + use zeroize::__internal::AssertZeroize; + use zeroize::__internal::AssertZeroizeOnDrop; + 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 { + /// Derive a `Drop` impl which calls zeroize on this type + drop: bool, + /// Custom bounds as defined by the user + bound: Option<Bounds>, +} + +/// 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)?)) + } +} + +impl ZeroizeAttrs { + /// Parse attributes from the incoming AST + fn parse(s: &synstructure::Structure<'_>) -> Self { + let mut result = Self::default(); + + for attr in s.ast().attrs.iter() { + result.parse_attr(attr, None, None); + } + for v in s.variants().iter() { + // only process actual enum variants here, as we don't want to process struct attributes twice + if v.prefix.is_some() { + for attr in v.ast().attrs.iter() { + result.parse_attr(attr, Some(v), None); + } + } + for binding in v.bindings().iter() { + for attr in binding.ast().attrs.iter() { + result.parse_attr(attr, Some(v), Some(binding)); + } + } + } + + result + } + + /// Parse attribute and handle `#[zeroize(...)]` attributes + fn parse_attr( + &mut self, + attr: &Attribute, + variant: Option<&VariantInfo<'_>>, + binding: Option<&BindingInfo<'_>>, + ) { + let meta_list = match attr + .parse_meta() + .unwrap_or_else(|e| panic!("error parsing attribute: {:?} ({})", attr, e)) + { + Meta::List(list) => list, + _ => return, + }; + + // Ignore any non-zeroize attributes + if !meta_list.path.is_ident(ZEROIZE_ATTR) { + return; + } + + for nested_meta in &meta_list.nested { + if let NestedMeta::Meta(meta) = nested_meta { + self.parse_meta(meta, variant, binding); + } else { + panic!("malformed #[zeroize] attribute: {:?}", nested_meta); + } + } + } + + /// Parse `#[zeroize(...)]` attribute metadata (e.g. `drop`) + fn parse_meta( + &mut self, + meta: &Meta, + variant: Option<&VariantInfo<'_>>, + binding: Option<&BindingInfo<'_>>, + ) { + 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.and_then(|variant| variant.prefix) { + 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.and_then(|variant| variant.prefix) { + 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 Lit::Str(lit) = &meta_name_value.lit { + 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 generate_fields(s: &mut synstructure::Structure<'_>, method: TokenStream) -> 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.#method(); }) +} + +fn filter_skip(attrs: &[Attribute], start: bool) -> bool { + let mut result = start; + + for attr in attrs.iter().filter_map(|attr| attr.parse_meta().ok()) { + if let Meta::List(list) = attr { + if list.path.is_ident(ZEROIZE_ATTR) { + for nested in list.nested { + if let NestedMeta::Meta(Meta::Path(path)) = nested { + if path.is_ident("skip") { + assert!(result, "duplicate #[zeroize] skip flags"); + result = false; + } + } + } + } + } + } + + result +} + +/// Custom derive for `Zeroize` (without `Drop`) +fn derive_zeroize_without_drop(mut s: synstructure::Structure<'_>) -> TokenStream { + let zeroizers = generate_fields(&mut s, quote! { zeroize }); + + s.bound_impl( + quote!(zeroize::Zeroize), + quote! { + fn zeroize(&mut self) { + match self { + #zeroizers + } + } + }, + ) +} + +/// Custom derive for `Zeroize` and `Drop` +fn derive_zeroize_with_drop(s: synstructure::Structure<'_>) -> TokenStream { + let drop_impl = s.gen_impl(quote! { + gen impl Drop for @Self { + fn drop(&mut self) { + self.zeroize(); + } + } + }); + + let zeroize_impl = derive_zeroize_without_drop(s); + + quote! { + #zeroize_impl + + #[doc(hidden)] + #drop_impl + } +} + +fn impl_zeroize_on_drop(s: &synstructure::Structure<'_>) -> TokenStream { + #[allow(unused_qualifications)] + s.bound_impl(quote!(zeroize::ZeroizeOnDrop), Option::<TokenStream>::None) +} + +#[cfg(test)] +mod tests { + use super::*; + use syn::parse_str; + use synstructure::{test_derive, Structure}; + + #[test] + fn zeroize_without_drop() { + test_derive! { + derive_zeroize_without_drop { + struct Z { + a: String, + b: Vec<u8>, + c: [u8; 3], + } + } + expands to { + #[allow(non_upper_case_globals)] + #[doc(hidden)] + const _DERIVE_zeroize_Zeroize_FOR_Z: () = { + extern crate zeroize; + impl zeroize::Zeroize for Z { + fn zeroize(&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(); } + } + } + } + } + }; + } + no_build // tests the code compiles are in the `zeroize` crate + } + } + + #[test] + fn zeroize_with_drop() { + test_derive! { + derive_zeroize_with_drop { + struct Z { + a: String, + b: Vec<u8>, + c: [u8; 3], + } + } + expands to { + #[allow(non_upper_case_globals)] + #[doc(hidden)] + const _DERIVE_zeroize_Zeroize_FOR_Z: () = { + extern crate zeroize; + impl zeroize::Zeroize for Z { + fn zeroize(&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(); } + } + } + } + } + }; + #[doc(hidden)] + #[allow(non_upper_case_globals)] + const _DERIVE_Drop_FOR_Z: () = { + impl Drop for Z { + fn drop(&mut self) { + self.zeroize(); + } + } + }; + } + no_build // tests the code compiles are in the `zeroize` crate + } + } + + #[test] + fn zeroize_with_skip() { + test_derive! { + derive_zeroize_without_drop { + struct Z { + a: String, + b: Vec<u8>, + #[zeroize(skip)] + c: [u8; 3], + } + } + expands to { + #[allow(non_upper_case_globals)] + #[doc(hidden)] + const _DERIVE_zeroize_Zeroize_FOR_Z: () = { + extern crate zeroize; + impl zeroize::Zeroize for Z { + fn zeroize(&mut self) { + match self { + Z { + a: ref mut __binding_0, + b: ref mut __binding_1, + .. + } => { + { __binding_0.zeroize(); } + { __binding_1.zeroize(); } + } + } + } + } + }; + } + no_build // tests the code compiles are in the `zeroize` crate + } + } + + #[test] + fn zeroize_with_bound() { + test_derive! { + derive_zeroize { + #[zeroize(bound = "T: MyTrait")] + struct Z<T>(T); + } + expands to { + #[allow(non_upper_case_globals)] + #[doc(hidden)] + const _DERIVE_zeroize_Zeroize_FOR_Z: () = { + extern crate zeroize; + impl<T> zeroize::Zeroize for Z<T> + where T: MyTrait + { + fn zeroize(&mut self) { + match self { + Z(ref mut __binding_0,) => { + { __binding_0.zeroize(); } + } + } + } + } + }; + } + no_build // tests the code compiles are in the `zeroize` crate + } + } + + #[test] + fn zeroize_only_drop() { + test_derive! { + derive_zeroize_on_drop { + struct Z { + a: String, + b: Vec<u8>, + c: [u8; 3], + } + } + expands to { + #[allow(non_upper_case_globals)] + const _DERIVE_Drop_FOR_Z: () = { + impl Drop for Z { + fn drop(&mut self) { + use zeroize::__internal::AssertZeroize; + use zeroize::__internal::AssertZeroizeOnDrop; + match self { + Z { + a: ref mut __binding_0, + b: ref mut __binding_1, + c: ref mut __binding_2, + } => { + { __binding_0.zeroize_or_on_drop(); } + { __binding_1.zeroize_or_on_drop(); } + { __binding_2.zeroize_or_on_drop(); } + } + } + } + } + }; + #[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!( + #[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); + )); + } + + fn parse_zeroize_test(unparsed: &str) -> TokenStream { + derive_zeroize(Structure::new( + &parse_str(unparsed).expect("Failed to parse test input"), + )) + } +} |