1#[cfg(not(feature = "use_os"))]
5use alloc::alloc::Layout;
6
7use super::{Error, SecureVec, alloc};
8use core::{marker::PhantomData, mem, ptr::NonNull};
9use zeroize::Zeroize;
10
11#[cfg(feature = "use_os")]
12use super::free;
13#[cfg(feature = "use_os")]
14use memsec::Prot;
15
16struct UnlockGuard<'a, T: Zeroize, const LENGTH: usize> {
19 array: &'a SecureArray<T, LENGTH>,
20}
21
22impl<'a, T: Zeroize, const LENGTH: usize> UnlockGuard<'a, T, LENGTH> {
23 fn new(array: &'a SecureArray<T, LENGTH>) -> Self {
24 let ok = array.unlock_memory();
25 debug_assert!(ok, "UnlockGuard::new: unlock_memory failed");
26 UnlockGuard { array }
27 }
28}
29
30impl<'a, T: Zeroize, const LENGTH: usize> Drop for UnlockGuard<'a, T, LENGTH> {
31 fn drop(&mut self) {
32 let ok = self.array.lock_memory();
33 debug_assert!(ok, "UnlockGuard::drop: lock_memory failed");
34 }
35}
36
37pub struct SecureArray<T, const LENGTH: usize>
82where
83 T: Zeroize,
84{
85 ptr: NonNull<T>,
86 _marker: PhantomData<T>,
87}
88
89unsafe impl<T: Zeroize + Send, const LENGTH: usize> Send for SecureArray<T, LENGTH> {}
90unsafe impl<T: Zeroize + Send + Sync, const LENGTH: usize> Sync for SecureArray<T, LENGTH> {}
91
92impl<T, const LENGTH: usize> SecureArray<T, LENGTH>
93where
94 T: Zeroize,
95{
96 pub fn empty() -> Result<Self, Error> {
100 let size = LENGTH * mem::size_of::<T>();
101 if size == 0 {
102 return Err(Error::LengthCannotBeZero);
104 }
105
106 let ptr = unsafe { alloc::<T>(size)? };
107
108 let secure_array = SecureArray {
109 ptr,
110 _marker: PhantomData,
111 };
112
113 let _locked = secure_array.lock_memory();
114
115 #[cfg(feature = "use_os")]
116 if !_locked {
117 return Err(Error::LockFailed);
118 }
119
120 Ok(secure_array)
121 }
122
123 pub fn from_slice_mut(content: &mut [T; LENGTH]) -> Result<Self, Error>
127 where
128 T: Clone,
129 {
130 let secure_array = match Self::empty() {
131 Ok(secure_array) => secure_array,
132 Err(e) => {
133 content.zeroize();
134 return Err(e);
135 }
136 };
137
138 let unlocked = secure_array.unlock_memory();
139
140 if !unlocked {
141 content.zeroize();
142 return Err(Error::UnlockFailed);
143 }
144
145 unsafe {
146 let dst = secure_array.ptr.as_ptr();
147 for (i, item) in content.iter().enumerate() {
148 core::ptr::write(dst.add(i), item.clone());
149 }
150 }
151
152 content.zeroize();
153
154 let _locked = secure_array.lock_memory();
155
156 #[cfg(feature = "use_os")]
157 if !_locked {
158 return Err(Error::LockFailed);
159 }
160
161 Ok(secure_array)
162 }
163
164 pub fn from_slice(content: &[T; LENGTH]) -> Result<Self, Error>
168 where
169 T: Clone,
170 {
171 let secure_array = Self::empty()?;
172
173 let unlocked = secure_array.unlock_memory();
174
175 if !unlocked {
176 return Err(Error::UnlockFailed);
177 }
178
179 unsafe {
180 let dst = secure_array.ptr.as_ptr();
181 for (i, item) in content.iter().enumerate() {
182 core::ptr::write(dst.add(i), item.clone());
183 }
184 }
185
186 let _locked = secure_array.lock_memory();
187
188 #[cfg(feature = "use_os")]
189 if !_locked {
190 return Err(Error::LockFailed);
191 }
192
193 Ok(secure_array)
194 }
195
196 pub fn len(&self) -> usize {
197 LENGTH
198 }
199
200 pub fn is_empty(&self) -> bool {
201 self.len() == 0
202 }
203
204 #[cfg(feature = "expose-ptr")]
212 #[deprecated(
213 since = "0.3.0",
214 note = "This method is intended only for testing/crash reproduction. Use unlock() or unlock_mut() instead."
215 )]
216 pub fn ptr(&self) -> NonNull<T> {
217 self.ptr
218 }
219
220 pub(crate) fn lock_memory(&self) -> bool {
221 #[cfg(feature = "use_os")]
222 {
223 #[cfg(windows)]
224 {
225 super::mprotect(self.ptr, Prot::NoAccess)
226 }
227 #[cfg(unix)]
228 {
229 super::mprotect(self.ptr, Prot::NoAccess)
230 }
231 }
232 #[cfg(not(feature = "use_os"))]
233 {
234 true }
236 }
237
238 pub(crate) fn unlock_memory(&self) -> bool {
239 #[cfg(feature = "use_os")]
240 {
241 #[cfg(windows)]
242 {
243 super::mprotect(self.ptr, Prot::ReadWrite)
244 }
245 #[cfg(unix)]
246 {
247 super::mprotect(self.ptr, Prot::ReadWrite)
248 }
249 }
250
251 #[cfg(not(feature = "use_os"))]
252 {
253 true }
255 }
256
257 pub fn unlock<F, R>(&self, f: F) -> R
259 where
260 F: FnOnce(&[T]) -> R,
261 {
262 let _guard = UnlockGuard::new(self);
263 let slice = unsafe { core::slice::from_raw_parts(self.ptr.as_ptr(), LENGTH) };
264 let result = f(slice);
265 result
266 }
267
268 pub fn unlock_mut<F, R>(&mut self, f: F) -> R
270 where
271 F: FnOnce(&mut [T]) -> R,
272 {
273 let _guard = UnlockGuard::new(self);
274 let slice = unsafe { core::slice::from_raw_parts_mut(self.ptr.as_ptr(), LENGTH) };
275 let result = f(slice);
276 result
277 }
278
279 pub fn erase(&mut self) {
281 self.unlock_mut(|slice| {
282 for element in slice.iter_mut() {
283 element.zeroize();
284 }
285 });
286 }
287
288 pub(crate) fn init_from_clone(&mut self, src: &[T])
291 where
292 T: Clone,
293 {
294 debug_assert_eq!(src.len(), LENGTH);
295
296 let ok = self.unlock_memory();
297 debug_assert!(
298 ok,
299 "SecureArray::init_from_clone: unlock_memory failed"
300 );
301
302 unsafe {
303 let dst = self.ptr.as_ptr();
304 for (i, item) in src.iter().enumerate() {
305 core::ptr::write(dst.add(i), item.clone());
306 }
307 }
308 let ok = self.lock_memory();
309 debug_assert!(
310 ok,
311 "SecureArray::init_from_clone: lock_memory failed"
312 );
313 }
314}
315
316impl<T: Zeroize, const LENGTH: usize> Drop for SecureArray<T, LENGTH> {
317 fn drop(&mut self) {
318 let ok = self.unlock_memory();
319 debug_assert!(ok, "SecureArray::drop: unlock_memory failed");
320
321 let slice = unsafe { core::slice::from_raw_parts_mut(self.ptr.as_ptr(), LENGTH) };
322 for element in slice.iter_mut() {
323 element.zeroize();
324 }
325
326 let size = LENGTH * mem::size_of::<T>();
327 if size == 0 {
328 return;
329 }
330
331 #[cfg(feature = "use_os")]
332 free(self.ptr);
333
334 #[cfg(not(feature = "use_os"))]
335 unsafe {
336 let layout = Layout::from_size_align_unchecked(size, mem::align_of::<T>());
337 alloc::alloc::dealloc(self.ptr.as_ptr() as *mut u8, layout);
338 }
339 }
340}
341
342impl<T: Clone + Zeroize, const LENGTH: usize> Clone for SecureArray<T, LENGTH> {
343 fn clone(&self) -> Self {
344 let mut new_array = Self::empty().unwrap();
345 self.unlock(|src_slice| {
346 new_array.init_from_clone(src_slice);
347 });
348 new_array
349 }
350}
351
352impl<const LENGTH: usize> TryFrom<SecureVec<u8>> for SecureArray<u8, LENGTH> {
353 type Error = Error;
354
355 fn try_from(vec: SecureVec<u8>) -> Result<Self, Self::Error> {
361 if vec.len() != LENGTH {
362 return Err(Error::LengthMismatch);
363 }
364
365 let mut new_array = Self::empty()?;
366
367 vec.unlock_slice(|vec_slice| {
368 new_array.init_from_clone(vec_slice);
369 });
370
371 Ok(new_array)
372 }
373}
374
375#[cfg(feature = "serde")]
376impl<const LENGTH: usize> serde::Serialize for SecureArray<u8, LENGTH> {
377 fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
378 where
379 S: serde::Serializer,
380 {
381 self.unlock(|slice| serializer.collect_seq(slice.iter()))
382 }
383}
384
385#[cfg(feature = "serde")]
386impl<'de, const LENGTH: usize> serde::Deserialize<'de> for SecureArray<u8, LENGTH> {
387 fn deserialize<D>(deserializer: D) -> Result<SecureArray<u8, LENGTH>, D::Error>
388 where
389 D: serde::Deserializer<'de>,
390 {
391 struct SecureArrayVisitor<const L: usize>;
392
393 impl<'de, const L: usize> serde::de::Visitor<'de> for SecureArrayVisitor<L> {
394 type Value = SecureArray<u8, L>;
395
396 fn expecting(&self, formatter: &mut ::core::fmt::Formatter) -> ::core::fmt::Result {
397 write!(formatter, "a byte array of length {}", L)
398 }
399
400 fn visit_seq<A>(self, mut seq: A) -> Result<Self::Value, A::Error>
401 where
402 A: serde::de::SeqAccess<'de>,
403 {
404 let mut data: SecureVec<u8> =
405 SecureVec::new_with_capacity(L).map_err(serde::de::Error::custom)?;
406 while let Some(byte) = seq.next_element()? {
407 data.push(byte);
408 }
409
410 if data.len() != L {
412 return Err(serde::de::Error::invalid_length(
413 data.len(),
414 &self,
415 ));
416 }
417
418 SecureArray::try_from(data).map_err(serde::de::Error::custom)
419 }
420 }
421
422 deserializer.deserialize_bytes(SecureArrayVisitor::<LENGTH>)
423 }
424}
425
426#[cfg(all(test, feature = "use_os"))]
427mod tests {
428 use super::*;
429 use std::process::{Command, Stdio};
430 use std::sync::{Arc, Mutex};
431
432 #[test]
433 fn test_creation() {
434 let exposed_mut = &mut [1, 2, 3];
435 let array: SecureArray<u8, 3> = SecureArray::from_slice_mut(exposed_mut).unwrap();
436 assert_eq!(array.len(), 3);
437
438 array.unlock(|slice| {
439 assert_eq!(slice, &[1, 2, 3]);
440 });
441
442 assert_eq!(exposed_mut, &[0u8; 3]);
443
444 let exposed = &[1, 2, 3];
445 let array: SecureArray<u8, 3> = SecureArray::from_slice(exposed).unwrap();
446 assert_eq!(array.len(), 3);
447
448 array.unlock(|slice| {
449 assert_eq!(slice, &[1, 2, 3]);
450 });
451
452 assert_eq!(exposed, &[1, 2, 3]);
453 }
454
455 #[test]
456 fn test_from_secure_vec() {
457 let vec: SecureVec<u8> = SecureVec::from_slice(&[1, 2, 3]).unwrap();
458 let array: SecureArray<u8, 3> = vec.try_into().unwrap();
459 assert_eq!(array.len(), 3);
460 array.unlock(|slice| {
461 assert_eq!(slice, &[1, 2, 3]);
462 });
463 }
464
465 #[test]
466 fn test_erase() {
467 let exposed: &mut [u8; 3] = &mut [1, 2, 3];
468 let mut array: SecureArray<u8, 3> = SecureArray::from_slice_mut(exposed).unwrap();
469 array.erase();
470 array.unlock(|slice| {
471 assert_eq!(slice, &[0u8; 3]);
472 });
473 }
474
475 #[test]
476 #[should_panic]
477 fn test_length_cannot_be_zero() {
478 let secure_vec = SecureVec::new().unwrap();
479 let _secure_array: SecureArray<u8, 0> = SecureArray::try_from(secure_vec).unwrap();
480 }
481
482 #[test]
483 fn lock_unlock() {
484 let exposed: &mut [u8; 3] = &mut [1, 2, 3];
485 let secure: SecureArray<u8, 3> = SecureArray::from_slice_mut(exposed).unwrap();
486
487 let unlocked = secure.unlock_memory();
488 assert!(unlocked);
489
490 let locked = secure.lock_memory();
491 assert!(locked);
492 }
493
494 #[test]
495 fn test_clone() {
496 let mut array1: SecureArray<u8, 3> = SecureArray::empty().unwrap();
497 array1.unlock_mut(|slice| {
498 slice[0] = 1;
499 slice[1] = 2;
500 slice[2] = 3;
501 });
502
503 let array2 = array1.clone();
504
505 array2.unlock(|slice| {
506 assert_eq!(slice, &[1, 2, 3]);
507 });
508
509 array1.unlock(|slice| {
510 assert_eq!(slice, &[1, 2, 3]);
511 });
512 }
513
514 #[test]
515 fn test_thread_safety() {
516 let exposed: &mut [u8; 3] = &mut [1, 2, 3];
517 let array: SecureArray<u8, 3> = SecureArray::from_slice_mut(exposed).unwrap();
518 let arc_array = Arc::new(Mutex::new(array));
519 let mut handles = Vec::new();
520
521 for _ in 0..5u8 {
522 let array_clone = Arc::clone(&arc_array);
523 let handle = std::thread::spawn(move || {
524 let mut guard = array_clone.lock().unwrap();
525 guard.unlock_mut(|slice| {
526 slice[0] += 1;
527 });
528 });
529 handles.push(handle);
530 }
531
532 for handle in handles {
533 handle.join().unwrap();
534 }
535
536 let final_array = arc_array.lock().unwrap();
537 final_array.unlock(|slice| {
538 assert_eq!(slice[0], 6);
539 assert_eq!(slice[1], 2);
540 assert_eq!(slice[2], 3);
541 });
542 }
543
544 #[test]
545 fn test_index_should_fail_when_locked() {
546 let arg = "CRASH_TEST_ARRAY_LOCKED";
547
548 if std::env::args().any(|a| a == arg) {
549 let exposed: &mut [u8; 3] = &mut [1, 2, 3];
550 let array: SecureArray<u8, 3> = SecureArray::from_slice_mut(exposed).unwrap();
551 let _value = unsafe { core::hint::black_box(*array.ptr.as_ptr()) };
554
555 std::process::exit(1);
556 }
557
558 let child = Command::new(std::env::current_exe().unwrap())
559 .arg("array::tests::test_index_should_fail_when_locked")
560 .arg(arg)
561 .arg("--nocapture")
562 .stdout(Stdio::piped())
563 .stderr(Stdio::piped())
564 .spawn()
565 .expect("Failed to spawn child process");
566
567 let output = child.wait_with_output().expect("Failed to wait on child");
568 let status = output.status;
569
570 assert!(
571 !status.success(),
572 "Process exited successfully with code {:?}, but it should have crashed.",
573 status.code()
574 );
575
576 #[cfg(unix)]
577 {
578 use std::os::unix::process::ExitStatusExt;
579 let signal = status
580 .signal()
581 .expect("Process was not terminated by a signal on Unix.");
582 assert!(
583 signal == libc::SIGSEGV || signal == libc::SIGBUS,
584 "Process terminated with unexpected signal: {}",
585 signal
586 );
587 println!(
588 "Test passed: Process correctly terminated with signal {}.",
589 signal
590 );
591 }
592
593 #[cfg(windows)]
594 {
595 const STATUS_ACCESS_VIOLATION: i32 = 0xC0000005_u32 as i32;
596 assert_eq!(
597 status.code(),
598 Some(STATUS_ACCESS_VIOLATION),
599 "Process exited with unexpected code: {:x?}. Expected STATUS_ACCESS_VIOLATION.",
600 status.code()
601 );
602 eprintln!("Test passed: Process correctly terminated with STATUS_ACCESS_VIOLATION.");
603 }
604 }
605
606 #[test]
607 fn test_unlock_mut() {
608 let exposed: &mut [u8; 3] = &mut [1, 2, 3];
609 let mut array: SecureArray<u8, 3> = SecureArray::from_slice_mut(exposed).unwrap();
610
611 array.unlock_mut(|slice| {
612 slice[1] = 100;
613 });
614
615 array.unlock(|slice| {
616 assert_eq!(slice, &[1, 100, 3]);
617 });
618 }
619
620 #[cfg(feature = "serde")]
621 #[test]
622 fn test_serde() {
623 let exposed: &mut [u8; 3] = &mut [1, 2, 3];
624 let array: SecureArray<u8, 3> = SecureArray::from_slice_mut(exposed).unwrap();
625 let json_string = serde_json::to_string(&array).expect("Serialization failed");
626 let json_bytes = serde_json::to_vec(&array).expect("Serialization failed");
627
628 let deserialized_string: SecureArray<u8, 3> =
629 serde_json::from_str(&json_string).expect("Deserialization failed");
630
631 let deserialized_bytes: SecureArray<u8, 3> =
632 serde_json::from_slice(&json_bytes).expect("Deserialization failed");
633
634 deserialized_string.unlock(|slice| {
635 assert_eq!(slice, &[1, 2, 3]);
636 });
637
638 deserialized_bytes.unlock(|slice| {
639 assert_eq!(slice, &[1, 2, 3]);
640 });
641 }
642
643 use std::fmt::Debug;
644 use zeroize::Zeroize;
645
646 #[derive(Clone, Debug, PartialEq)]
648 struct SmallStruct {
649 a: u8,
650 b: u16,
651 }
652 impl Zeroize for SmallStruct {
653 fn zeroize(&mut self) {
654 self.a.zeroize();
655 self.b.zeroize();
656 }
657 }
658
659 #[derive(Clone, Debug, PartialEq)]
660 struct LargeStruct {
661 data: [u64; 4],
662 flag: bool,
663 }
664 impl Zeroize for LargeStruct {
665 fn zeroize(&mut self) {
666 self.data.zeroize();
667 self.flag.zeroize();
668 }
669 }
670
671 #[derive(Clone, Debug, PartialEq)]
672 #[repr(align(64))]
673 struct AlignedStruct {
674 value: u64,
675 }
676 impl Zeroize for AlignedStruct {
677 fn zeroize(&mut self) {
678 self.value.zeroize();
679 }
680 }
681
682 #[derive(Clone, Debug, PartialEq)]
683 struct Person {
684 name: String,
685 age: u32,
686 notes: String,
687 }
688 impl Person {
689 fn new(name: impl Into<String>, age: u32, notes: impl Into<String>) -> Self {
690 Self {
691 name: name.into(),
692 age,
693 notes: notes.into(),
694 }
695 }
696 }
697 impl Zeroize for Person {
698 fn zeroize(&mut self) {
699 self.name.zeroize();
700 self.age.zeroize();
701 self.notes.zeroize();
702 }
703 }
704 fn create_test_person(id: usize) -> Person {
705 Person::new(
706 format!("Person{}", id),
707 (id % 100) as u32,
708 format!("Notes #{}", id),
709 )
710 }
711
712 fn test_array_generic_basics<T: Zeroize + Clone + PartialEq + Debug, const N: usize>(
713 initial: &[T; N],
714 ) {
715 let secure: SecureArray<T, N> = SecureArray::from_slice(initial).unwrap();
716 assert_eq!(secure.len(), N);
717 secure.unlock(|slice| {
718 assert_eq!(slice, initial);
719 });
720 let cloned = secure.clone();
721 cloned.unlock(|slice| {
722 assert_eq!(slice, initial);
723 });
724 let mut er = SecureArray::from_slice(initial).unwrap();
725 er.erase();
726 er.unlock(|slice| {
727 assert_eq!(slice.len(), N);
728 });
729 }
730
731 #[test]
732 fn test_array_u8_variety() {
733 let data: [u8; 3] = [1, 2, 3];
734 test_array_generic_basics(&data);
735 }
736
737 #[test]
738 fn test_array_u64() {
739 let data: [u64; 2] = [100u64, 200];
740 test_array_generic_basics(&data);
741 }
742
743 #[test]
744 fn test_array_byte_array() {
745 let data: [[u8; 16]; 2] = [[1u8; 16], [2u8; 16]];
746 test_array_generic_basics(&data);
747 }
748
749 #[test]
750 fn test_array_small_struct() {
751 let data: [SmallStruct; 2] = [SmallStruct { a: 1, b: 2 }, SmallStruct { a: 3, b: 4 }];
752 test_array_generic_basics(&data);
753 }
754
755 #[test]
756 fn test_array_large_struct() {
757 let data = [
758 LargeStruct {
759 data: [1, 2, 3, 4],
760 flag: true,
761 },
762 LargeStruct {
763 data: [5, 6, 7, 8],
764 flag: false,
765 },
766 ];
767 test_array_generic_basics(&data);
768 }
769
770 #[test]
771 fn test_array_person() {
772 let data = [create_test_person(42), create_test_person(43)];
773 test_array_generic_basics(&data);
774 }
775
776 #[test]
777 fn test_array_aligned() {
778 let data = [
779 AlignedStruct { value: 0xDEAD_BEEF },
780 AlignedStruct { value: 0xCAFE_BABE },
781 ];
782 test_array_generic_basics(&data);
783 }
784}