sodium_alloc/
lib.rs

1//! [`Allocator`](std::alloc::Allocator) type that allocates memory using
2//! [Sodium](https://doc.libsodium.org/)'s secure memory utilities.
3//!
4//! **Requires nightly Rust**, as the `Allocator` API is not yet stable.
5//!
6//! This library implements [`SodiumAllocator`], an `Allocator` which uses the
7//! [`sodium_malloc`](https://doc.libsodium.org/memory_management#guarded-heap-allocations) and
8//! corresponding `sodium_free` functions to manage memory. When managing sensitive data in memory,
9//! there are a number of steps we can take to help harden our software against revealing these
10//! secrets.
11//!
12//! Sodium's `sodium_malloc` implementation introduces many of these hardening steps to the memory
13//! management process: Allocated memory is placed at the end of a page boundary, immediately
14//! followed by a guard page (a region of memory which is marked as inaccessible, any attempt to
15//! access it will result in termination of the program). A canary is placed before the allocated
16//! memory, any modifications to which are detected on free, again resulting in program
17//! termination, and a guard page is placed before this.
18//! [`sodium_mlock`](https://doc.libsodium.org/memory_management#locking-memory) is used to
19//! instruct the operating system not to swap the memory to disk, or to include it in core dumps.
20//!
21//! When memory is freed with `SodiumAllocator`, the `sodium_free` function is called, which will
22//! securely zero the memory before marking it as free. This means that for types allocated with
23//! `SodiumAllocator`, there is no need to implement `Zeroize` or a similar `Drop` implementation
24//! to zero the memory when no longer in use: It will automatically be zeroed when freed.
25//!
26//! This library is not suitable for use as a general-purpose allocator or global allocator: The
27//! overhead of this API is *much* greater than Rust's standard allocator, and the implementation
28//! is more likely to encounter errors. It is intended for use when allocating sensitive data types
29//! only, for example, a key or password which needs to be stored in memory.
30//!
31//! ## Examples
32//! Here we create a standard Rust vector, but use Sodium's memory management to allocate/grow/free
33//! its memory:
34//!
35//! ```
36//! // Currently necessary: Allocators are feature-gated on nightly
37//! #![feature(allocator_api)]
38//!
39//! use std::alloc::Allocator;
40//! use sodium_alloc::SodiumAllocator;
41//!
42//! // Allocate a vector using Sodium's memory management functions
43//! let mut my_vec = Vec::with_capacity_in(4, SodiumAllocator);
44//! my_vec.push(0);
45//! my_vec.push(1);
46//! my_vec.extend_from_slice(&[3, 4]);
47//! println!("{:?}", my_vec);
48//! // Grow the vector, works just like normal :)
49//! my_vec.reserve(10);
50//! // Drop the vector, the SodiumAllocator will securely zero the memory when freed. Dropping like
51//! // this isn't necessary, things going out of scope as normal works too, this is just for
52//! // illustrative purposes.
53//! std::mem::drop(my_vec);
54//! ```
55//!
56//! Boxes also currently support the Allocator API:
57//!
58//! ```
59//! #![feature(allocator_api)]
60//!
61//! use std::alloc::Allocator;
62//! use sodium_alloc::SodiumAllocator;
63//!
64//! // Store something on the heap, allocating memory with Sodium
65//! let key = Box::new_in([0xca, 0xfe, 0xba, 0xbe], SodiumAllocator);
66//! println!("{:x?}", key);
67//! ```
68#![doc(html_root_url = "https://docs.rs/sodium-alloc/0.1.1")]
69#![feature(allocator_api)]
70#![feature(nonnull_slice_from_raw_parts)]
71#![feature(slice_ptr_get)]
72#![feature(slice_ptr_len)]
73
74use libsodium_sys as sodium;
75use std::alloc::{AllocError, Allocator, Layout};
76use std::ffi::c_void;
77use std::ptr::NonNull;
78
79/// An [`Allocator`](std::alloc::Allocator) which allocates and frees memory using Sodium's secure
80/// memory utilities.
81///
82/// Allocation of memory using this struct is expensive - it shouldn't be used as a global
83/// allocator, but rather confied to manage memory for data structures storing sensitive
84/// information, such as keys, passwords, etc.
85///
86/// When this Allocator frees memory, it is securely zeroed, so there is no need to implement
87/// Zeroize or similar constructions for types with memory managed via this struct.
88///
89/// If the canary Sodium places before the allocated memory is altered, or if an attempt to access
90/// a guard page surrounding the allocated memory is made, the program will automatically
91/// terminate. This behaviour should never occur in safe Rust.
92#[derive(Copy, Clone, Debug)]
93pub struct SodiumAllocator;
94
95unsafe impl Allocator for SodiumAllocator {
96    fn allocate(&self, mut layout: Layout) -> Result<NonNull<[u8]>, AllocError> {
97        // Initialise libsodium, okay to call this multiple times from multiple threads, the actual
98        // initialisation will only happen once.
99        // We don't call this in other functions, as it's assumed we have to have called
100        // `Self::allocate` to get some memory to do other things with (e.g: deallocate, grow).
101        init()?;
102
103        // Increase the size of the layout so it's a multiple of layout.align - as Sodium allocates
104        // memory at the end of the page, as long as the layout size is a multiple of the
105        // alignment, and the alignment is a power of 2, the allocation will be correctly aligned.
106        layout = layout.pad_to_align();
107
108        // Calling `sodium_malloc` with a size that's a multiple of n produces a pointer aligned to
109        // n.
110        // SAFETY: This function returns a pointer to `layout.size()` of allocated memory, or NULL
111        // if allocation failed. We immediately check for NULL in the next line, and return an
112        // error if it occurs. If the result is not NULL, Sodium guarantees that the pointer will
113        // reference at least `layout.size()` of allocated, mutable memory.
114        let ptr = unsafe { sodium::sodium_malloc(layout.size()) as *mut u8 };
115        // NonNull::new() will return Some if `ptr` was non-null, but will return None if `ptr` was
116        // null. We convert the latter result into an error.
117        let ptr = NonNull::new(ptr).ok_or(AllocError)?;
118
119        Ok(NonNull::slice_from_raw_parts(ptr, layout.size()))
120    }
121
122    unsafe fn deallocate(&self, ptr: NonNull<u8>, _layout: Layout) {
123        sodium::sodium_free(ptr.as_ptr() as *mut c_void);
124    }
125
126    // We just use the default implementations of the other methods: Sodium doesn't provide any API
127    // to grow/shrink memory, so we would have to just allocate new memory then copy for any of
128    // these types of operations, which is what the default operations already do.
129}
130
131/// Initialise libsodium.
132///
133/// Called automatically when an attempt to allocate is made.
134fn init() -> Result<(), AllocError> {
135    unsafe {
136        if sodium::sodium_init() >= 0 {
137            Ok(())
138        } else {
139            Err(AllocError)
140        }
141    }
142}
143
144#[cfg(test)]
145mod tests {
146    use super::*;
147    use std::alloc::Layout;
148    use std::error::Error;
149
150    #[test]
151    fn basic_allocation() -> Result<(), Box<dyn Error>> {
152        // Tries to allocate up to 0.5GiB
153        for i in 0..29 {
154            let layout = Layout::from_size_align(1 << i, 1)?;
155            let ptr = SodiumAllocator.allocate(layout)?;
156
157            assert_eq!(ptr.len(), 1 << i);
158
159            unsafe {
160                SodiumAllocator.deallocate(ptr.cast(), layout);
161            }
162        }
163        Ok(())
164    }
165
166    #[test]
167    fn alignment_correct() -> Result<(), Box<dyn Error>> {
168        // Test some repeated allocations, ensure that they're always aligned correctly
169        for _ in 0..100 {
170            let layout_a = Layout::from_size_align(13, 4)?;
171            let ptr_a = SodiumAllocator.allocate(layout_a)?;
172            assert_eq!(ptr_a.as_ptr() as *mut () as u8 % 4, 0);
173
174            let layout_b = Layout::from_size_align(12, 4)?;
175            let ptr_b = SodiumAllocator.allocate(layout_b)?;
176            assert_eq!(ptr_b.as_ptr() as *mut () as u8 % 4, 0);
177
178            let layout_c = Layout::from_size_align(20, 16)?;
179            let ptr_c = SodiumAllocator.allocate(layout_c)?;
180            assert_eq!(ptr_c.as_ptr() as *mut () as u8 % 16, 0);
181
182            unsafe {
183                SodiumAllocator.deallocate(ptr_a.cast(), layout_a);
184                SodiumAllocator.deallocate(ptr_b.cast(), layout_b);
185                SodiumAllocator.deallocate(ptr_c.cast(), layout_c);
186            }
187        }
188
189        Ok(())
190    }
191
192    #[test]
193    fn zero_size_alloc() -> Result<(), Box<dyn Error>> {
194        let layout = Layout::from_size_align(0, 1)?;
195        let ptr = SodiumAllocator.allocate(layout)?;
196
197        assert_eq!(ptr.len(), 0);
198
199        unsafe {
200            SodiumAllocator.deallocate(ptr.cast(), layout);
201        }
202
203        Ok(())
204    }
205
206    #[test]
207    fn test_writing() {
208        for i in 0..29 {
209            let mut v: Vec<u8, _> = Vec::with_capacity_in(1 << i, SodiumAllocator);
210            for _ in 0..(1 << i) {
211                v.push(0x13);
212            }
213        }
214    }
215}