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> {
281 mapping: NonNull<libc::c_void>,
283 mapping_size: usize,
284
285 secret_ptr: NonNull<S>,
287
288 guard_pages: [NonNull<libc::c_void>; 2],
290 guard_page_size: usize,
291
292 #[cfg(debug_assertions)]
294 canary: u64,
295
296 locked: AtomicBool,
298 #[allow(dead_code)]
299 lock_id: usize,
300
301 #[cfg(feature = "encryption")]
302 nonce: [u8; 12],
303 #[cfg(feature = "encryption")]
304 is_encrypted: bool,
305}
306
307impl<S: Zeroize> SecMem<S> {
308 pub fn new(secret: S) -> Self {
310 let size = size_of::<S>();
312 if size == 0 {
313 return Self {
314 mapping: NonNull::dangling(),
315 mapping_size: 0,
316 secret_ptr: NonNull::dangling(),
317 guard_pages: [NonNull::dangling(), NonNull::dangling()],
318 guard_page_size: 0,
319 #[cfg(debug_assertions)]
320 canary: 0,
321 locked: AtomicBool::new(false),
322 lock_id: 0,
323 #[cfg(feature = "encryption")]
324 nonce: [0; 12],
325 #[cfg(feature = "encryption")]
326 is_encrypted: false,
327 };
328 }
329
330 let lock_id = GLOBAL_LOCK_COUNTER.fetch_add(1, SeqCst);
331 let page_size = unsafe { libc::sysconf(libc::_SC_PAGESIZE) } as usize;
332
333 let alloc_size = (size + page_size - 1) & !(page_size - 1);
335 let total_size = alloc_size + 2 * page_size;
336
337 let full_region = MmapRegion::new(total_size, PROT_NONE);
339
340 let guard_before_ptr = full_region.ptr;
341 let secret_region_ptr = unsafe { NonNull::new((full_region.ptr.as_ptr() as *mut u8).add(page_size) as *mut libc::c_void).unwrap() };
342 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() };
343
344 if secure_mprotect(secret_region_ptr.as_ptr(), alloc_size, PROT_READ | PROT_WRITE) != 0 {
346 panic!("Failed to set memory protection for secret region");
347 }
348
349 #[cfg(target_os = "linux")]
352 unsafe {
353 if madvise(full_region.ptr.as_ptr(), total_size, libc::MADV_DONTDUMP) != 0 {
354 println!("Failed to set MADV_DONTDUMP on memory region");
355 }
356 if madvise(full_region.ptr.as_ptr(), total_size, libc::MADV_DONTFORK) != 0 {
357 println!("Failed to set MADV_DONTFORK on memory region");
358 }
359 }
360 #[cfg(not(target_os = "linux"))]
361 unsafe {
362 if madvise(full_region.ptr.as_ptr(), total_size, libc::MADV_DONTDUMP) != 0 {
363 println!("Failed to set MADV_DONTDUMP on memory region");
364 }
365 }
366
367 if unsafe { mlock(secret_region_ptr.as_ptr(), alloc_size) } != 0 {
368 println!("Failed to lock secret region in memory");
369 }
370
371 let secret_ptr = secret_region_ptr.as_ptr() as *mut S;
373 unsafe { ptr::write(secret_ptr, secret) };
374
375 #[cfg(feature = "encryption")]
376 let mut nonce = [0u8; 12];
377 #[cfg(feature = "encryption")]
378 let is_encrypted = true;
379
380 #[cfg(feature = "encryption")]
381 {
382 get_random_bytes(&mut nonce);
383 let secret_bytes = unsafe { std::slice::from_raw_parts_mut(secret_ptr as *mut u8, size)
384 };
385 encrypt_decrypt_memory(secret_bytes, &nonce);
386 }
387
388 if secure_mprotect(secret_region_ptr.as_ptr(), alloc_size, PROT_NONE) != 0 {
390 unsafe { ptr::drop_in_place(secret_ptr) };
391 panic!("Failed to set memory protection after secret initialization");
392 }
393
394 std::mem::forget(full_region);
396
397 Self {
398 mapping: secret_region_ptr,
399 mapping_size: alloc_size,
400 secret_ptr: NonNull::new(secret_ptr).unwrap(),
401 guard_pages: [guard_before_ptr, guard_after_ptr],
402 guard_page_size: page_size,
403 #[cfg(debug_assertions)]
404 canary: 0xDEADBEEFCAFEBABE,
405 locked: AtomicBool::new(true),
406 lock_id,
407 #[cfg(feature = "encryption")]
408 nonce,
409 #[cfg(feature = "encryption")]
410 is_encrypted,
411 }
412 }
413
414 pub fn access<F, R>(&self, f: F) -> R
416 where
417 F: FnOnce(&S) -> R,
418 {
419 self.check_canary();
420
421 #[cfg(feature = "encryption")]
422 {
423 while !self.locked.swap(false, Ordering::SeqCst) {
424 std::hint::spin_loop();
425 }
426 }
427
428 struct AccessGuard<'a, S: Zeroize> {
429 sec_mem: &'a SecMem<S>,
430 }
431
432 impl<'a, S: Zeroize> Drop for AccessGuard<'a, S> {
433 fn drop(&mut self) {
434 unsafe { set_pkey_rights(get_global_pkey(), 3); } #[cfg(feature = "encryption")]
437 if self.sec_mem.is_encrypted {
438 unsafe { set_pkey_rights(get_global_pkey(), 0); } let secret_bytes = unsafe {
440 std::slice::from_raw_parts_mut(self.sec_mem.secret_ptr.as_ptr() as *mut u8, size_of::<S>())
441 };
442 encrypt_decrypt_memory(secret_bytes, &self.sec_mem.nonce);
443 unsafe { set_pkey_rights(get_global_pkey(), 3); } }
445
446 unsafe {
447 flush_cache(
448 self.sec_mem.secret_ptr.as_ptr() as *const u8,
449 size_of::<S>()
450 );
451 }
452
453 #[cfg(feature = "encryption")]
454 {
455 self.sec_mem.locked.store(true, Ordering::SeqCst);
456 }
457 }
458 }
459
460 self.set_protection(PROT_READ | PROT_WRITE); let _prot_guard = ProtectionGuard::new(self.mapping.as_ptr(), self.mapping_size);
462
463 let _access_guard = AccessGuard { sec_mem: self };
464
465 #[cfg(feature = "encryption")]
466 if self.is_encrypted {
467 let secret_bytes = unsafe {
468 std::slice::from_raw_parts_mut(self.secret_ptr.as_ptr() as *mut u8, size_of::<S>())
469 };
470 encrypt_decrypt_memory(secret_bytes, &self.nonce);
471 }
472
473 unsafe {
474 core::sync::atomic::compiler_fence(Ordering::SeqCst);
475 let res = f(self.secret_ptr.as_ref());
476 core::sync::atomic::compiler_fence(Ordering::SeqCst);
477 res
478 }
479 }
480
481 pub fn access_mut<F, R>(&mut self, f: F) -> R
483 where
484 F: FnOnce(&mut S) -> R,
485 {
486 if !self.locked.swap(false, Ordering::SeqCst) {
487 panic!("Attempted to create multiple mutable references to secret data");
488 }
489
490 struct MutGuard<'a, S: Zeroize> {
491 box_ref: &'a mut SecMem<S>,
492 }
493
494 impl<'a, S: Zeroize> Drop for MutGuard<'a, S> {
495 fn drop(&mut self) {
496 #[cfg(feature = "encryption")]
497 if self.box_ref.is_encrypted {
498 let secret_bytes = unsafe {
499 std::slice::from_raw_parts_mut(self.box_ref.secret_ptr.as_ptr() as *mut u8, size_of::<S>())
500 };
501 get_random_bytes(&mut self.box_ref.nonce);
502 encrypt_decrypt_memory(secret_bytes, &self.box_ref.nonce);
503 }
504
505 unsafe {
506 flush_cache(
507 self.box_ref.secret_ptr.as_ptr() as *const u8,
508 size_of::<S>()
509 );
510 }
511 self.box_ref.locked.store(true, Ordering::SeqCst);
512 }
513 }
514
515 self.check_canary();
516 self.set_protection(PROT_READ | PROT_WRITE);
517 let _prot_guard = ProtectionGuard::new(self.mapping.as_ptr(), self.mapping_size);
518
519 let mut _mut_guard = MutGuard { box_ref: self };
520
521 #[cfg(feature = "encryption")]
522 if _mut_guard.box_ref.is_encrypted {
523 let secret_bytes = unsafe {
524 std::slice::from_raw_parts_mut(_mut_guard.box_ref.secret_ptr.as_ptr() as *mut u8, size_of::<S>())
525 };
526 encrypt_decrypt_memory(secret_bytes, &_mut_guard.box_ref.nonce);
527 }
528
529 let result = unsafe { f(_mut_guard.box_ref.secret_ptr.as_mut()) };
530
531 _mut_guard.box_ref.check_canary();
532 result
533 }
534
535 pub fn constant_time_eq(&self, other: &Self) -> Choice
537 where
538 S: AsRef<[u8]>,
539 {
540 self.access(|s| {
541 other.access(|o| {
542 let s_bytes = s.as_ref();
543 let o_bytes = o.as_ref();
544
545 let len_equal = s_bytes.len().ct_eq(&o_bytes.len());
546
547 let min_len = s_bytes.len().min(o_bytes.len());
548 let content_equal = s_bytes[..min_len].ct_eq(&o_bytes[..min_len]);
549
550 len_equal & content_equal
551 })
552 })
553 }
554
555 pub fn seal_guard_pages(&self) {
564 #[cfg(target_os = "linux")]
565 unsafe {
566 libc::syscall(462, self.guard_pages[0].as_ptr(), self.guard_page_size, 0);
568 libc::syscall(462, self.guard_pages[1].as_ptr(), self.guard_page_size, 0);
569 }
570 }
571
572 #[cfg(debug_assertions)]
578 fn check_canary(&self) {
579 if self.canary != 0xDEADBEEFCAFEBABE {
580 panic!("Memory corruption detected (canary check failed)");
581 }
582 }
583
584 #[cfg(not(debug_assertions))]
585 fn check_canary(&self) {}
586
587 fn set_protection(&self, protection: i32) {
589 if size_of::<S>() == 0 {
590 return;
591 }
592
593 if secure_mprotect(self.mapping.as_ptr(), self.mapping_size, protection) != 0 {
594 println!("Failed to set memory protection");
595 }
596 }
597}
598
599impl<S: Zeroize> Drop for SecMem<S> {
604 fn drop(&mut self) {
605 let size = size_of::<S>();
606 if size == 0 {
607 return;
608 }
609
610 if !self.locked.swap(false, Ordering::SeqCst) {
612 #[cfg(debug_assertions)]
613 panic!("Double-free detected for SecMem with lock_id {}", self.lock_id);
614
615 #[cfg(not(debug_assertions))]
616 {
617 println!("[SECURITY CRITICAL] Double-free attempt detected");
618 return;
619 }
620 }
621
622 if secure_mprotect(
624 self.mapping.as_ptr(),
625 self.mapping_size,
626 PROT_READ | PROT_WRITE
627 ) != 0 {
628 let _ = unsafe { mlock(self.mapping.as_ptr(), self.mapping_size) };
629 println!("[SECURITY CRITICAL] Failed to restore memory protection during drop");
630 return;
631 }
632
633 #[cfg(feature = "encryption")]
634 if self.is_encrypted {
635 let secret_bytes = unsafe {
636 std::slice::from_raw_parts_mut(self.secret_ptr.as_ptr() as *mut u8, size)
637 };
638 encrypt_decrypt_memory(secret_bytes, &self.nonce);
639 }
640
641 unsafe {
643 let secret = self.secret_ptr.as_mut();
644 secret.zeroize();
645
646 flush_cache(self.secret_ptr.as_ptr() as *const u8, size);
647 ptr::drop_in_place(secret);
648 flush_cache(self.secret_ptr.as_ptr() as *const u8, size);
649 }
650
651 if unsafe { munlock(self.mapping.as_ptr(), self.mapping_size) } != 0 {
653 println!("Failed to unlock memory during drop");
654 }
655
656 unsafe {
657 let total_size = self.mapping_size + 2 * self.guard_page_size;
658
659 #[cfg(target_os = "linux")]
660 libc::madvise(self.guard_pages[0].as_ptr(), total_size, libc::MADV_REMOVE);
661
662 munmap(self.guard_pages[0].as_ptr(), total_size);
663 }
664 }
665}
666
667impl<S: Zeroize + Default> Default for SecMem<S> {
668 fn default() -> Self {
669 Self::new(S::default())
670 }
671}
672
673impl<S: Zeroize + Clone> Clone for SecMem<S> {
674 fn clone(&self) -> Self {
675 self.access(|s| Self::new(s.clone()))
676 }
677}
678
679impl<S: Zeroize + fmt::Debug> fmt::Debug for SecMem<S> {
680 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
681 write!(f, "SecMem<{}>([REDACTED])", std::any::type_name::<S>())
682 }
683}
684
685impl<S: Zeroize> Zeroize for SecMem<S> {
686 fn zeroize(&mut self) {
687 let size = size_of::<S>();
688 if size == 0 {
689 return;
690 }
691
692 if secure_mprotect(self.mapping.as_ptr(), self.mapping_size, PROT_READ | PROT_WRITE) != 0 {
694 println!("[SECURITY WARNING] Failed to enable write protection during zeroization");
695 return;
696 }
697
698 #[cfg(feature = "encryption")]
699 if self.is_encrypted {
700 let secret_bytes = unsafe {
701 std::slice::from_raw_parts_mut(self.secret_ptr.as_ptr() as *mut u8, size)
702 };
703 encrypt_decrypt_memory(secret_bytes, &self.nonce);
704 self.is_encrypted = false; }
706
707 unsafe { self.secret_ptr.as_mut().zeroize();
709 flush_cache(self.secret_ptr.as_ptr() as *const u8, size);
710 }
711
712 if secure_mprotect(self.mapping.as_ptr(), self.mapping_size, PROT_NONE) != 0 {
714 println!("[SECURITY WARNING] Failed to restore protection after zeroization");
715 let _ = unsafe { mlock(self.mapping.as_ptr(), self.mapping_size) };
716 }
717 }
718}
719
720impl<S: Zeroize> ZeroizeOnDrop for SecMem<S> {}
721
722unsafe impl<S: Zeroize + Send> Send for SecMem<S> {}
723unsafe impl<S: Zeroize + Sync> Sync for SecMem<S> {}
724
725fn get_cache_line_size() -> usize {
736 CACHE_LINE_SIZE.get_or_init(|| {
737 #[cfg(target_os = "linux")]
738 {
739 match unsafe { sysconf(_SC_LEVEL1_DCACHE_LINESIZE) } {
741 size if size > 0 => size as usize,
742 _ => fallback_cache_line_size(),
743 }
744 }
745
746 #[cfg(not(target_os = "linux"))]
747 {
748 fallback_cache_line_size()
749 }
750 }).to_owned() }
752
753const fn fallback_cache_line_size() -> usize {
755 64
757}
758
759unsafe fn flush_cache(ptr: *const u8, len: usize) {
766 if len == 0 {
767 return;
768 }
769
770 let alignment = get_cache_line_size();
772 if !(ptr as usize).is_multiple_of(alignment) {
773 panic!("Pointer is not aligned to cache line boundary");
774 }
775
776 #[cfg(target_arch = "x86_64")]
777 {
778 use core::arch::x86_64::_mm_clflush;
779
780 let mut addr = ptr as usize;
781 let end_addr = addr.saturating_add(len);
782
783 while addr < end_addr {
784 unsafe { _mm_clflush(addr as *const _) };
785 addr = addr.saturating_add(alignment);
786 }
787 core::sync::atomic::compiler_fence(Ordering::SeqCst);
788 }
789
790 #[cfg(target_arch = "x86")]
791 {
792 use core::arch::x86::_mm_clflush;
793
794 let mut addr = ptr as usize;
795 let end_addr = addr.saturating_add(len);
796
797 while addr < end_addr {
798 unsafe { _mm_clflush(addr as *const _) };
799 addr = addr.saturating_add(alignment);
800 }
801 core::sync::atomic::compiler_fence(Ordering::SeqCst);
802 }
803
804 #[cfg(target_arch = "aarch64")]
805 {
806 let mut addr = ptr as usize;
807 let end_addr = addr.saturating_add(len);
808
809 while addr < end_addr {
810 unsafe { core::arch::asm!("dc cvau, {}", in(reg) addr) };
811 addr = addr.saturating_add(alignment);
812 }
813 unsafe {
814 core::arch::asm!("dsb ish");
815 core::arch::asm!("isb");
816 }
817 }
818
819 #[cfg(not(any(target_arch = "x86", target_arch = "x86_64", target_arch = "aarch64")))]
820 {
821 let _ = (ptr, len);
823 }
825}
826
827pub trait SecretAccess<T: Zeroize + ?Sized> {
833 fn access<F, R>(&self, f: F) -> R
835 where
836 F: FnOnce(&T) -> R;
837
838 fn access_mut<F, R>(&mut self, f: F) -> R
840 where
841 F: FnOnce(&mut T) -> R;
842}
843
844impl<const N: usize> SecretAccess<[u8]> for SecMem<[u8; N]> {
848 fn access<F, R>(&self, f: F) -> R
849 where
850 F: FnOnce(&[u8]) -> R,
851 {
852 self.access(|array| f(array.as_slice()))
853 }
854
855 fn access_mut<F, R>(&mut self, f: F) -> R
856 where
857 F: FnOnce(&mut [u8]) -> R,
858 {
859 self.access_mut(|array| f(array.as_mut_slice()))
860 }
861}
862
863impl SecretAccess<[u8]> for SecMem<Vec<u8>> {
865 fn access<F, R>(&self, f: F) -> R
866 where
867 F: FnOnce(&[u8]) -> R,
868 {
869 self.access(|vec| f(vec.as_slice()))
870 }
871
872 fn access_mut<F, R>(&mut self, f: F) -> R
873 where
874 F: FnOnce(&mut [u8]) -> R,
875 {
876 self.access_mut(|vec| f(vec.as_mut_slice()))
877 }
878}
879
880impl<const N: usize> SecretAccess<[u8]> for [u8; N] {
882 fn access<F, R>(&self, f: F) -> R
883 where
884 F: FnOnce(&[u8]) -> R,
885 {
886 f(self.as_slice())
887 }
888
889 fn access_mut<F, R>(&mut self, f: F) -> R
890 where
891 F: FnOnce(&mut [u8]) -> R,
892 {
893 f(self.as_mut_slice())
894 }
895}
896
897impl SecretAccess<[u8]> for &[u8] {
899 fn access<F, R>(&self, f: F) -> R
900 where
901 F: FnOnce(&[u8]) -> R,
902 {
903 f(self)
904 }
905
906 fn access_mut<F, R>(&mut self, _f: F) -> R
907 where
908 F: FnOnce(&mut [u8]) -> R,
909 {
910 panic!("Cannot get mutable access through immutable reference")
911 }
912}
913
914impl SecretAccess<[u8]> for &mut [u8] {
916 fn access<F, R>(&self, f: F) -> R
917 where
918 F: FnOnce(&[u8]) -> R,
919 {
920 f(self)
921 }
922
923 fn access_mut<F, R>(&mut self, f: F) -> R
924 where
925 F: FnOnce(&mut [u8]) -> R,
926 {
927 f(self)
928 }
929}
930
931impl SecretAccess<[u8]> for Box<[u8]> {
933 fn access<F, R>(&self, f: F) -> R
934 where
935 F: FnOnce(&[u8]) -> R,
936 {
937 f(self.as_ref())
938 }
939
940 fn access_mut<F, R>(&mut self, f: F) -> R
941 where
942 F: FnOnce(&mut [u8]) -> R,
943 {
944 f(self.as_mut())
945 }
946}
947
948impl<T: Zeroize> SecretAccess<T> for SecMem<T> {
954 fn access<F, R>(&self, f: F) -> R
955 where
956 F: FnOnce(&T) -> R,
957 {
958 self.access(f)
959 }
960
961 fn access_mut<F, R>(&mut self, f: F) -> R
962 where
963 F: FnOnce(&mut T) -> R,
964 {
965 self.access_mut(f)
966 }
967}
968
969impl<T: Zeroize, const N: usize> SecretAccess<[T; N]> for [T; N] {
971 fn access<F, R>(&self, f: F) -> R
972 where
973 F: FnOnce(&[T; N]) -> R,
974 {
975 f(self)
976 }
977
978 fn access_mut<F, R>(&mut self, f: F) -> R
979 where
980 F: FnOnce(&mut [T; N]) -> R,
981 {
982 f(self)
983 }
984}
985
986impl<T: Zeroize> SecretAccess<[T]> for [T] where [T]: Zeroize {
988 fn access<F, R>(&self, f: F) -> R
989 where
990 F: FnOnce(&[T]) -> R,
991 {
992 f(self)
993 }
994
995 fn access_mut<F, R>(&mut self, f: F) -> R
996 where
997 F: FnOnce(&mut [T]) -> R,
998 {
999 f(self)
1000 }
1001}
1002
1003impl<T: Zeroize> SecretAccess<[T]> for Vec<T> where [T]: Zeroize {
1005 fn access<F, R>(&self, f: F) -> R
1006 where
1007 F: FnOnce(&[T]) -> R,
1008 {
1009 f(self.as_slice())
1010 }
1011
1012 fn access_mut<F, R>(&mut self, f: F) -> R
1013 where
1014 F: FnOnce(&mut [T]) -> R,
1015 {
1016 f(self.as_mut_slice())
1017 }
1018}
1019
1020impl<T: Zeroize> SecretAccess<T> for Box<T> {
1022 fn access<F, R>(&self, f: F) -> R
1023 where
1024 F: FnOnce(&T) -> R,
1025 {
1026 f(self.as_ref())
1027 }
1028
1029 fn access_mut<F, R>(&mut self, f: F) -> R
1030 where
1031 F: FnOnce(&mut T) -> R,
1032 {
1033 f(self.as_mut())
1034 }
1035}
1036
1037#[cfg(feature = "encryption")]
1042struct KeyStorage {
1043 ptr: *mut u8,
1044 size: usize,
1045}
1046
1047#[cfg(feature = "encryption")]
1048unsafe impl Sync for KeyStorage {}
1049#[cfg(feature = "encryption")]
1050unsafe impl Send for KeyStorage {}
1051
1052#[cfg(feature = "encryption")]
1053impl KeyStorage {
1054 fn new_uninitialized() -> Self {
1055 let size = 32;
1056 let mut ptr = libc::MAP_FAILED;
1057
1058 #[cfg(target_os = "linux")]
1059 unsafe {
1060 let fd = libc::syscall(447, 0);
1062 if fd >= 0 {
1063 if libc::ftruncate(fd as i32, size as libc::off_t) == 0 {
1064 ptr = libc::mmap(
1065 std::ptr::null_mut(),
1066 size,
1067 libc::PROT_READ | libc::PROT_WRITE,
1068 libc::MAP_SHARED,
1069 fd as i32,
1070 0,
1071 );
1072 }
1073 libc::close(fd as i32);
1074 }
1075 }
1076
1077 if ptr == libc::MAP_FAILED {
1078 unsafe {
1079 let flags = libc::MAP_PRIVATE | libc::MAP_ANONYMOUS;
1080 ptr = libc::mmap(
1081 std::ptr::null_mut(),
1082 size,
1083 libc::PROT_READ | libc::PROT_WRITE,
1084 flags,
1085 -1,
1086 0,
1087 );
1088 if ptr == libc::MAP_FAILED {
1089 panic!("Failed to allocate global encryption key segment");
1090 }
1091 libc::mlock(ptr, size);
1092 #[cfg(target_os = "linux")]
1093 libc::madvise(ptr, size, libc::MADV_DONTDUMP);
1094 #[cfg(target_os = "linux")]
1095 libc::madvise(ptr, size, libc::MADV_DONTFORK);
1096 }
1097 }
1098
1099 Self {
1100 ptr: ptr as *mut u8,
1101 size,
1102 }
1103 }
1104}
1105
1106#[cfg(feature = "encryption")]
1107struct SplitKeyStorage {
1108 mask: KeyStorage,
1109 blinded: KeyStorage,
1110}
1111
1112#[cfg(feature = "encryption")]
1113impl SplitKeyStorage {
1114 fn new() -> Self {
1115 let mask = KeyStorage::new_uninitialized();
1116 let blinded = KeyStorage::new_uninitialized();
1117
1118 let mut k = [0u8; 32];
1119 let mut m = [0u8; 32];
1120
1121 get_random_bytes(&mut k);
1122 get_random_bytes(&mut m);
1123
1124 let mut b = [0u8; 32];
1125 for i in 0..32 {
1126 b[i] = k[i] ^ m[i];
1127 }
1128
1129 unsafe {
1130 std::ptr::copy_nonoverlapping(m.as_ptr(), mask.ptr, 32);
1131 std::ptr::copy_nonoverlapping(b.as_ptr(), blinded.ptr, 32);
1132
1133 libc::mprotect(mask.ptr as *mut libc::c_void, mask.size, libc::PROT_READ);
1134 libc::mprotect(blinded.ptr as *mut libc::c_void, blinded.size, libc::PROT_READ);
1135 }
1136
1137 zeroize::Zeroize::zeroize(&mut k);
1138 zeroize::Zeroize::zeroize(&mut m);
1139 zeroize::Zeroize::zeroize(&mut b);
1140
1141 Self { mask, blinded }
1142 }
1143}
1144
1145#[cfg(feature = "encryption")]
1146fn get_split_keys() -> &'static SplitKeyStorage {
1147 static GLOBAL_SPLIT_KEY: std::sync::OnceLock<SplitKeyStorage> = std::sync::OnceLock::new();
1148 GLOBAL_SPLIT_KEY.get_or_init(SplitKeyStorage::new)
1149}
1150
1151#[cfg(feature = "encryption")]
1152fn get_random_bytes(buf: &mut [u8]) {
1153 #[cfg(target_os = "linux")]
1154 unsafe {
1155 let mut total = 0;
1156 while total < buf.len() {
1157 let res = libc::getrandom(
1158 buf.as_mut_ptr().add(total) as *mut libc::c_void,
1159 buf.len() - total,
1160 0,
1161 );
1162 if res > 0 {
1163 total += res as usize;
1164 } else {
1165 use std::fs::File;
1167 use std::io::Read;
1168 let mut file = File::open("/dev/urandom").expect("Failed to open /dev/urandom");
1169 file.read_exact(buf).expect("Failed to read random bytes");
1170 return;
1171 }
1172 }
1173 }
1174
1175 #[cfg(not(target_os = "linux"))]
1176 {
1177 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 }
1182}
1183
1184#[cfg(feature = "encryption")]
1185fn encrypt_decrypt_memory(data: &mut [u8], nonce: &[u8; 12]) {
1186 use chacha20::cipher::{KeyIvInit, StreamCipher};
1187 use chacha20::ChaCha20;
1188
1189 let storage = get_split_keys();
1190 let mut key = [0u8; 32];
1191
1192 unsafe {
1193 let m = &*(storage.mask.ptr as *const [u8; 32]);
1194 let b = &*(storage.blinded.ptr as *const [u8; 32]);
1195
1196 for i in 0..32 {
1198 key[i] = m[i] ^ b[i];
1199 }
1200 }
1201
1202 let mut cipher = ChaCha20::new((&key).into(), nonce.into());
1203 cipher.apply_keystream(data);
1204
1205 key.zeroize();
1207}