1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 198 199 200 201 202 203 204 205 206 207 208 209 210 211 212 213 214 215 216 217 218 219 220 221 222 223 224 225 226 227 228 229 230 231 232 233 234 235 236 237 238 239 240 241 242 243 244 245 246 247 248 249 250 251 252 253 254 255
//! This crate provides the securely overwriting memory allocator `MAProper` 🧹 //! //! //! ## What is `MAProper` //! `MAProper` is an extension around `std::alloc::System` which ensures that the allocated memory //! is always erased before it is deallocated by using one of //! `memset_s`/`SecureZeroMemory`/`explicit_bzero`/`explicit_memset`. //! //! //! ## Whats the purpose of `MAProper` //! `MAProper` becomes handy if you're dealing with a lot of sensitive data: because the memory //! management of dynamically allocating types like `Vec` or `String` is opaque, you basically have //! no real chance to reliably erase their sensitive contents. //! //! However they all use the global allocator – so all ways lead to Rome (or in this case to the //! global allocator's `alloc` and `dealloc` functions) – which is where `MAProper` is sitting and //! waiting to take care of the discarded memory. //! //! //! ## Using `MAProper` as global allocator (example) //! ``` //! # use ma_proper::MAProper; //! #[global_allocator] //! static MA_PROPER: MAProper = MAProper; //! //! fn main() { //! // This `Vec` will allocate memory through `MA_PROPER` above //! let mut v = Vec::new(); //! v.push(1); //! } //! ``` //! //! Please note that `MAProper` only erases memory that is deallocated properly. This especially //! means that: //! - stack items are __not erased__ by this allocator – to erase stack memory, we expose //! `MAProper::erase_slice` and `MAProper::erase_ptr<T>` so that you can erase them manually if //! necessary //! - depending on your panic-policy and your `Rc`/`Arc` use (retain-cycles), the destructor (and //! thus the deallocator) may never be called use std::{ mem, ptr, os::raw::c_char, alloc::{ GlobalAlloc, System, Layout, handle_alloc_error } }; // Validate that the `usize` byte length is supported #[cfg(not(any(target_pointer_width = "64", target_pointer_width = "32")))] compile_error!("Unsupported pointer width"); // Define the sizes /// The byte length of an `usize` const USIZE_LEN: usize = mem::size_of::<usize>(); /// The metadata length (this __MUST__ be a power of two) const META_LEN: usize = 16; /// A struct that implements the information necessary to handle a pointers metadata struct Metadata; impl Metadata { /// Computes a CRC-64-code (`CRC-64/XZ`) over `data` pub fn crc64(data: &[u8]) -> u64 { !data.iter().fold(0xFFFFFFFFFFFFFFFFu64, |crc, b| { (0..8).fold(crc ^ *b as u64, |crc, _| { let mask = (!(crc & 1)).wrapping_add(1); (crc >> 1) ^ (0xC96C5795D7870F42 & mask) }) }) } /// Returns the metadata-length necessary to maintain the alignment pub fn aligned_len(layout: Layout) -> Option<usize> { // Validate the alignment if !layout.align().is_power_of_two() { return None } // Compute the meta-length necessary to maintain the alignment match layout.align() { align if align < META_LEN => Some(META_LEN), align => Some(align) } } /// Creates the metadata for `allocated` and writes it to `ptr` pub unsafe fn write(ptr: *mut u8, allocated: usize) { // Compute CRC-64 let len: [u8; USIZE_LEN] = allocated.to_ne_bytes(); let crc64: [u8; 8] = Self::crc64(&len).to_ne_bytes(); // Zero the memory and copy the length and CRC erase_ptr(ptr, META_LEN); ptr::copy(len.as_ptr(), ptr, USIZE_LEN); ptr::copy(crc64.as_ptr(), ptr.add(META_LEN - 8), 8); } /// Decodes the metadata pub unsafe fn read(ptr: *const u8) -> Option<usize> { // Decode the length let mut len = [0u8; USIZE_LEN]; ptr::copy(ptr, len.as_mut_ptr(), USIZE_LEN); // Compute and validate the checksum let mut crc64 = [0u8; 8]; ptr::copy(ptr.add(META_LEN - 8), crc64.as_mut_ptr(), 8); match Self::crc64(&len) == u64::from_ne_bytes(crc64) { true => Some(usize::from_ne_bytes(len)), false => None } } } /// The `MAProper` memory allocator /// /// This memory allocator is an extension around `std::alloc::System` which ensures that the /// allocated memory is always erased before it is deallocated. /// /// ## Using `MAProper` as global allocator /// ``` /// # use ma_proper::MAProper; /// #[global_allocator] /// static MA_PROPER: MAProper = MAProper; /// /// fn main() { /// // This `Vec` will allocate memory through `MA_PROPER` above /// let mut v = Vec::new(); /// v.push(1); /// } /// ``` /// /// ## How it works /// /// ### Allocation /// To ensure that we have enough information to erase everything, we allocate slightly more memory /// than requested and prepend some checksummed metadata to it. So a final chunk looks like this: /// ```asciiart /// Layout: [ metadata | alignment padding | requested memory ] /// Length: META_LEN | dynamic | user specified /// ``` /// /// Then we increment the pointer so that it points to `requested memory` and return it. /// /// ## Deallocation /// Once the pointer is to be deallocated, we rewind the pointer so that it points to /// `metadata/length info` again to read and verify it. Once we know the length, we overwrite the /// _entire_ allocated space using one of /// `memset_s`/`SecureZeroMemory`/`explicit_bzero`/`explicit_memset`. /// /// Then we deallocate it. /// /// ## Important /// Please note that `MAProper` only erases memory that is deallocated properly. This especially /// means that: /// - stack items are __not overwritten__ by this allocator – to erase stack memory, we expose /// `MAProper::erase_slice` and `MAProper::erase_ptr<T>` so that you can erase them manually if /// necessary /// - depending on your panic-policy and your `Rc`/`Arc` use (retain-cycles), the destructor (and /// thus the deallocator) may never be called pub struct MAProper; unsafe impl GlobalAlloc for MAProper { unsafe fn alloc(&self, layout: Layout) -> *mut u8 { // Check for `0` allocation if layout.size() == 0 { return ptr::null_mut() }; // Precompute the meta length let meta_len = match Metadata::aligned_len(layout) { Some(meta_len) => meta_len, None => die(b"ma_proper: Invalid layout\0") }; // Allocate and zero memory let to_allocate = meta_len + layout.size(); let ptr = match Layout::from_size_align(to_allocate, layout.align()) { Ok(layout) => GlobalAlloc::alloc(&System, layout), Err(_) => handle_alloc_error(layout) }; // Write the metadata and return the usable pointer Metadata::write(ptr, to_allocate); trace('+', ptr, layout.size(), to_allocate, layout.align()); ptr.add(meta_len) } unsafe fn dealloc(&self, mut ptr: *mut u8, layout: Layout) { // Precompute the meta length and decrement the pointer let meta_len = match Metadata::aligned_len(layout) { Some(meta_len) => meta_len, None => die(b"ma_proper: Invalid layout\0") }; ptr = ptr.sub(meta_len); // Read the allocate length and erase the pointer let allocated = match Metadata::read(ptr) { Some(allocated) => allocated, None => die(b"ma_proper: Invalid CRC for metadata\0") }; erase_ptr(ptr, allocated); // Free the pointer match Layout::from_size_align(allocated, layout.align()) { Ok(layout) => GlobalAlloc::dealloc(&System, ptr, layout), Err(_) => die(b"ma_proper: Invalid layout\0") }; trace('-', ptr, layout.size(), allocated, layout.align()); } } /// Erases a byte slice pub fn erase_slice(mut s: impl AsMut<[u8]>) { let s = s.as_mut(); unsafe{ erase_ptr(s.as_mut_ptr(), s.len()) } } /// Erases `element_count` elements of type `T` referenced by `ptr` pub unsafe fn erase_ptr<T>(ptr: *mut T, element_count: usize) { // Create the `u8` pointer and compute it's length let ptr = ptr as *mut u8; let len = element_count * mem::size_of::<T>(); // Erase the pointer extern "C" { fn ma_proper_memzero(ptr: *mut u8, len: usize); } ma_proper_memzero(ptr, len); } /// Prints tracing information if the `trace` feature is enabled #[cfg(feature = "trace")] fn trace(prefix: char, ptr: *const u8, requested: usize, allocated: usize, alignment: usize) { extern "C" { fn trace( prefix: c_char, ptr: *const u8, requested: usize, allocated: usize, alignment: usize ); } unsafe{ trace(prefix as c_char, ptr, requested, allocated, alignment) } } /// No-op if the `trace` feature is disabled #[cfg(not(feature = "trace"))] fn trace(_prefix: char, _ptr: *const u8, _requested: usize, _allocated: usize, _alignment: usize) {} /// Prints information about an error and aborts the process fn die(message: &'static[u8]) -> ! { extern "C" { fn die(message: *const c_char) -> !; } unsafe{ die(message.as_ptr() as *const c_char) } }