zeroize/
lib.rs

1//! Securely zero memory with a simple trait ([`Zeroize`]) built on stable Rust
2//! primitives which guarantee the operation will not be "optimized away".
3//!
4//! ## About
5//!
6//! [Zeroing memory securely is hard] - compilers optimize for performance, and
7//! in doing so they love to "optimize away" unnecessary zeroing calls. There are
8//! many documented "tricks" to attempt to avoid these optimizations and ensure
9//! that a zeroing routine is performed reliably.
10//!
11//! This crate isn't about tricks: it uses [`core::ptr::write_volatile`]
12//! and [`core::sync::atomic`] memory fences to provide easy-to-use, portable
13//! zeroing behavior which works on all of Rust's core number types and slices
14//! thereof, implemented in pure Rust with no usage of FFI or assembly.
15//!
16//! - No insecure fallbacks!
17//! - No dependencies!
18//! - No FFI or inline assembly! **WASM friendly** (and tested)!
19//! - `#![no_std]` i.e. **embedded-friendly**!
20//! - No functionality besides securely zeroing memory!
21//! - (Optional) Custom derive support for zeroing complex structures
22//!
23//! ## Minimum Supported Rust Version
24//!
25//! Requires Rust **1.51** or newer.
26//!
27//! In the future, we reserve the right to change MSRV (i.e. MSRV is out-of-scope
28//! for this crate's SemVer guarantees), however when we do it will be accompanied
29//! by a minor version bump.
30//!
31//! ## Usage
32//!
33//! ```
34//! use zeroize::Zeroize;
35//!
36//! fn main() {
37//!     // Protip: don't embed secrets in your source code.
38//!     // This is just an example.
39//!     let mut secret = b"Air shield password: 1,2,3,4,5".to_vec();
40//!     // [ ... ] open the air shield here
41//!
42//!     // Now that we're done using the secret, zero it out.
43//!     secret.zeroize();
44//! }
45//! ```
46//!
47//! The [`Zeroize`] trait is impl'd on all of Rust's core scalar types including
48//! integers, floats, `bool`, and `char`.
49//!
50//! Additionally, it's implemented on slices and `IterMut`s of the above types.
51//!
52//! When the `alloc` feature is enabled (which it is by default), it's also
53//! impl'd for `Vec<T>` for the above types as well as `String`, where it provides
54//! [`Vec::clear`] / [`String::clear`]-like behavior (truncating to zero-length)
55//! but ensures the backing memory is securely zeroed with some caveats.
56//! (NOTE: see "Stack/Heap Zeroing Notes" for important `Vec`/`String` details)
57//!
58//! The [`DefaultIsZeroes`] marker trait can be impl'd on types which also
59//! impl [`Default`], which implements [`Zeroize`] by overwriting a value with
60//! the default value.
61//!
62//! ## Custom Derive Support
63//!
64//! This crate has custom derive support for the `Zeroize` trait,
65//! gated under the `zeroize` crate's `zeroize_derive` Cargo feature,
66//! which automatically calls `zeroize()` on all members of a struct
67//! or tuple struct.
68//!
69//! Additionally it supports the following attribute:
70//!
71//! - `#[zeroize(drop)]`: call `zeroize()` when this item is dropped
72//!
73//! Example which derives `Drop`:
74//!
75//! ```
76//! # #[cfg(feature = "derive")]
77//! # {
78//! use zeroize::Zeroize;
79//!
80//! // This struct will be zeroized on drop
81//! #[derive(Zeroize)]
82//! #[zeroize(drop)]
83//! struct MyStruct([u8; 32]);
84//! # }
85//! ```
86//!
87//! Example which does not derive `Drop` (useful for e.g. `Copy` types)
88//!
89//! ```
90//! #[cfg(feature = "derive")]
91//! # {
92//! use zeroize::Zeroize;
93//!
94//! // This struct will *NOT* be zeroized on drop
95//! #[derive(Copy, Clone, Zeroize)]
96//! struct MyStruct([u8; 32]);
97//! # }
98//! ```
99//!
100//! ## `Zeroizing<Z>`: wrapper for zeroizing arbitrary values on drop
101//!
102//! `Zeroizing<Z: Zeroize>` is a generic wrapper type that impls `Deref`
103//! and `DerefMut`, allowing access to an inner value of type `Z`, and also
104//! impls a `Drop` handler which calls `zeroize()` on its contents:
105//!
106//! ```
107//! use zeroize::Zeroizing;
108//!
109//! fn main() {
110//!     let mut secret = Zeroizing::new([0u8; 5]);
111//!
112//!     // Set the air shield password
113//!     // Protip (again): don't embed secrets in your source code.
114//!     secret.copy_from_slice(&[1, 2, 3, 4, 5]);
115//!     assert_eq!(secret.as_ref(), &[1, 2, 3, 4, 5]);
116//!
117//!     // The contents of `secret` will be automatically zeroized on drop
118//! }
119//! ```
120//!
121//! ## What guarantees does this crate provide?
122//!
123//! This crate guarantees the following:
124//!
125//! 1. The zeroing operation can't be "optimized away" by the compiler.
126//! 2. All subsequent reads to memory will see "zeroized" values.
127//!
128//! LLVM's volatile semantics ensure #1 is true.
129//!
130//! Additionally, thanks to work by the [Unsafe Code Guidelines Working Group],
131//! we can now fairly confidently say #2 is true as well. Previously there were
132//! worries that the approach used by this crate (mixing volatile and
133//! non-volatile accesses) was undefined behavior due to language contained
134//! in the documentation for `write_volatile`, however after some discussion
135//! [these remarks have been removed] and the specific usage pattern in this
136//! crate is considered to be well-defined.
137//!
138//! Additionally this crate leverages [`core::sync::atomic::compiler_fence`]
139//! with the strictest ordering
140//! ([`Ordering::SeqCst`]) as a
141//! precaution to help ensure reads are not reordered before memory has been
142//! zeroed.
143//!
144//! All of that said, there is still potential for microarchitectural attacks
145//! (ala Spectre/Meltdown) to leak "zeroized" secrets through covert channels.
146//! This crate makes no guarantees that zeroized values cannot be leaked
147//! through such channels, as they represent flaws in the underlying hardware.
148//!
149//! ## Stack/Heap Zeroing Notes
150//!
151//! This crate can be used to zero values from either the stack or the heap.
152//!
153//! However, be aware several operations in Rust can unintentionally leave
154//! copies of data in memory. This includes but is not limited to:
155//!
156//! - Moves and [`Copy`]
157//! - Heap reallocation when using [`Vec`] and [`String`]
158//! - Borrowers of a reference making copies of the data
159//!
160//! [`Pin`][`core::pin::Pin`] can be leveraged in conjunction with this crate
161//! to ensure data kept on the stack isn't moved.
162//!
163//! The `Zeroize` impls for `Vec` and `String` zeroize the entire capacity of
164//! their backing buffer, but cannot guarantee copies of the data were not
165//! previously made by buffer reallocation. It's therefore important when
166//! attempting to zeroize such buffers to initialize them to the correct
167//! capacity, and take care to prevent subsequent reallocation.
168//!
169//! The `secrecy` crate provides higher-level abstractions for eliminating
170//! usage patterns which can cause reallocations:
171//!
172//! <https://crates.io/crates/secrecy>
173//!
174//! ## What about: clearing registers, mlock, mprotect, etc?
175//!
176//! This crate is focused on providing simple, unobtrusive support for reliably
177//! zeroing memory using the best approach possible on stable Rust.
178//!
179//! Clearing registers is a difficult problem that can't easily be solved by
180//! something like a crate, and requires either inline ASM or rustc support.
181//! See <https://github.com/rust-lang/rust/issues/17046> for background on
182//! this particular problem.
183//!
184//! Other memory protection mechanisms are interesting and useful, but often
185//! overkill (e.g. defending against RAM scraping or attackers with swap access).
186//! In as much as there may be merit to these approaches, there are also many
187//! other crates that already implement more sophisticated memory protections.
188//! Such protections are explicitly out-of-scope for this crate.
189//!
190//! Zeroing memory is [good cryptographic hygiene] and this crate seeks to promote
191//! it in the most unobtrusive manner possible. This includes omitting complex
192//! `unsafe` memory protection systems and just trying to make the best memory
193//! zeroing crate available.
194//!
195//! [Zeroing memory securely is hard]: http://www.daemonology.net/blog/2014-09-04-how-to-zero-a-buffer.html
196//! [Unsafe Code Guidelines Working Group]: https://github.com/rust-lang/unsafe-code-guidelines
197//! [these remarks have been removed]: https://github.com/rust-lang/rust/pull/60972
198//! [good cryptographic hygiene]: https://github.com/veorq/cryptocoding#clean-memory-of-secret-data
199//! [`Ordering::SeqCst`]: core::sync::atomic::Ordering::SeqCst
200
201#![no_std]
202#![cfg_attr(docsrs, feature(doc_cfg))]
203#![doc(html_root_url = "https://docs.rs/zeroize/1.4.3")]
204#![warn(missing_docs, rust_2018_idioms, unused_qualifications)]
205
206#[cfg(feature = "alloc")]
207#[cfg_attr(test, macro_use)]
208extern crate alloc;
209
210#[cfg(feature = "zeroize_derive")]
211#[cfg_attr(docsrs, doc(cfg(feature = "zeroize_derive")))]
212pub use zeroize_derive::Zeroize;
213
214#[cfg(any(target_arch = "x86", target_arch = "x86_64"))]
215mod x86;
216
217use core::mem::{self, MaybeUninit};
218use core::num::{
219    NonZeroI128, NonZeroI16, NonZeroI32, NonZeroI64, NonZeroI8, NonZeroIsize, NonZeroU128,
220    NonZeroU16, NonZeroU32, NonZeroU64, NonZeroU8, NonZeroUsize,
221};
222use core::{ops, ptr, slice::IterMut, sync::atomic};
223
224#[cfg(feature = "alloc")]
225use alloc::{boxed::Box, string::String, vec::Vec};
226
227/// Trait for securely erasing types from memory
228pub trait Zeroize {
229    /// Zero out this object from memory using Rust intrinsics which ensure the
230    /// zeroization operation is not "optimized away" by the compiler.
231    fn zeroize(&mut self);
232}
233
234/// Marker trait for types whose `Default` is the desired zeroization result
235pub trait DefaultIsZeroes: Copy + Default + Sized {}
236
237impl<Z> Zeroize for Z
238where
239    Z: DefaultIsZeroes,
240{
241    fn zeroize(&mut self) {
242        volatile_write(self, Z::default());
243        atomic_fence();
244    }
245}
246
247macro_rules! impl_zeroize_with_default {
248    ($($type:ty),+) => {
249        $(impl DefaultIsZeroes for $type {})+
250    };
251}
252
253impl_zeroize_with_default!(i8, i16, i32, i64, i128, isize);
254impl_zeroize_with_default!(u8, u16, u32, u64, u128, usize);
255impl_zeroize_with_default!(f32, f64, char, bool);
256
257macro_rules! impl_zeroize_for_non_zero {
258    ($($type:ty),+) => {
259        $(impl Zeroize for $type
260        {
261            fn zeroize(&mut self) {
262                volatile_write(self, unsafe { <$type>::new_unchecked(1) });
263                atomic_fence();
264            }
265        })+
266    };
267}
268
269impl_zeroize_for_non_zero!(
270    NonZeroI8,
271    NonZeroI16,
272    NonZeroI32,
273    NonZeroI64,
274    NonZeroI128,
275    NonZeroIsize
276);
277impl_zeroize_for_non_zero!(
278    NonZeroU8,
279    NonZeroU16,
280    NonZeroU32,
281    NonZeroU64,
282    NonZeroU128,
283    NonZeroUsize
284);
285
286/// Implement `Zeroize` on arrays of types that impl `Zeroize`
287impl<Z, const N: usize> Zeroize for [Z; N]
288where
289    Z: Zeroize,
290{
291    fn zeroize(&mut self) {
292        self.iter_mut().zeroize();
293    }
294}
295
296impl<'a, Z> Zeroize for IterMut<'a, Z>
297where
298    Z: Zeroize,
299{
300    fn zeroize(&mut self) {
301        for elem in self {
302            elem.zeroize();
303        }
304    }
305}
306
307impl<Z> Zeroize for Option<Z>
308where
309    Z: Zeroize,
310{
311    fn zeroize(&mut self) {
312        if let Some(value) = self {
313            value.zeroize();
314
315            // Ensures self is None and that the value was dropped. Without the take, the drop
316            // of the (zeroized) value isn't called, which might lead to a leak or other
317            // unexpected behavior. For example, if this were Option<Vec<T>>, the above call to
318            // zeroize would not free the allocated memory, but the the `take` call will.
319            self.take();
320        }
321
322        // Ensure that if the `Option` were previously `Some` but a value was copied/moved out
323        // that the remaining space in the `Option` is zeroized.
324        //
325        // Safety:
326        //
327        // The memory pointed to by `self` is valid for `mem::size_of::<Self>()` bytes.
328        // It is also properly aligned, because `u8` has an alignment of `1`.
329        unsafe {
330            volatile_set(self as *mut _ as *mut u8, 0, mem::size_of::<Self>());
331        }
332
333        // Ensures self is overwritten with the default bit pattern. volatile_write can't be
334        // used because Option<Z> is not copy.
335        //
336        // Safety:
337        //
338        // self is safe to replace with the default, which the take() call above should have
339        // already done semantically. Any value which needed to be dropped will have been
340        // done so by take().
341        unsafe { ptr::write_volatile(self, Option::default()) }
342
343        atomic_fence();
344    }
345}
346
347/// Impl `Zeroize` on slices of MaybeUninit types
348/// This impl can eventually be optimized using an memset intrinsic,
349/// such as `core::intrinsics::volatile_set_memory`.
350/// This fills the slice with zeros
351/// Note that this ignore invariants that Z might have, because MaybeUninit removes all invariants.
352impl<Z> Zeroize for [MaybeUninit<Z>] {
353    fn zeroize(&mut self) {
354        let ptr = self.as_mut_ptr() as *mut MaybeUninit<u8>;
355        let size = self.len().checked_mul(mem::size_of::<Z>()).unwrap();
356        assert!(size <= core::isize::MAX as usize);
357        // Safety:
358        //
359        // This is safe, because every valid pointer is well aligned for u8
360        // and it is backed by a single allocated object for at least `self.len() * size_pf::<Z>()` bytes.
361        // and 0 is a valid value for `MaybeUninit<Z>`
362        // The memory of the slice should not wrap around the address space.
363        unsafe { volatile_set(ptr, MaybeUninit::new(0), size) }
364        atomic_fence();
365    }
366}
367
368/// Impl `Zeroize` on slices of types that can be zeroized with `Default`.
369///
370/// This impl can eventually be optimized using an memset intrinsic,
371/// such as `core::intrinsics::volatile_set_memory`. For that reason the blanket
372/// impl on slices is bounded by `DefaultIsZeroes`.
373///
374/// To zeroize a mut slice of `Z: Zeroize` which does not impl
375/// `DefaultIsZeroes`, call `iter_mut().zeroize()`.
376impl<Z> Zeroize for [Z]
377where
378    Z: DefaultIsZeroes,
379{
380    fn zeroize(&mut self) {
381        assert!(self.len() <= core::isize::MAX as usize);
382        // Safety:
383        //
384        // This is safe, because the slice is well aligned and is backed by a single allocated
385        // object for at least `self.len()` elements of type `Z`.
386        // `self.len()` is also not larger than an `isize`, because of the assertion above.
387        // The memory of the slice should not wrap around the address space.
388        unsafe { volatile_set(self.as_mut_ptr(), Z::default(), self.len()) };
389        atomic_fence();
390    }
391}
392
393#[cfg(feature = "alloc")]
394#[cfg_attr(docsrs, doc(cfg(feature = "alloc")))]
395impl<Z> Zeroize for Vec<Z>
396where
397    Z: Zeroize,
398{
399    /// "Best effort" zeroization for `Vec`.
400    ///
401    /// Ensures the entire capacity of the `Vec` is zeroed. Cannot ensure that
402    /// previous reallocations did not leave values on the heap.
403    fn zeroize(&mut self) {
404        use core::slice;
405        // Zeroize all the initialized elements.
406        self.iter_mut().zeroize();
407
408        // Set the Vec's length to 0 and drop all the elements.
409        self.clear();
410        // Zero the full capacity of `Vec`.
411        // Safety:
412        //
413        // This is safe, because `Vec` never allocates more than `isize::MAX` bytes.
414        // This exact use case is even mentioned in the documentation of `pointer::add`.
415        // This is safe because MaybeUninit ignores all invariants,
416        // so we can create a slice of MaybeUninit<Z> using the full capacity of the Vec
417        let uninit_slice = unsafe {
418            slice::from_raw_parts_mut(self.as_mut_ptr() as *mut MaybeUninit<Z>, self.capacity())
419        };
420        uninit_slice.zeroize();
421    }
422}
423
424#[cfg(feature = "alloc")]
425#[cfg_attr(docsrs, doc(cfg(feature = "alloc")))]
426impl<Z> Zeroize for Box<[Z]>
427where
428    Z: Zeroize,
429{
430    /// Unlike `Vec`, `Box<[Z]>` cannot reallocate, so we can be sure that we are not leaving
431    /// values on the heap.
432    fn zeroize(&mut self) {
433        self.iter_mut().zeroize();
434    }
435}
436
437#[cfg(feature = "alloc")]
438#[cfg_attr(docsrs, doc(cfg(feature = "alloc")))]
439impl Zeroize for String {
440    fn zeroize(&mut self) {
441        unsafe { self.as_mut_vec() }.zeroize();
442    }
443}
444
445/// Fallible trait for representing cases where zeroization may or may not be
446/// possible.
447///
448/// This is primarily useful for scenarios like reference counted data, where
449/// zeroization is only possible when the last reference is dropped.
450pub trait TryZeroize {
451    /// Try to zero out this object from memory using Rust intrinsics which
452    /// ensure the zeroization operation is not "optimized away" by the
453    /// compiler.
454    #[must_use]
455    fn try_zeroize(&mut self) -> bool;
456}
457
458/// `Zeroizing` is a a wrapper for any `Z: Zeroize` type which implements a
459/// `Drop` handler which zeroizes dropped values.
460#[derive(Debug, Default, Eq, PartialEq)]
461pub struct Zeroizing<Z: Zeroize>(Z);
462
463impl<Z> Zeroizing<Z>
464where
465    Z: Zeroize,
466{
467    /// Move value inside a `Zeroizing` wrapper which ensures it will be
468    /// zeroized when it's dropped.
469    pub fn new(value: Z) -> Self {
470        value.into()
471    }
472}
473
474impl<Z: Zeroize + Clone> Clone for Zeroizing<Z> {
475    fn clone(&self) -> Self {
476        Self(self.0.clone())
477    }
478
479    fn clone_from(&mut self, source: &Self) {
480        self.0.zeroize();
481        self.0.clone_from(&source.0);
482    }
483}
484
485impl<Z> From<Z> for Zeroizing<Z>
486where
487    Z: Zeroize,
488{
489    fn from(value: Z) -> Zeroizing<Z> {
490        Zeroizing(value)
491    }
492}
493
494impl<Z> ops::Deref for Zeroizing<Z>
495where
496    Z: Zeroize,
497{
498    type Target = Z;
499
500    fn deref(&self) -> &Z {
501        &self.0
502    }
503}
504
505impl<Z> ops::DerefMut for Zeroizing<Z>
506where
507    Z: Zeroize,
508{
509    fn deref_mut(&mut self) -> &mut Z {
510        &mut self.0
511    }
512}
513
514impl<Z> Zeroize for Zeroizing<Z>
515where
516    Z: Zeroize,
517{
518    fn zeroize(&mut self) {
519        self.0.zeroize();
520    }
521}
522
523impl<Z> Drop for Zeroizing<Z>
524where
525    Z: Zeroize,
526{
527    fn drop(&mut self) {
528        self.0.zeroize()
529    }
530}
531
532/// Use fences to prevent accesses from being reordered before this
533/// point, which should hopefully help ensure that all accessors
534/// see zeroes after this point.
535#[inline]
536fn atomic_fence() {
537    atomic::compiler_fence(atomic::Ordering::SeqCst);
538}
539
540/// Perform a volatile write to the destination
541#[inline]
542fn volatile_write<T: Copy + Sized>(dst: &mut T, src: T) {
543    unsafe { ptr::write_volatile(dst, src) }
544}
545
546/// Perform a volatile `memset` operation which fills a slice with a value
547///
548/// Safety:
549/// The memory pointed to by `dst` must be a single allocated object that is valid for `count`
550/// contiguous elements of `T`.
551/// `count` must not be larger than an `isize`.
552/// `dst` being offset by `mem::size_of::<T> * count` bytes must not wrap around the address space.
553/// Also `dst` must be properly aligned.
554#[inline]
555unsafe fn volatile_set<T: Copy + Sized>(dst: *mut T, src: T, count: usize) {
556    // TODO(tarcieri): use `volatile_set_memory` when stabilized
557    for i in 0..count {
558        // Safety:
559        //
560        // This is safe because there is room for at least `count` objects of type `T` in the
561        // allocation pointed to by `dst`, because `count <= isize::MAX` and because
562        // `dst.add(count)` must not wrap around the address space.
563        let ptr = dst.add(i);
564        // Safety:
565        //
566        // This is safe, because the pointer is valid and because `dst` is well aligned for `T` and
567        // `ptr` is an offset of `dst` by a multiple of `mem::size_of::<T>()` bytes.
568        ptr::write_volatile(ptr, src);
569    }
570}
571
572#[cfg(test)]
573mod tests {
574    use super::*;
575    #[cfg(feature = "alloc")]
576    use alloc::boxed::Box;
577
578    #[test]
579    fn non_zero() {
580        macro_rules! non_zero_test {
581            ($($type:ty),+) => {
582                $(let mut value = <$type>::new(42).unwrap();
583                value.zeroize();
584                assert_eq!(value.get(), 1);)+
585            };
586        }
587
588        non_zero_test!(
589            NonZeroI8,
590            NonZeroI16,
591            NonZeroI32,
592            NonZeroI64,
593            NonZeroI128,
594            NonZeroIsize
595        );
596        non_zero_test!(
597            NonZeroU8,
598            NonZeroU16,
599            NonZeroU32,
600            NonZeroU64,
601            NonZeroU128,
602            NonZeroUsize
603        );
604    }
605
606    #[test]
607    fn zeroize_byte_arrays() {
608        let mut arr = [42u8; 137];
609        arr.zeroize();
610        assert_eq!(arr.as_ref(), [0u8; 137].as_ref());
611    }
612
613    #[test]
614    fn zeroize_maybeuninit_byte_arrays() {
615        let mut arr = [MaybeUninit::new(42u64); 64];
616        arr.zeroize();
617        let arr_init: [u64; 64] = unsafe { core::mem::transmute(arr) };
618        assert_eq!(arr_init, [0u64; 64]);
619    }
620
621    #[cfg(feature = "alloc")]
622    #[test]
623    fn zeroize_vec() {
624        let mut vec = vec![42; 3];
625        vec.zeroize();
626        assert!(vec.is_empty());
627    }
628
629    #[cfg(feature = "alloc")]
630    #[test]
631    fn zeroize_vec_entire_capacity() {
632        #[derive(Clone)]
633        struct PanicOnNonZeroDrop(u64);
634
635        impl Zeroize for PanicOnNonZeroDrop {
636            fn zeroize(&mut self) {
637                self.0 = 0;
638            }
639        }
640
641        impl Drop for PanicOnNonZeroDrop {
642            fn drop(&mut self) {
643                if self.0 != 0 {
644                    panic!("dropped non-zeroized data");
645                }
646            }
647        }
648
649        // Ensure that the entire capacity of the vec is zeroized and that no unitinialized data
650        // is ever interpreted as initialized
651        let mut vec = vec![PanicOnNonZeroDrop(42); 2];
652
653        unsafe {
654            vec.set_len(1);
655        }
656
657        vec.zeroize();
658
659        unsafe {
660            vec.set_len(2);
661        }
662
663        drop(vec);
664    }
665
666    #[cfg(feature = "alloc")]
667    #[test]
668    fn zeroize_string() {
669        let mut string = String::from("Hello, world!");
670        string.zeroize();
671        assert!(string.is_empty());
672    }
673
674    #[cfg(feature = "alloc")]
675    #[test]
676    fn zeroize_string_entire_capacity() {
677        let mut string = String::from("Hello, world!");
678        string.truncate(5);
679
680        string.zeroize();
681
682        // convert the string to a vec to easily access the unused capacity
683        let mut as_vec = string.into_bytes();
684        unsafe { as_vec.set_len(as_vec.capacity()) };
685
686        assert!(as_vec.iter().all(|byte| *byte == 0));
687    }
688
689    #[cfg(feature = "alloc")]
690    #[test]
691    fn zeroize_box() {
692        let mut boxed_arr = Box::new([42u8; 3]);
693        boxed_arr.zeroize();
694        assert_eq!(boxed_arr.as_ref(), &[0u8; 3]);
695    }
696}