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}