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}