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
256
257
258
259
260
261
262
263
//! Low level memory-management utilities.
//!
//! You probably don't want to use these directly, the rest of this crate provides much safer
//! abstractions for interacting with memory.
use super::HardError;
use errno::errno;
use libsodium_sys as sodium;
use std::ffi::c_void;
use std::ptr::NonNull;

/// Allocate sufficient hardened memory to store a value of type `T`, returning a pointer to the
/// start of the allocated memory.
///
/// Uses the Sodium function `sodium_malloc` to securely allocate a region of memory, which will be
/// `mlock`ed, and surrounded with guard pages.
///
/// Returns `Ok(ptr)`, where `ptr` is a pointer to the newly-allocated memory, if allocation was
/// successful, otherwise returns a [`HardError`].
///
/// # Safety
/// This function returns a pointer to uninitialised memory, allocated outside of Rust's memory
/// management. As such, all the issues associated with manual memory management in languages like
/// C apply: Memory must be initialised before use, it must be freed exactly once, and not used
/// after having been freed. Memory allocated with this function should be freed using [`free`]
/// from this module, rather than any other memory management tool, to preserve Sodium's security
/// invariants.
///
/// You must call [`super::init`] before using this function.
pub unsafe fn malloc<T>() -> Result<NonNull<T>, HardError> {
    // Returns a `*mut c_void`, cast to `*mut ()`. If allocation is successful, this will be a
    // pointer to sufficient allocated memory to store a `T` value. Otherwise, it will be NULL, and
    // errno should be set.
    let ptr = sodium::sodium_malloc(std::mem::size_of::<T>()) as *mut ();
    // We use the `NonNull::new` method, which returns `None` in the case that we pass it a NULL
    // pointer, to detect the case where allocation fails.
    NonNull::new(ptr)
        .map(|p| p.cast())
        .ok_or(HardError::AllocationFailed(errno()))
}

/// Free the memory pointed to by `ptr`, previously allocated using [`malloc`] from this module.
///
/// Uses the Sodium function `sodium_free` to securely zero and deallocate memory previously
/// allocated using `sodium_malloc`.
///
/// # Safety
/// This function should only be called with a pointer to memory previously allocated using
/// [`malloc`] from this module. This function will cause the program to exit if a buffer overrun
/// is detected (i.e: the canary placed next to the allocated region has been overwritten).
///
/// You must call [`super::init`] before using this function.
pub unsafe fn free<T>(ptr: NonNull<T>) {
    // `sodium_free` has no return type in libsodium: It will simply exit if there is an error, as
    // there can only be an error if something dangerous has occurred.
    sodium::sodium_free(ptr.as_ptr() as *mut c_void)
}

/// Zero the memory region pointed to by `ptr`.
///
/// Uses `sodium_memzero` to zero memory in such a way that the compiler will not optimise away the
/// opteration.
///
/// # Safety
/// This function should only be called with a pointer to at least `size` bytes of allocated,
/// writeable memory, where `size` is the size of a value of type `T`. If `size` is larger than the
/// allocated region, undefined behaviour will occur.
///
/// You must call [`super::init`] before using this function.
pub unsafe fn memzero<T>(ptr: NonNull<T>) {
    sodium::sodium_memzero(ptr.as_ptr() as *mut c_void, std::mem::size_of::<T>())
}

/// Compare two regions of memory for equality in constant-time.
///
/// Uses `sodium_memcmp` for constant-time comparison of two memory regions, to prevent timing
/// attacks. The length of the region compared is determined by the size of the type `T`. Returns
/// true if the contents of the memory regions are equal, false otherwise.
///
/// # Safety
/// Both `a` and `b` must be pointers to regions of allocated memory of length at least `size`
/// bytes, where `size` is the size of a value of type `T`.
///
/// You must call [`super::init`] before using this function.
pub unsafe fn memcmp<T>(a: NonNull<T>, b: NonNull<T>) -> bool {
    sodium::sodium_memcmp(
        a.as_ptr() as *const c_void,
        b.as_ptr() as *const c_void,
        std::mem::size_of::<T>(),
    ) == 0
}

/// Sets a region of memory allocated with libsodium to be inaccessible.
///
/// This is used to protect a buffer when its contents are not required for any operations. When it
/// is next needed, its contents can be marked readable or mutable via [`mprotect_readonly`] or
/// [`mprotect_readwrite`].
///
/// # Safety
/// `ptr` must be a pointer to memory allocated using [`malloc`] from libsodium, and must point to
/// at least `size` bytes of allocated memory, where size is the size of a value of type `T`. After
/// this function is called, any attempt to access the associated memory will result in the
/// immediate termination of the program, unless its protected status is changed.
///
/// You must call [`super::init`] before using this function.
pub unsafe fn mprotect_noaccess<T>(ptr: NonNull<T>) -> Result<(), HardError> {
    if sodium::sodium_mprotect_noaccess(ptr.as_ptr() as *mut c_void) == 0 {
        Ok(())
    } else {
        Err(HardError::MprotectNoAccessFailed(errno()))
    }
}

