Added type for clearing temporary values.

This commit is contained in:
DrPeterVanNostrand 2018-09-10 19:36:53 +00:00
parent b48a4b6812
commit 5f09f96345
3 changed files with 249 additions and 310 deletions

View File

@ -26,18 +26,15 @@ extern crate tiny_keccak;
pub mod error; pub mod error;
mod into_fr; mod into_fr;
pub mod poly; pub mod poly;
mod secret;
pub mod serde_impl; pub mod serde_impl;
use std::env;
use std::fmt; use std::fmt;
use std::hash::{Hash, Hasher}; use std::hash::{Hash, Hasher};
use std::mem::{size_of, size_of_val};
use std::ptr::copy_nonoverlapping; use std::ptr::copy_nonoverlapping;
use byteorder::{BigEndian, ByteOrder}; use byteorder::{BigEndian, ByteOrder};
use errno::errno;
use init_with::InitWith; use init_with::InitWith;
use memsec::{memzero, mlock, munlock};
use pairing::bls12_381::{Bls12, Fr, G1Affine, G2Affine, G1, G2}; use pairing::bls12_381::{Bls12, Fr, G1Affine, G2Affine, G1, G2};
use pairing::{CurveAffine, CurveProjective, Engine, Field}; use pairing::{CurveAffine, CurveProjective, Engine, Field};
use rand::{ChaChaRng, OsRng, Rand, Rng, SeedableRng}; use rand::{ChaChaRng, OsRng, Rand, Rng, SeedableRng};
@ -46,56 +43,7 @@ use tiny_keccak::sha3_256;
use error::{Error, Result}; use error::{Error, Result};
use into_fr::IntoFr; use into_fr::IntoFr;
use poly::{Commitment, Poly}; use poly::{Commitment, Poly};
use secret::{clear_fr, ContainsSecret, MemRange, FR_SIZE};
lazy_static! {
// Sets whether or not `mlock`ing is enabled. Memory locking is enabled by default; it can be
// disabled by setting the environment variable `MLOCK_SECRETS=false`. This is useful when you
// are running on a system where you do not have the ability to increase the systems locked
// memory limit (which can be found using the Unix command: `ulimit -l`). For example, we
// disable `mlock`ing of secrets when testing crates that depend on `threshold_crypto` when
// running in Travis CI because Travis has a locked memory limit of 64kb, which we may exceed
// while running `cargo test`. Disabling `mlock`ing for secret values allows secret keys to be
// swapped/core-dumped to disk, resulting in unmanaged copies of secrets to hang around in
// memory; this is significantly less secure than enabling memory locking (the default). Only
// set `MLOCK_SECRETS=false` in development/testing.
pub(crate) static ref SHOULD_MLOCK_SECRETS: bool = match env::var("MLOCK_SECRETS") {
Ok(s) => s.parse().unwrap_or(true),
_ => true,
};
// The size in bytes of a single `Fr` field element.
static ref FR_SIZE: usize = size_of::<Fr>();
}
// Overwrites a single field element with zeros.
pub(crate) fn clear_fr(fr_ptr: *mut u8) {
unsafe { memzero(fr_ptr, *FR_SIZE) };
}
/// Marks a type as containing one or more secret prime field elements.
pub(crate) trait ContainsSecret {
/// Calls the `mlock` system call on the region of memory allocated for the secret prime field
/// element or elements. This results in that region of memory not being being copied to disk,
/// either in a swap to disk or core dump. This method is called on every created instance of
/// a secret type.
///
/// # Errors
///
/// An `Error::MlockFailed` is returned if we failed to `mlock` the secret data.
fn mlock_secret_memory(&self) -> Result<()>;
/// Undoes the `mlock` on the secret region of memory via the `munlock` system call.
///
/// # Errors
///
/// An `Error::MunlockFailed` is returned if we failed to `munlock` the secret data; this
/// method is called on each secret type when it goes out of scope.
fn munlock_secret_memory(&self) -> Result<()>;
/// Overwrites the secret prime field element or elements with zeros; this method is called on
/// each each secret type when it goes out of scope.
fn zero_secret_memory(&self);
}
/// Wrapper for a byte array, whose `Debug` implementation outputs shortened hexadecimal strings. /// Wrapper for a byte array, whose `Debug` implementation outputs shortened hexadecimal strings.
pub struct HexBytes<'a>(pub &'a [u8]); pub struct HexBytes<'a>(pub &'a [u8]);
@ -303,15 +251,15 @@ impl Clone for SecretKey {
} }
} }
// Zeroes out and unlocks the memory allocated from the `SecretKey`'s field element. /// Zeroes out and unlocks the memory allocated from the `SecretKey`'s field element.
// ///
// # Panics /// # Panics
// ///
// Panics if we fail to unlock the memory containing the field element. /// Panics if we fail to unlock the memory containing the field element.
impl Drop for SecretKey { impl Drop for SecretKey {
fn drop(&mut self) { fn drop(&mut self) {
self.zero_secret_memory(); self.zero_secret();
if let Err(e) = self.munlock_secret_memory() { if let Err(e) = self.munlock_secret() {
panic!("Failed to drop `SecretKey`: {}", e); panic!("Failed to drop `SecretKey`: {}", e);
} }
} }
@ -325,47 +273,10 @@ impl fmt::Debug for SecretKey {
} }
impl ContainsSecret for SecretKey { impl ContainsSecret for SecretKey {
fn mlock_secret_memory(&self) -> Result<()> { fn secret_memory(&self) -> MemRange {
if !*SHOULD_MLOCK_SECRETS {
return Ok(());
}
let ptr = &*self.0 as *const Fr as *mut u8; let ptr = &*self.0 as *const Fr as *mut u8;
let n_bytes = size_of_val(&*self.0); let n_bytes = *FR_SIZE;
let mlock_succeeded = unsafe { mlock(ptr, n_bytes) }; MemRange { ptr, n_bytes }
if mlock_succeeded {
Ok(())
} else {
let e = Error::MlockFailed {
errno: errno(),
addr: format!("{:?}", ptr),
n_bytes,
};
Err(e)
}
}
fn munlock_secret_memory(&self) -> Result<()> {
if !*SHOULD_MLOCK_SECRETS {
return Ok(());
}
let ptr = &*self.0 as *const Fr as *mut u8;
let n_bytes = size_of_val(&*self.0);
let munlock_succeeded = unsafe { munlock(ptr, n_bytes) };
if munlock_succeeded {
Ok(())
} else {
let e = Error::MunlockFailed {
errno: errno(),
addr: format!("{:?}", ptr),
n_bytes,
};
Err(e)
}
}
fn zero_secret_memory(&self) {
let ptr = &*self.0 as *const Fr as *mut u8;
clear_fr(ptr);
} }
} }
@ -415,7 +326,7 @@ impl SecretKey {
} }
clear_fr(fr_ptr as *mut u8); clear_fr(fr_ptr as *mut u8);
let sk = SecretKey(boxed_fr); let sk = SecretKey(boxed_fr);
sk.mlock_secret_memory()?; sk.mlock_secret()?;
Ok(sk) Ok(sk)
} }

