1#![cfg(unix)]
2#![forbid(unsafe_op_in_unsafe_fn)]
3
4use libc::{
5 madvise, mlock, mmap, mprotect, munlock, munmap,
6 MAP_ANONYMOUS, MAP_FAILED, MAP_PRIVATE, PROT_NONE, PROT_READ, PROT_WRITE,
7};
8use std::{
9 fmt,
10 mem::size_of,
11 ptr::{self, NonNull},
12 sync::atomic::{AtomicBool, AtomicUsize, Ordering, Ordering::SeqCst},
13};
14use subtle::{Choice, ConstantTimeEq};
15use zeroize::{Zeroize, ZeroizeOnDrop};
16
17#[cfg(target_os = "linux")]
19use libc::{sysconf, _SC_LEVEL1_DCACHE_LINESIZE};
20
21static CACHE_LINE_SIZE: std::sync::OnceLock<usize> = std::sync::OnceLock::new();
27
28static GLOBAL_LOCK_COUNTER: AtomicUsize = AtomicUsize::new(0);
30
31#[cfg(target_os = "linux")]
33const MAP_LOCKED: i32 = libc::MAP_LOCKED;
34#[cfg(not(target_os = "linux"))]
35const MAP_LOCKED: i32 = 0;
36
37struct MmapRegion {
43 ptr: NonNull<libc::c_void>,
44 size: usize,
45 #[cfg(target_os = "linux")]
46 locked: bool,
47}
48
49impl MmapRegion {
50 fn new(size: usize, prot: i32) -> Self {
52 let mut flags = MAP_PRIVATE | MAP_ANONYMOUS;
53 #[cfg(target_os = "linux")]
54 let mut locked = false;
55
56 #[cfg(target_os = "linux")]
58 {
59 flags |= MAP_LOCKED;
60 }
61
62 let ptr = unsafe { mmap(ptr::null_mut(), size, prot, flags, -1, 0) };
63
64 #[cfg(target_os = "linux")]
66 if ptr == MAP_FAILED && (flags & MAP_LOCKED) != 0 {
67 let ptr = unsafe { mmap(ptr::null_mut(), size, prot, flags ^ MAP_LOCKED, -1, 0) };
68 if ptr != MAP_FAILED {
69 if unsafe { mlock(ptr, size) } == 0 {
70 locked = true;
71 } else {
72 unsafe { munmap(ptr, size) };
73 panic!("Failed to lock memory region after fallback mmap");
74 }
75 return Self {
76 ptr: NonNull::new(ptr).expect("mmap returned non-null"),
77 size,
78 locked,
79 };
80 }
81 }
82
83 if ptr == MAP_FAILED {
84 panic!("Failed to allocate memory region");
85 }
86
87 Self {
88 ptr: NonNull::new(ptr).expect("mmap returned non-null"),
89 size,
90 #[cfg(target_os = "linux")]
91 locked,
92 }
93 }
94
95 #[allow(dead_code)]
97 fn as_ptr(&self) -> *mut libc::c_void {
98 self.ptr.as_ptr()
99 }
100}
101
102impl Drop for MmapRegion {
103 fn drop(&mut self) {
104 if self.size > 0 {
105 let prot = PROT_READ | PROT_WRITE;
107 if secure_mprotect(self.ptr.as_ptr(), self.size, prot) == 0 {
108 unsafe { ptr::write_bytes(self.ptr.as_ptr() as *mut u8, 0, self.size) };
109 }
110
111 #[cfg(target_os = "linux")]
112 if self.locked {
113 unsafe { munlock(self.ptr.as_ptr(), self.size) };
114 }
115
116 let result = unsafe { munmap(self.ptr.as_ptr(), self.size) };
117 if result != 0 {
118 eprintln!("Failed to unmap memory region");
119 }
120 }
121 }
122}
123
124struct ProtectionGuard {
126 mapping: *mut libc::c_void,
127 size: usize,
128 original_prot: i32,
129}
130
131impl ProtectionGuard {
132 fn new(mapping: *mut libc::c_void, size: usize) -> Self {
134 let original_prot = PROT_NONE;
137
138 let guard = Self {
139 mapping,
140 size,
141 original_prot,
142 };
143
144 if size > 0 {
146 unsafe { set_pkey_rights(get_global_pkey(), 0); } if secure_mprotect(mapping, size, PROT_READ | PROT_WRITE) != 0 {
148 panic!("Failed to set memory protection");
149 }
150 }
151
152 guard
153 }
154}
155
156impl Drop for ProtectionGuard {
157 fn drop(&mut self) {
158 if self.size > 0 {
159 if secure_mprotect(self.mapping, self.size, self.original_prot) != 0 {
160 eprintln!("Failed to restore memory protection");
161 }
162 unsafe { set_pkey_rights(get_global_pkey(), 3); } }
164 }
165}
166
167pub fn harden_process() -> Result<(), &'static str> {
174 #[cfg(target_os = "linux")]
175 unsafe {
176 if libc::prctl(4, 0, 0, 0, 0) != 0 {
178 return Err("Failed to set PR_SET_DUMPABLE");
179 }
180 Ok(())
181 }
182 #[cfg(not(target_os = "linux"))]
183 {
184 Ok(())
185 }
186}
187
188
189#[cfg(target_os = "linux")]
194fn get_global_pkey() -> i32 {
195 static GLOBAL_PKEY: std::sync::OnceLock<i32> = std::sync::OnceLock::new();
196 *GLOBAL_PKEY.get_or_init(|| {
197 unsafe {
198 let pkey = libc::syscall(330, 0, 3); if pkey >= 0 {
201 pkey as i32
202 } else {
203 -1
204 }
205 }
206 })
207}
208
209#[cfg(not(target_os = "linux"))]
210fn get_global_pkey() -> i32 {
211 -1
212}
213
214#[cfg(target_arch = "x86_64")]
215unsafe fn set_pkey_rights(pkey: i32, rights: u32) {
216 if pkey < 0 { return; }
217 let mut pkru: u32;
218 unsafe { std::arch::asm!(
219 "rdpkru",
220 out("eax") pkru,
221 in("ecx") 0,
222 out("edx") _,
223 ); }
224 let shift = pkey * 2;
225 pkru &= !(3 << shift);
226 pkru |= (rights & 3) << shift;
227 unsafe { std::arch::asm!(
228 "wrpkru",
229 in("eax") pkru,
230 in("ecx") 0,
231 in("edx") 0,
232 ); }
233}
234
235#[cfg(not(target_arch = "x86_64"))]
236unsafe fn set_pkey_rights(_pkey: i32, _rights: u32) {
237 }
239
240fn secure_mprotect(addr: *mut libc::c_void, len: usize, prot: i32) -> i32 {
241 let pkey = get_global_pkey();
242 #[cfg(target_os = "linux")]
243 if pkey >= 0 {
244 let res = unsafe { libc::syscall(329, addr, len, prot, pkey) as i32 }; if res == 0 {
246 return 0;
247 }
248 }
249 unsafe { mprotect(addr, len, prot) }
250}
251
252pub struct SecMem<S: Zeroize> {
283 mapping: NonNull<libc::c_void>,
285 mapping_size: usize,
286
287 secret_ptr: NonNull<S>,
289
290 guard_pages: [NonNull<libc::c_void>; 2],
292 guard_page_size: usize,
293
294 #[cfg(debug_assertions)]
296 canary: u64,
297
298 locked: AtomicBool,
300 #[allow(dead_code)]
301 lock_id: usize,
302
303 #[cfg(feature = "encryption")]
304 nonce: [u8; 12],
305 #[cfg(feature = "encryption")]
306 is_encrypted: bool,
307}
308
309impl<S: Zeroize> SecMem<S> {
310 pub fn new(secret: S) -> Self {
312 let size = size_of::<S>();
314 if size == 0 {
315 return Self {
316 mapping: NonNull::dangling(),
317 mapping_size: 0,
318 secret_ptr: NonNull::dangling(),
319 guard_pages: [NonNull::dangling(), NonNull::dangling()],
320 guard_page_size: 0,
321 #[cfg(debug_assertions)]
322 canary: 0,
323 locked: AtomicBool::new(false),
324 lock_id: 0,
325 #[cfg(feature = "encryption")]
326 nonce: [0; 12],
327 #[cfg(feature = "encryption")]
328 is_encrypted: false,
329 };
330 }
331
332 let lock_id = GLOBAL_LOCK_COUNTER.fetch_add(1, SeqCst);
333 let page_size = unsafe { libc::sysconf(libc::_SC_PAGESIZE) } as usize;
334
335 let alloc_size = (size + page_size - 1) & !(page_size - 1);
337 let total_size = alloc_size + 2 * page_size;
338
339 let full_region = MmapRegion::new(total_size, PROT_NONE);
341
342 let guard_before_ptr = full_region.ptr;
343 let secret_region_ptr = unsafe { NonNull::new((full_region.ptr.as_ptr() as *mut u8).add(page_size) as *mut libc::c_void).unwrap() };
344 let guard_after_ptr = unsafe { NonNull::new((full_region.ptr.as_ptr() as *mut u8).add(page_size + alloc_size) as *mut libc::c_void).unwrap() };
345
346 if secure_mprotect(secret_region_ptr.as_ptr(), alloc_size, PROT_READ | PROT_WRITE) != 0 {
348 panic!("Failed to set memory protection for secret region");
349 }
350
351 #[cfg(target_os = "linux")]
354 unsafe {
355 if madvise(full_region.ptr.as_ptr(), total_size, libc::MADV_DONTDUMP) != 0 {
356 println!("Failed to set MADV_DONTDUMP on memory region");
357 }
358 if madvise(full_region.ptr.as_ptr(), total_size, libc::MADV_DONTFORK) != 0 {
359 println!("Failed to set MADV_DONTFORK on memory region");
360 }
361 }
362 #[cfg(not(target_os = "linux"))]
363 unsafe {
364 if madvise(full_region.ptr.as_ptr(), total_size, libc::MADV_DONTDUMP) != 0 {
365 println!("Failed to set MADV_DONTDUMP on memory region");
366 }
367 }
368
369 if unsafe { mlock(secret_region_ptr.as_ptr(), alloc_size) } != 0 {
370 println!("Failed to lock secret region in memory");
371 }
372
373 let secret_ptr = secret_region_ptr.as_ptr() as *mut S;
375 unsafe { ptr::write(secret_ptr, secret) };
376
377 #[cfg(feature = "encryption")]
378 let mut nonce = [0u8; 12];
379 #[cfg(feature = "encryption")]
380 let is_encrypted = true;
381
382 #[cfg(feature = "encryption")]
383 {
384 get_random_bytes(&mut nonce);
385 let secret_bytes = unsafe { std::slice::from_raw_parts_mut(secret_ptr as *mut u8, size)
386 };
387 encrypt_decrypt_memory(secret_bytes, &nonce);
388 }
389
390 if secure_mprotect(secret_region_ptr.as_ptr(), alloc_size, PROT_NONE) != 0 {
392 unsafe { ptr::drop_in_place(secret_ptr) };
393 panic!("Failed to set memory protection after secret initialization");
394 }
395
396 std::mem::forget(full_region);
398
399 Self {
400 mapping: secret_region_ptr,
401 mapping_size: alloc_size,
402 secret_ptr: NonNull::new(secret_ptr).unwrap(),
403 guard_pages: [guard_before_ptr, guard_after_ptr],
404 guard_page_size: page_size,
405 #[cfg(debug_assertions)]
406 canary: 0xDEADBEEFCAFEBABE,
407 locked: AtomicBool::new(true),
408 lock_id,
409 #[cfg(feature = "encryption")]
410 nonce,
411 #[cfg(feature = "encryption")]
412 is_encrypted,
413 }
414 }
415
416 pub fn access<F, R>(&self, f: F) -> R
418 where
419 F: FnOnce(&S) -> R,
420 {
421 self.check_canary();
422
423 #[cfg(feature = "encryption")]
424 {
425 while !self.locked.swap(false, Ordering::SeqCst) {
426 std::hint::spin_loop();
427 }
428 }
429
430 struct AccessGuard<'a, S: Zeroize> {
431 sec_mem: &'a SecMem<S>,
432 }
433
434 impl<'a, S: Zeroize> Drop for AccessGuard<'a, S> {
435 fn drop(&mut self) {
436 unsafe { set_pkey_rights(get_global_pkey(), 3); } #[cfg(feature = "encryption")]
439 if self.sec_mem.is_encrypted {
440 unsafe { set_pkey_rights(get_global_pkey(), 0); } let secret_bytes = unsafe {
442 std::slice::from_raw_parts_mut(self.sec_mem.secret_ptr.as_ptr() as *mut u8, size_of::<S>())
443 };
444 encrypt_decrypt_memory(secret_bytes, &self.sec_mem.nonce);
445 unsafe { set_pkey_rights(get_global_pkey(), 3); } }
447
448 unsafe {
449 flush_cache(
450 self.sec_mem.secret_ptr.as_ptr() as *const u8,
451 size_of::<S>()
452 );
453 }
454
455 #[cfg(feature = "encryption")]
456 {
457 self.sec_mem.locked.store(true, Ordering::SeqCst);
458 }
459 }
460 }
461
462 self.set_protection(PROT_READ | PROT_WRITE); let _prot_guard = ProtectionGuard::new(self.mapping.as_ptr(), self.mapping_size);
464
465 let _access_guard = AccessGuard { sec_mem: self };
466
467 #[cfg(feature = "encryption")]
468 if self.is_encrypted {
469 let secret_bytes = unsafe {
470 std::slice::from_raw_parts_mut(self.secret_ptr.as_ptr() as *mut u8, size_of::<S>())
471 };
472 encrypt_decrypt_memory(secret_bytes, &self.nonce);
473 }
474
475 unsafe {
476 core::sync::atomic::compiler_fence(Ordering::SeqCst);
477 let res = f(self.secret_ptr.as_ref());
478 core::sync::atomic::compiler_fence(Ordering::SeqCst);
479 res
480 }
481 }
482
483 pub fn access_mut<F, R>(&mut self, f: F) -> R
485 where
486 F: FnOnce(&mut S) -> R,
487 {
488 if !self.locked.swap(false, Ordering::SeqCst) {
489 panic!("Attempted to create multiple mutable references to secret data");
490 }
491
492 struct MutGuard<'a, S: Zeroize> {
493 box_ref: &'a mut SecMem<S>,
494 }
495
496 impl<'a, S: Zeroize> Drop for MutGuard<'a, S> {
497 fn drop(&mut self) {
498 #[cfg(feature = "encryption")]
499 if self.box_ref.is_encrypted {
500 let secret_bytes = unsafe {
501 std::slice::from_raw_parts_mut(self.box_ref.secret_ptr.as_ptr() as *mut u8, size_of::<S>())
502 };
503 get_random_bytes(&mut self.box_ref.nonce);
504 encrypt_decrypt_memory(secret_bytes, &self.box_ref.nonce);
505 }
506
507 unsafe {
508 flush_cache(
509 self.box_ref.secret_ptr.as_ptr() as *const u8,
510 size_of::<S>()
511 );
512 }
513 self.box_ref.locked.store(true, Ordering::SeqCst);
514 }
515 }
516
517 self.check_canary();
518 self.set_protection(PROT_READ | PROT_WRITE);
519 let _prot_guard = ProtectionGuard::new(self.mapping.as_ptr(), self.mapping_size);
520
521 let mut _mut_guard = MutGuard { box_ref: self };
522
523 #[cfg(feature = "encryption")]
524 if _mut_guard.box_ref.is_encrypted {
525 let secret_bytes = unsafe {
526 std::slice::from_raw_parts_mut(_mut_guard.box_ref.secret_ptr.as_ptr() as *mut u8, size_of::<S>())
527 };
528 encrypt_decrypt_memory(secret_bytes, &_mut_guard.box_ref.nonce);
529 }
530
531 let result = unsafe { f(_mut_guard.box_ref.secret_ptr.as_mut()) };
532
533 _mut_guard.box_ref.check_canary();
534 result
535 }
536
537 pub fn constant_time_eq(&self, other: &Self) -> Choice
539 where
540 S: AsRef<[u8]>,
541 {
542 self.access(|s| {
543 other.access(|o| {
544 let s_bytes = s.as_ref();
545 let o_bytes = o.as_ref();
546
547 let len_equal = s_bytes.len().ct_eq(&o_bytes.len());
548
549 let min_len = s_bytes.len().min(o_bytes.len());
550 let content_equal = s_bytes[..min_len].ct_eq(&o_bytes[..min_len]);
551
552 len_equal & content_equal
553 })
554 })
555 }
556
557 pub fn seal_guard_pages(&self) {
566 #[cfg(target_os = "linux")]
567 unsafe {
568 libc::syscall(462, self.guard_pages[0].as_ptr(), self.guard_page_size, 0);
570 libc::syscall(462, self.guard_pages[1].as_ptr(), self.guard_page_size, 0);
571 }
572 }
573
574 #[cfg(debug_assertions)]
580 fn check_canary(&self) {
581 if self.canary != 0xDEADBEEFCAFEBABE {
582 panic!("Memory corruption detected (canary check failed)");
583 }
584 }
585
586 #[cfg(not(debug_assertions))]
587 fn check_canary(&self) {}
588
589 fn set_protection(&self, protection: i32) {
591 if size_of::<S>() == 0 {
592 return;
593 }
594
595 if secure_mprotect(self.mapping.as_ptr(), self.mapping_size, protection) != 0 {
596 println!("Failed to set memory protection");
597 }
598 }
599}
600
601impl<S: Zeroize> Drop for SecMem<S> {
606 fn drop(&mut self) {
607 let size = size_of::<S>();
608 if size == 0 {
609 return;
610 }
611
612 if !self.locked.swap(false, Ordering::SeqCst) {
614 #[cfg(debug_assertions)]
615 panic!("Double-free detected for SecMem with lock_id {}", self.lock_id);
616
617 #[cfg(not(debug_assertions))]
618 {
619 println!("[SECURITY CRITICAL] Double-free attempt detected");
620 return;
621 }
622 }
623
624 if secure_mprotect(
626 self.mapping.as_ptr(),
627 self.mapping_size,
628 PROT_READ | PROT_WRITE
629 ) != 0 {
630 let _ = unsafe { mlock(self.mapping.as_ptr(), self.mapping_size) };
631 println!("[SECURITY CRITICAL] Failed to restore memory protection during drop");
632 return;
633 }
634
635 #[cfg(feature = "encryption")]
636 if self.is_encrypted {
637 let secret_bytes = unsafe {
638 std::slice::from_raw_parts_mut(self.secret_ptr.as_ptr() as *mut u8, size)
639 };
640 encrypt_decrypt_memory(secret_bytes, &self.nonce);
641 }
642
643 unsafe {
645 let secret = self.secret_ptr.as_mut();
646 secret.zeroize();
647
648 flush_cache(self.secret_ptr.as_ptr() as *const u8, size);
649 ptr::drop_in_place(secret);
650 flush_cache(self.secret_ptr.as_ptr() as *const u8, size);
651 }
652
653 if unsafe { munlock(self.mapping.as_ptr(), self.mapping_size) } != 0 {
655 println!("Failed to unlock memory during drop");
656 }
657
658 unsafe {
659 let total_size = self.mapping_size + 2 * self.guard_page_size;
660
661 #[cfg(target_os = "linux")]
662 libc::madvise(self.guard_pages[0].as_ptr(), total_size, libc::MADV_REMOVE);
663
664 munmap(self.guard_pages[0].as_ptr(), total_size);
665 }
666 }
667}
668
669impl<S: Zeroize + Default> Default for SecMem<S> {
670 fn default() -> Self {
671 Self::new(S::default())
672 }
673}
674
675impl<S: Zeroize + Clone> Clone for SecMem<S> {
676 fn clone(&self) -> Self {
677 self.access(|s| Self::new(s.clone()))
678 }
679}
680
681impl<S: Zeroize + fmt::Debug> fmt::Debug for SecMem<S> {
682 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
683 write!(f, "SecMem<{}>([REDACTED])", std::any::type_name::<S>())
684 }
685}
686
687impl<S: Zeroize> Zeroize for SecMem<S> {
688 fn zeroize(&mut self) {
689 let size = size_of::<S>();
690 if size == 0 {
691 return;
692 }
693
694 if secure_mprotect(self.mapping.as_ptr(), self.mapping_size, PROT_READ | PROT_WRITE) != 0 {
696 println!("[SECURITY WARNING] Failed to enable write protection during zeroization");
697 return;
698 }
699
700 #[cfg(feature = "encryption")]
701 if self.is_encrypted {
702 let secret_bytes = unsafe {
703 std::slice::from_raw_parts_mut(self.secret_ptr.as_ptr() as *mut u8, size)
704 };
705 encrypt_decrypt_memory(secret_bytes, &self.nonce);
706 self.is_encrypted = false; }
708
709 unsafe { self.secret_ptr.as_mut().zeroize();
711 flush_cache(self.secret_ptr.as_ptr() as *const u8, size);
712 }
713
714 if secure_mprotect(self.mapping.as_ptr(), self.mapping_size, PROT_NONE) != 0 {
716 println!("[SECURITY WARNING] Failed to restore protection after zeroization");
717 let _ = unsafe { mlock(self.mapping.as_ptr(), self.mapping_size) };
718 }
719 }
720}
721
722impl<S: Zeroize> ZeroizeOnDrop for SecMem<S> {}
723
724unsafe impl<S: Zeroize + Send> Send for SecMem<S> {}
725unsafe impl<S: Zeroize + Sync> Sync for SecMem<S> {}
726
727fn get_cache_line_size() -> usize {
738 CACHE_LINE_SIZE.get_or_init(|| {
739 #[cfg(target_os = "linux")]
740 {
741 match unsafe { sysconf(_SC_LEVEL1_DCACHE_LINESIZE) } {
743 size if size > 0 => size as usize,
744 _ => fallback_cache_line_size(),
745 }
746 }
747
748 #[cfg(not(target_os = "linux"))]
749 {
750 fallback_cache_line_size()
751 }
752 }).to_owned() }
754
755const fn fallback_cache_line_size() -> usize {
757 64
759}
760
761unsafe fn flush_cache(ptr: *const u8, len: usize) {
768 if len == 0 {
769 return;
770 }
771
772 let alignment = get_cache_line_size();
774 if !(ptr as usize).is_multiple_of(alignment) {
775 panic!("Pointer is not aligned to cache line boundary");
776 }
777
778 #[cfg(target_arch = "x86_64")]
779 {
780 use core::arch::x86_64::_mm_clflush;
781
782 let mut addr = ptr as usize;
783 let end_addr = addr.saturating_add(len);
784
785 while addr < end_addr {
786 unsafe { _mm_clflush(addr as *const _) };
787 addr = addr.saturating_add(alignment);
788 }
789 core::sync::atomic::compiler_fence(Ordering::SeqCst);
790 }
791
792 #[cfg(target_arch = "x86")]
793 {
794 use core::arch::x86::_mm_clflush;
795
796 let mut addr = ptr as usize;
797 let end_addr = addr.saturating_add(len);
798
799 while addr < end_addr {
800 unsafe { _mm_clflush(addr as *const _) };
801 addr = addr.saturating_add(alignment);
802 }
803 core::sync::atomic::compiler_fence(Ordering::SeqCst);
804 }
805
806 #[cfg(target_arch = "aarch64")]
807 {
808 let mut addr = ptr as usize;
809 let end_addr = addr.saturating_add(len);
810
811 while addr < end_addr {
812 unsafe { core::arch::asm!("dc cvau, {}", in(reg) addr) };
813 addr = addr.saturating_add(alignment);
814 }
815 unsafe {
816 core::arch::asm!("dsb ish");
817 core::arch::asm!("isb");
818 }
819 }
820
821 #[cfg(not(any(target_arch = "x86", target_arch = "x86_64", target_arch = "aarch64")))]
822 {
823 let _ = (ptr, len);
825 }
827}
828
829pub trait SecretAccess<T: Zeroize + ?Sized> {
835 fn access<F, R>(&self, f: F) -> R
837 where
838 F: FnOnce(&T) -> R;
839
840 fn access_mut<F, R>(&mut self, f: F) -> R
842 where
843 F: FnOnce(&mut T) -> R;
844}
845
846impl<const N: usize> SecretAccess<[u8]> for SecMem<[u8; N]> {
850 fn access<F, R>(&self, f: F) -> R
851 where
852 F: FnOnce(&[u8]) -> R,
853 {
854 self.access(|array| f(array.as_slice()))
855 }
856
857 fn access_mut<F, R>(&mut self, f: F) -> R
858 where
859 F: FnOnce(&mut [u8]) -> R,
860 {
861 self.access_mut(|array| f(array.as_mut_slice()))
862 }
863}
864
865impl SecretAccess<[u8]> for SecMem<Vec<u8>> {
867 fn access<F, R>(&self, f: F) -> R
868 where
869 F: FnOnce(&[u8]) -> R,
870 {
871 self.access(|vec| f(vec.as_slice()))
872 }
873
874 fn access_mut<F, R>(&mut self, f: F) -> R
875 where
876 F: FnOnce(&mut [u8]) -> R,
877 {
878 self.access_mut(|vec| f(vec.as_mut_slice()))
879 }
880}
881
882impl<const N: usize> SecretAccess<[u8]> for [u8; N] {
884 fn access<F, R>(&self, f: F) -> R
885 where
886 F: FnOnce(&[u8]) -> R,
887 {
888 f(self.as_slice())
889 }
890
891 fn access_mut<F, R>(&mut self, f: F) -> R
892 where
893 F: FnOnce(&mut [u8]) -> R,
894 {
895 f(self.as_mut_slice())
896 }
897}
898
899impl SecretAccess<[u8]> for &[u8] {
901 fn access<F, R>(&self, f: F) -> R
902 where
903 F: FnOnce(&[u8]) -> R,
904 {
905 f(self)
906 }
907
908 fn access_mut<F, R>(&mut self, _f: F) -> R
909 where
910 F: FnOnce(&mut [u8]) -> R,
911 {
912 panic!("Cannot get mutable access through immutable reference")
913 }
914}
915
916impl SecretAccess<[u8]> for &mut [u8] {
918 fn access<F, R>(&self, f: F) -> R
919 where
920 F: FnOnce(&[u8]) -> R,
921 {
922 f(self)
923 }
924
925 fn access_mut<F, R>(&mut self, f: F) -> R
926 where
927 F: FnOnce(&mut [u8]) -> R,
928 {
929 f(self)
930 }
931}
932
933impl SecretAccess<[u8]> for Box<[u8]> {
935 fn access<F, R>(&self, f: F) -> R
936 where
937 F: FnOnce(&[u8]) -> R,
938 {
939 f(self.as_ref())
940 }
941
942 fn access_mut<F, R>(&mut self, f: F) -> R
943 where
944 F: FnOnce(&mut [u8]) -> R,
945 {
946 f(self.as_mut())
947 }
948}
949
950impl<T: Zeroize> SecretAccess<T> for SecMem<T> {
956 fn access<F, R>(&self, f: F) -> R
957 where
958 F: FnOnce(&T) -> R,
959 {
960 self.access(f)
961 }
962
963 fn access_mut<F, R>(&mut self, f: F) -> R
964 where
965 F: FnOnce(&mut T) -> R,
966 {
967 self.access_mut(f)
968 }
969}
970
971impl<T: Zeroize, const N: usize> SecretAccess<[T; N]> for [T; N] {
973 fn access<F, R>(&self, f: F) -> R
974 where
975 F: FnOnce(&[T; N]) -> R,
976 {
977 f(self)
978 }
979
980 fn access_mut<F, R>(&mut self, f: F) -> R
981 where
982 F: FnOnce(&mut [T; N]) -> R,
983 {
984 f(self)
985 }
986}
987
988impl<T: Zeroize> SecretAccess<[T]> for [T] where [T]: Zeroize {
990 fn access<F, R>(&self, f: F) -> R
991 where
992 F: FnOnce(&[T]) -> R,
993 {
994 f(self)
995 }
996
997 fn access_mut<F, R>(&mut self, f: F) -> R
998 where
999 F: FnOnce(&mut [T]) -> R,
1000 {
1001 f(self)
1002 }
1003}
1004
1005impl<T: Zeroize> SecretAccess<[T]> for Vec<T> where [T]: Zeroize {
1007 fn access<F, R>(&self, f: F) -> R
1008 where
1009 F: FnOnce(&[T]) -> R,
1010 {
1011 f(self.as_slice())
1012 }
1013
1014 fn access_mut<F, R>(&mut self, f: F) -> R
1015 where
1016 F: FnOnce(&mut [T]) -> R,
1017 {
1018 f(self.as_mut_slice())
1019 }
1020}
1021
1022impl<T: Zeroize> SecretAccess<T> for Box<T> {
1024 fn access<F, R>(&self, f: F) -> R
1025 where
1026 F: FnOnce(&T) -> R,
1027 {
1028 f(self.as_ref())
1029 }
1030
1031 fn access_mut<F, R>(&mut self, f: F) -> R
1032 where
1033 F: FnOnce(&mut T) -> R,
1034 {
1035 f(self.as_mut())
1036 }
1037}
1038
1039#[cfg(feature = "encryption")]
1044struct KeyStorage {
1045 ptr: *mut u8,
1046 size: usize,
1047}
1048
1049#[cfg(feature = "encryption")]
1050unsafe impl Sync for KeyStorage {}
1051#[cfg(feature = "encryption")]
1052unsafe impl Send for KeyStorage {}
1053
1054#[cfg(feature = "encryption")]
1055impl KeyStorage {
1056 fn new_uninitialized() -> Self {
1057 #[cfg(feature = "key_shielding")]
1058 let size = 16384; #[cfg(not(feature = "key_shielding"))]
1060 let size = 32;
1061
1062 let mut ptr = libc::MAP_FAILED;
1063
1064 #[cfg(target_os = "linux")]
1065 unsafe {
1066 let fd = libc::syscall(447, 0);
1068 if fd >= 0 {
1069 if libc::ftruncate(fd as i32, size as libc::off_t) == 0 {
1070 ptr = libc::mmap(
1071 std::ptr::null_mut(),
1072 size,
1073 libc::PROT_READ | libc::PROT_WRITE,
1074 libc::MAP_SHARED,
1075 fd as i32,
1076 0,
1077 );
1078 }
1079 libc::close(fd as i32);
1080 }
1081 }
1082
1083 if ptr == libc::MAP_FAILED {
1084 unsafe {
1085 let flags = libc::MAP_PRIVATE | libc::MAP_ANONYMOUS;
1086 ptr = libc::mmap(
1087 std::ptr::null_mut(),
1088 size,
1089 libc::PROT_READ | libc::PROT_WRITE,
1090 flags,
1091 -1,
1092 0,
1093 );
1094 if ptr == libc::MAP_FAILED {
1095 panic!("Failed to allocate global encryption key segment");
1096 }
1097 libc::mlock(ptr, size);
1098 #[cfg(target_os = "linux")]
1099 libc::madvise(ptr, size, libc::MADV_DONTDUMP);
1100 #[cfg(target_os = "linux")]
1101 libc::madvise(ptr, size, libc::MADV_DONTFORK);
1102 }
1103 }
1104
1105 Self {
1106 ptr: ptr as *mut u8,
1107 size,
1108 }
1109 }
1110}
1111
1112#[cfg(feature = "encryption")]
1113struct SplitKeyStorage {
1114 mask: KeyStorage,
1115 blinded: KeyStorage,
1116}
1117
1118#[cfg(feature = "encryption")]
1119impl SplitKeyStorage {
1120 fn new() -> Self {
1121 let mask = KeyStorage::new_uninitialized();
1122 let blinded = KeyStorage::new_uninitialized();
1123
1124 #[cfg(feature = "key_shielding")]
1125 let (mut k, mut m, mut b, copy_size) = {
1126 (vec![0u8; 16384], vec![0u8; 16384], vec![0u8; 16384], 16384)
1127 };
1128 #[cfg(not(feature = "key_shielding"))]
1129 let (mut k, mut m, mut b, copy_size) = {
1130 (vec![0u8; 32], vec![0u8; 32], vec![0u8; 32], 32)
1131 };
1132
1133 get_random_bytes(&mut k);
1134 get_random_bytes(&mut m);
1135
1136 for i in 0..copy_size {
1137 b[i] = k[i] ^ m[i];
1138 }
1139
1140 unsafe {
1141 std::ptr::copy_nonoverlapping(m.as_ptr(), mask.ptr, copy_size);
1142 std::ptr::copy_nonoverlapping(b.as_ptr(), blinded.ptr, copy_size);
1143
1144 libc::mprotect(mask.ptr as *mut libc::c_void, mask.size, libc::PROT_READ);
1145 libc::mprotect(blinded.ptr as *mut libc::c_void, blinded.size, libc::PROT_READ);
1146 }
1147
1148 zeroize::Zeroize::zeroize(&mut k);
1149 zeroize::Zeroize::zeroize(&mut m);
1150 zeroize::Zeroize::zeroize(&mut b);
1151
1152 Self { mask, blinded }
1153 }
1154}
1155
1156#[cfg(feature = "encryption")]
1157fn get_split_keys() -> &'static SplitKeyStorage {
1158 static GLOBAL_SPLIT_KEY: std::sync::OnceLock<SplitKeyStorage> = std::sync::OnceLock::new();
1159 GLOBAL_SPLIT_KEY.get_or_init(SplitKeyStorage::new)
1160}
1161
1162#[cfg(feature = "encryption")]
1163fn get_random_bytes(buf: &mut [u8]) {
1164 #[cfg(target_os = "linux")]
1165 unsafe {
1166 let mut total = 0;
1167 while total < buf.len() {
1168 let res = libc::getrandom(
1169 buf.as_mut_ptr().add(total) as *mut libc::c_void,
1170 buf.len() - total,
1171 0,
1172 );
1173 if res > 0 {
1174 total += res as usize;
1175 } else {
1176 use std::fs::File;
1178 use std::io::Read;
1179 let mut file = File::open("/dev/urandom").expect("Failed to open /dev/urandom");
1180 file.read_exact(buf).expect("Failed to read random bytes");
1181 return;
1182 }
1183 }
1184 }
1185
1186 #[cfg(not(target_os = "linux"))]
1187 {
1188 use std::fs::File;
1189 use std::io::Read;
1190 let mut file = File::open("/dev/urandom").expect("Failed to open /dev/urandom");
1191 file.read_exact(buf).expect("Failed to read random bytes");
1192 }
1193}
1194
1195#[cfg(feature = "encryption")]
1196fn encrypt_decrypt_memory(data: &mut [u8], nonce: &[u8; 12]) {
1197 use chacha20::cipher::{KeyIvInit, StreamCipher};
1198 use chacha20::ChaCha20;
1199
1200 let storage = get_split_keys();
1201 let mut key = [0u8; 32];
1202
1203 #[cfg(feature = "key_shielding")]
1204 let mut combined = vec![0u8; 16384];
1205
1206 unsafe {
1207 let m_ptr = storage.mask.ptr;
1208 let b_ptr = storage.blinded.ptr;
1209
1210 #[cfg(feature = "key_shielding")]
1211 {
1212 for i in 0..16384 {
1214 combined[i] = *m_ptr.add(i) ^ *b_ptr.add(i);
1215 }
1216 }
1217
1218 #[cfg(not(feature = "key_shielding"))]
1219 {
1220 for i in 0..32 {
1222 key[i] = *m_ptr.add(i) ^ *b_ptr.add(i);
1223 }
1224 }
1225 }
1226
1227 #[cfg(feature = "key_shielding")]
1228 {
1229 let mut hasher = blake3::Hasher::new();
1231 hasher.update(&combined);
1232 key.copy_from_slice(hasher.finalize().as_bytes());
1233 }
1234
1235 let mut cipher = ChaCha20::new((&key).into(), nonce.into());
1236 cipher.apply_keystream(data);
1237
1238 key.zeroize();
1240 #[cfg(feature = "key_shielding")]
1241 combined.zeroize();
1242}