blob: 00cbb1c8e651b7c97c5e709cc3d3dd0d638b15c4 [file] [log] [blame]
// Copyright 2020, The Android Open Source Project
//
// 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.
//! Implements ZVec, a vector that is mlocked during its lifetime and zeroed
//! when dropped.
use nix::sys::mman::{mlock, munlock};
use std::convert::TryFrom;
use std::fmt;
use std::ops::{Deref, DerefMut};
use std::ptr::write_volatile;
use std::ptr::NonNull;
/// A semi fixed size u8 vector that is zeroed when dropped. It can shrink in
/// size but cannot grow larger than the original size (and if it shrinks it
/// still owns the entire buffer). Also the data is pinned in memory with
/// mlock.
#[derive(Default, Eq, PartialEq)]
pub struct ZVec {
elems: Box<[u8]>,
len: usize,
}
/// ZVec specific error codes.
#[derive(Debug, thiserror::Error, Eq, PartialEq)]
pub enum Error {
/// Underlying libc error.
#[error(transparent)]
NixError(#[from] nix::Error),
}
impl ZVec {
/// Create a ZVec with the given size.
pub fn new(size: usize) -> Result<Self, Error> {
let v: Vec<u8> = vec![0; size];
let b = v.into_boxed_slice();
if size > 0 {
// SAFETY: The address range is part of our address space.
unsafe { mlock(NonNull::from(&b).cast(), b.len()) }?;
}
Ok(Self { elems: b, len: size })
}
/// Reduce the length to the given value. Does nothing if that length is
/// greater than the length of the vector. Note that it still owns the
/// original allocation even if the length is reduced.
pub fn reduce_len(&mut self, len: usize) {
if len <= self.elems.len() {
self.len = len;
}
}
/// Attempts to make a clone of the Zvec. This may fail due trying to mlock
/// the new memory region.
pub fn try_clone(&self) -> Result<Self, Error> {
let mut result = Self::new(self.len())?;
result[..].copy_from_slice(&self[..]);
Ok(result)
}
}
impl Drop for ZVec {
fn drop(&mut self) {
for i in 0..self.elems.len() {
// SAFETY: The pointer is valid and properly aligned because it came from a reference.
unsafe { write_volatile(&mut self.elems[i], 0) };
}
if !self.elems.is_empty() {
if let Err(e) =
// SAFETY: The address range is part of our address space, and was previously locked
// by `mlock` in `ZVec::new` or the `TryFrom<Vec<u8>>` implementation.
unsafe { munlock(NonNull::from(&self.elems).cast(), self.elems.len()) }
{
log::error!("In ZVec::drop: `munlock` failed: {:?}.", e);
}
}
}
}
impl Deref for ZVec {
type Target = [u8];
fn deref(&self) -> &Self::Target {
&self.elems[0..self.len]
}
}
impl DerefMut for ZVec {
fn deref_mut(&mut self) -> &mut Self::Target {
&mut self.elems[0..self.len]
}
}
impl fmt::Debug for ZVec {
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
if self.elems.is_empty() {
write!(f, "Zvec empty")
} else {
write!(f, "Zvec size: {} [ Sensitive information redacted ]", self.len)
}
}
}
impl TryFrom<&[u8]> for ZVec {
type Error = Error;
fn try_from(v: &[u8]) -> Result<Self, Self::Error> {
let mut z = ZVec::new(v.len())?;
if !v.is_empty() {
z.clone_from_slice(v);
}
Ok(z)
}
}
impl TryFrom<Vec<u8>> for ZVec {
type Error = Error;
fn try_from(mut v: Vec<u8>) -> Result<Self, Self::Error> {
let len = v.len();
// into_boxed_slice calls shrink_to_fit, which may move the pointer.
// But sometimes the contents of the Vec are already sensitive and
// mustn't be copied. So ensure the shrink_to_fit call is a NOP.
v.resize(v.capacity(), 0);
let b = v.into_boxed_slice();
if !b.is_empty() {
// SAFETY: The address range is part of our address space.
unsafe { mlock(NonNull::from(&b).cast(), b.len()) }?;
}
Ok(Self { elems: b, len })
}
}