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>
81where
82 T: Zeroize,
83{
84 ptr: NonNull<T>,
85 _marker: PhantomData<T>,
86}
87
88unsafe impl<T: Zeroize + Send, const LENGTH: usize> Send for SecureArray<T, LENGTH> {}
89unsafe impl<T: Zeroize + Send + Sync, const LENGTH: usize> Sync for SecureArray<T, LENGTH> {}
90
91impl<T, const LENGTH: usize> SecureArray<T, LENGTH>
92where
93 T: Zeroize,
94{
95 pub fn empty() -> Result<Self, Error> {
99 let size = LENGTH * mem::size_of::<T>();
100 if size == 0 {
101 return Err(Error::LengthCannotBeZero);
103 }
104
105 let ptr = unsafe { alloc::<T>(size)? };
106
107 let secure_array = SecureArray {
108 ptr,
109 _marker: PhantomData,
110 };
111
112 let _locked = secure_array.lock_memory();
113
114 #[cfg(feature = "use_os")]
115 if !_locked {
116 return Err(Error::LockFailed);
117 }
118
119 Ok(secure_array)
120 }
121
122 pub fn from_slice_mut(content: &mut [T; LENGTH]) -> Result<Self, Error> {
126 let secure_array = match Self::empty() {
127 Ok(secure_array) => secure_array,
128 Err(e) => {
129 content.zeroize();
130 return Err(e);
131 }
132 };
133
134 let unlocked = secure_array.unlock_memory();
135
136 if !unlocked {
137 content.zeroize();
138 return Err(Error::UnlockFailed);
139 }
140
141 unsafe {
142 core::ptr::copy_nonoverlapping(
144 content.as_ptr(),
145 secure_array.ptr.as_ptr(),
146 LENGTH,
147 );
148 }
149
150 content.zeroize();
151
152 let _locked = secure_array.lock_memory();
153
154 #[cfg(feature = "use_os")]
155 if !_locked {
156 return Err(Error::LockFailed);
157 }
158
159 Ok(secure_array)
160 }
161
162 pub fn from_slice(content: &[T; LENGTH]) -> Result<Self, Error> {
166 let secure_array = Self::empty()?;
167
168 let unlocked = secure_array.unlock_memory();
169
170 if !unlocked {
171 return Err(Error::UnlockFailed);
172 }
173
174 unsafe {
175 core::ptr::copy_nonoverlapping(
177 content.as_ptr(),
178 secure_array.ptr.as_ptr(),
179 LENGTH,
180 );
181 }
182
183 let _locked = secure_array.lock_memory();
184
185 #[cfg(feature = "use_os")]
186 if !_locked {
187 return Err(Error::LockFailed);
188 }
189
190 Ok(secure_array)
191 }
192
193 pub fn len(&self) -> usize {
194 LENGTH
195 }
196
197 pub fn is_empty(&self) -> bool {
198 self.len() == 0
199 }
200
201 pub(crate) fn lock_memory(&self) -> bool {
202 #[cfg(feature = "use_os")]
203 {
204 #[cfg(windows)]
205 {
206 super::mprotect(self.ptr, Prot::NoAccess)
207 }
208 #[cfg(unix)]
209 {
210 super::mprotect(self.ptr, Prot::NoAccess)
211 }
212 }
213 #[cfg(not(feature = "use_os"))]
214 {
215 true }
217 }
218
219 pub(crate) fn unlock_memory(&self) -> bool {
220 #[cfg(feature = "use_os")]
221 {
222 #[cfg(windows)]
223 {
224 super::mprotect(self.ptr, Prot::ReadWrite)
225 }
226 #[cfg(unix)]
227 {
228 super::mprotect(self.ptr, Prot::ReadWrite)
229 }
230 }
231
232 #[cfg(not(feature = "use_os"))]
233 {
234 true }
236 }
237
238 pub fn unlock<F, R>(&self, f: F) -> R
240 where
241 F: FnOnce(&[T]) -> R,
242 {
243 let _guard = UnlockGuard::new(self);
244 let slice = unsafe { core::slice::from_raw_parts(self.ptr.as_ptr(), LENGTH) };
245 let result = f(slice);
246 result
247 }
248
249 pub fn unlock_mut<F, R>(&mut self, f: F) -> R
251 where
252 F: FnOnce(&mut [T]) -> R,
253 {
254 let _guard = UnlockGuard::new(self);
255 let slice = unsafe { core::slice::from_raw_parts_mut(self.ptr.as_ptr(), LENGTH) };
256 let result = f(slice);
257 result
258 }
259
260 pub fn erase(&mut self) {
262 self.unlock_mut(|slice| {
263 for element in slice.iter_mut() {
264 element.zeroize();
265 }
266 });
267 }
268
269 pub(crate) fn init_from_clone(&mut self, src: &[T])
272 where
273 T: Clone,
274 {
275 debug_assert_eq!(src.len(), LENGTH);
276
277 let ok = self.unlock_memory();
278 debug_assert!(
279 ok,
280 "SecureArray::init_from_clone: unlock_memory failed"
281 );
282
283 unsafe {
284 let dst = self.ptr.as_ptr();
285 for (i, item) in src.iter().enumerate() {
286 core::ptr::write(dst.add(i), item.clone());
287 }
288 }
289 let ok = self.lock_memory();
290 debug_assert!(
291 ok,
292 "SecureArray::init_from_clone: lock_memory failed"
293 );
294 }
295}
296
297impl<T: Zeroize, const LENGTH: usize> core::ops::Index<usize> for SecureArray<T, LENGTH> {
298 type Output = T;
299 fn index(&self, index: usize) -> &Self::Output {
300 assert!(index < self.len(), "Index out of bounds");
301 unsafe {
302 let ptr = self.ptr.as_ptr().add(index);
303 &*ptr
304 }
305 }
306}
307
308impl<T: Zeroize, const LENGTH: usize> core::ops::IndexMut<usize> for SecureArray<T, LENGTH> {
309 fn index_mut(&mut self, index: usize) -> &mut Self::Output {
310 assert!(index < self.len(), "Index out of bounds");
311 unsafe {
312 let ptr = self.ptr.as_ptr().add(index);
313 &mut *ptr
314 }
315 }
316}
317
318impl<T: Zeroize, const LENGTH: usize> Drop for SecureArray<T, LENGTH> {
319 fn drop(&mut self) {
320 self.erase();
321 let ok = self.unlock_memory();
322 debug_assert!(ok, "SecureArray::drop: unlock_memory failed");
323
324 let size = LENGTH * mem::size_of::<T>();
325 if size == 0 {
326 return;
327 }
328
329 #[cfg(feature = "use_os")]
330 free(self.ptr);
331
332 #[cfg(not(feature = "use_os"))]
333 unsafe {
334 let layout = Layout::from_size_align_unchecked(size, mem::align_of::<T>());
335 alloc::alloc::dealloc(self.ptr.as_ptr() as *mut u8, layout);
336 }
337 }
338}
339
340impl<T: Clone + Zeroize, const LENGTH: usize> Clone for SecureArray<T, LENGTH> {
341 fn clone(&self) -> Self {
342 let mut new_array = Self::empty().unwrap();
343 self.unlock(|src_slice| {
344 new_array.init_from_clone(src_slice);
345 });
346 new_array
347 }
348}
349
350impl<const LENGTH: usize> TryFrom<SecureVec<u8>> for SecureArray<u8, LENGTH> {
351 type Error = Error;
352
353 fn try_from(vec: SecureVec<u8>) -> Result<Self, Self::Error> {
359 if vec.len() != LENGTH {
360 return Err(Error::LengthMismatch);
361 }
362
363 let mut new_array = Self::empty()?;
364
365 vec.unlock_slice(|vec_slice| {
366 new_array.init_from_clone(vec_slice);
367 });
368
369 Ok(new_array)
370 }
371}
372
373#[cfg(feature = "serde")]
374impl<const LENGTH: usize> serde::Serialize for SecureArray<u8, LENGTH> {
375 fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
376 where
377 S: serde::Serializer,
378 {
379 self.unlock(|slice| serializer.collect_seq(slice.iter()))
380 }
381}
382
383#[cfg(feature = "serde")]
384impl<'de, const LENGTH: usize> serde::Deserialize<'de> for SecureArray<u8, LENGTH> {
385 fn deserialize<D>(deserializer: D) -> Result<SecureArray<u8, LENGTH>, D::Error>
386 where
387 D: serde::Deserializer<'de>,
388 {
389 struct SecureArrayVisitor<const L: usize>;
390
391 impl<'de, const L: usize> serde::de::Visitor<'de> for SecureArrayVisitor<L> {
392 type Value = SecureArray<u8, L>;
393
394 fn expecting(&self, formatter: &mut ::core::fmt::Formatter) -> ::core::fmt::Result {
395 write!(formatter, "a byte array of length {}", L)
396 }
397
398 fn visit_seq<A>(self, mut seq: A) -> Result<Self::Value, A::Error>
399 where
400 A: serde::de::SeqAccess<'de>,
401 {
402 let mut data: SecureVec<u8> =
403 SecureVec::new_with_capacity(L).map_err(serde::de::Error::custom)?;
404 while let Some(byte) = seq.next_element()? {
405 data.push(byte);
406 }
407
408 if data.len() != L {
410 return Err(serde::de::Error::invalid_length(
411 data.len(),
412 &self,
413 ));
414 }
415
416 SecureArray::try_from(data).map_err(serde::de::Error::custom)
417 }
418 }
419
420 deserializer.deserialize_bytes(SecureArrayVisitor::<LENGTH>)
421 }
422}
423
424#[cfg(all(test, feature = "use_os"))]
425mod tests {
426 use super::*;
427 use std::process::{Command, Stdio};
428 use std::sync::{Arc, Mutex};
429
430 #[test]
431 fn test_creation() {
432 let exposed_mut = &mut [1, 2, 3];
433 let array: SecureArray<u8, 3> = SecureArray::from_slice_mut(exposed_mut).unwrap();
434 assert_eq!(array.len(), 3);
435
436 array.unlock(|slice| {
437 assert_eq!(slice, &[1, 2, 3]);
438 });
439
440 assert_eq!(exposed_mut, &[0u8; 3]);
441
442 let exposed = &[1, 2, 3];
443 let array: SecureArray<u8, 3> = SecureArray::from_slice(exposed).unwrap();
444 assert_eq!(array.len(), 3);
445
446 array.unlock(|slice| {
447 assert_eq!(slice, &[1, 2, 3]);
448 });
449
450 assert_eq!(exposed, &[1, 2, 3]);
451 }
452
453 #[test]
454 fn test_from_secure_vec() {
455 let vec: SecureVec<u8> = SecureVec::from_slice(&[1, 2, 3]).unwrap();
456 let array: SecureArray<u8, 3> = vec.try_into().unwrap();
457 assert_eq!(array.len(), 3);
458 array.unlock(|slice| {
459 assert_eq!(slice, &[1, 2, 3]);
460 });
461 }
462
463 #[test]
464 fn test_erase() {
465 let exposed: &mut [u8; 3] = &mut [1, 2, 3];
466 let mut array: SecureArray<u8, 3> = SecureArray::from_slice_mut(exposed).unwrap();
467 array.erase();
468 array.unlock(|slice| {
469 assert_eq!(slice, &[0u8; 3]);
470 });
471 }
472
473 #[test]
474 #[should_panic]
475 fn test_length_cannot_be_zero() {
476 let secure_vec = SecureVec::new().unwrap();
477 let _secure_array: SecureArray<u8, 0> = SecureArray::try_from(secure_vec).unwrap();
478 }
479
480 #[test]
481 fn lock_unlock() {
482 let exposed: &mut [u8; 3] = &mut [1, 2, 3];
483 let secure: SecureArray<u8, 3> = SecureArray::from_slice_mut(exposed).unwrap();
484
485 let unlocked = secure.unlock_memory();
486 assert!(unlocked);
487
488 let locked = secure.lock_memory();
489 assert!(locked);
490 }
491
492 #[test]
493 fn test_clone() {
494 let mut array1: SecureArray<u8, 3> = SecureArray::empty().unwrap();
495 array1.unlock_mut(|slice| {
496 slice[0] = 1;
497 slice[1] = 2;
498 slice[2] = 3;
499 });
500
501 let array2 = array1.clone();
502
503 array2.unlock(|slice| {
504 assert_eq!(slice, &[1, 2, 3]);
505 });
506
507 array1.unlock(|slice| {
508 assert_eq!(slice, &[1, 2, 3]);
509 });
510 }
511
512 #[test]
513 fn test_thread_safety() {
514 let exposed: &mut [u8; 3] = &mut [1, 2, 3];
515 let array: SecureArray<u8, 3> = SecureArray::from_slice_mut(exposed).unwrap();
516 let arc_array = Arc::new(Mutex::new(array));
517 let mut handles = Vec::new();
518
519 for _ in 0..5u8 {
520 let array_clone = Arc::clone(&arc_array);
521 let handle = std::thread::spawn(move || {
522 let mut guard = array_clone.lock().unwrap();
523 guard.unlock_mut(|slice| {
524 slice[0] += 1;
525 });
526 });
527 handles.push(handle);
528 }
529
530 for handle in handles {
531 handle.join().unwrap();
532 }
533
534 let final_array = arc_array.lock().unwrap();
535 final_array.unlock(|slice| {
536 assert_eq!(slice[0], 6);
537 assert_eq!(slice[1], 2);
538 assert_eq!(slice[2], 3);
539 });
540 }
541
542 #[test]
543 fn test_index_should_fail_when_locked() {
544 let arg = "CRASH_TEST_ARRAY_LOCKED";
545
546 if std::env::args().any(|a| a == arg) {
547 let exposed: &mut [u8; 3] = &mut [1, 2, 3];
548 let array: SecureArray<u8, 3> = SecureArray::from_slice_mut(exposed).unwrap();
549 let _value = core::hint::black_box(array[0]);
550
551 std::process::exit(1);
552 }
553
554 let child = Command::new(std::env::current_exe().unwrap())
555 .arg("array::tests::test_index_should_fail_when_locked")
556 .arg(arg)
557 .arg("--nocapture")
558 .stdout(Stdio::piped())
559 .stderr(Stdio::piped())
560 .spawn()
561 .expect("Failed to spawn child process");
562
563 let output = child.wait_with_output().expect("Failed to wait on child");
564 let status = output.status;
565
566 assert!(
567 !status.success(),
568 "Process exited successfully with code {:?}, but it should have crashed.",
569 status.code()
570 );
571
572 #[cfg(unix)]
573 {
574 use std::os::unix::process::ExitStatusExt;
575 let signal = status
576 .signal()
577 .expect("Process was not terminated by a signal on Unix.");
578 assert!(
579 signal == libc::SIGSEGV || signal == libc::SIGBUS,
580 "Process terminated with unexpected signal: {}",
581 signal
582 );
583 println!(
584 "Test passed: Process correctly terminated with signal {}.",
585 signal
586 );
587 }
588
589 #[cfg(windows)]
590 {
591 const STATUS_ACCESS_VIOLATION: i32 = 0xC0000005_u32 as i32;
592 assert_eq!(
593 status.code(),
594 Some(STATUS_ACCESS_VIOLATION),
595 "Process exited with unexpected code: {:x?}. Expected STATUS_ACCESS_VIOLATION.",
596 status.code()
597 );
598 eprintln!("Test passed: Process correctly terminated with STATUS_ACCESS_VIOLATION.");
599 }
600 }
601
602 #[test]
603 fn test_unlock_mut() {
604 let exposed: &mut [u8; 3] = &mut [1, 2, 3];
605 let mut array: SecureArray<u8, 3> = SecureArray::from_slice_mut(exposed).unwrap();
606
607 array.unlock_mut(|slice| {
608 slice[1] = 100;
609 });
610
611 array.unlock(|slice| {
612 assert_eq!(slice, &[1, 100, 3]);
613 });
614 }
615
616 #[cfg(feature = "serde")]
617 #[test]
618 fn test_serde() {
619 let exposed: &mut [u8; 3] = &mut [1, 2, 3];
620 let array: SecureArray<u8, 3> = SecureArray::from_slice_mut(exposed).unwrap();
621 let json_string = serde_json::to_string(&array).expect("Serialization failed");
622 let json_bytes = serde_json::to_vec(&array).expect("Serialization failed");
623
624 let deserialized_string: SecureArray<u8, 3> =
625 serde_json::from_str(&json_string).expect("Deserialization failed");
626
627 let deserialized_bytes: SecureArray<u8, 3> =
628 serde_json::from_slice(&json_bytes).expect("Deserialization failed");
629
630 deserialized_string.unlock(|slice| {
631 assert_eq!(slice, &[1, 2, 3]);
632 });
633
634 deserialized_bytes.unlock(|slice| {
635 assert_eq!(slice, &[1, 2, 3]);
636 });
637 }
638}