Added type for clearing temporary values.
This commit is contained in:
parent
b48a4b6812
commit
5f09f96345
115
src/lib.rs
115
src/lib.rs
|
@ -26,18 +26,15 @@ extern crate tiny_keccak;
|
|||
pub mod error;
|
||||
mod into_fr;
|
||||
pub mod poly;
|
||||
mod secret;
|
||||
pub mod serde_impl;
|
||||
|
||||
use std::env;
|
||||
use std::fmt;
|
||||
use std::hash::{Hash, Hasher};
|
||||
use std::mem::{size_of, size_of_val};
|
||||
use std::ptr::copy_nonoverlapping;
|
||||
|
||||
use byteorder::{BigEndian, ByteOrder};
|
||||
use errno::errno;
|
||||
use init_with::InitWith;
|
||||
use memsec::{memzero, mlock, munlock};
|
||||
use pairing::bls12_381::{Bls12, Fr, G1Affine, G2Affine, G1, G2};
|
||||
use pairing::{CurveAffine, CurveProjective, Engine, Field};
|
||||
use rand::{ChaChaRng, OsRng, Rand, Rng, SeedableRng};
|
||||
|
@ -46,56 +43,7 @@ use tiny_keccak::sha3_256;
|
|||
use error::{Error, Result};
|
||||
use into_fr::IntoFr;
|
||||
use poly::{Commitment, Poly};
|
||||
|
||||
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);
|
||||
}
|
||||
use secret::{clear_fr, ContainsSecret, MemRange, FR_SIZE};
|
||||
|
||||
/// Wrapper for a byte array, whose `Debug` implementation outputs shortened hexadecimal strings.
|
||||
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.
|
||||
//
|
||||
// # Panics
|
||||
//
|
||||
// Panics if we fail to unlock the memory containing the field element.
|
||||
/// Zeroes out and unlocks the memory allocated from the `SecretKey`'s field element.
|
||||
///
|
||||
/// # Panics
|
||||
///
|
||||
/// Panics if we fail to unlock the memory containing the field element.
|
||||
impl Drop for SecretKey {
|
||||
fn drop(&mut self) {
|
||||
self.zero_secret_memory();
|
||||
if let Err(e) = self.munlock_secret_memory() {
|
||||
self.zero_secret();
|
||||
if let Err(e) = self.munlock_secret() {
|
||||
panic!("Failed to drop `SecretKey`: {}", e);
|
||||
}
|
||||
}
|
||||
|
@ -325,47 +273,10 @@ impl fmt::Debug for SecretKey {
|
|||
}
|
||||
|
||||
impl ContainsSecret for SecretKey {
|
||||
fn mlock_secret_memory(&self) -> Result<()> {
|
||||
if !*SHOULD_MLOCK_SECRETS {
|
||||
return Ok(());
|
||||
}
|
||||
fn secret_memory(&self) -> MemRange {
|
||||
let ptr = &*self.0 as *const Fr as *mut u8;
|
||||
let n_bytes = size_of_val(&*self.0);
|
||||
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.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);
|
||||
let n_bytes = *FR_SIZE;
|
||||
MemRange { ptr, n_bytes }
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -415,7 +326,7 @@ impl SecretKey {
|
|||
}
|
||||
clear_fr(fr_ptr as *mut u8);
|
||||
let sk = SecretKey(boxed_fr);
|
||||
sk.mlock_secret_memory()?;
|
||||
sk.mlock_secret()?;
|
||||
Ok(sk)
|
||||
}
|
||||
|
||||
|
|
257
src/poly.rs
257
src/poly.rs
|
@ -22,13 +22,13 @@ use std::hash::{Hash, Hasher};
|
|||
use std::mem::size_of_val;
|
||||
use std::{cmp, iter, ops};
|
||||
|
||||
use errno::errno;
|
||||
use memsec::{memzero, mlock, munlock};
|
||||
use pairing::bls12_381::{Fr, G1Affine, G1};
|
||||
use pairing::{CurveAffine, CurveProjective, Field};
|
||||
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.
|
||||
#[derive(Serialize, Deserialize, PartialEq, Eq)]
|
||||
|
@ -42,8 +42,8 @@ pub struct Poly {
|
|||
///
|
||||
/// # Panics
|
||||
///
|
||||
/// Panics if we have hit the system's locked memory limit when `mlock`ing the new instance of
|
||||
/// `Poly`.
|
||||
/// Panics if we reach the system's locked memory limit when locking the polynomial's coefficients
|
||||
/// into RAM.
|
||||
impl Clone for Poly {
|
||||
fn clone(&self) -> Self {
|
||||
Poly::try_from(self.coeff.clone())
|
||||
|
@ -60,8 +60,7 @@ impl Debug for Poly {
|
|||
|
||||
/// # Panics
|
||||
///
|
||||
/// Panics if we hit the system's locked memory limit or if we fail to unlock memory that has been
|
||||
/// truncated from the `coeff` vector.
|
||||
/// Panics if we reach the system's locked memory limit.
|
||||
#[cfg_attr(feature = "cargo-clippy", allow(suspicious_op_assign_impl))]
|
||||
impl<B: Borrow<Poly>> ops::AddAssign<B> for Poly {
|
||||
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();
|
||||
if rhs_len > len {
|
||||
self.coeff.resize(rhs_len, Fr::zero());
|
||||
let n_coeffs_added = rhs_len - len;
|
||||
if let Err(e) = self.extend_mlock(n_coeffs_added) {
|
||||
if let Err(e) = self.mlock_secret() {
|
||||
panic!(
|
||||
"Failed to extend `Poly` memory lock during add-assign: {}",
|
||||
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) {
|
||||
self_c.add_assign(rhs_c);
|
||||
}
|
||||
if let Err(e) = self.remove_zeros() {
|
||||
panic!("Failed to unlock `Poly` memory during add-assign: {}", e);
|
||||
}
|
||||
self.remove_zeros();
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -105,24 +101,19 @@ impl<B: Borrow<Poly>> ops::Add<B> for Poly {
|
|||
|
||||
/// # Panics
|
||||
///
|
||||
/// Panics if we hit the system's locked memory limit or if we fail to unlock memory that has been
|
||||
/// truncated from the `coeff` vector.
|
||||
/// Panics if we reach the system's locked memory limit.
|
||||
impl<'a> ops::Add<Fr> for Poly {
|
||||
type Output = Poly;
|
||||
|
||||
fn add(mut self, rhs: Fr) -> Self::Output {
|
||||
if self.coeff.is_empty() {
|
||||
if !rhs.is_zero() {
|
||||
self.coeff.push(rhs);
|
||||
if let Err(e) = self.extend_mlock(1) {
|
||||
panic!("Failed to extend `Poly` memory lock during add: {}", e);
|
||||
}
|
||||
if self.is_zero() && !rhs.is_zero() {
|
||||
self.coeff.push(rhs);
|
||||
if let Err(e) = self.mlock_secret() {
|
||||
panic!("Failed to extend `Poly` memory lock during add: {}", e);
|
||||
}
|
||||
} else {
|
||||
self.coeff[0].add_assign(&rhs);
|
||||
if let Err(e) = self.remove_zeros() {
|
||||
panic!("Failed to unlock `Poly` memory during add: {}", e);
|
||||
}
|
||||
self.remove_zeros();
|
||||
}
|
||||
self
|
||||
}
|
||||
|
@ -138,16 +129,14 @@ impl<'a> ops::Add<u64> for Poly {
|
|||
|
||||
/// # Panics
|
||||
///
|
||||
/// Panics if we hit the system's locked memory limit or if we fail to unlock memory that has been
|
||||
/// truncated from the `coeff` vector.
|
||||
/// Panics if we reach the system's locked memory limit.
|
||||
impl<B: Borrow<Poly>> ops::SubAssign<B> for Poly {
|
||||
fn sub_assign(&mut self, rhs: B) {
|
||||
let len = self.coeff.len();
|
||||
let rhs_len = rhs.borrow().coeff.len();
|
||||
if rhs_len > len {
|
||||
self.coeff.resize(rhs_len, Fr::zero());
|
||||
let n_coeffs_added = rhs_len - len;
|
||||
if let Err(e) = self.extend_mlock(n_coeffs_added) {
|
||||
if let Err(e) = self.mlock_secret() {
|
||||
panic!(
|
||||
"Failed to extend `Poly` memory lock during sub-assign: {}",
|
||||
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) {
|
||||
self_c.sub_assign(rhs_c);
|
||||
}
|
||||
if let Err(e) = self.remove_zeros() {
|
||||
panic!("Failed to unlock `Poly` memory during sub-assign: {}", e);
|
||||
}
|
||||
self.remove_zeros();
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -201,8 +188,7 @@ impl<'a> ops::Sub<u64> for Poly {
|
|||
|
||||
/// # Panics
|
||||
///
|
||||
/// Panics if we hit the system's locked memory limit or if we fail to unlock memory that has been
|
||||
/// truncated from the `coeff` vector.
|
||||
/// Panics if we reach the system's locked memory limit.
|
||||
// Clippy thinks using any `+` and `-` in a `Mul` implementation is suspicious.
|
||||
#[cfg_attr(feature = "cargo-clippy", allow(suspicious_arithmetic_impl))]
|
||||
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 {
|
||||
let rhs = rhs.borrow();
|
||||
if rhs.coeff.is_empty() || self.coeff.is_empty() {
|
||||
if rhs.is_zero() || self.is_zero() {
|
||||
return Poly::zero();
|
||||
}
|
||||
let mut coeff = vec![Fr::zero(); self.coeff.len() + rhs.borrow().coeff.len() - 1];
|
||||
let mut s; // TODO: Mlock and zero on drop.
|
||||
for (i, cs) in self.coeff.iter().enumerate() {
|
||||
for (j, cr) in rhs.coeff.iter().enumerate() {
|
||||
s = *cs;
|
||||
s.mul_assign(cr);
|
||||
coeff[i + j].add_assign(&s);
|
||||
let n_coeffs = self.coeff.len() + rhs.coeff.len() - 1;
|
||||
let mut coeffs = vec![Fr::zero(); n_coeffs];
|
||||
let mut tmp = Safe::new(Box::new(Fr::zero()));
|
||||
for (i, ca) in self.coeff.iter().enumerate() {
|
||||
for (j, cb) in rhs.coeff.iter().enumerate() {
|
||||
*tmp = *ca;
|
||||
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))
|
||||
}
|
||||
}
|
||||
|
@ -244,7 +231,8 @@ impl<B: Borrow<Self>> ops::MulAssign<B> for Poly {
|
|||
impl ops::MulAssign<Fr> for Poly {
|
||||
fn mul_assign(&mut self, rhs: Fr) {
|
||||
if rhs.is_zero() {
|
||||
*self = Poly::zero();
|
||||
self.zero_secret();
|
||||
self.coeff.clear();
|
||||
} else {
|
||||
for c in &mut self.coeff {
|
||||
c.mul_assign(&rhs);
|
||||
|
@ -262,10 +250,7 @@ impl<'a> ops::Mul<&'a Fr> for Poly {
|
|||
|
||||
fn mul(mut self, rhs: &Fr) -> Self::Output {
|
||||
if rhs.is_zero() {
|
||||
self.zero_secret_memory();
|
||||
if let Err(e) = self.munlock_secret_memory() {
|
||||
panic!("Failed to unlock `Poly` during multiplication: {}", e);
|
||||
}
|
||||
self.zero_secret();
|
||||
self.coeff.clear();
|
||||
} else {
|
||||
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.
|
||||
impl Drop for Poly {
|
||||
fn drop(&mut self) {
|
||||
self.zero_secret_memory();
|
||||
if let Err(e) = self.munlock_secret_memory() {
|
||||
self.zero_secret();
|
||||
if let Err(e) = self.munlock_secret() {
|
||||
panic!("Failed to munlock `Poly` during drop: {}", e);
|
||||
}
|
||||
}
|
||||
|
@ -333,56 +318,10 @@ impl From<Vec<Fr>> for Poly {
|
|||
}
|
||||
|
||||
impl ContainsSecret for Poly {
|
||||
fn mlock_secret_memory(&self) -> Result<()> {
|
||||
if !*SHOULD_MLOCK_SECRETS {
|
||||
return Ok(());
|
||||
}
|
||||
fn secret_memory(&self) -> MemRange {
|
||||
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 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);
|
||||
}
|
||||
MemRange { ptr, n_bytes }
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -396,7 +335,7 @@ impl Poly {
|
|||
/// Returns an `Error::MlockFailed` if we have reached the systems's locked memory limit.
|
||||
pub fn try_from(coeff: Vec<Fr>) -> Result<Self> {
|
||||
let poly = Poly { coeff };
|
||||
poly.mlock_secret_memory()?;
|
||||
poly.mlock_secret()?;
|
||||
Ok(poly)
|
||||
}
|
||||
|
||||
|
@ -433,6 +372,11 @@ impl Poly {
|
|||
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
|
||||
/// `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`.
|
||||
|
@ -596,16 +540,10 @@ impl Poly {
|
|||
}
|
||||
|
||||
/// Removes all trailing zero coefficients.
|
||||
///
|
||||
/// # Errors
|
||||
///
|
||||
/// An `Error::MunlockFailed` is returned if we failed to `munlock` the truncated portion of
|
||||
/// the `coeff` vector.
|
||||
fn remove_zeros(&mut self) -> Result<()> {
|
||||
fn remove_zeros(&mut self) {
|
||||
let zeros = self.coeff.iter().rev().take_while(|c| c.is_zero()).count();
|
||||
let len = self.coeff.len() - zeros;
|
||||
self.coeff.truncate(len);
|
||||
self.truncate_mlock(zeros)
|
||||
}
|
||||
|
||||
/// Returns the unique polynomial `f` of degree `samples.len() - 1` with the given values
|
||||
|
@ -645,57 +583,6 @@ impl 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
|
||||
/// the `Debug` implementation in that it *does* leak the secret prime
|
||||
/// field elements.
|
||||
|
@ -799,7 +686,7 @@ impl Clone for BivarPoly {
|
|||
degree: self.degree,
|
||||
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);
|
||||
}
|
||||
poly
|
||||
|
@ -811,8 +698,8 @@ impl Clone for BivarPoly {
|
|||
/// Panics if we fail to munlock the `coeff` vector.
|
||||
impl Drop for BivarPoly {
|
||||
fn drop(&mut self) {
|
||||
self.zero_secret_memory();
|
||||
if let Err(e) = self.munlock_secret_memory() {
|
||||
self.zero_secret();
|
||||
if let Err(e) = self.munlock_secret() {
|
||||
panic!("Failed to munlock `BivarPoly` during drop: {}", e);
|
||||
}
|
||||
}
|
||||
|
@ -829,56 +716,10 @@ impl Debug for BivarPoly {
|
|||
}
|
||||
|
||||
impl ContainsSecret for BivarPoly {
|
||||
fn mlock_secret_memory(&self) -> Result<()> {
|
||||
if !*SHOULD_MLOCK_SECRETS {
|
||||
return Ok(());
|
||||
}
|
||||
let ptr = self.coeff.as_ptr() as *mut u8;
|
||||
fn secret_memory(&self) -> MemRange {
|
||||
let ptr = self.coeff.as_ptr() as *const Fr as *mut u8;
|
||||
let n_bytes = size_of_val(self.coeff.as_slice());
|
||||
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)
|
||||
}
|
||||
}
|
||||
|
||||
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);
|
||||
}
|
||||
MemRange { ptr, n_bytes }
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -907,7 +748,7 @@ impl BivarPoly {
|
|||
degree,
|
||||
coeff: (0..coeff_pos(degree + 1, 0)).map(|_| rng.gen()).collect(),
|
||||
};
|
||||
poly.mlock_secret_memory()?;
|
||||
poly.mlock_secret()?;
|
||||
Ok(poly)
|
||||
}
|
||||
|
||||
|
|
|
@ -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)
|
||||
}
|
||||
}
|
Loading…
Reference in New Issue