/// Sets a region of memory allocated with libsodium to be readable, but not mutable.
///
/// This is used to protect a buffer when its contents do not need to be modified, but its value is
/// still required to be used. If it needs to be modified, its contents can be marked mutable via
/// [`mprotect_readwrite`]. If there is a period of time for which it does not need to be read or
/// modified, it can be marked as no access via [`mprotect_noaccess`].
///
/// # Safety
/// `ptr` must be a pointer to memory allocated using [`malloc`] from libsodium, and must point to
/// at least `size` bytes of allocated memory, where size is the size of a value of type `T`. After
/// this function is called, any attempt to mutate the associated memory will result in the
/// immediate termination of the program, unless its protected status is changed.
///
/// You must call [`super::init`] before using this function.
pub unsafe fn mprotect_readonly<T>(ptr: NonNull<T>) -> Result<(), HardError> {
    if sodium::sodium_mprotect_readonly(ptr.as_ptr() as *mut c_void) == 0 {
        Ok(())
    } else {
        Err(HardError::MprotectReadOnlyFailed(errno()))
    }
}

/// Sets a region of memory allocated with libsodium to be readable and writeable.
///
/// This is used after we have already invoked [`mprotect_noaccess`] or [`mprotect_readonly`] to
/// reset the region to its normal (mutable) state.
///
/// # Safety
/// `ptr` must be a pointer to memory allocated using [`malloc`] from libsodium, and must point to
/// at least `size` bytes of allocated memory, where size is the size of a value of type `T`.
///
/// You must call [`super::init`] before using this function.
pub unsafe fn mprotect_readwrite<T>(ptr: NonNull<T>) -> Result<(), HardError> {
    if sodium::sodium_mprotect_readwrite(ptr.as_ptr() as *mut c_void) == 0 {
        Ok(())
    } else {
        Err(HardError::MprotectReadWriteFailed(errno()))
    }
}

#[cfg(test)]
mod tests {
    // This set of tests relies on having at least 1 MiB of memory available to allocate.
    // Therefore, these tests may fail on platforms with very limited resources.
    use super::*;
    use crate::{init, HardError};
    use std::ptr::NonNull;

    #[test]
    fn malloc_and_free() -> Result<(), HardError> {
        unsafe {
            init()?;

            // Test allocations of various sizes
            let ptr_a: NonNull<u8> = malloc()?; // 1 byte
            let ptr_b: NonNull<[u8; 1 << 3]> = malloc()?; // 8 bytes
            let ptr_c: NonNull<[u8; 1 << 10]> = malloc()?; // 1 KiB
            let ptr_d: NonNull<[u8; 1 << 20]> = malloc()?; // 1 MiB

            // Free the allocated memory
            free(ptr_a);
            free(ptr_b);
            free(ptr_c);
            free(ptr_d);

            Ok(())
        }
    }

    #[test]
    fn memzero_does_zero() -> Result<(), HardError> {
        unsafe {
            init()?;

            let ptr_a: NonNull<u8> = malloc()?; // 1 byte
            let ptr_b: NonNull<[u8; 1 << 3]> = malloc()?; // 8 bytes
            let ptr_c: NonNull<[u8; 1 << 10]> = malloc()?; // 1 KiB
            let ptr_d: NonNull<[u8; 1 << 20]> = malloc()?; // 1 MiB

            memzero(ptr_a);
            memzero(ptr_b);
            memzero(ptr_c);
            memzero(ptr_d);

            // The value represented by all zeros for these array/integer types is well defined.
            assert_eq!(ptr_a.as_ref(), &0);
            assert_eq!(&ptr_b.as_ref()[..], &[0; 1 << 3][..]);
            assert_eq!(&ptr_c.as_ref()[..], &[0; 1 << 10][..]);
            assert_eq!(&ptr_d.as_ref()[..], &[0; 1 << 20][..]);

            free(ptr_a);
            free(ptr_b);
            free(ptr_c);
            free(ptr_d);

            Ok(())
        }
    }

    #[test]
    fn memcmp_compare_works() -> Result<(), HardError> {
        unsafe {
            init()?;

            let mut ptr_a: NonNull<[u8; 1 << 3]> = malloc()?;
            let mut ptr_b: NonNull<[u8; 1 << 3]> = malloc()?;

            ptr_a
                .as_mut()
                .copy_from_slice(&[0xde, 0xad, 0xbe, 0xef, 0xca, 0xfe, 0xba, 0xbe]);
            ptr_b
                .as_mut()
                .copy_from_slice(&[0xde, 0xad, 0xbe, 0xef, 0xca, 0xfe, 0xba, 0xbe]);
            assert!(memcmp(ptr_a, ptr_b));
            ptr_b
                .as_mut()
                .copy_from_slice(&[0xff, 0xad, 0xbe, 0xef, 0xca, 0xfe, 0xba, 0xbe]);
            assert!(!memcmp(ptr_a, ptr_b));
            ptr_b
                .as_mut()
                .copy_from_slice(&[0xde, 0xad, 0xbe, 0xef, 0xca, 0xfe, 0xba, 0xff]);
            assert!(!memcmp(ptr_a, ptr_b));

            free(ptr_a);
            free(ptr_b);

            Ok(())
        }
    }

    #[test]
    fn mprotect_works() -> Result<(), HardError> {
        unsafe {
            init()?;

            let mut ptr: NonNull<[u8; 32]> = malloc()?;
            ptr.as_mut().copy_from_slice(&[0xfe; 32]);

            mprotect_noaccess(ptr)?;
            mprotect_readonly(ptr)?;
            assert_eq!(&ptr.as_ref()[..], &[0xfe; 32][..]);
            mprotect_readwrite(ptr)?;
            ptr.as_mut().copy_from_slice(&[0xba; 32]);
            assert_eq!(&ptr.as_ref()[..], &[0xba; 32][..]);

            free(ptr);

            Ok(())
        }
    }
}