1 // Copyright 2020, The Android Open Source Project 2 // 3 // Licensed under the Apache License, Version 2.0 (the "License"); 4 // you may not use this file except in compliance with the License. 5 // You may obtain a copy of the License at 6 // 7 // http://www.apache.org/licenses/LICENSE-2.0 8 // 9 // Unless required by applicable law or agreed to in writing, software 10 // distributed under the License is distributed on an "AS IS" BASIS, 11 // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 // See the License for the specific language governing permissions and 13 // limitations under the License. 14 15 //! Implements ZVec, a vector that is mlocked during its lifetime and zeroed 16 //! when dropped. 17 18 use nix::sys::mman::{mlock, munlock}; 19 use std::convert::TryFrom; 20 use std::fmt; 21 use std::ops::{Deref, DerefMut}; 22 use std::ptr::write_volatile; 23 24 /// A semi fixed size u8 vector that is zeroed when dropped. It can shrink in 25 /// size but cannot grow larger than the original size (and if it shrinks it 26 /// still owns the entire buffer). Also the data is pinned in memory with 27 /// mlock. 28 #[derive(Default, Eq, PartialEq)] 29 pub struct ZVec { 30 elems: Box<[u8]>, 31 len: usize, 32 } 33 34 /// ZVec specific error codes. 35 #[derive(Debug, thiserror::Error, Eq, PartialEq)] 36 pub enum Error { 37 /// Underlying libc error. 38 #[error(transparent)] 39 NixError(#[from] nix::Error), 40 } 41 42 impl ZVec { 43 /// Create a ZVec with the given size. new(size: usize) -> Result<Self, Error>44 pub fn new(size: usize) -> Result<Self, Error> { 45 let v: Vec<u8> = vec![0; size]; 46 let b = v.into_boxed_slice(); 47 if size > 0 { 48 unsafe { mlock(b.as_ptr() as *const std::ffi::c_void, b.len()) }?; 49 } 50 Ok(Self { elems: b, len: size }) 51 } 52 53 /// Reduce the length to the given value. Does nothing if that length is 54 /// greater than the length of the vector. Note that it still owns the 55 /// original allocation even if the length is reduced. reduce_len(&mut self, len: usize)56 pub fn reduce_len(&mut self, len: usize) { 57 if len <= self.elems.len() { 58 self.len = len; 59 } 60 } 61 62 /// Attempts to make a clone of the Zvec. This may fail due trying to mlock 63 /// the new memory region. try_clone(&self) -> Result<Self, Error>64 pub fn try_clone(&self) -> Result<Self, Error> { 65 let mut result = Self::new(self.len())?; 66 result[..].copy_from_slice(&self[..]); 67 Ok(result) 68 } 69 } 70 71 impl Drop for ZVec { drop(&mut self)72 fn drop(&mut self) { 73 for i in 0..self.elems.len() { 74 unsafe { write_volatile(self.elems.as_mut_ptr().add(i), 0) }; 75 } 76 if !self.elems.is_empty() { 77 if let Err(e) = 78 unsafe { munlock(self.elems.as_ptr() as *const std::ffi::c_void, self.elems.len()) } 79 { 80 log::error!("In ZVec::drop: `munlock` failed: {:?}.", e); 81 } 82 } 83 } 84 } 85 86 impl Deref for ZVec { 87 type Target = [u8]; 88 deref(&self) -> &Self::Target89 fn deref(&self) -> &Self::Target { 90 &self.elems[0..self.len] 91 } 92 } 93 94 impl DerefMut for ZVec { deref_mut(&mut self) -> &mut Self::Target95 fn deref_mut(&mut self) -> &mut Self::Target { 96 &mut self.elems[0..self.len] 97 } 98 } 99 100 impl fmt::Debug for ZVec { fmt(&self, f: &mut fmt::Formatter) -> fmt::Result101 fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { 102 if self.elems.is_empty() { 103 write!(f, "Zvec empty") 104 } else { 105 write!(f, "Zvec size: {} [ Sensitive information redacted ]", self.len) 106 } 107 } 108 } 109 110 impl TryFrom<&[u8]> for ZVec { 111 type Error = Error; 112 try_from(v: &[u8]) -> Result<Self, Self::Error>113 fn try_from(v: &[u8]) -> Result<Self, Self::Error> { 114 let mut z = ZVec::new(v.len())?; 115 if !v.is_empty() { 116 z.clone_from_slice(v); 117 } 118 Ok(z) 119 } 120 } 121 122 impl TryFrom<Vec<u8>> for ZVec { 123 type Error = Error; 124 try_from(mut v: Vec<u8>) -> Result<Self, Self::Error>125 fn try_from(mut v: Vec<u8>) -> Result<Self, Self::Error> { 126 let len = v.len(); 127 // into_boxed_slice calls shrink_to_fit, which may move the pointer. 128 // But sometimes the contents of the Vec are already sensitive and 129 // mustn't be copied. So ensure the shrink_to_fit call is a NOP. 130 v.resize(v.capacity(), 0); 131 let b = v.into_boxed_slice(); 132 if !b.is_empty() { 133 unsafe { mlock(b.as_ptr() as *const std::ffi::c_void, b.len()) }?; 134 } 135 Ok(Self { elems: b, len }) 136 } 137 } 138