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 XOR/ChaCha20 blinded at rest in RAM,
264/// mathematically neutralizing `/proc/self/mem` exploit attempts or physical cold-boot attacks.
265///
266/// # Examples
267/// ```
268/// use sec_mem::SecMem;
269///
270/// // Create a highly protected memory vault
271/// let mut secure_key = SecMem::new([0xAAu8; 32]);
272///
273/// // Access is granted exclusively within this closure bounds
274/// secure_key.access_mut(|key| {
275///     key[0] = 0xBB;
276///     // Data is processed here...
277/// }); 
278/// // Instantly upon closure exit, memory is zeroized, re-encrypted, and hardware-locked.
279/// ```
280pub struct SecMem<S: Zeroize> {
281    // Memory mapping details
282    mapping: NonNull<libc::c_void>,
283    mapping_size: usize,
284
285    // Secret data pointer
286    secret_ptr: NonNull<S>,
287
288    // Guard pages
289    guard_pages: [NonNull<libc::c_void>; 2],
290    guard_page_size: usize,
291
292    // Debug checks
293    #[cfg(debug_assertions)]
294    canary: u64,
295
296    // Access control
297    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    /// Creates a new protected secret
309    pub fn new(secret: S) -> Self {
310        // Handle zero-sized types specially
311        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        // Calculate allocation size (round up to nearest page)
334        let alloc_size = (size + page_size - 1) & !(page_size - 1);
335        let total_size = alloc_size + 2 * page_size;
336
337        // Allocate a single contiguous protected memory region
338        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        // Make the middle region writable for initialization
345        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        // Additional memory hardening applied to the entire region (including guard pages)
350        // to prevent core dumps and inheriting memory across forks.
351        #[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        // Initialize secret
372        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        // Lock down memory after initialization
389        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        // Extract pointers before forgetting regions
395        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    /// Access secret for reading
415    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); } // Hardware Lock ON
435
436                #[cfg(feature = "encryption")]
437                if self.sec_mem.is_encrypted {
438                    unsafe { set_pkey_rights(get_global_pkey(), 0); } // Temporarily unlock for encryption
439                    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); } // Hardware Lock ON
444                }
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); // Must be writable for decryption
461        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    /// Access secret for mutation
482    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    /// Constant-time equality comparison
536    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    /// Explicitly applies Linux memory sealing (`mseal`) to the guard pages.
556    /// 
557    /// **WARNING:** `mseal` permanently locks the memory region's permissions. 
558    /// Because Linux prevents `munmap` on sealed memory, calling this method 
559    /// means the guard pages (and thus the allocation) will leak and remain in RAM 
560    /// until the process terminates. It should only be used for global, long-lived 
561    /// singletons (like root application keys).
562    /// Requires Linux 6.10+. Fails silently on older kernels.
563    pub fn seal_guard_pages(&self) {
564        #[cfg(target_os = "linux")]
565        unsafe {
566            // SYS_mseal = 462 for x86_64, aarch64, etc.
567            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    // -------------------------------------------------------------------------
573    // Internal Helpers
574    // -------------------------------------------------------------------------
575
576    /// Debug check for memory corruption
577    #[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    /// Updates memory protection flags
588    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
599// --------------------------------------------------------------------------------
600// Trait Implementations
601// --------------------------------------------------------------------------------
602
603impl<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        // Check for double-free
611        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        // Enable write access for secure cleanup
623        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        // Securely erase secret
642        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        // Clean up memory
652        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        // Temporarily enable write access
693        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; // Prevent re-decryption on Drop
705        }
706
707        // Securely erase contents
708        unsafe { self.secret_ptr.as_mut().zeroize();
709            flush_cache(self.secret_ptr.as_ptr() as *const u8, size);
710        }
711
712        // Restore protection
713        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
725// --------------------------------------------------------------------------------
726// Custom Implementations
727// --------------------------------------------------------------------------------
728
729
730// --------------------------------------------------------------------------------
731// Utility Functions
732// --------------------------------------------------------------------------------
733
734/// Gets platform-specific cache line size with proper error handling
735fn get_cache_line_size() -> usize {
736    CACHE_LINE_SIZE.get_or_init(|| {
737        #[cfg(target_os = "linux")]
738        {
739            // Safe because we're reading a system constant
740            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()  // Returns a copy of the value
751}
752
753/// Default cache line size when detection fails
754const fn fallback_cache_line_size() -> usize {
755    // Common cache line sizes: 64 (x86), 128 (ARM), 64 (others)
756    64
757}
758
759/// Safely flushes cache for the given memory region
760///
761/// # Safety
762/// - `ptr` must be valid for reads of `len` bytes
763/// - `ptr` must be properly aligned
764/// - The memory region must not be concurrently modified
765unsafe fn flush_cache(ptr: *const u8, len: usize) {
766    if len == 0 {
767        return;
768    }
769
770    // Validate pointer alignment
771    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        // Explicitly ignore parameters to prevent unused warnings
822        let _ = (ptr, len);
823        // No-op on unsupported architectures
824    }
825}
826
827// --------------------------------------------------------------------------------
828// Uniform Access Trait
829// --------------------------------------------------------------------------------
830
831/// Trait for types that can provide access to underlying data, whether protected or raw
832pub trait SecretAccess<T: Zeroize + ?Sized> {
833    /// Access the underlying data for reading
834    fn access<F, R>(&self, f: F) -> R
835    where
836        F: FnOnce(&T) -> R;
837    
838    /// Access the underlying data for mutation
839    fn access_mut<F, R>(&mut self, f: F) -> R
840    where
841        F: FnOnce(&mut T) -> R;
842}
843
844// Primary Implementations for [u8] (Most Common Case)
845
846// SecMem<[u8; N]> as [u8]
847impl<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
863// SecMem<Vec<u8>> as [u8]
864impl 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
880// [u8; N] as [u8]
881impl<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
897// &[u8] as [u8]
898impl 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
914// &mut [u8] as [u8]
915impl 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
931// Box<[u8]> as [u8]
932impl 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
948// --------------------------------------------------------------------------------
949// Fallback Implementations for Exact Types (Remove conflicting ones)
950// --------------------------------------------------------------------------------
951
952// SecMem<T> as T (fallback for non-u8 types)
953impl<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
969// [T; N] as [T; N] (fallback for non-u8 arrays)
970impl<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
986// [T] as [T] (fallback for non-u8 slices)
987impl<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
1003// Vec<T> as [T] (fallback for non-u8 vectors)
1004impl<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
1020// Box<T> as T (fallback)
1021impl<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// --------------------------------------------------------------------------------
1038// Encryption-at-Rest Module
1039// --------------------------------------------------------------------------------
1040
1041#[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            // SYS_memfd_secret = 447 (x86_64, aarch64)
1061            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                // Fallback to /dev/urandom
1166                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        // Reconstruct the true key K = M ^ B directly into a local stack register
1197        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    // Explicitly zeroize the reconstructed key from stack
1206    key.zeroize();
1207}