hard/
mem.rs

1//! Low level memory-management utilities.
2//!
3//! You probably don't want to use these directly, the rest of this crate provides much safer
4//! abstractions for interacting with memory.
5use super::HardError;
6use errno::errno;
7use libsodium_sys as sodium;
8use std::ffi::c_void;
9use std::ptr::NonNull;
10
11/// Allocate sufficient hardened memory to store a value of type `T`, returning a pointer to the
12/// start of the allocated memory.
13///
14/// Uses the Sodium function `sodium_malloc` to securely allocate a region of memory, which will be
15/// `mlock`ed, and surrounded with guard pages.
16///
17/// Returns `Ok(ptr)`, where `ptr` is a pointer to the newly-allocated memory, if allocation was
18/// successful, otherwise returns a [`HardError`].
19///
20/// # Safety
21/// This function returns a pointer to uninitialised memory, allocated outside of Rust's memory
22/// management. As such, all the issues associated with manual memory management in languages like
23/// C apply: Memory must be initialised before use, it must be freed exactly once, and not used
24/// after having been freed. Memory allocated with this function should be freed using [`free`]
25/// from this module, rather than any other memory management tool, to preserve Sodium's security
26/// invariants.
27///
28/// You must call [`super::init`] before using this function.
29pub unsafe fn malloc<T>() -> Result<NonNull<T>, HardError> {
30    // Returns a `*mut c_void`, cast to `*mut ()`. If allocation is successful, this will be a
31    // pointer to sufficient allocated memory to store a `T` value. Otherwise, it will be NULL, and
32    // errno should be set.
33    let ptr = sodium::sodium_malloc(std::mem::size_of::<T>()) as *mut ();
34    // We use the `NonNull::new` method, which returns `None` in the case that we pass it a NULL
35    // pointer, to detect the case where allocation fails.
36    NonNull::new(ptr)
37        .map(|p| p.cast())
38        .ok_or(HardError::AllocationFailed(errno()))
39}
40
41/// Free the memory pointed to by `ptr`, previously allocated using [`malloc`] from this module.
42///
43/// Uses the Sodium function `sodium_free` to securely zero and deallocate memory previously
44/// allocated using `sodium_malloc`.
45///
46/// # Safety
47/// This function should only be called with a pointer to memory previously allocated using
48/// [`malloc`] from this module. This function will cause the program to exit if a buffer overrun
49/// is detected (i.e: the canary placed next to the allocated region has been overwritten).
50///
51/// You must call [`super::init`] before using this function.
52pub unsafe fn free<T>(ptr: NonNull<T>) {
53    // `sodium_free` has no return type in libsodium: It will simply exit if there is an error, as
54    // there can only be an error if something dangerous has occurred.
55    sodium::sodium_free(ptr.as_ptr() as *mut c_void)
56}
57
58/// Zero the memory region pointed to by `ptr`.
59///
60/// Uses `sodium_memzero` to zero memory in such a way that the compiler will not optimise away the
61/// opteration.
62///
63/// # Safety
64/// This function should only be called with a pointer to at least `size` bytes of allocated,
65/// writeable memory, where `size` is the size of a value of type `T`. If `size` is larger than the
66/// allocated region, undefined behaviour will occur.
67///
68/// You must call [`super::init`] before using this function.
69pub unsafe fn memzero<T>(ptr: NonNull<T>) {
70    sodium::sodium_memzero(ptr.as_ptr() as *mut c_void, std::mem::size_of::<T>())
71}
72
73/// Compare two regions of memory for equality in constant-time.
74///
75/// Uses `sodium_memcmp` for constant-time comparison of two memory regions, to prevent timing
76/// attacks. The length of the region compared is determined by the size of the type `T`. Returns
77/// true if the contents of the memory regions are equal, false otherwise.
78///
79/// # Safety
80/// Both `a` and `b` must be pointers to regions of allocated memory of length at least `size`
81/// bytes, where `size` is the size of a value of type `T`.
82///
83/// You must call [`super::init`] before using this function.
84pub unsafe fn memcmp<T>(a: NonNull<T>, b: NonNull<T>) -> bool {
85    sodium::sodium_memcmp(
86        a.as_ptr() as *const c_void,
87        b.as_ptr() as *const c_void,
88        std::mem::size_of::<T>(),
89    ) == 0
90}
91
92/// Sets a region of memory allocated with libsodium to be inaccessible.
93///
94/// This is used to protect a buffer when its contents are not required for any operations. When it
95/// is next needed, its contents can be marked readable or mutable via [`mprotect_readonly`] or
96/// [`mprotect_readwrite`].
97///
98/// # Safety
99/// `ptr` must be a pointer to memory allocated using [`malloc`] from libsodium, and must point to
100/// at least `size` bytes of allocated memory, where size is the size of a value of type `T`. After
101/// this function is called, any attempt to access the associated memory will result in the
102/// immediate termination of the program, unless its protected status is changed.
103///
104/// You must call [`super::init`] before using this function.
105pub unsafe fn mprotect_noaccess<T>(ptr: NonNull<T>) -> Result<(), HardError> {
106    if sodium::sodium_mprotect_noaccess(ptr.as_ptr() as *mut c_void) == 0 {
107        Ok(())
108    } else {
109        Err(HardError::MprotectNoAccessFailed(errno()))
110    }
111}
112
113/// Sets a region of memory allocated with libsodium to be readable, but not mutable.
114///
115/// This is used to protect a buffer when its contents do not need to be modified, but its value is
116/// still required to be used. If it needs to be modified, its contents can be marked mutable via
117/// [`mprotect_readwrite`]. If there is a period of time for which it does not need to be read or
118/// modified, it can be marked as no access via [`mprotect_noaccess`].
119///
120/// # Safety
121/// `ptr` must be a pointer to memory allocated using [`malloc`] from libsodium, and must point to
122/// at least `size` bytes of allocated memory, where size is the size of a value of type `T`. After
123/// this function is called, any attempt to mutate the associated memory will result in the
124/// immediate termination of the program, unless its protected status is changed.
125///
126/// You must call [`super::init`] before using this function.
127pub unsafe fn mprotect_readonly<T>(ptr: NonNull<T>) -> Result<(), HardError> {
128    if sodium::sodium_mprotect_readonly(ptr.as_ptr() as *mut c_void) == 0 {
129        Ok(())
130    } else {
131        Err(HardError::MprotectReadOnlyFailed(errno()))
132    }
133}
134
135/// Sets a region of memory allocated with libsodium to be readable and writeable.
136///
137/// This is used after we have already invoked [`mprotect_noaccess`] or [`mprotect_readonly`] to
138/// reset the region to its normal (mutable) state.
139///
140/// # Safety
141/// `ptr` must be a pointer to memory allocated using [`malloc`] from libsodium, and must point to
142/// at least `size` bytes of allocated memory, where size is the size of a value of type `T`.
143///
144/// You must call [`super::init`] before using this function.
145pub unsafe fn mprotect_readwrite<T>(ptr: NonNull<T>) -> Result<(), HardError> {
146    if sodium::sodium_mprotect_readwrite(ptr.as_ptr() as *mut c_void) == 0 {
147        Ok(())
148    } else {
149        Err(HardError::MprotectReadWriteFailed(errno()))
150    }
151}
152
153#[cfg(test)]
154mod tests {
155    // This set of tests relies on having at least 1 MiB of memory available to allocate.
156    // Therefore, these tests may fail on platforms with very limited resources.
157    use super::*;
158    use crate::{init, HardError};
159    use std::ptr::NonNull;
160
161    #[test]
162    fn malloc_and_free() -> Result<(), HardError> {
163        unsafe {
164            init()?;
165
166            // Test allocations of various sizes
167            let ptr_a: NonNull<u8> = malloc()?; // 1 byte
168            let ptr_b: NonNull<[u8; 1 << 3]> = malloc()?; // 8 bytes
169            let ptr_c: NonNull<[u8; 1 << 10]> = malloc()?; // 1 KiB
170            let ptr_d: NonNull<[u8; 1 << 20]> = malloc()?; // 1 MiB
171
172            // Free the allocated memory
173            free(ptr_a);
174            free(ptr_b);
175            free(ptr_c);
176            free(ptr_d);
177
178            Ok(())
179        }
180    }
181
182    #[test]
183    fn memzero_does_zero() -> Result<(), HardError> {
184        unsafe {
185            init()?;
186
187            let ptr_a: NonNull<u8> = malloc()?; // 1 byte
188            let ptr_b: NonNull<[u8; 1 << 3]> = malloc()?; // 8 bytes
189            let ptr_c: NonNull<[u8; 1 << 10]> = malloc()?; // 1 KiB
190            let ptr_d: NonNull<[u8; 1 << 20]> = malloc()?; // 1 MiB
191
192            memzero(ptr_a);
193            memzero(ptr_b);
194            memzero(ptr_c);
195            memzero(ptr_d);
196
197            // The value represented by all zeros for these array/integer types is well defined.
198            assert_eq!(ptr_a.as_ref(), &0);
199            assert_eq!(&ptr_b.as_ref()[..], &[0; 1 << 3][..]);
200            assert_eq!(&ptr_c.as_ref()[..], &[0; 1 << 10][..]);
201            assert_eq!(&ptr_d.as_ref()[..], &[0; 1 << 20][..]);
202
203            free(ptr_a);
204            free(ptr_b);
205            free(ptr_c);
206            free(ptr_d);
207
208            Ok(())
209        }
210    }
211
212    #[test]
213    fn memcmp_compare_works() -> Result<(), HardError> {
214        unsafe {
215            init()?;
216
217            let mut ptr_a: NonNull<[u8; 1 << 3]> = malloc()?;
218            let mut ptr_b: NonNull<[u8; 1 << 3]> = malloc()?;
219
220            ptr_a
221                .as_mut()
222                .copy_from_slice(&[0xde, 0xad, 0xbe, 0xef, 0xca, 0xfe, 0xba, 0xbe]);
223            ptr_b
224                .as_mut()
225                .copy_from_slice(&[0xde, 0xad, 0xbe, 0xef, 0xca, 0xfe, 0xba, 0xbe]);
226            assert!(memcmp(ptr_a, ptr_b));
227            ptr_b
228                .as_mut()
229                .copy_from_slice(&[0xff, 0xad, 0xbe, 0xef, 0xca, 0xfe, 0xba, 0xbe]);
230            assert!(!memcmp(ptr_a, ptr_b));
231            ptr_b
232                .as_mut()
233                .copy_from_slice(&[0xde, 0xad, 0xbe, 0xef, 0xca, 0xfe, 0xba, 0xff]);
234            assert!(!memcmp(ptr_a, ptr_b));
235
236            free(ptr_a);
237            free(ptr_b);
238
239            Ok(())
240        }
241    }
242
243    #[test]
244    fn mprotect_works() -> Result<(), HardError> {
245        unsafe {
246            init()?;
247
248            let mut ptr: NonNull<[u8; 32]> = malloc()?;
249            ptr.as_mut().copy_from_slice(&[0xfe; 32]);
250
251            mprotect_noaccess(ptr)?;
252            mprotect_readonly(ptr)?;
253            assert_eq!(&ptr.as_ref()[..], &[0xfe; 32][..]);
254            mprotect_readwrite(ptr)?;
255            ptr.as_mut().copy_from_slice(&[0xba; 32]);
256            assert_eq!(&ptr.as_ref()[..], &[0xba; 32][..]);
257
258            free(ptr);
259
260            Ok(())
261        }
262    }
263}