diff --git a/src/lib.rs b/src/lib.rs index 81fc609..5421053 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -139,7 +139,6 @@ #![warn(rust_2018_idioms)] #![warn(rust_2021_compatibility)] #![warn(unused)] - #![warn(bare_trait_objects)] #![warn(dead_code)] #![warn(missing_copy_implementations)] @@ -156,18 +155,23 @@ #![warn(unused_results)] #![warn(unsafe_code)] #![warn(variant_size_differences)] - #![cfg_attr(feature = "cargo-clippy", warn(clippy::all))] #![cfg_attr(feature = "cargo-clippy", warn(clippy::pedantic))] #![cfg_attr(feature = "cargo-clippy", warn(clippy::nursery))] #![cfg_attr(feature = "cargo-clippy", warn(clippy::clone_on_ref_ptr))] -#![cfg_attr(feature = "cargo-clippy", warn(clippy::decimal_literal_representation))] +#![cfg_attr( + feature = "cargo-clippy", + warn(clippy::decimal_literal_representation) +)] #![cfg_attr(feature = "cargo-clippy", warn(clippy::else_if_without_else))] #![cfg_attr(feature = "cargo-clippy", warn(clippy::float_arithmetic))] #![cfg_attr(feature = "cargo-clippy", warn(clippy::float_cmp_const))] #![cfg_attr(feature = "cargo-clippy", warn(clippy::indexing_slicing))] #![cfg_attr(feature = "cargo-clippy", warn(clippy::mem_forget))] -#![cfg_attr(feature = "cargo-clippy", warn(clippy::missing_docs_in_private_items))] +#![cfg_attr( + feature = "cargo-clippy", + warn(clippy::missing_docs_in_private_items) +)] #![cfg_attr(feature = "cargo-clippy", warn(clippy::multiple_inherent_impl))] #![cfg_attr(feature = "cargo-clippy", warn(clippy::multiple_inherent_impl))] #![cfg_attr(feature = "cargo-clippy", warn(clippy::print_stdout))] @@ -176,13 +180,10 @@ #![cfg_attr(feature = "cargo-clippy", warn(clippy::shadow_same))] #![cfg_attr(feature = "cargo-clippy", warn(clippy::unimplemented))] #![cfg_attr(feature = "cargo-clippy", warn(clippy::use_debug))] - #![cfg_attr(feature = "cargo-clippy", allow(clippy::module_name_repetitions))] #![cfg_attr(feature = "cargo-clippy", allow(clippy::must_use_candidate))] - // disabled due to https://github.com/rust-lang/rust-clippy/issues/5369 #![cfg_attr(feature = "cargo-clippy", allow(clippy::redundant_pub_crate))] - // disabled due to https://github.com/rust-lang/rust/issues/69952 #![cfg_attr(feature = "cargo-clippy", allow(clippy::wildcard_imports))] @@ -239,7 +240,7 @@ mod assert { /// is performed at least once (and therefore the condition was /// tested). macro_rules! tested { - ($cond:expr) => () + ($cond:expr) => {}; } } @@ -249,7 +250,7 @@ mod assert { mod assert { #![allow(unused_macros)] macro_rules! proven { - ($($arg:tt)*) => (); + ($($arg:tt)*) => {}; } macro_rules! always { @@ -299,7 +300,7 @@ mod assert { mod assert { #![allow(unused_macros)] macro_rules! proven { - ($($arg:tt)*) => (); + ($($arg:tt)*) => {}; } macro_rules! always { @@ -323,7 +324,7 @@ mod assert { } macro_rules! tested { - ($cond:expr) => (); + ($cond:expr) => {}; } } @@ -339,10 +340,10 @@ mod boxed; mod secret; /// Container for `SecretBox`. -mod secret_box; +pub mod secret_box; /// Container for `SecretVec`. -mod secret_vec; +pub mod secret_vec; pub mod traits; diff --git a/src/secret_vec.rs b/src/secret_vec.rs index 1df86a9..8aad114 100644 --- a/src/secret_vec.rs +++ b/src/secret_vec.rs @@ -2,6 +2,7 @@ use crate::boxed::Box; use crate::traits::*; use std::fmt::{self, Debug, Formatter}; +use std::iter::FusedIterator; use std::ops::{Deref, DerefMut}; /// A type for protecting variable-length secrets allocated on the heap. @@ -156,6 +157,52 @@ pub struct RefMut<'a, T: Bytes> { boxed: &'a mut Box, } +/// An immutable wrapper around an item in a [`SecretVec`]. +/// +/// When this wrapper is dropped, it ensures that the underlying memory +/// is re-locked. +pub struct ItemRef<'a, T: Bytes> { + /// an imutably-unlocked reference to the protected memory of a + /// [`SecretVec`]. + boxed: &'a Box, + /// A reference to an item inside the box. + item: &'a T, +} + +/// A mutable wrapper around an item in a [`SecretVec`]. +/// +/// When this wrapper is dropped, it ensures that the underlying memory +/// is re-locked. +pub struct ItemMut<'a, T: Bytes> { + /// a mutably-unlocked reference to the protected memory of a + /// [`SecretVec`]. + boxed: &'a Box, + /// A reference to an item inside the box. + item: &'a mut T, +} + +/// An iterator for [`SecretVec`] that returns [`ItemRef`]. +/// +/// When all values from the iterator are dropped, the underlying memory is +/// re-locked. +pub struct IterRef<'a, T: Bytes> { + /// Vec to be iterated through. + vec: &'a SecretVec, + /// Position in Vec, protected to conceal information. + idx: usize, +} + +/// An iterator for [`SecretVec`] that returns [`ItemMut`]. +/// +/// When all values from the iterator are dropped, the underlying memory is +/// re-locked. +pub struct IterMut<'a, T: Bytes> { + /// Vec to be iterated through. + vec: &'a mut SecretVec, + /// Position in Vec, protected to conceal information. + idx: usize, +} + impl SecretVec { /// Instantiates and returns a new `SecretVec`. /// @@ -194,8 +241,7 @@ impl SecretVec { where F: FnOnce(&mut [T]) -> Result, { - Box::try_new(1, |b| f(b.as_mut_slice())) - .map(|b| Self { boxed: b }) + Box::try_new(1, |b| f(b.as_mut_slice())).map(|b| Self { boxed: b }) } /// Returns the number of elements in the [`SecretVec`]. @@ -257,6 +303,62 @@ impl SecretVec { pub fn borrow_mut(&mut self) -> RefMut<'_, T> { RefMut::new(&mut self.boxed) } + + /// Immutably borrows an item in the [`SecretVec`], returning a wrapper + /// that ensures the underlying memory is [`mprotect(2)`][mprotect]ed when + /// all borrowed items exit scope. + /// + /// Example: + /// + /// ``` + /// # use secrets::SecretVec; + /// let mut secret = SecretVec::::new(2, |s| { + /// s[0] = 3; + /// s[1] = 5; + /// }); + /// + /// assert_eq!(*secret.get(0).unwrap(), 3); + /// assert_eq!(secret.get(2), None); + /// assert_eq!((*secret.get(0).unwrap(), *secret.get(1).unwrap()), (3, 5)); + /// ``` + /// + /// Note: not implemented with the standard [`Index`] trait, as it requires + /// returning a reference. Giving back a wrapper type is not supported. + /// + /// [mprotect]: http://man7.org/linux/man-pages/man2/mprotect.2.html + pub fn get(&self, index: usize) -> Option> { + ItemRef::new(&self.boxed, index) + } + + /// Mutably borrows an item in the [`SecretVec`], returning a wrapper + /// that ensures the underlying memory is [`mprotect(2)`][mprotect]ed when + /// all borrowed items exit scope. + /// + /// Example: + /// + /// ``` + /// # use secrets::SecretVec; + /// let mut secret = SecretVec::::new(2, |s| { + /// s[0] = 3; + /// s[1] = 5; + /// }); + /// + /// assert_eq!(*secret.get_mut(0).unwrap(), 3); + /// assert_eq!(secret.get_mut(2), None); + /// + /// *secret.get_mut(1).unwrap() = 1; + /// + /// assert_eq!(*secret.get_mut(1).unwrap(), 1); + /// ``` + /// + /// Note: not implemented with the standard [`IndexMut`] trait, as it + /// requires returning a reference. Giving back a wrapper type is not + /// supported. + /// + /// [mprotect]: http://man7.org/linux/man-pages/man2/mprotect.2.html + pub fn get_mut(&mut self, index: usize) -> Option> { + ItemMut::new(&mut self.boxed, index) + } } impl SecretVec { @@ -415,6 +517,218 @@ impl PartialEq> for RefMut<'_, T> { impl Eq for RefMut<'_, T> {} +/// ----- Single item representations ---- /// + +impl<'a, T: Bytes> ItemRef<'a, T> { + /// Instantiates a new `ItemRef`. + #[allow(clippy::shadow_reuse)] + fn new(boxed: &'a Box, item: usize) -> Option { + let boxed = boxed.unlock(); + boxed.as_slice().get(item).map_or_else( + || { + // Need to re-lock here, as item is not being created. + boxed.lock(); + None + }, + |item| Some(Self { boxed, item }), + ) + } +} + +impl Clone for ItemRef<'_, T> { + fn clone(&self) -> Self { + Self { + boxed: self.boxed.unlock(), + item: self.item, + } + } +} + +impl Drop for ItemRef<'_, T> { + fn drop(&mut self) { + self.boxed.lock(); + } +} + +impl Deref for ItemRef<'_, T> { + type Target = T; + + fn deref(&self) -> &Self::Target { + self.item + } +} + +impl Debug for ItemRef<'_, T> { + fn fmt(&self, fmt: &mut Formatter<'_>) -> fmt::Result { + write!(fmt, "{{ {} bytes redacted }}", self.item.size()) + } +} + +impl PartialEq for ItemRef<'_, T> { + fn eq(&self, rhs: &Self) -> bool { + // technically we could punt to `self.boxed.eq(&other.boxed), + // but the handler for that performs some extra locks and + // unlocks which are unnecessary here since we know both sides + // are already unlocked + self.constant_eq(rhs) + } +} + +impl Eq for ItemRef<'_, T> {} + +impl<'a, T: Bytes> ItemMut<'a, T> { + /// Instantiates a new `ItemMut`. + #[allow(clippy::shadow_reuse)] + fn new(boxed: &'a mut Box, item: usize) -> Option { + let boxed = boxed.unlock_mut(); + + // Need to borrow the box immutably, for re-locking, but also borrow + // the box mutably to access the value inside. + // The mutable access to an item inside the box does not disrupt the + // immutable access to box state - the immutable changes to box only + // occur when mutable item access is dropped. + // The immutable access to the box state is not disrupted by mutating + // an item inside the box. + // The end effect outside of this struct is equivalent to a regular + // mutable borrow of the box. + let boxed_ptr: *mut _ = boxed; + let item = unsafe { &mut *boxed_ptr }.as_mut_slice().get_mut(item); + + // Needs this form to solve lifetimes + #[allow(clippy::option_if_let_else)] + if let Some(item) = item { + Some(Self { boxed, item }) + } else { + boxed.lock(); + None + } + } +} + +impl Drop for ItemMut<'_, T> { + fn drop(&mut self) { + self.boxed.lock(); + } +} + +impl Deref for ItemMut<'_, T> { + type Target = T; + + fn deref(&self) -> &Self::Target { + self.item + } +} + +impl DerefMut for ItemMut<'_, T> { + fn deref_mut(&mut self) -> &mut Self::Target { + self.item + } +} + +impl Debug for ItemMut<'_, T> { + fn fmt(&self, fmt: &mut Formatter<'_>) -> fmt::Result { + write!(fmt, "{{ {} bytes redacted }}", self.item.size()) + } +} + +impl PartialEq for ItemMut<'_, T> { + fn eq(&self, rhs: &Self) -> bool { + // technically we could punt to `self.boxed.eq(&other.boxed), + // but the handler for that performs some extra locks and + // unlocks which are unnecessary here since we know both sides + // are already unlocked + self.constant_eq(rhs) + } +} + +impl Eq for ItemMut<'_, T> {} + +impl<'a, T: Bytes> Iterator for IterRef<'a, T> { + type Item = ItemRef<'a, T>; + fn next(&mut self) -> Option { + if self.idx < self.vec.len() { + let val = self.vec.get(self.idx); + self.idx += 1; + val + } else { + None + } + } + + fn size_hint(&self) -> (usize, Option) { + let len = self.vec.len() - self.idx; + (len, Some(len)) + } + + fn nth(&mut self, n: usize) -> Option { + self.idx += n; + self.next() + } +} + +impl ExactSizeIterator for IterRef<'_, T> {} +impl FusedIterator for IterRef<'_, T> {} + +impl<'a, T: Bytes> IntoIterator for &'a SecretVec { + type IntoIter = IterRef<'a, T>; + type Item = ItemRef<'a, T>; + + fn into_iter(self) -> Self::IntoIter { + IterRef { vec: self, idx: 0 } + } +} + +impl SecretVec { + /// Wraps `into_iter`. + pub fn iter(&self) -> IterRef<'_, T> { + self.into_iter() + } +} + +impl<'a, T: Bytes> Iterator for IterMut<'a, T> { + type Item = ItemMut<'a, T>; + fn next(&mut self) -> Option { + if self.idx < self.vec.len() { + // Need to use unsafe to solve lifetimes with trait + let this: *mut _ = self; + let val = unsafe { &mut *this }.vec.get_mut(self.idx); + self.idx += 1; + val + } else { + None + } + } + + fn size_hint(&self) -> (usize, Option) { + let len = self.vec.len() - self.idx; + (len, Some(len)) + } + + fn nth(&mut self, n: usize) -> Option { + self.idx += n; + self.next() + } +} + +impl ExactSizeIterator for IterMut<'_, T> {} +impl FusedIterator for IterMut<'_, T> {} + +impl<'a, T: Bytes> IntoIterator for &'a mut SecretVec { + type IntoIter = IterMut<'a, T>; + type Item = ItemMut<'a, T>; + + fn into_iter(self) -> Self::IntoIter { + IterMut { vec: self, idx: 0 } + } +} + +impl SecretVec { + /// Wraps `into_iter`. + pub fn iter_mut(&mut self) -> IterMut<'_, T> { + self.into_iter() + } +} + // LCOV_EXCL_START #[cfg(test)] @@ -438,7 +752,7 @@ mod test { #[test] fn it_allows_borrowing_immutably() { let secret = SecretVec::::zero(2); - let s = secret.borrow(); + let s = secret.borrow(); assert_eq!(*s, [0, 0]); } @@ -446,7 +760,7 @@ mod test { #[test] fn it_allows_borrowing_mutably() { let mut secret = SecretVec::::zero(2); - let mut s = secret.borrow_mut(); + let mut s = secret.borrow_mut(); s.clone_from_slice(&[7, 1][..]); @@ -504,7 +818,7 @@ mod test { #[test] fn it_safely_clones_immutable_references() { - let secret = SecretVec::::random(4); + let secret = SecretVec::::random(4); let borrow_1 = secret.borrow(); let borrow_2 = borrow_1.clone();