Skip to main content

russh_cryptovec/
cryptovec.rs

1use std::fmt::Debug;
2use std::ops::{Deref, DerefMut, Index, IndexMut, Range, RangeFrom, RangeFull, RangeTo};
3
4use crate::platform::{mlock, munlock};
5
6/// A buffer which zeroes its memory on `.clear()`, `.resize()`, and
7/// reallocations, to avoid copying secrets around.
8pub struct CryptoVec {
9    p: *mut u8, // `pub(crate)` allows access from platform modules
10    size: usize,
11    capacity: usize,
12}
13
14impl Debug for CryptoVec {
15    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
16        if self.size == 0 {
17            return f.write_str("<empty>");
18        }
19        write!(f, "<{:?}>", self.size)
20    }
21}
22
23impl Unpin for CryptoVec {}
24unsafe impl Send for CryptoVec {}
25unsafe impl Sync for CryptoVec {}
26
27// Common traits implementations
28impl AsRef<[u8]> for CryptoVec {
29    fn as_ref(&self) -> &[u8] {
30        self.deref()
31    }
32}
33
34impl AsMut<[u8]> for CryptoVec {
35    fn as_mut(&mut self) -> &mut [u8] {
36        self.deref_mut()
37    }
38}
39
40impl Deref for CryptoVec {
41    type Target = [u8];
42    fn deref(&self) -> &[u8] {
43        unsafe { std::slice::from_raw_parts(self.p, self.size) }
44    }
45}
46
47impl DerefMut for CryptoVec {
48    fn deref_mut(&mut self) -> &mut [u8] {
49        unsafe { std::slice::from_raw_parts_mut(self.p, self.size) }
50    }
51}
52
53impl From<String> for CryptoVec {
54    fn from(e: String) -> Self {
55        CryptoVec::from(e.into_bytes())
56    }
57}
58
59impl From<&str> for CryptoVec {
60    fn from(e: &str) -> Self {
61        CryptoVec::from(e.as_bytes())
62    }
63}
64
65impl From<&[u8]> for CryptoVec {
66    fn from(e: &[u8]) -> Self {
67        CryptoVec::from_slice(e)
68    }
69}
70
71impl From<Vec<u8>> for CryptoVec {
72    fn from(e: Vec<u8>) -> Self {
73        let mut c = CryptoVec::new_zeroed(e.len());
74        c.clone_from_slice(&e[..]);
75        c
76    }
77}
78
79// Indexing implementations
80impl Index<RangeFrom<usize>> for CryptoVec {
81    type Output = [u8];
82    fn index(&self, index: RangeFrom<usize>) -> &[u8] {
83        self.deref().index(index)
84    }
85}
86impl Index<RangeTo<usize>> for CryptoVec {
87    type Output = [u8];
88    fn index(&self, index: RangeTo<usize>) -> &[u8] {
89        self.deref().index(index)
90    }
91}
92impl Index<Range<usize>> for CryptoVec {
93    type Output = [u8];
94    fn index(&self, index: Range<usize>) -> &[u8] {
95        self.deref().index(index)
96    }
97}
98impl Index<RangeFull> for CryptoVec {
99    type Output = [u8];
100    fn index(&self, _: RangeFull) -> &[u8] {
101        self.deref()
102    }
103}
104
105impl IndexMut<RangeFull> for CryptoVec {
106    fn index_mut(&mut self, _: RangeFull) -> &mut [u8] {
107        self.deref_mut()
108    }
109}
110impl IndexMut<RangeFrom<usize>> for CryptoVec {
111    fn index_mut(&mut self, index: RangeFrom<usize>) -> &mut [u8] {
112        self.deref_mut().index_mut(index)
113    }
114}
115impl IndexMut<RangeTo<usize>> for CryptoVec {
116    fn index_mut(&mut self, index: RangeTo<usize>) -> &mut [u8] {
117        self.deref_mut().index_mut(index)
118    }
119}
120impl IndexMut<Range<usize>> for CryptoVec {
121    fn index_mut(&mut self, index: Range<usize>) -> &mut [u8] {
122        self.deref_mut().index_mut(index)
123    }
124}
125
126impl Index<usize> for CryptoVec {
127    type Output = u8;
128    fn index(&self, index: usize) -> &u8 {
129        self.deref().index(index)
130    }
131}
132
133// IO-related implementation
134impl std::io::Write for CryptoVec {
135    fn write(&mut self, buf: &[u8]) -> Result<usize, std::io::Error> {
136        self.extend(buf);
137        Ok(buf.len())
138    }
139
140    fn flush(&mut self) -> Result<(), std::io::Error> {
141        Ok(())
142    }
143}
144
145// Default implementation
146impl Default for CryptoVec {
147    fn default() -> Self {
148        CryptoVec {
149            p: std::ptr::NonNull::dangling().as_ptr(),
150            size: 0,
151            capacity: 0,
152        }
153    }
154}
155
156const MAX_CAPACITY: usize = 1usize << (usize::BITS - 2);
157
158#[cold]
159#[inline(never)]
160#[allow(clippy::panic)]
161fn capacity_overflow(len: usize) -> ! {
162    panic!("CryptoVec capacity overflow: {len}")
163}
164
165#[cold]
166#[inline(never)]
167#[allow(clippy::panic)]
168fn length_overflow(lhs: usize, rhs: usize) -> ! {
169    panic!("CryptoVec length overflow: {lhs} + {rhs}")
170}
171
172#[cold]
173#[inline(never)]
174fn alloc_failed(layout: std::alloc::Layout) -> ! {
175    std::alloc::handle_alloc_error(layout)
176}
177
178#[inline]
179fn checked_capacity(len: usize) -> usize {
180    if len > MAX_CAPACITY {
181        capacity_overflow(len);
182    }
183    len.next_power_of_two()
184}
185
186#[inline]
187unsafe fn alloc_zeroed(capacity: usize) -> *mut u8 {
188    debug_assert!(capacity > 0);
189    let layout = unsafe { std::alloc::Layout::from_size_align_unchecked(capacity, 1) };
190    let p = unsafe { std::alloc::alloc_zeroed(layout) };
191    if p.is_null() {
192        alloc_failed(layout);
193    }
194    let _ = mlock(p, capacity);
195    p
196}
197
198#[inline]
199fn checked_len_sum(lhs: usize, rhs: usize) -> usize {
200    let sum = lhs.wrapping_add(rhs);
201    if sum < lhs {
202        length_overflow(lhs, rhs);
203    }
204    sum
205}
206
207impl CryptoVec {
208    /// Creates a new `CryptoVec`.
209    pub fn new() -> CryptoVec {
210        CryptoVec::default()
211    }
212
213    /// Creates a new `CryptoVec` with `n` zeros.
214    pub fn new_zeroed(size: usize) -> CryptoVec {
215        if size == 0 {
216            return CryptoVec::default();
217        }
218
219        let capacity = checked_capacity(size);
220        let p = unsafe { alloc_zeroed(capacity) };
221        CryptoVec { p, capacity, size }
222    }
223
224    /// Creates a new `CryptoVec` with capacity `capacity`.
225    pub fn with_capacity(capacity: usize) -> CryptoVec {
226        if capacity == 0 {
227            return CryptoVec::default();
228        }
229
230        let capacity = checked_capacity(capacity);
231        let p = unsafe { alloc_zeroed(capacity) };
232        CryptoVec {
233            p,
234            capacity,
235            size: 0,
236        }
237    }
238
239    /// Length of this `CryptoVec`.
240    ///
241    /// ```
242    /// assert_eq!(russh_cryptovec::CryptoVec::new().len(), 0)
243    /// ```
244    pub fn len(&self) -> usize {
245        self.size
246    }
247
248    /// Returns `true` if and only if this CryptoVec is empty.
249    ///
250    /// ```
251    /// assert!(russh_cryptovec::CryptoVec::new().is_empty())
252    /// ```
253    pub fn is_empty(&self) -> bool {
254        self.len() == 0
255    }
256
257    /// Resize this CryptoVec, appending zeros at the end. This may
258    /// perform at most one reallocation, overwriting the previous
259    /// version with zeros.
260    pub fn resize(&mut self, size: usize) {
261        if size <= self.capacity && size > self.size {
262            // If this is an expansion within capacity, the memory is already zeroed.
263            self.size = size
264        } else if size <= self.size {
265            // If this is a truncation, securely erase the extra memory.
266            // Uses zeroize (optimization_barrier) to prevent dead-store elimination.
267            unsafe {
268                zeroize(self.p.add(size), self.size - size);
269            }
270            self.size = size;
271        } else {
272            // realloc ! and erase the previous memory.
273            unsafe {
274                let next_capacity = checked_capacity(size);
275                let old_ptr = self.p;
276                let next_ptr = alloc_zeroed(next_capacity);
277
278                if self.capacity > 0 {
279                    std::ptr::copy_nonoverlapping(old_ptr, next_ptr, self.size);
280                    zeroize(old_ptr, self.size);
281                    let _ = munlock(old_ptr, self.capacity);
282                    let layout = std::alloc::Layout::from_size_align_unchecked(self.capacity, 1);
283                    std::alloc::dealloc(old_ptr, layout);
284                }
285
286                self.p = next_ptr;
287                self.capacity = next_capacity;
288                self.size = size;
289            }
290        }
291    }
292
293    /// Clear this CryptoVec (retaining the memory).
294    ///
295    /// ```
296    /// let mut v = russh_cryptovec::CryptoVec::new();
297    /// v.extend(b"blabla");
298    /// v.clear();
299    /// assert!(v.is_empty())
300    /// ```
301    pub fn clear(&mut self) {
302        self.resize(0);
303    }
304
305    /// Append a new byte at the end of this CryptoVec.
306    pub fn push(&mut self, s: u8) {
307        let size = self.size;
308        self.resize(checked_len_sum(size, 1));
309        unsafe { *self.p.add(size) = s }
310    }
311
312    /// Read `n_bytes` from `r`, and append them at the end of this
313    /// `CryptoVec`. Returns the number of bytes read (and appended).
314    pub fn read<R: std::io::Read>(
315        &mut self,
316        n_bytes: usize,
317        mut r: R,
318    ) -> Result<usize, std::io::Error> {
319        let cur_size = self.size;
320        let target_size = checked_len_sum(cur_size, n_bytes);
321        self.resize(target_size);
322        let s = unsafe { std::slice::from_raw_parts_mut(self.p.add(cur_size), n_bytes) };
323        // Resize the buffer to its appropriate size.
324        match r.read(s) {
325            Ok(n) => {
326                self.resize(cur_size + n);
327                Ok(n)
328            }
329            Err(e) => {
330                self.resize(cur_size);
331                Err(e)
332            }
333        }
334    }
335
336    /// Write all this CryptoVec to the provided `Write`. Returns the
337    /// number of bytes actually written.
338    ///
339    /// ```
340    /// let mut v = russh_cryptovec::CryptoVec::new();
341    /// v.extend(b"blabla");
342    /// let mut s = std::io::stdout();
343    /// v.write_all_from(0, &mut s).unwrap();
344    /// ```
345    pub fn write_all_from<W: std::io::Write>(
346        &self,
347        offset: usize,
348        mut w: W,
349    ) -> Result<usize, std::io::Error> {
350        assert!(offset < self.size);
351        // if we're past this point, self.p cannot be null.
352        unsafe {
353            let s = std::slice::from_raw_parts(self.p.add(offset), self.size - offset);
354            w.write(s)
355        }
356    }
357
358    /// Resize this CryptoVec, returning a mutable borrow to the extra bytes.
359    ///
360    /// ```
361    /// let mut v = russh_cryptovec::CryptoVec::new();
362    /// v.resize_mut(4).clone_from_slice(b"test");
363    /// ```
364    pub fn resize_mut(&mut self, n: usize) -> &mut [u8] {
365        let size = self.size;
366        self.resize(checked_len_sum(size, n));
367        unsafe { std::slice::from_raw_parts_mut(self.p.add(size), n) }
368    }
369
370    /// Append a slice at the end of this CryptoVec.
371    ///
372    /// ```
373    /// let mut v = russh_cryptovec::CryptoVec::new();
374    /// v.extend(b"test");
375    /// ```
376    pub fn extend(&mut self, s: &[u8]) {
377        let size = self.size;
378        let added = s.len();
379        self.resize(checked_len_sum(size, added));
380        unsafe {
381            std::ptr::copy_nonoverlapping(s.as_ptr(), self.p.add(size), s.len());
382        }
383    }
384
385    /// Create a `CryptoVec` from a slice
386    ///
387    /// ```
388    /// russh_cryptovec::CryptoVec::from_slice(b"test");
389    /// ```
390    pub fn from_slice(s: &[u8]) -> CryptoVec {
391        let mut v = CryptoVec::new();
392        v.resize(s.len());
393        unsafe {
394            std::ptr::copy_nonoverlapping(s.as_ptr(), v.p, s.len());
395        }
396        v
397    }
398}
399
400impl Clone for CryptoVec {
401    fn clone(&self) -> Self {
402        let mut v = Self::new();
403        v.extend(self);
404        v
405    }
406}
407
408// Drop implementation
409impl Drop for CryptoVec {
410    fn drop(&mut self) {
411        if self.capacity > 0 {
412            unsafe {
413                zeroize(self.p, self.size);
414                let _ = munlock(self.p, self.capacity);
415                let layout = std::alloc::Layout::from_size_align_unchecked(self.capacity, 1);
416                std::alloc::dealloc(self.p, layout);
417            }
418        }
419    }
420}
421
422unsafe fn zeroize(dst: *mut u8, size: usize) {
423    unsafe {
424        std::ptr::write_bytes(dst, 0, size);
425    }
426    optimization_barrier(dst, size);
427}
428
429// https://github.com/RustCrypto/utils/blob/a9f3f461baa3e02f69a205c772b6b2d3eac4eda8/zeroize/src/barrier.rs
430fn optimization_barrier(dst: *mut u8, size: usize) {
431    #[cfg(all(
432        not(miri),
433        any(
434            target_arch = "aarch64",
435            target_arch = "arm",
436            target_arch = "arm64ec",
437            target_arch = "loongarch64",
438            target_arch = "riscv32",
439            target_arch = "riscv64",
440            target_arch = "s390x",
441            target_arch = "x86",
442            target_arch = "x86_64",
443        )
444    ))]
445    {
446        let _ = size;
447        unsafe {
448            core::arch::asm!(
449                "# {}",
450                in(reg) dst,
451                options(readonly, preserves_flags, nostack),
452            );
453        }
454    }
455    #[cfg(not(all(
456        not(miri),
457        any(
458            target_arch = "aarch64",
459            target_arch = "arm",
460            target_arch = "arm64ec",
461            target_arch = "loongarch64",
462            target_arch = "riscv32",
463            target_arch = "riscv64",
464            target_arch = "s390x",
465            target_arch = "x86",
466            target_arch = "x86_64",
467        )
468    )))]
469    {
470        /// Custom version of `core::hint::black_box` implemented using
471        /// `#[inline(never)]` and `read_volatile`.
472        #[inline(never)]
473        fn custom_black_box(p: *const u8) {
474            let _ = unsafe { core::ptr::read_volatile(p) };
475        }
476
477        core::hint::black_box(dst);
478        if size > 0 {
479            custom_black_box(dst);
480        }
481    }
482}
483
484#[cfg(test)]
485mod test {
486    use super::{CryptoVec, checked_capacity};
487
488    #[test]
489    fn test_new() {
490        let crypto_vec = CryptoVec::new();
491        assert_eq!(crypto_vec.size, 0);
492        assert_eq!(crypto_vec.capacity, 0);
493    }
494
495    #[test]
496    fn test_resize_expand() {
497        let mut crypto_vec = CryptoVec::new_zeroed(5);
498        crypto_vec.resize(10);
499        assert_eq!(crypto_vec.size, 10);
500        assert!(crypto_vec.capacity >= 10);
501        assert!(crypto_vec.iter().skip(5).all(|&x| x == 0)); // Ensure newly added elements are zeroed
502    }
503
504    #[test]
505    fn test_resize_shrink() {
506        let mut crypto_vec = CryptoVec::new_zeroed(10);
507        crypto_vec.resize(5);
508        assert_eq!(crypto_vec.size, 5);
509        // Ensure shrinking keeps the previous elements intact
510        assert_eq!(crypto_vec.len(), 5);
511    }
512
513    #[test]
514    fn test_resize_zero() {
515        let mut crypto_vec = CryptoVec::new();
516        crypto_vec.resize(0);
517        assert_eq!(crypto_vec.size, 0);
518        assert_eq!(crypto_vec.len(), 0);
519    }
520
521    #[test]
522    fn test_push() {
523        let mut crypto_vec = CryptoVec::new();
524        crypto_vec.push(1);
525        crypto_vec.push(2);
526        assert_eq!(crypto_vec.size, 2);
527        assert_eq!(crypto_vec[0], 1);
528        assert_eq!(crypto_vec[1], 2);
529    }
530
531    #[test]
532    fn test_write_trait() {
533        use std::io::Write;
534
535        let mut crypto_vec = CryptoVec::new();
536        let bytes_written = crypto_vec.write(&[1, 2, 3]).unwrap();
537        assert_eq!(bytes_written, 3);
538        assert_eq!(crypto_vec.size, 3);
539        assert_eq!(crypto_vec.as_ref(), &[1, 2, 3]);
540    }
541
542    #[test]
543    fn test_as_ref_as_mut() {
544        let mut crypto_vec = CryptoVec::new_zeroed(5);
545        let slice_ref: &[u8] = crypto_vec.as_ref();
546        assert_eq!(slice_ref.len(), 5);
547        let slice_mut: &mut [u8] = crypto_vec.as_mut();
548        slice_mut[0] = 1;
549        assert_eq!(crypto_vec[0], 1);
550    }
551
552    #[test]
553    fn test_from_string() {
554        let input = String::from("hello");
555        let crypto_vec: CryptoVec = input.into();
556        assert_eq!(crypto_vec.as_ref(), b"hello");
557    }
558
559    #[test]
560    fn test_from_str() {
561        let input = "hello";
562        let crypto_vec: CryptoVec = input.into();
563        assert_eq!(crypto_vec.as_ref(), b"hello");
564    }
565
566    #[test]
567    fn test_from_byte_slice() {
568        let input = b"hello".as_slice();
569        let crypto_vec: CryptoVec = input.into();
570        assert_eq!(crypto_vec.as_ref(), b"hello");
571    }
572
573    #[test]
574    fn test_from_vec() {
575        let input = vec![1, 2, 3, 4];
576        let crypto_vec: CryptoVec = input.into();
577        assert_eq!(crypto_vec.as_ref(), &[1, 2, 3, 4]);
578    }
579
580    #[test]
581    fn test_index() {
582        let crypto_vec = CryptoVec::from(vec![1, 2, 3, 4, 5]);
583        assert_eq!(crypto_vec[0], 1);
584        assert_eq!(crypto_vec[4], 5);
585        assert_eq!(&crypto_vec[1..3], &[2, 3]);
586    }
587
588    #[test]
589    fn test_drop() {
590        let mut crypto_vec = CryptoVec::new_zeroed(10);
591        // Ensure vector is filled with non-zero data
592        crypto_vec.extend(&[1, 2, 3, 4, 5, 6, 7, 8, 9, 10]);
593        drop(crypto_vec);
594
595        // Check that memory zeroing was done during the drop
596        // This part is more difficult to test directly since it involves
597        // private memory management. However, with Rust's unsafe features,
598        // it may be checked using tools like Valgrind or manual inspection.
599    }
600
601    #[test]
602    fn test_new_zeroed() {
603        let crypto_vec = CryptoVec::new_zeroed(10);
604        assert_eq!(crypto_vec.size, 10);
605        assert!(crypto_vec.capacity >= 10);
606        assert!(crypto_vec.iter().all(|&x| x == 0)); // Ensure all bytes are zeroed
607    }
608
609    #[test]
610    fn test_clear() {
611        let mut crypto_vec = CryptoVec::new();
612        crypto_vec.extend(b"blabla");
613        crypto_vec.clear();
614        assert!(crypto_vec.is_empty());
615    }
616
617    #[test]
618    fn test_with_capacity_zero() {
619        let crypto_vec = CryptoVec::with_capacity(0);
620        assert_eq!(crypto_vec.size, 0);
621        assert_eq!(crypto_vec.capacity, 0);
622    }
623
624    #[test]
625    fn test_new_zeroed_zero() {
626        let crypto_vec = CryptoVec::new_zeroed(0);
627        assert_eq!(crypto_vec.size, 0);
628        assert_eq!(crypto_vec.capacity, 0);
629    }
630
631    #[test]
632    fn test_extend() {
633        let mut crypto_vec = CryptoVec::new();
634        crypto_vec.extend(b"test");
635        assert_eq!(crypto_vec.as_ref(), b"test");
636    }
637
638    #[test]
639    #[should_panic(expected = "CryptoVec capacity overflow")]
640    fn test_checked_capacity_overflow_panics() {
641        let _ = checked_capacity(usize::MAX);
642    }
643
644    #[test]
645    #[should_panic(expected = "CryptoVec capacity overflow")]
646    fn test_checked_capacity_rejects_values_above_max_capacity() {
647        let _ = checked_capacity(super::MAX_CAPACITY + 1);
648    }
649
650    #[test]
651    fn test_write_all_from() {
652        let mut crypto_vec = CryptoVec::new();
653        crypto_vec.extend(b"blabla");
654
655        let mut output: Vec<u8> = Vec::new();
656        let written_size = crypto_vec.write_all_from(0, &mut output).unwrap();
657        assert_eq!(written_size, 6); // "blabla" has 6 bytes
658        assert_eq!(output, b"blabla");
659    }
660
661    #[test]
662    fn test_resize_mut() {
663        let mut crypto_vec = CryptoVec::new();
664        crypto_vec.resize_mut(4).clone_from_slice(b"test");
665        assert_eq!(crypto_vec.as_ref(), b"test");
666    }
667
668    // DocTests cannot be run on with wasm_bindgen_test
669    #[cfg(target_arch = "wasm32")]
670    mod wasm32 {
671        use wasm_bindgen_test::wasm_bindgen_test;
672
673        use super::*;
674
675        wasm_bindgen_test::wasm_bindgen_test_configure!(run_in_browser);
676
677        #[wasm_bindgen_test]
678        fn test_push_u32_be() {
679            let mut crypto_vec = CryptoVec::new();
680            let value = 43554u32;
681            crypto_vec.push_u32_be(value);
682            assert_eq!(crypto_vec.len(), 4); // u32 is 4 bytes long
683            assert_eq!(crypto_vec.read_u32_be(0), value);
684        }
685
686        #[wasm_bindgen_test]
687        fn test_read_u32_be() {
688            let mut crypto_vec = CryptoVec::new();
689            let value = 99485710u32;
690            crypto_vec.push_u32_be(value);
691            assert_eq!(crypto_vec.read_u32_be(0), value);
692        }
693    }
694}