secretmangle/
option.rs

1use std::ptr::{NonNull, null_mut, write};
2
3use crate::MangledBoxArbitrary;
4
5
6/// [`MangledOption`] is a variant of [`Option`] that is mangled with a random key.
7/// It guarantees that value is initialized whenever [`Some`] variant is used.
8///
9/// [`Option`]: std::option::Option
10/// [`Some`]: std::option::Option::Some
11/// [`None`]: std::option::Option::None
12pub enum MangledOption<T> {
13    Some(MangledBoxArbitrary<T>),
14    None,
15}
16
17impl<T> MangledOption<T> {
18    /// Creates a new [`MangledOption`] with the [`None`] variant.
19    pub fn new() -> Self {
20        Self::None
21    }
22
23    /// Creates a new [`MangledOption`] with the [`Some`] variant.
24    ///
25    /// Please note that often you don't want to have an unmasked T value in the first place.
26    /// You can construct it in-place using [`Self::insert_by_ptr`].
27    pub fn filled_with_unmasked_value(value: T) -> Self {
28        let mut this = Self::new();
29        this.insert_unmasked_value(value);
30        this
31    }
32
33    /// Returns `true` if the option is a [`Some`] variant.
34    pub fn is_some(&self) -> bool {
35        matches!(self, Self::Some(_))
36    }
37
38    /// Returns `true` if the option is a [`None`] variant.
39    pub fn is_none(&self) -> bool {
40        !self.is_some()
41    }
42
43    /// Takes the value out of the option, leaving a [`None`] in its place.
44    pub fn take(&mut self) -> MangledOption<T> {
45        std::mem::take(self)
46    }
47
48    /// Clears the option, dropping the value if it is a [`Some`] variant.
49    pub fn clear(&mut self) {
50        // Drop implementation will handle the old value
51        *self = Self::None;
52    }
53
54    /// Replaces the value in the option, leaving a [`Some`] variant in its place.
55    /// The old value is dropped if it was present.
56    ///
57    /// Please note that often you don't want to have an unmasked T value in the first place.
58    /// You can construct it in-place using [`Self::insert_by_ptr`].
59    pub fn insert_unmasked_value(&mut self, value: T) {
60        self.insert_by_ptr(|p| unsafe { p.write(value); });
61    }
62
63    /// Replaces the value in the option, leaving a [`Some`] variant in its place.
64    /// The old value is dropped if it was present, after construction of the new one.
65    ///
66    /// The pointer passed to the "constructor" is pointing into an uninitialized memory, allocation
67    /// suitable for `T` both in size and alignment.
68    pub fn insert_by_ptr(&mut self, f: impl FnOnce(NonNull<T>)) {
69        let mut new_content_box = MangledBoxArbitrary::new();
70        new_content_box.with_unmangled(f);
71        *self = Self::Some(new_content_box);
72    }
73
74    /// Unmangles the contents and invokes the provided closure on it. Invokes a default
75    /// closure if the option is [`None`] instead.
76    ///
77    /// An immutable version is not available because it would still need to make a mutation, to
78    /// read the data, and it is not possible to do concurrently.
79    ///
80    /// Please check the compiled code to determine if your function makes a spurious copy
81    /// which could be a security issue.
82    pub fn map_mut_or_else<F, G, R>(&mut self, default: G, f: F) -> R
83    where
84        F: FnOnce(&mut T) -> R,
85        G: FnOnce() -> R,
86    {
87        match self {
88            MangledOption::Some(mangled_box) => {
89                mangled_box.with_unmangled(|mut ptr| f(unsafe { ptr.as_mut() }))
90            }
91            MangledOption::None => default(),
92        }
93    }
94
95    /// Unmangles the contents and invokes the provided closure on it.
96    ///
97    /// An immutable version is not available because it would still need to make a mutation, to
98    /// read the data, and it is not possible to do concurrently.
99    ///
100    /// Please check the compiled code to determine if your function makes a spurious copy
101    /// which could be a security issue.
102    pub fn map_mut<F, R>(&mut self, f: F) -> Option<R>
103    where
104        F: FnOnce(&mut T) -> R,
105    {
106        self.map_mut_or_else(|| None, |m| Some(f(m)))
107    }
108
109    /// Rekeys the box, preserving its contents.
110    pub fn rekey(&mut self) {
111        match self {
112            MangledOption::Some(mangled_box) => {
113                mangled_box.rekey();
114            }
115            MangledOption::None => {}
116        }
117    }
118
119    /// Returns pointer to mangled data.
120    pub fn as_ptr(&mut self) -> *mut T {
121        match self {
122            MangledOption::Some(mangled_box) => mangled_box.with_mangled(|p| p.as_ptr()),
123            MangledOption::None              => null_mut(),
124        }
125    }
126}
127
128impl<T> Drop for MangledOption<T> {
129    fn drop(&mut self) {
130        match self {
131            MangledOption::Some(mangled_box) => {
132                unsafe { mangled_box.drop_in_place(); }
133            }
134            MangledOption::None => {}
135        }
136        unsafe { write(self as *mut Self, Self::None); }
137    }
138}
139
140impl<T> Default for MangledOption<T> {
141    fn default() -> Self {
142        Self::None
143    }
144}
145
146
147#[cfg(all(test, not(miri)))]
148mod tests {
149    use std::sync::atomic::{AtomicUsize, Ordering};
150    use std::mem::size_of;
151
152    use super::*;
153
154
155    #[test]
156    fn test_map_mut() {
157        let mut option = MangledOption::filled_with_unmasked_value(42);
158        assert_eq!(option.map_mut(|x| { *x += 1; *x }), Some(43));
159    }
160
161    #[test]
162    fn test_map_mut_or_else() {
163        let mut option = MangledOption::filled_with_unmasked_value(42);
164        assert_eq!(option.map_mut_or_else(|| 5, |x| { *x += 1; *x }), 43);
165
166        option = MangledOption::None;
167        assert_eq!(option.map_mut_or_else(|| 5, |x| { *x += 1; *x }), 5);
168    }
169    
170    #[test]
171    fn test_new_is_none() {
172        let option: MangledOption<i32> = MangledOption::new();
173        assert!(option.is_none());
174    }
175
176    #[test]
177    fn test_filled_with_unmasked_value() {
178        let mut option = MangledOption::filled_with_unmasked_value(10);
179        assert!(option.is_some());
180        assert_eq!(option.map_mut(|x| *x), Some(10));
181    }
182
183    #[test]
184    fn test_take() {
185        let mut option = MangledOption::filled_with_unmasked_value(20);
186        let mut taken = option.take();
187        
188        assert!(option.is_none());
189        assert_eq!(taken.map_mut(|x| *x), Some(20));
190    }
191
192    #[test]
193    fn test_clear() {
194        let mut option = MangledOption::filled_with_unmasked_value(30);
195        option.clear();
196        assert!(option.is_none());
197    }
198
199    #[test]
200    fn test_insert_unmasked_value() {
201        let mut option = MangledOption::new();
202        option.insert_unmasked_value(40);
203        assert_eq!(option.map_mut(|x| *x), Some(40));
204        
205        option.insert_unmasked_value(50); // Replace existing
206        assert_eq!(option.map_mut(|x| *x), Some(50));
207    }
208
209    #[test]
210    fn test_insert_by_ptr() {
211        let mut option = MangledOption::<usize>::new();
212        option.insert_by_ptr(|ptr| unsafe { ptr.as_ptr().write(60) });
213        assert_eq!(option.map_mut(|x| *x), Some(60));
214        
215        // `*ptr.as_ptr() = 70` would be UB because of touching uninit bytes
216        option.insert_by_ptr(|ptr| unsafe { ptr.as_ptr().write(70) });
217        assert_eq!(option.map_mut(|x| *x), Some(70));
218    }
219
220    #[test]
221    fn test_rekey() {
222        let mut option = MangledOption::filled_with_unmasked_value(80);
223        let original_value = option.map_mut(|x| *x).unwrap();
224        
225        option.rekey(); // Should preserve value
226        assert_eq!(option.map_mut(|x| *x), Some(original_value));
227    }
228
229    #[test]
230    fn test_drop_behavior() {
231        static DROP_COUNT: AtomicUsize = AtomicUsize::new(0);
232
233        struct DropCounter;
234        impl Drop for DropCounter {
235            fn drop(&mut self) {
236                DROP_COUNT.fetch_add(1, Ordering::SeqCst);
237            }
238        }
239
240        {
241            let _option = MangledOption::filled_with_unmasked_value(DropCounter);
242            assert_eq!(DROP_COUNT.load(Ordering::SeqCst), 0);
243        }
244        assert_eq!(DROP_COUNT.load(Ordering::SeqCst), 1);
245
246        {
247            let _option = MangledOption::<DropCounter>::new();
248            assert_eq!(DROP_COUNT.load(Ordering::SeqCst), 1);
249        }
250        assert_eq!(DROP_COUNT.load(Ordering::SeqCst), 1);
251
252        {
253            let mut option = MangledOption::filled_with_unmasked_value(DropCounter);
254            assert_eq!(DROP_COUNT.load(Ordering::SeqCst), 1);
255            option.clear();
256            assert_eq!(DROP_COUNT.load(Ordering::SeqCst), 2);
257        }
258        assert_eq!(DROP_COUNT.load(Ordering::SeqCst), 2);
259
260        {
261            let mut option = MangledOption::new();
262            assert_eq!(DROP_COUNT.load(Ordering::SeqCst), 2);
263            option.insert_unmasked_value(DropCounter);
264        }
265        assert_eq!(DROP_COUNT.load(Ordering::SeqCst), 3);
266    }
267    
268    #[test]
269    fn test_string_content() {
270        let s = String::from("test_value");
271        let mut option = MangledOption::filled_with_unmasked_value(s);
272        
273        option.map_mut(|inner| assert_eq!(inner, "test_value"));
274        
275        option.map_mut(|inner| inner.push_str("_modified"));
276        option.map_mut(|inner| assert_eq!(inner, "test_value_modified"));
277        
278        // Rekey and verify integrity
279        option.rekey();
280        option.map_mut(|inner| assert_eq!(inner, "test_value_modified"));
281    }
282
283    #[test]
284    fn test_padded_struct() {
285        #[repr(C)]
286        #[derive(Debug, PartialEq)]
287        struct Padded {
288            a: u8,
289            b: u16,
290            c: u32,
291        }
292
293        let val = Padded { a: 0xAA, b: 0xBBBB, c: 0xCCCCCCCC };
294        let mut option = MangledOption::filled_with_unmasked_value(val);
295        
296        option.map_mut(|inner| assert_eq!(*inner, Padded { a: 0xAA, b: 0xBBBB, c: 0xCCCCCCCC }));
297        
298        option.map_mut(|inner| inner.a = 0x11);
299        option.map_mut(|inner| {
300            assert_eq!(inner.a, 0x11);
301            assert_eq!(inner.b, 0xBBBB);
302            assert_eq!(inner.c, 0xCCCCCCCC);
303        });
304        
305        // Rekey and verify integrity
306        option.rekey();
307        option.map_mut(|inner| {
308            inner.b = 0x2222;
309            assert_eq!(inner.a, 0x11);
310        });
311        option.map_mut(|inner| assert_eq!(*inner, Padded { a: 0x11, b: 0x2222, c: 0xCCCCCCCC }));
312    }
313
314    #[test]
315    fn test_large_inline_struct() {
316        #[derive(PartialEq, Eq, Debug)]
317        struct LargeStruct([u64; 8]);
318        
319        let val = LargeStruct([0xDEADBEEF; 8]);
320        let mut option = MangledOption::filled_with_unmasked_value(val);
321        
322        option.map_mut(|inner| assert_eq!(inner.0, [0xDEADBEEF; 8]));
323        
324        // Modify and verify
325        option.map_mut(|inner| inner.0[4] = 0xCAFEBABE);
326        option.map_mut(|inner| {
327            assert_eq!(inner.0[0], 0xDEADBEEF);
328            assert_eq!(inner.0[4], 0xCAFEBABE);
329        });
330    }
331
332    #[test]
333    fn test_rekey_integrity() {
334        struct Nested {
335            a: u32,
336            b: MangledOption<u64>,
337        }
338        
339        let mut option = MangledOption::filled_with_unmasked_value(Nested {
340            a: 0x12345678,
341            b: MangledOption::filled_with_unmasked_value(0xABCDEF),
342        });
343        
344        option.map_mut(|inner| {
345            assert_eq!(inner.a, 0x12345678);
346            assert_eq!(inner.b.map_mut(|x| *x), Some(0xABCDEF));
347        });
348        
349        // Rekey outer and inner
350        option.rekey();
351        option.map_mut(|inner| {
352            inner.b.rekey();
353            inner.a = 0x87654321;
354        });
355        
356        option.map_mut(|inner| {
357            assert_eq!(inner.a, 0x87654321);
358            assert_eq!(inner.b.map_mut(|x| *x), Some(0xABCDEF));
359        });
360        option.map_mut(|inner| {
361            inner.b.map_mut(|x| *x = 0x123456789);
362        });
363        
364        // Final verification
365        option.map_mut(|inner| {
366            assert_eq!(inner.b.map_mut(|x| *x), Some(0x123456789));
367        });
368    }
369    
370    #[test]
371    fn xor_behavior() {
372        #[repr(C)]
373        #[derive(Debug, PartialEq)]
374        struct Padded {
375            a: u8,
376            b: u16,
377            c: u32,
378        }
379        
380        let mut option = MangledOption::new();
381        option.insert_by_ptr(|ptr: NonNull<Padded>| {
382            let a = ptr.addr().trailing_zeros() as u8;
383            let c = size_of::<Padded>() as u32;
384            
385            // Constructing by parts.
386            unsafe {
387                let place: *mut u8 = ptr.as_ptr().cast();
388                place.write(a);
389                place.add(2).write(0xAA);
390                place.add(3).write(0xBB);
391                place.add(4).cast::<u32>().write(c);
392            }
393        });
394        
395        let had = option.map_mut(|inner| {
396            assert!(inner.a < 64);
397            assert_eq!(inner.b, u16::from_ne_bytes([0xAA, 0xBB]));
398            assert_eq!(inner.c as usize, size_of::<Padded>());
399        });
400        assert!(had.is_some());
401        
402        let p: *mut u8 = option.as_ptr().cast();
403        unsafe {
404            p.write(p.read() ^ 128);
405        }
406        let had = option.map_mut(|inner| {
407            assert!(inner.a > 128);
408            assert_eq!(inner.b, u16::from_ne_bytes([0xAA, 0xBB]));
409            assert_eq!(inner.c as usize, size_of::<Padded>());
410        });
411        assert!(had.is_some());
412    }
413}
414