bitwarden_crypto/
allocator.rs

1use core::slice;
2use std::alloc::{GlobalAlloc, Layout};
3
4use zeroize::Zeroize;
5
6/// Custom allocator that zeroizes memory before deallocating it
7///
8/// This is highly recommended to be enabled when using the Bitwarden crates to avoid sensitive data
9/// persisting in memory after it has been deallocated.
10///
11/// This allocator is a decorator around another allocator.
12///
13/// # Example
14///
15/// This example shows how to use the `ZeroizingAllocator` with the system allocator.
16///
17/// ```rust,ignore
18/// #[global_allocator]
19/// static ALLOC: bitwarden_crypto::ZeroizingAllocator<std::alloc::System> =
20///    bitwarden_crypto::ZeroizingAllocator(std::alloc::System);
21/// ```
22pub struct ZeroizingAllocator<Alloc: GlobalAlloc>(pub Alloc);
23
24unsafe impl<T: GlobalAlloc> GlobalAlloc for ZeroizingAllocator<T> {
25    unsafe fn alloc(&self, layout: Layout) -> *mut u8 {
26        self.0.alloc(layout)
27    }
28
29    unsafe fn dealloc(&self, ptr: *mut u8, layout: Layout) {
30        slice::from_raw_parts_mut(ptr, layout.size()).zeroize();
31
32        self.0.dealloc(ptr, layout);
33    }
34
35    unsafe fn alloc_zeroed(&self, layout: Layout) -> *mut u8 {
36        self.0.alloc_zeroed(layout)
37    }
38}
39
40#[cfg(test)]
41mod tests {
42    #[test]
43    #[ignore = "It produces inconsistent results on some platforms"]
44    fn string() {
45        let s = String::from("hello");
46
47        let p1 = s.as_str().as_ptr();
48        let c1 = s.capacity();
49
50        assert_eq!(
51            unsafe { std::slice::from_raw_parts(p1, c1) },
52            b"hello",
53            "String is not at the expected memory location"
54        );
55
56        drop(s);
57
58        assert_eq!(
59            unsafe { std::slice::from_raw_parts(p1, c1) },
60            [0, 0, 0, 0, 0],
61            "memory was not zeroized after dropping the string"
62        );
63    }
64
65    #[test]
66    #[ignore = "It produces inconsistent results on some platforms"]
67    fn string_expand() {
68        let mut s = String::from("hello");
69
70        let p1 = s.as_str().as_ptr();
71        let c1 = s.capacity();
72
73        assert_eq!(unsafe { std::slice::from_raw_parts(p1, c1) }, b"hello");
74
75        s.push_str(" world");
76
77        let p2 = s.as_str().as_ptr();
78        let c2 = s.capacity();
79
80        // We allocated a new string
81        assert_ne!(p1, p2);
82        assert_eq!(
83            unsafe { std::slice::from_raw_parts(p1, c1) },
84            [0, 0, 0, 0, 0],
85            "old string was not zeroized"
86        );
87
88        assert_eq!(
89            unsafe { std::slice::from_raw_parts(p2, c2) },
90            b"hello world"
91        );
92
93        // Drop the expanded string
94        drop(s);
95
96        assert_eq!(
97            unsafe { std::slice::from_raw_parts(p2, c2) },
98            [0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0],
99            "expanded string was not zeroized"
100        );
101    }
102
103    #[test]
104    #[ignore = "It produces inconsistent results on some platforms"]
105    fn vec() {
106        let v = vec![1, 2, 3, 4, 5];
107
108        let p1 = v.as_slice().as_ptr();
109        let c1 = v.capacity();
110
111        assert_eq!(
112            unsafe { std::slice::from_raw_parts(p1, c1) },
113            [1, 2, 3, 4, 5],
114            "vec is not at the expected memory location"
115        );
116
117        drop(v);
118
119        assert_eq!(
120            unsafe { std::slice::from_raw_parts(p1, c1) },
121            [0, 0, 0, 0, 0],
122            "vec was not zeroized after dropping"
123        );
124    }
125
126    #[test]
127    #[ignore = "It produces inconsistent results on some platforms"]
128    fn vec_expand() {
129        let mut v = vec![1, 2, 3, 4, 5];
130
131        let p1 = v.as_slice().as_ptr();
132        let c1 = v.capacity();
133
134        assert_eq!(
135            unsafe { std::slice::from_raw_parts(p1, c1) },
136            [1, 2, 3, 4, 5],
137            "vec is not at the expected memory location"
138        );
139
140        v.extend_from_slice(&[6, 7, 8, 9, 10]);
141
142        let p2 = v.as_slice().as_ptr();
143        let c2 = v.capacity();
144
145        // We allocated a new vector
146        assert_ne!(p1, p2);
147        assert_eq!(
148            unsafe { std::slice::from_raw_parts(p1, c1) },
149            [0, 0, 0, 0, 0],
150            "old vec was not zeroized"
151        );
152
153        assert_eq!(
154            unsafe { std::slice::from_raw_parts(p2, c2) },
155            [1, 2, 3, 4, 5, 6, 7, 8, 9, 10]
156        );
157
158        // Drop the expanded vector
159        drop(v);
160
161        assert_eq!(
162            unsafe { std::slice::from_raw_parts(p2, c2) },
163            [0, 0, 0, 0, 0, 0, 0, 0, 0, 0],
164            "expanded vec was not zeroized"
165        );
166    }
167}