Skip to main content

sec_mem/
sec_mem.rs

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// Platform-specific imports
18#[cfg(target_os = "linux")]
19use libc::{sysconf, _SC_LEVEL1_DCACHE_LINESIZE};
20
21// --------------------------------------------------------------------------------
22// Global Constants and Statics
23// --------------------------------------------------------------------------------
24
25/// Cache line size for the current platform (initialized once)
26static CACHE_LINE_SIZE: std::sync::OnceLock<usize> = std::sync::OnceLock::new();
27
28/// Global counter for tracking lock IDs
29static GLOBAL_LOCK_COUNTER: AtomicUsize = AtomicUsize::new(0);
30
31/// Platform-specific MAP_LOCKED flag
32#[cfg(target_os = "linux")]
33const MAP_LOCKED: i32 = libc::MAP_LOCKED;
34#[cfg(not(target_os = "linux"))]
35const MAP_LOCKED: i32 = 0;
36
37// --------------------------------------------------------------------------------
38// Helper Types
39// --------------------------------------------------------------------------------
40
41/// RAII wrapper for memory-mapped regions
42struct MmapRegion {
43    ptr: NonNull<libc::c_void>,
44    size: usize,
45    #[cfg(target_os = "linux")]
46    locked: bool,
47}
48
49impl MmapRegion {
50    /// Creates a new memory-mapped region with specified protection
51    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        // On Linux, try to use MAP_LOCKED for atomic allocation+locking
57        #[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        // Fallback for Linux if MAP_LOCKED fails
65        #[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    /// Returns raw pointer to the mapped region
96    #[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            // Only zero memory if it was writable
106            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
124/// RAII guard for temporary memory protection changes
125struct ProtectionGuard {
126    mapping: *mut libc::c_void,
127    size: usize,
128    original_prot: i32,
129}
130
131impl ProtectionGuard {
132    /// Creates new guard and sets specified protection
133    fn new(mapping: *mut libc::c_void, size: usize) -> Self {
134        // Since SecMem always keeps memory at PROT_NONE when not accessed,
135        // we can safely assume we should restore to PROT_NONE.
136        let original_prot = PROT_NONE;
137        
138        let guard = Self {
139            mapping,
140            size,
141            original_prot,
142        };
143        
144        // Only change protection if we have something to protect
145        if size > 0 {
146            unsafe { set_pkey_rights(get_global_pkey(), 0); } // 0 = allow read/write
147            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); } // 3 = disable access
163        }
164    }
165}
166
167// --------------------------------------------------------------------------------
168// Global Hardening
169// --------------------------------------------------------------------------------
170
171/// Hardens the entire process against memory dumping and tracing (Linux-only).
172/// This applies `prctl(PR_SET_DUMPABLE, 0)` to disable `ptrace` and core dumps globally.
173pub fn harden_process() -> Result<(), &'static str> {
174    #[cfg(target_os = "linux")]
175    unsafe {
176        // PR_SET_DUMPABLE = 4
177        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// --------------------------------------------------------------------------------
190// MPK (Memory Protection Keys) Module
191// --------------------------------------------------------------------------------
192
193#[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            // Allocate a key with PKEY_DISABLE_ACCESS (1) | PKEY_DISABLE_WRITE (2) = 3
199            let pkey = libc::syscall(330, 0, 3); // SYS_pkey_alloc for x86_64
200            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    // Fallback for non-x86_64
238}
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 }; // SYS_pkey_mprotect
245        if res == 0 {
246            return 0;
247        }
248    }
249    unsafe { mprotect(addr, len, prot) }
250}
251
252// --------------------------------------------------------------------------------
253// Main SecMem Implementation
254// --------------------------------------------------------------------------------
255
256/// A hardware-accelerated secure memory container for isolating sensitive data from the OS.
257///
258/// `SecMem` uses raw Linux syscalls (`mmap`, `mprotect`, `mseal`, `mlock`) and Intel Memory Protection Keys
259/// (MPK/`WRPKRU`) to physically isolate cryptographic keys or passwords from the rest of the process.
260/// It strictly guards against buffer overflows via `mseal`ed guard pages, and actively prevents memory
261/// dumping using `MADV_DONTDUMP`, `MADV_DONTFORK`, and `PR_SET_DUMPABLE(0)`.
262///
263/// When the `encryption` feature is enabled, data is permanently blinded at rest in RAM using ChaCha20. 
264/// It utilizes OpenSSH-style Key Shielding, splitting a massive 16 KiB pre-key buffer across kernel `memfd_secret` 
265/// mappings and dynamically deriving the true key using `blake3`. This mathematically neutralizes `/proc/self/mem` 
266/// dumping, physical cold-boot attacks, and side-channel exploits like Spectre or Rowhammer.
267///
268/// # Examples
269/// ```
270/// use sec_mem::SecMem;
271///
272/// // Create a highly protected memory vault
273/// let mut secure_key = SecMem::new([0xAAu8; 32]);
274///
275/// // Access is granted exclusively within this closure bounds
276/// secure_key.access_mut(|key| {
277///     key[0] = 0xBB;
278///     // Data is processed here...
279/// }); 
280/// // Instantly upon closure exit, memory is zeroized, re-encrypted, and hardware-locked.
281/// ```
282pub struct SecMem<S: Zeroize> {
283    // Memory mapping details
284    mapping: NonNull<libc::c_void>,
285    mapping_size: usize,
286
287    // Secret data pointer
288    secret_ptr: NonNull<S>,
289
290    // Guard pages
291    guard_pages: [NonNull<libc::c_void>; 2],
292    guard_page_size: usize,
293
294    // Debug checks
295    #[cfg(debug_assertions)]
296    canary: u64,
297
298    // Access control
299    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    /// Creates a new protected secret
311    pub fn new(secret: S) -> Self {
312        // Handle zero-sized types specially
313        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        // Calculate allocation size (round up to nearest page)
336        let alloc_size = (size + page_size - 1) & !(page_size - 1);
337        let total_size = alloc_size + 2 * page_size;
338
339        // Allocate a single contiguous protected memory region
340        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        // Make the middle region writable for initialization
347        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        // Additional memory hardening applied to the entire region (including guard pages)
352        // to prevent core dumps and inheriting memory across forks.
353        #[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        // Initialize secret
374        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        // Lock down memory after initialization
391        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        // Extract pointers before forgetting regions
397        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    /// Access secret for reading
417    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); } // Hardware Lock ON
437
438                #[cfg(feature = "encryption")]
439                if self.sec_mem.is_encrypted {
440                    unsafe { set_pkey_rights(get_global_pkey(), 0); } // Temporarily unlock for encryption
441                    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); } // Hardware Lock ON
446                }
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); // Must be writable for decryption
463        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    /// Access secret for mutation
484    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    /// Constant-time equality comparison
538    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    /// Explicitly applies Linux memory sealing (`mseal`) to the guard pages.
558    /// 
559    /// **WARNING:** `mseal` permanently locks the memory region's permissions. 
560    /// Because Linux prevents `munmap` on sealed memory, calling this method 
561    /// means the guard pages (and thus the allocation) will leak and remain in RAM 
562    /// until the process terminates. It should only be used for global, long-lived 
563    /// singletons (like root application keys).
564    /// Requires Linux 6.10+. Fails silently on older kernels.
565    pub fn seal_guard_pages(&self) {
566        #[cfg(target_os = "linux")]
567        unsafe {
568            // SYS_mseal = 462 for x86_64, aarch64, etc.
569            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    // -------------------------------------------------------------------------
575    // Internal Helpers
576    // -------------------------------------------------------------------------
577
578    /// Debug check for memory corruption
579    #[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    /// Updates memory protection flags
590    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
601// --------------------------------------------------------------------------------
602// Trait Implementations
603// --------------------------------------------------------------------------------
604
605impl<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        // Check for double-free
613        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        // Enable write access for secure cleanup
625        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        // Securely erase secret
644        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        // Clean up memory
654        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        // Temporarily enable write access
695        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; // Prevent re-decryption on Drop
707        }
708
709        // Securely erase contents
710        unsafe { self.secret_ptr.as_mut().zeroize();
711            flush_cache(self.secret_ptr.as_ptr() as *const u8, size);
712        }
713
714        // Restore protection
715        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
727// --------------------------------------------------------------------------------
728// Custom Implementations
729// --------------------------------------------------------------------------------
730
731
732// --------------------------------------------------------------------------------
733// Utility Functions
734// --------------------------------------------------------------------------------
735
736/// Gets platform-specific cache line size with proper error handling
737fn get_cache_line_size() -> usize {
738    CACHE_LINE_SIZE.get_or_init(|| {
739        #[cfg(target_os = "linux")]
740        {
741            // Safe because we're reading a system constant
742            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()  // Returns a copy of the value
753}
754
755/// Default cache line size when detection fails
756const fn fallback_cache_line_size() -> usize {
757    // Common cache line sizes: 64 (x86), 128 (ARM), 64 (others)
758    64
759}
760
761/// Safely flushes cache for the given memory region
762///
763/// # Safety
764/// - `ptr` must be valid for reads of `len` bytes
765/// - `ptr` must be properly aligned
766/// - The memory region must not be concurrently modified
767unsafe fn flush_cache(ptr: *const u8, len: usize) {
768    if len == 0 {
769        return;
770    }
771
772    // Validate pointer alignment
773    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        // Explicitly ignore parameters to prevent unused warnings
824        let _ = (ptr, len);
825        // No-op on unsupported architectures
826    }
827}
828
829// --------------------------------------------------------------------------------
830// Uniform Access Trait
831// --------------------------------------------------------------------------------
832
833/// Trait for types that can provide access to underlying data, whether protected or raw
834pub trait SecretAccess<T: Zeroize + ?Sized> {
835    /// Access the underlying data for reading
836    fn access<F, R>(&self, f: F) -> R
837    where
838        F: FnOnce(&T) -> R;
839    
840    /// Access the underlying data for mutation
841    fn access_mut<F, R>(&mut self, f: F) -> R
842    where
843        F: FnOnce(&mut T) -> R;
844}
845
846// Primary Implementations for [u8] (Most Common Case)
847
848// SecMem<[u8; N]> as [u8]
849impl<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
865// SecMem<Vec<u8>> as [u8]
866impl 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
882// [u8; N] as [u8]
883impl<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
899// &[u8] as [u8]
900impl 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
916// &mut [u8] as [u8]
917impl 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
933// Box<[u8]> as [u8]
934impl 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
950// --------------------------------------------------------------------------------
951// Fallback Implementations for Exact Types (Remove conflicting ones)
952// --------------------------------------------------------------------------------
953
954// SecMem<T> as T (fallback for non-u8 types)
955impl<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
971// [T; N] as [T; N] (fallback for non-u8 arrays)
972impl<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
988// [T] as [T] (fallback for non-u8 slices)
989impl<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
1005// Vec<T> as [T] (fallback for non-u8 vectors)
1006impl<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
1022// Box<T> as T (fallback)
1023impl<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// --------------------------------------------------------------------------------
1040// Encryption-at-Rest Module
1041// --------------------------------------------------------------------------------
1042
1043#[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; // 16 KiB for Key Shielding against Spectre/Rowhammer
1059        #[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            // SYS_memfd_secret = 447 (x86_64, aarch64)
1067            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                // Fallback to /dev/urandom
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                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            // Reconstruct the 16 KiB pre-key buffer directly into a local Vec
1213            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            // Reconstruct the true key K = M ^ B directly into a local stack register
1221            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        // Hash the 16 KiB buffer with Blake3 to derive the true 32-byte ChaCha20 key
1230        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    // Explicitly zeroize the reconstructed key and temporary pre-key buffer
1239    key.zeroize();
1240    #[cfg(feature = "key_shielding")]
1241    combined.zeroize();
1242}