il2_utils/mem/mod.rs
1/*
2 * BSD 3-Clause License
3 *
4 * Copyright (c) 2019-2020, InterlockLedger Network
5 * All rights reserved.
6 *
7 * Redistribution and use in source and binary forms, with or without
8 * modification, are permitted provided that the following conditions are met:
9 *
10 * * Redistributions of source code must retain the above copyright notice, this
11 * list of conditions and the following disclaimer.
12 *
13 * * Redistributions in binary form must reproduce the above copyright notice,
14 * this list of conditions and the following disclaimer in the documentation
15 * and/or other materials provided with the distribution.
16 *
17 * * Neither the name of the copyright holder nor the names of its
18 * contributors may be used to endorse or promote products derived from
19 * this software without specific prior written permission.
20 *
21 * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
22 * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
23 * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
24 * DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE
25 * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
26 * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
27 * SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
28 * CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
29 * OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
30 * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
31 */
32//! This module implement functions that can be used to control the page locking
33//! in memory. This is useful to prevent critical values from being written into
34//! the the disk by the virtual memory system.
35#[cfg(not(any(target_os = "windows", target_os = "linux")))]
36pub mod impl_default;
37#[cfg(target_os = "linux")]
38pub mod impl_linux;
39#[cfg(target_os = "windows")]
40pub mod impl_win32;
41#[cfg(test)]
42mod tests;
43
44use core::ffi::c_void;
45#[cfg(not(any(target_os = "windows", target_os = "linux")))]
46use impl_default::*;
47#[cfg(target_os = "linux")]
48use impl_linux::*;
49#[cfg(target_os = "windows")]
50use impl_win32::*;
51use rand::random;
52use std::cmp::min;
53use std::mem::size_of;
54use std::ops::{Deref, DerefMut};
55use std::sync::Arc;
56use zeroize::Zeroize;
57
58/// Try to lock the memory segment into memory, preventing it from
59/// being moved to the disk. All calls to this function must be
60/// followed by a call to [`unlock_mem()`].
61///
62/// Use this method with extreme care because it interferes with tne
63/// OS ability to manage virtual memory.
64///
65/// Arguments:
66/// - `ptr`: The pointer to the memory segment;
67/// - `size`: The size of the ptr in units;
68///
69/// Retunrs true on success or false otherwise.
70pub fn lock_mem<T: Sized>(ptr: *const T, size: usize) -> bool {
71 if size > 0 {
72 lock_mem_core(ptr as *const c_void, size * size_of::<T>())
73 } else {
74 false
75 }
76}
77
78/// Unlocks the memory segment. It reverts the effects of [`lock_mem()`].
79///
80/// Arguments:
81/// - `ptr`: The pointer to the memory segment;
82/// - `size`: The size of the ptr in units;
83///
84/// Retunrs true on success or false otherwise.
85pub fn unlock_mem<T: Sized>(ptr: *const T, size: usize) -> bool {
86 if size > 0 {
87 unlock_mem_core(ptr as *const c_void, size * size_of::<T>())
88 } else {
89 false
90 }
91}
92
93/// Determines if this platform supports memory locking or not.
94///
95/// Returns true if it is supported or false otherwise.
96pub fn lock_supported() -> bool {
97 lock_supported_core()
98}
99
100//=============================================================================
101// SecretBytes
102//-----------------------------------------------------------------------------
103/// This struct wraps a byte array that is guaranteed to have its contents
104/// shredded upon destruction.
105///
106/// It also allows the locking of the value in memory if required, preventing it
107/// from being moved into the disk.
108///
109/// This struct also implements a mechanism to set a logical length that differs
110pub struct SecretBytes {
111 value: Vec<u8>,
112 locked: bool,
113 len: usize,
114}
115
116impl SecretBytes {
117 /// Creates a new `SecretBytes`.
118 ///
119 /// Arguments:
120 /// - `size`: The size in bytes;
121 /// - `locked`: Locks the value in memory;
122 pub fn new(size: usize, locked: bool) -> Self {
123 let mut ret = Self {
124 value: Vec::<u8>::with_capacity(size),
125 locked: false,
126 len: size,
127 };
128 ret.value.resize(size, 0);
129 if locked {
130 ret.lock();
131 }
132 ret
133 }
134
135 /// Creates a new `SecretBytes` and initializes it
136 /// with the given value.
137 ///
138 /// Arguments:
139 /// - `value`: The initial value;
140 /// - `locked`: Locks the value in memory;
141 pub fn with_value(value: &[u8], locked: bool) -> Self {
142 let mut ret = Self::new(value.len(), locked);
143 ret.value.copy_from_slice(value);
144 ret
145 }
146
147 /// Returns the value as a mutable byte slice.
148 pub fn mut_value(&mut self) -> &mut [u8] {
149 &mut self.value.as_mut_slice()[..self.len]
150 }
151
152 /// Returns the value as an immutable byte slice.
153 pub fn value(&self) -> &[u8] {
154 &self.value.as_slice()[..self.len]
155 }
156
157 /// Returns the buffer as a mutable byte slice. The buffer may be larger
158 /// than the value itself.
159 pub fn mut_buffer(&mut self) -> &mut [u8] {
160 self.value.as_mut_slice()
161 }
162
163 /// Returns the buffer as an immutable byte slice. The buffer may be larger
164 /// than the value itself.
165 pub fn buffer(&self) -> &[u8] {
166 self.value.as_slice()
167 }
168
169 /// Returns true if the value is locked in memory or false
170 /// otherwise.
171 pub fn locked(&self) -> bool {
172 self.locked
173 }
174
175 /// Returns the logical size of this value. It may be equal
176 /// or smaller than the actual buffer size.
177 pub fn len(&self) -> usize {
178 self.len
179 }
180
181 /// Sets the logical size of this value. If the new size is larger
182 /// than the buffer size, this method will set the logical size to the
183 /// current buffer size.
184 ///
185 /// Arguments:
186 ///
187 /// - `size`: The logical size of the value.
188 pub fn set_len(&mut self, size: usize) {
189 self.len = min(size, self.buffer_len());
190 }
191
192 /// Returns true if this value has length 0.
193 pub fn is_empty(&self) -> bool {
194 self.len == 0
195 }
196
197 /// Returns the size of the inner buffer of this value.
198 pub fn buffer_len(&self) -> usize {
199 self.value.len()
200 }
201
202 /// Locks the value in memory, preventing it from being moved
203 /// into the disk by the the virtual memory system.
204 ///
205 /// If this feature is not supported, this function does nothing.
206 fn lock(&mut self) {
207 if !self.is_empty() && !self.locked {
208 self.locked = lock_mem(self.value.as_ptr(), self.value.len());
209 }
210 }
211
212 /// Unlocks the value in memory.
213 ///
214 /// This function does nothing if the memory
215 fn unlock(&mut self) {
216 if self.locked {
217 self.locked = !unlock_mem(self.value.as_ptr(), self.value.len());
218 }
219 }
220
221 /// Verifies if the underlying platform supports memory locking.
222 ///
223 /// Returns true if locking is supported or false otherwise.
224 pub fn lock_supported() -> bool {
225 lock_supported()
226 }
227}
228
229impl Clone for SecretBytes {
230 fn clone(&self) -> Self {
231 let mut ret = Self::with_value(self.value.as_slice(), self.locked);
232 ret.set_len(self.len());
233 ret
234 }
235}
236
237impl Drop for SecretBytes {
238 fn drop(&mut self) {
239 self.value.as_mut_slice().zeroize();
240 self.unlock();
241 }
242}
243
244impl Deref for SecretBytes {
245 type Target = [u8];
246
247 fn deref(&self) -> &Self::Target {
248 self.value()
249 }
250}
251
252impl DerefMut for SecretBytes {
253 fn deref_mut(&mut self) -> &mut Self::Target {
254 self.mut_value()
255 }
256}
257
258//=============================================================================
259// ByteMaskGenerator
260//-----------------------------------------------------------------------------
261struct ByteMaskGenerator {
262 state: u64,
263}
264
265impl ByteMaskGenerator {
266 pub fn new(seed: u64) -> Self {
267 Self { state: seed }
268 }
269
270 pub fn next(&mut self) -> u8 {
271 // This code is partially based on the random implementation by Newlib
272 self.state = self.state.wrapping_mul(6364136223846793005) + 1;
273 ((self.state >> 32) & 0xFF) as u8
274 }
275}
276
277//=============================================================================
278// ProtectedValue
279//-----------------------------------------------------------------------------
280/// This trait implements a way to protect secret values stored in memory
281/// against potential memory scan techniques. The value is stored in a
282/// obfuscated and/or encrypted form that is reversed only when the actual value
283/// is needed by the application.
284///
285/// Although not enough to provide a long term protection, it should be enough
286/// to make memory scan techniques way more difficult to perform.
287pub trait ProtectedValue: Send + Sync {
288 /// Returns the protected value as a [`SecretBytes`] instance.
289 fn get_secret(&self) -> SecretBytes;
290}
291
292//=============================================================================
293// DefaultProtectedValue
294//-----------------------------------------------------------------------------
295/// This struct implements the the default implementation of the
296/// [`ProtectedValue`] trait. It uses a random mask to protect the value stored
297/// in memory from simple memory scan attacks.
298///
299/// It is not the most sophisticated approach to this problem but is guaranteed
300/// to work on all platforms.
301pub struct DefaultProtectedValue {
302 secret: SecretBytes,
303 seed: u64,
304}
305
306impl DefaultProtectedValue {
307 /// Creates a new DefaultProtectedValue with the given value.
308 ///
309 /// Arguments:
310 /// - `value`: The value to be protected;
311 pub fn new(value: &[u8]) -> Self {
312 let mut secret = SecretBytes::with_value(value, true);
313 let mut seed: u64 = 0;
314 while seed == 0 {
315 seed = random();
316 }
317 Self::apply_mask(seed, &mut secret);
318 Self { secret, seed }
319 }
320
321 fn apply_mask(seed: u64, value: &mut [u8]) {
322 let mut g = ByteMaskGenerator::new(seed);
323 for v in value {
324 *v ^= g.next();
325 }
326 }
327}
328
329impl ProtectedValue for DefaultProtectedValue {
330 fn get_secret(&self) -> SecretBytes {
331 let mut ret = self.secret.clone();
332 Self::apply_mask(self.seed, &mut ret);
333 ret
334 }
335}
336
337/// Creates a protected value repository. It always uses the best
338/// protection method available to the underlying platform.
339///
340/// It always returns a [`std::sync::Arc`] of the value because the
341/// protection mechanism may be too expensive to create and/or maintain.
342/// Furthermore, it is better to keep this kind of secret as isolated as
343/// possible inside the memory.
344///
345/// Returns the protected value.
346#[cfg(not(target_os = "windows"))]
347pub fn create_protected_value(value: &[u8]) -> Arc<dyn ProtectedValue> {
348 Arc::new(DefaultProtectedValue::new(value))
349}
350
351/// Creates a protected value repository. It always uses the best
352/// protection method available to the underlying platform.
353///
354/// On Windows platforms, it uses an opaque implementation that relies on
355/// `CryptProtectMemory()` and `CryptUnprotectMemory()` to protect the value
356/// in memory.
357///
358/// It always returns a [`std::sync::Arc`] of the value because the
359/// protection mechanism may be too expensive to create and/or maintain.
360/// Furthermore, it is better to keep this kind of secret as isolated as
361/// possible inside the memory.
362///
363/// Returns the protected value.
364#[cfg(target_os = "windows")]
365pub fn create_protected_value(value: &[u8]) -> Arc<dyn ProtectedValue> {
366 Arc::new(impl_win32::Win32ProtectedValue::new(value))
367}