View File

@ -22,13 +22,13 @@ use std::hash::{Hash, Hasher};
use std::mem::size_of_val; use std::mem::size_of_val;
use std::{cmp, iter, ops}; use std::{cmp, iter, ops};
use errno::errno;
use memsec::{memzero, mlock, munlock};
use pairing::bls12_381::{Fr, G1Affine, G1}; use pairing::bls12_381::{Fr, G1Affine, G1};
use pairing::{CurveAffine, CurveProjective, Field}; use pairing::{CurveAffine, CurveProjective, Field};
use rand::Rng; use rand::Rng;
use super::{clear_fr, ContainsSecret, Error, IntoFr, Result, FR_SIZE, SHOULD_MLOCK_SECRETS}; use error::Result;
use into_fr::IntoFr;
use secret::{clear_fr, ContainsSecret, MemRange, Safe};
/// A univariate polynomial in the prime field. /// A univariate polynomial in the prime field.
#[derive(Serialize, Deserialize, PartialEq, Eq)] #[derive(Serialize, Deserialize, PartialEq, Eq)]
@ -42,8 +42,8 @@ pub struct Poly {
/// ///
/// # Panics /// # Panics
/// ///
/// Panics if we have hit the system's locked memory limit when `mlock`ing the new instance of /// Panics if we reach the system's locked memory limit when locking the polynomial's coefficients
/// `Poly`. /// into RAM.
impl Clone for Poly { impl Clone for Poly {
fn clone(&self) -> Self { fn clone(&self) -> Self {
Poly::try_from(self.coeff.clone()) Poly::try_from(self.coeff.clone())
@ -60,8 +60,7 @@ impl Debug for Poly {
/// # Panics /// # Panics
/// ///
/// Panics if we hit the system's locked memory limit or if we fail to unlock memory that has been /// Panics if we reach the system's locked memory limit.
/// truncated from the `coeff` vector.
#[cfg_attr(feature = "cargo-clippy", allow(suspicious_op_assign_impl))] #[cfg_attr(feature = "cargo-clippy", allow(suspicious_op_assign_impl))]
impl<B: Borrow<Poly>> ops::AddAssign<B> for Poly { impl<B: Borrow<Poly>> ops::AddAssign<B> for Poly {
fn add_assign(&mut self, rhs: B) { fn add_assign(&mut self, rhs: B) {
@ -69,8 +68,7 @@ impl<B: Borrow<Poly>> ops::AddAssign<B> for Poly {
let rhs_len = rhs.borrow().coeff.len(); let rhs_len = rhs.borrow().coeff.len();
if rhs_len > len { if rhs_len > len {
self.coeff.resize(rhs_len, Fr::zero()); self.coeff.resize(rhs_len, Fr::zero());
let n_coeffs_added = rhs_len - len; if let Err(e) = self.mlock_secret() {
if let Err(e) = self.extend_mlock(n_coeffs_added) {
panic!( panic!(
"Failed to extend `Poly` memory lock during add-assign: {}", "Failed to extend `Poly` memory lock during add-assign: {}",
e e
@ -80,9 +78,7 @@ impl<B: Borrow<Poly>> ops::AddAssign<B> for Poly {
for (self_c, rhs_c) in self.coeff.iter_mut().zip(&rhs.borrow().coeff) { for (self_c, rhs_c) in self.coeff.iter_mut().zip(&rhs.borrow().coeff) {
self_c.add_assign(rhs_c); self_c.add_assign(rhs_c);
} }
if let Err(e) = self.remove_zeros() { self.remove_zeros();
panic!("Failed to unlock `Poly` memory during add-assign: {}", e);
}
} }
} }
@ -105,24 +101,19 @@ impl<B: Borrow<Poly>> ops::Add<B> for Poly {
/// # Panics /// # Panics
/// ///
/// Panics if we hit the system's locked memory limit or if we fail to unlock memory that has been /// Panics if we reach the system's locked memory limit.
/// truncated from the `coeff` vector.
impl<'a> ops::Add<Fr> for Poly { impl<'a> ops::Add<Fr> for Poly {
type Output = Poly; type Output = Poly;
fn add(mut self, rhs: Fr) -> Self::Output { fn add(mut self, rhs: Fr) -> Self::Output {
if self.coeff.is_empty() { if self.is_zero() && !rhs.is_zero() {
if !rhs.is_zero() {
self.coeff.push(rhs); self.coeff.push(rhs);
if let Err(e) = self.extend_mlock(1) { if let Err(e) = self.mlock_secret() {
panic!("Failed to extend `Poly` memory lock during add: {}", e); panic!("Failed to extend `Poly` memory lock during add: {}", e);
} }
}
} else { } else {
self.coeff[0].add_assign(&rhs); self.coeff[0].add_assign(&rhs);
if let Err(e) = self.remove_zeros() { self.remove_zeros();
panic!("Failed to unlock `Poly` memory during add: {}", e);
}
} }
self self
} }
@ -138,16 +129,14 @@ impl<'a> ops::Add<u64> for Poly {
/// # Panics /// # Panics
/// ///
/// Panics if we hit the system's locked memory limit or if we fail to unlock memory that has been /// Panics if we reach the system's locked memory limit.
/// truncated from the `coeff` vector.
impl<B: Borrow<Poly>> ops::SubAssign<B> for Poly { impl<B: Borrow<Poly>> ops::SubAssign<B> for Poly {
fn sub_assign(&mut self, rhs: B) { fn sub_assign(&mut self, rhs: B) {
let len = self.coeff.len(); let len = self.coeff.len();
let rhs_len = rhs.borrow().coeff.len(); let rhs_len = rhs.borrow().coeff.len();
if rhs_len > len { if rhs_len > len {
self.coeff.resize(rhs_len, Fr::zero()); self.coeff.resize(rhs_len, Fr::zero());
let n_coeffs_added = rhs_len - len; if let Err(e) = self.mlock_secret() {
if let Err(e) = self.extend_mlock(n_coeffs_added) {
panic!( panic!(
"Failed to extend `Poly` memory lock during sub-assign: {}", "Failed to extend `Poly` memory lock during sub-assign: {}",
e e
@ -157,9 +146,7 @@ impl<B: Borrow<Poly>> ops::SubAssign<B> for Poly {
for (self_c, rhs_c) in self.coeff.iter_mut().zip(&rhs.borrow().coeff) { for (self_c, rhs_c) in self.coeff.iter_mut().zip(&rhs.borrow().coeff) {
self_c.sub_assign(rhs_c); self_c.sub_assign(rhs_c);
} }
if let Err(e) = self.remove_zeros() { self.remove_zeros();
panic!("Failed to unlock `Poly` memory during sub-assign: {}", e);
}
} }
} }
@ -201,8 +188,7 @@ impl<'a> ops::Sub<u64> for Poly {
/// # Panics /// # Panics
/// ///
/// Panics if we hit the system's locked memory limit or if we fail to unlock memory that has been /// Panics if we reach the system's locked memory limit.
/// truncated from the `coeff` vector.
// Clippy thinks using any `+` and `-` in a `Mul` implementation is suspicious. // Clippy thinks using any `+` and `-` in a `Mul` implementation is suspicious.
#[cfg_attr(feature = "cargo-clippy", allow(suspicious_arithmetic_impl))] #[cfg_attr(feature = "cargo-clippy", allow(suspicious_arithmetic_impl))]
impl<'a, B: Borrow<Poly>> ops::Mul<B> for &'a Poly { impl<'a, B: Borrow<Poly>> ops::Mul<B> for &'a Poly {
@ -210,19 +196,20 @@ impl<'a, B: Borrow<Poly>> ops::Mul<B> for &'a Poly {
fn mul(self, rhs: B) -> Self::Output { fn mul(self, rhs: B) -> Self::Output {
let rhs = rhs.borrow(); let rhs = rhs.borrow();
if rhs.coeff.is_empty() || self.coeff.is_empty() { if rhs.is_zero() || self.is_zero() {
return Poly::zero(); return Poly::zero();
} }
let mut coeff = vec![Fr::zero(); self.coeff.len() + rhs.borrow().coeff.len() - 1]; let n_coeffs = self.coeff.len() + rhs.coeff.len() - 1;
let mut s; // TODO: Mlock and zero on drop. let mut coeffs = vec![Fr::zero(); n_coeffs];
for (i, cs) in self.coeff.iter().enumerate() { let mut tmp = Safe::new(Box::new(Fr::zero()));
for (j, cr) in rhs.coeff.iter().enumerate() { for (i, ca) in self.coeff.iter().enumerate() {
s = *cs; for (j, cb) in rhs.coeff.iter().enumerate() {
s.mul_assign(cr); *tmp = *ca;
coeff[i + j].add_assign(&s); tmp.mul_assign(cb);
coeffs[i + j].add_assign(&*tmp);
} }
} }
Poly::try_from(coeff) Poly::try_from(coeffs)
.unwrap_or_else(|e| panic!("Failed to create a new `Poly` during muliplication: {}", e)) .unwrap_or_else(|e| panic!("Failed to create a new `Poly` during muliplication: {}", e))
} }
} }
@ -244,7 +231,8 @@ impl<B: Borrow<Self>> ops::MulAssign<B> for Poly {
impl ops::MulAssign<Fr> for Poly { impl ops::MulAssign<Fr> for Poly {
fn mul_assign(&mut self, rhs: Fr) { fn mul_assign(&mut self, rhs: Fr) {
if rhs.is_zero() { if rhs.is_zero() {
*self = Poly::zero(); self.zero_secret();
self.coeff.clear();
} else { } else {
for c in &mut self.coeff { for c in &mut self.coeff {
c.mul_assign(&rhs); c.mul_assign(&rhs);
@ -262,10 +250,7 @@ impl<'a> ops::Mul<&'a Fr> for Poly {
fn mul(mut self, rhs: &Fr) -> Self::Output { fn mul(mut self, rhs: &Fr) -> Self::Output {
if rhs.is_zero() { if rhs.is_zero() {
self.zero_secret_memory(); self.zero_secret();
if let Err(e) = self.munlock_secret_memory() {
panic!("Failed to unlock `Poly` during multiplication: {}", e);
}
self.coeff.clear(); self.coeff.clear();
} else { } else {
self.coeff.iter_mut().for_each(|c| c.mul_assign(rhs)); self.coeff.iter_mut().for_each(|c| c.mul_assign(rhs));
@ -312,8 +297,8 @@ impl ops::Mul<u64> for Poly {
/// Panics if we fail to munlock the `coeff` vector. /// Panics if we fail to munlock the `coeff` vector.
impl Drop for Poly { impl Drop for Poly {
fn drop(&mut self) { fn drop(&mut self) {
self.zero_secret_memory(); self.zero_secret();
if let Err(e) = self.munlock_secret_memory() { if let Err(e) = self.munlock_secret() {
panic!("Failed to munlock `Poly` during drop: {}", e); panic!("Failed to munlock `Poly` during drop: {}", e);
} }
} }
@ -333,56 +318,10 @@ impl From<Vec<Fr>> for Poly {
} }
impl ContainsSecret for Poly { impl ContainsSecret for Poly {
fn mlock_secret_memory(&self) -> Result<()> { fn secret_memory(&self) -> MemRange {
if !*SHOULD_MLOCK_SECRETS {
return Ok(());
}
let ptr = self.coeff.as_ptr() as *mut u8; let ptr = self.coeff.as_ptr() as *mut u8;
let n_bytes = size_of_val(self.coeff.as_slice()); let n_bytes = size_of_val(self.coeff.as_slice());
if n_bytes == 0 { MemRange { ptr, n_bytes }
return Ok(());
}
let mlock_succeeded = unsafe { mlock(ptr, n_bytes) };
if mlock_succeeded {
Ok(())
} else {
let e = Error::MlockFailed {
errno: errno(),
addr: format!("{:?}", ptr),
n_bytes,
};
Err(e)
}
}
fn munlock_secret_memory(&self) -> Result<()> {
if !*SHOULD_MLOCK_SECRETS {
return Ok(());
}
let ptr = self.coeff.as_ptr() as *mut u8;
let n_bytes = size_of_val(self.coeff.as_slice());
if n_bytes == 0 {
return Ok(());
}
let munlock_succeeded = unsafe { munlock(ptr, n_bytes) };
if munlock_succeeded {
Ok(())
} else {
let e = Error::MunlockFailed {
errno: errno(),
addr: format!("{:?}", ptr),
n_bytes,
};
Err(e)
}
}
fn zero_secret_memory(&self) {
let ptr = self.coeff.as_ptr() as *mut u8;
let n_bytes = size_of_val(self.coeff.as_slice());
unsafe {
memzero(ptr, n_bytes);
}
} }
} }
@ -396,7 +335,7 @@ impl Poly {
/// Returns an `Error::MlockFailed` if we have reached the systems's locked memory limit. /// Returns an `Error::MlockFailed` if we have reached the systems's locked memory limit.
pub fn try_from(coeff: Vec<Fr>) -> Result<Self> { pub fn try_from(coeff: Vec<Fr>) -> Result<Self> {
let poly = Poly { coeff }; let poly = Poly { coeff };
poly.mlock_secret_memory()?; poly.mlock_secret()?;
Ok(poly) Ok(poly)
} }
@ -433,6 +372,11 @@ impl Poly {
Poly { coeff: vec![] } Poly { coeff: vec![] }
} }
/// Returns `true` if the polynomial is the constant value `0`.
pub fn is_zero(&self) -> bool {
self.coeff.iter().all(|coeff| coeff.is_zero())
}
/// Returns the polynomial with constant value `1`. This constructor is identical to /// Returns the polynomial with constant value `1`. This constructor is identical to
/// `Poly::try_one()` in every way except that this constructor panics if locking the `coeff` /// `Poly::try_one()` in every way except that this constructor panics if locking the `coeff`
/// vector into RAM fails, whereas `Poly::try_one()` returns an `Err`. /// vector into RAM fails, whereas `Poly::try_one()` returns an `Err`.
@ -596,16 +540,10 @@ impl Poly {
} }
/// Removes all trailing zero coefficients. /// Removes all trailing zero coefficients.
/// fn remove_zeros(&mut self) {
/// # Errors
///
/// An `Error::MunlockFailed` is returned if we failed to `munlock` the truncated portion of
/// the `coeff` vector.
fn remove_zeros(&mut self) -> Result<()> {
let zeros = self.coeff.iter().rev().take_while(|c| c.is_zero()).count(); let zeros = self.coeff.iter().rev().take_while(|c| c.is_zero()).count();
let len = self.coeff.len() - zeros; let len = self.coeff.len() - zeros;
self.coeff.truncate(len); self.coeff.truncate(len);
self.truncate_mlock(zeros)
} }
/// Returns the unique polynomial `f` of degree `samples.len() - 1` with the given values /// Returns the unique polynomial `f` of degree `samples.len() - 1` with the given values
@ -645,57 +583,6 @@ impl Poly {
Ok(poly) Ok(poly)
} }
// Removes the `mlock` for `len` elements that have been truncated from the `coeff` vector.
fn truncate_mlock(&self, len: usize) -> Result<()> {
if !*SHOULD_MLOCK_SECRETS {
return Ok(());
}
let n_bytes_truncated = *FR_SIZE * len;
if n_bytes_truncated == 0 {
return Ok(());
}
unsafe {
let ptr = self.coeff.as_ptr().offset(self.coeff.len() as isize) as *mut u8;
let munlock_succeeded = munlock(ptr, n_bytes_truncated);
if munlock_succeeded {
Ok(())
} else {
let e = Error::MunlockFailed {
errno: errno(),
addr: format!("{:?}", ptr),
n_bytes: n_bytes_truncated,
};
Err(e)
}
}
}
// Extends the `mlock` on the `coeff` vector when `len` new elements are added.
fn extend_mlock(&self, len: usize) -> Result<()> {
if !*SHOULD_MLOCK_SECRETS {
return Ok(());
}
let n_bytes_extended = *FR_SIZE * len;
if n_bytes_extended == 0 {
return Ok(());
}
let offset = (self.coeff.len() - len) as isize;
unsafe {
let ptr = self.coeff.as_ptr().offset(offset) as *mut u8;
let mlock_succeeded = mlock(ptr, n_bytes_extended);
if mlock_succeeded {
Ok(())
} else {
let e = Error::MunlockFailed {
errno: errno(),
addr: format!("{:?}", ptr),
n_bytes: n_bytes_extended,
};
Err(e)
}
}
}
/// Generates a non-redacted debug string. This method differs from /// Generates a non-redacted debug string. This method differs from
/// the `Debug` implementation in that it *does* leak the secret prime /// the `Debug` implementation in that it *does* leak the secret prime
/// field elements. /// field elements.
@ -799,7 +686,7 @@ impl Clone for BivarPoly {
degree: self.degree, degree: self.degree,
coeff: self.coeff.clone(), coeff: self.coeff.clone(),
}; };
if let Err(e) = poly.mlock_secret_memory() { if let Err(e) = poly.mlock_secret() {
panic!("Failed to clone `BivarPoly`: {}", e); panic!("Failed to clone `BivarPoly`: {}", e);
} }
poly poly
@ -811,8 +698,8 @@ impl Clone for BivarPoly {
/// Panics if we fail to munlock the `coeff` vector. /// Panics if we fail to munlock the `coeff` vector.
impl Drop for BivarPoly { impl Drop for BivarPoly {
fn drop(&mut self) { fn drop(&mut self) {
self.zero_secret_memory(); self.zero_secret();
if let Err(e) = self.munlock_secret_memory() { if let Err(e) = self.munlock_secret() {
panic!("Failed to munlock `BivarPoly` during drop: {}", e); panic!("Failed to munlock `BivarPoly` during drop: {}", e);
} }
} }
@ -829,56 +716,10 @@ impl Debug for BivarPoly {
} }
impl ContainsSecret for BivarPoly { impl ContainsSecret for BivarPoly {
fn mlock_secret_memory(&self) -> Result<()> { fn secret_memory(&self) -> MemRange {
if !*SHOULD_MLOCK_SECRETS { let ptr = self.coeff.as_ptr() as *const Fr as *mut u8;
return Ok(());
}
let ptr = self.coeff.as_ptr() as *mut u8;
let n_bytes = size_of_val(self.coeff.as_slice()); let n_bytes = size_of_val(self.coeff.as_slice());
if n_bytes == 0 { MemRange { ptr, n_bytes }
return Ok(());
}
let mlock_succeeded = unsafe { mlock(ptr, n_bytes) };
if mlock_succeeded {
Ok(())
} else {
let e = Error::MlockFailed {
errno: errno(),
addr: format!("{:?}", ptr),
n_bytes,
};
Err(e)
}
}
fn munlock_secret_memory(&self) -> Result<()> {
if !*SHOULD_MLOCK_SECRETS {
return Ok(());
}
let ptr = self.coeff.as_ptr() as *mut u8;
let n_bytes = size_of_val(self.coeff.as_slice());
if n_bytes == 0 {
return Ok(());
}
let munlock_succeeded = unsafe { munlock(ptr, n_bytes) };
if munlock_succeeded {
Ok(())
} else {
let e = Error::MunlockFailed {
errno: errno(),
addr: format!("{:?}", ptr),
n_bytes,
};
Err(e)
}
}
fn zero_secret_memory(&self) {
let ptr = self.coeff.as_ptr() as *mut u8;
let n_bytes = size_of_val(self.coeff.as_slice());
unsafe {
memzero(ptr, n_bytes);
}
} }
} }
@ -907,7 +748,7 @@ impl BivarPoly {
degree, degree,
coeff: (0..coeff_pos(degree + 1, 0)).map(|_| rng.gen()).collect(), coeff: (0..coeff_pos(degree + 1, 0)).map(|_| rng.gen()).collect(),
}; };
poly.mlock_secret_memory()?; poly.mlock_secret()?;
Ok(poly) Ok(poly)
} }

187
src/secret.rs Normal file
View File

@ -0,0 +1,187 @@
//! Utilities for working with secret values. This module includes functionality for locking (and
//! unlocking) memory into RAM and overwriting memory with zeros.
use std::env;
use std::mem::{size_of, size_of_val};
use std::ops::{Deref, DerefMut};
use errno::errno;
use memsec::{memzero, mlock, munlock};
use pairing::bls12_381::Fr;
use error::{Error, Result};
lazy_static! {
/// Sets whether or not `mlock`ing is enabled. Memory locking is enabled by default; it can be
/// disabled by setting the environment variable `MLOCK_SECRETS=false`. This is useful when you
/// are running on a system where you do not have the ability to increase the systems locked
/// memory limit (which can be found using the Unix command: `ulimit -l`). For example, we
/// disable `mlock`ing of secrets when testing crates that depend on `threshold_crypto` when
/// running in Travis CI because Travis has a locked memory limit of 64kb, which we may exceed
/// while running `cargo test`. Disabling `mlock`ing for secret values allows secret keys to
/// be swapped/core-dumped to disk, resulting in unmanaged copies of secrets to hang around in
/// memory; this is significantly less secure than enabling memory locking (the default). Only
/// set `MLOCK_SECRETS=false` in development/testing.
pub(crate) static ref SHOULD_MLOCK_SECRETS: bool = match env::var("MLOCK_SECRETS") {
Ok(s) => s.parse().unwrap_or(true),
_ => true,
};
/// The size in bytes of a single field element.
pub(crate) static ref FR_SIZE: usize = size_of::<Fr>();
}
/// Overwrites a single field element with zeros.
pub(crate) fn clear_fr(fr_ptr: *mut u8) {
unsafe { memzero(fr_ptr, *FR_SIZE) };
}
pub(crate) struct MemRange {
pub ptr: *mut u8,
pub n_bytes: usize,
}
/// Marks a type as containing some secret value.
pub(crate) trait ContainsSecret {
/// Returns the range of memory marked as secret.
fn secret_memory(&self) -> MemRange;
/// Locks a region of memory marked as secret into RAM.
///
/// The region of memory marked as secret will not be copied to disk, for example, during a
/// swap-to-disk or core dump. This method should be called upon instantiation of every type
/// that implements `ContainsSecret`.
///
/// Operating systems set a limit on the ammount of memory that a process may lock into RAM.
/// Due to this limitation, this method returns a `Result` in the event that memory locking
/// fails.
///
/// # Errors
///
/// An `Error::MlockFailed` is returned if we reach the system's locked memory limit or if we
/// attempt to lock an invalid region of memory.
fn mlock_secret(&self) -> Result<()> {
if !*SHOULD_MLOCK_SECRETS {
return Ok(());
}
let MemRange { ptr, n_bytes } = self.secret_memory();
if n_bytes == 0 {
return Ok(());
}
let mlock_succeeded = unsafe { mlock(ptr, n_bytes) };
if mlock_succeeded {
Ok(())
} else {
let e = Error::MlockFailed {
errno: errno(),
addr: format!("{:?}", ptr),
n_bytes,
};
Err(e)
}
}
/// Unlocks the memory lock for a region of memory marked as secret. If the secret region of
/// memory had not previosly been locked via the `.mlock_secret()` method, then this method
/// does nothing.
///
/// Once this method has been called, the secret region of memory will no longer be protected
/// from being copied to disk. This method should be called upon destruction of every type that
/// implements `ContainsSecret`.
///
/// # Errors
///
/// An `Error::MlockFailed` is returned if we attempt to lock an invalid region memory.
fn munlock_secret(&self) -> Result<()> {
if !*SHOULD_MLOCK_SECRETS {
return Ok(());
}
let MemRange { ptr, n_bytes } = self.secret_memory();
if n_bytes == 0 {
return Ok(());
}
let munlock_succeeded = unsafe { munlock(ptr, n_bytes) };
if munlock_succeeded {
Ok(())
} else {
let e = Error::MunlockFailed {
errno: errno(),
addr: format!("{:?}", ptr),
n_bytes,
};
Err(e)
}
}
/// Overwrites the secret region of memory with zeros.
///
/// This method should be called upon destruction of every type that implements `ContainsSecret`.
fn zero_secret(&self) {
let MemRange { ptr, n_bytes } = self.secret_memory();
unsafe { memzero(ptr, n_bytes) };
}
}
/// A wrapper around temporary values to ensuer that they are locked into RAM and cleared on drop.
///
/// `Safe<T>` is meant to be used a wrapper around `T`, where `T` is either an `&mut U` or
/// `Box<U>`.
pub(crate) struct Safe<T: DerefMut>(T);
impl<T> Deref for Safe<T>
where
T: DerefMut,
{
type Target = T::Target;
fn deref(&self) -> &Self::Target {
&*(self.0)
}
}
impl<T> DerefMut for Safe<T>
where
T: DerefMut,
{
fn deref_mut(&mut self) -> &mut Self::Target {
&mut *(self.0)
}
}
impl<T> Drop for Safe<T>
where
T: DerefMut,
{
fn drop(&mut self) {
self.zero_secret();
if let Err(e) = self.munlock_secret() {
panic!("Failed to drop `Safe`: {}", e);
}
}
}
impl<T> ContainsSecret for Safe<T>
where
T: DerefMut,
{
fn secret_memory(&self) -> MemRange {
let ptr = &*self.0 as *const T::Target as *mut u8;
let n_bytes = size_of_val(&*self.0);
MemRange { ptr, n_bytes }
}
}
impl<T> Safe<T>
where
T: DerefMut,
{
pub(crate) fn new(x: T) -> Self {
Safe::try_new(x).unwrap_or_else(|e| panic!("Failed to create `Safe`: {}", e))
}
pub(crate) fn try_new(x: T) -> Result<Self> {
let safe = Safe(x);
safe.mlock_secret()?;
Ok(safe)
}
}