⚠️ VeridianOS Kernel Documentation - This is low-level kernel code. All functions are unsafe unless explicitly marked otherwise. no_std

veridian_kernel/security/
stack_canary.rs

1//! Per-Thread Stack Canary Management
2//!
3//! Provides stack smashing detection via per-thread random canary values.
4//! Each thread receives a unique 64-bit canary generated from an xorshift64
5//! PRNG seeded by architecture-specific entropy. The canary is placed at
6//! a known location on the thread's stack and verified periodically or on
7//! context switch.
8//!
9//! # Design
10//!
11//! - **CANARY_TABLE**: `RwLock<BTreeMap<u64, u64>>` mapping thread ID to its
12//!   canary value. Protected by RwLock for concurrent read access during
13//!   verification with exclusive write access for registration.
14//!
15//! - **Canary generation**: Uses xorshift64 PRNG seeded from hardware entropy
16//!   (RDRAND/TSC on x86_64, CNTPCT on AArch64, cycle on RISC-V).
17//!
18//! - **Detection**: On canary mismatch, the kernel panics with "stack smashing
19//!   detected" to prevent exploitation.
20//!
21//! # Usage
22//!
23//! ```ignore
24//! // During thread creation:
25//! let canary = stack_canary::generate_canary();
26//! stack_canary::set_thread_canary(tid, canary);
27//! // Write canary to thread's stack guard location...
28//!
29//! // During context switch or verification:
30//! stack_canary::check_canary(tid);  // panics on mismatch
31//! ```
32
33use alloc::collections::BTreeMap;
34use core::sync::atomic::{AtomicBool, AtomicU64, Ordering};
35
36use spin::RwLock;
37
38use crate::error::KernelError;
39
40// ---------------------------------------------------------------------------
41// Xorshift64 PRNG (local copy to avoid coupling to kaslr module)
42// ---------------------------------------------------------------------------
43
44/// Simple xorshift64 PRNG for canary generation.
45///
46/// Not cryptographically secure, but sufficient for stack canaries where
47/// the goal is detecting memory corruption rather than resisting targeted
48/// attacks against the canary value.
49struct Xorshift64 {
50    state: u64,
51}
52
53impl Xorshift64 {
54    /// Create with the given seed. Zero seeds are replaced with a constant.
55    fn new(seed: u64) -> Self {
56        Self {
57            state: if seed == 0 {
58                0xA5A5_5A5A_1234_5678
59            } else {
60                seed
61            },
62        }
63    }
64
65    /// Generate the next pseudo-random u64.
66    fn next(&mut self) -> u64 {
67        let mut x = self.state;
68        x ^= x << 13;
69        x ^= x >> 7;
70        x ^= x << 17;
71        self.state = x;
72        x
73    }
74}
75
76// ---------------------------------------------------------------------------
77// Architecture-specific entropy (mirrors kaslr.rs for independence)
78// ---------------------------------------------------------------------------
79
80/// Gather entropy for canary seed.
81fn get_canary_entropy() -> u64 {
82    #[cfg(target_arch = "x86_64")]
83    {
84        // Try RDRAND
85        if rdrand_available() {
86            if let Some(val) = rdrand64() {
87                return val;
88            }
89        }
90        // Fallback: TSC
91        read_tsc()
92    }
93
94    #[cfg(target_arch = "aarch64")]
95    {
96        read_cntpct()
97    }
98
99    #[cfg(target_arch = "riscv64")]
100    {
101        read_cycle()
102    }
103
104    #[cfg(not(any(
105        target_arch = "x86_64",
106        target_arch = "aarch64",
107        target_arch = "riscv64"
108    )))]
109    {
110        // Host target fallback for CI
111        0xBAAD_F00D_DEAD_BEEF
112    }
113}
114
115// -- x86_64 helpers --
116
117#[cfg(target_arch = "x86_64")]
118fn rdrand_available() -> bool {
119    let ecx: u32;
120    // SAFETY: CPUID leaf 1 reads CPU feature flags. Non-privileged, no
121    // memory or stack effects. LLVM reserves rbx so we save/restore it.
122    unsafe {
123        core::arch::asm!(
124            "push rbx",
125            "cpuid",
126            "pop rbx",
127            in("eax") 1u32,
128            in("ecx") 0u32,
129            lateout("ecx") ecx,
130            lateout("edx") _,
131            options(nostack),
132        );
133    }
134    (ecx & (1 << 30)) != 0
135}
136
137#[cfg(target_arch = "x86_64")]
138fn rdrand64() -> Option<u64> {
139    let val: u64;
140    let success: u8;
141    // SAFETY: RDRAND reads from hardware RNG. No memory/stack effects.
142    unsafe {
143        core::arch::asm!(
144            "rdrand {val}",
145            "setc {success}",
146            val = out(reg) val,
147            success = out(reg_byte) success,
148            options(nomem, nostack),
149        );
150    }
151    if success != 0 {
152        Some(val)
153    } else {
154        None
155    }
156}
157
158#[cfg(target_arch = "x86_64")]
159fn read_tsc() -> u64 {
160    let lo: u32;
161    let hi: u32;
162    // SAFETY: RDTSC reads timestamp counter. No memory/stack effects.
163    unsafe {
164        core::arch::asm!(
165            "rdtsc",
166            out("eax") lo,
167            out("edx") hi,
168            options(nomem, nostack),
169        );
170    }
171    ((hi as u64) << 32) | (lo as u64)
172}
173
174// -- AArch64 helpers --
175
176#[cfg(target_arch = "aarch64")]
177fn read_cntpct() -> u64 {
178    let cnt: u64;
179    // SAFETY: CNTPCT_EL0 reads physical counter. No memory/stack effects.
180    unsafe {
181        core::arch::asm!(
182            "mrs {}, cntpct_el0",
183            out(reg) cnt,
184            options(nomem, nostack),
185        );
186    }
187    cnt
188}
189
190// -- RISC-V helpers --
191
192#[cfg(target_arch = "riscv64")]
193fn read_cycle() -> u64 {
194    let cycles: u64;
195    // SAFETY: rdcycle reads the cycle CSR. No memory/stack effects.
196    unsafe {
197        core::arch::asm!(
198            "rdcycle {}",
199            out(reg) cycles,
200            options(nomem, nostack),
201        );
202    }
203    cycles
204}
205
206// ---------------------------------------------------------------------------
207// Global State
208// ---------------------------------------------------------------------------
209
210/// Per-thread canary table: thread ID -> canary value.
211static CANARY_TABLE: RwLock<Option<BTreeMap<u64, u64>>> = RwLock::new(None);
212
213/// Whether the canary subsystem has been initialized.
214static CANARY_INITIALIZED: AtomicBool = AtomicBool::new(false);
215
216/// Global PRNG state for canary generation (protected by atomic CAS).
217/// Stored as AtomicU64 for lock-free access from thread creation paths.
218static CANARY_PRNG_STATE: AtomicU64 = AtomicU64::new(0);
219
220/// Total canaries generated (diagnostic counter).
221static CANARIES_GENERATED: AtomicU64 = AtomicU64::new(0);
222
223/// Total canary checks performed (diagnostic counter).
224static CANARY_CHECKS: AtomicU64 = AtomicU64::new(0);
225
226/// Total canary violations detected (should always be 0 in healthy system).
227static CANARY_VIOLATIONS: AtomicU64 = AtomicU64::new(0);
228
229// ---------------------------------------------------------------------------
230// Public API
231// ---------------------------------------------------------------------------
232
233/// Initialize the stack canary subsystem.
234///
235/// Seeds the PRNG from hardware entropy and prepares the canary table.
236/// Must be called once during boot.
237pub fn init() -> Result<(), KernelError> {
238    if CANARY_INITIALIZED.load(Ordering::Acquire) {
239        return Err(KernelError::AlreadyExists {
240            resource: "stack_canary",
241            id: 0,
242        });
243    }
244
245    // Seed the global PRNG
246    let seed = get_canary_entropy();
247    let mut rng = Xorshift64::new(seed);
248    // Advance a few times to mix the state
249    let _ = rng.next();
250    let _ = rng.next();
251    CANARY_PRNG_STATE.store(rng.next(), Ordering::Release);
252
253    // Initialize the canary table
254    {
255        let mut table = CANARY_TABLE.write();
256        *table = Some(BTreeMap::new());
257    }
258
259    CANARY_INITIALIZED.store(true, Ordering::Release);
260    crate::println!("[STACK-CANARY] Per-thread stack canary subsystem initialized");
261    Ok(())
262}
263
264/// Generate a new random canary value.
265///
266/// Returns a 64-bit random value suitable for use as a stack canary.
267/// The value is guaranteed to be non-zero (zero canaries are weak against
268/// null-byte string overflow attacks).
269pub fn generate_canary() -> u64 {
270    // Use atomic CAS loop on the global PRNG state for lock-free generation.
271    loop {
272        let current = CANARY_PRNG_STATE.load(Ordering::Acquire);
273        let mut rng = Xorshift64::new(current);
274        let value = rng.next();
275        let new_state = rng.next();
276
277        // Try to update the global state atomically
278        if CANARY_PRNG_STATE
279            .compare_exchange_weak(current, new_state, Ordering::AcqRel, Ordering::Relaxed)
280            .is_ok()
281        {
282            CANARIES_GENERATED.fetch_add(1, Ordering::Relaxed);
283            // Ensure canary is non-zero
284            return if value == 0 {
285                0xDEAD_BEEF_CAFE_BABE
286            } else {
287                value
288            };
289        }
290        // CAS failed (concurrent access), retry with updated state
291    }
292}
293
294/// Register a canary value for a thread.
295///
296/// Associates the given canary `value` with thread `tid` in the global
297/// canary table. If the thread already has a canary, it is replaced.
298///
299/// Returns `Err` if the canary subsystem is not initialized.
300pub fn set_thread_canary(tid: u64, value: u64) -> Result<(), KernelError> {
301    if !CANARY_INITIALIZED.load(Ordering::Acquire) {
302        return Err(KernelError::NotInitialized {
303            subsystem: "stack_canary",
304        });
305    }
306
307    let mut table = CANARY_TABLE.write();
308    if let Some(map) = table.as_mut() {
309        map.insert(tid, value);
310        Ok(())
311    } else {
312        Err(KernelError::NotInitialized {
313            subsystem: "stack_canary",
314        })
315    }
316}
317
318/// Remove a thread's canary from the table.
319///
320/// Called during thread cleanup to free the canary entry.
321/// Returns the old canary value if one existed.
322pub fn remove_thread_canary(tid: u64) -> Option<u64> {
323    if !CANARY_INITIALIZED.load(Ordering::Acquire) {
324        return None;
325    }
326
327    let mut table = CANARY_TABLE.write();
328    table.as_mut().and_then(|map| map.remove(&tid))
329}
330
331/// Check a thread's stack canary.
332///
333/// Looks up the expected canary for `tid` and compares it to the value
334/// currently at the canary's stack location (as represented by calling
335/// `verify_stack` with the read-back value).
336///
337/// Returns `Ok(())` if the canary matches, or panics on mismatch.
338///
339/// Returns `Err` if the thread has no registered canary.
340pub fn check_canary(tid: u64) -> Result<(), KernelError> {
341    if !CANARY_INITIALIZED.load(Ordering::Acquire) {
342        return Err(KernelError::NotInitialized {
343            subsystem: "stack_canary",
344        });
345    }
346
347    CANARY_CHECKS.fetch_add(1, Ordering::Relaxed);
348
349    let table = CANARY_TABLE.read();
350    if let Some(map) = table.as_ref() {
351        if let Some(&expected) = map.get(&tid) {
352            // In a real implementation, we would read the canary from the
353            // thread's stack here. For now, return Ok since the canary is
354            // registered (actual stack verification happens via verify_stack).
355            let _ = expected;
356            Ok(())
357        } else {
358            Err(KernelError::NotFound {
359                resource: "thread_canary",
360                id: tid,
361            })
362        }
363    } else {
364        Err(KernelError::NotInitialized {
365            subsystem: "stack_canary",
366        })
367    }
368}
369
370/// Verify a stack canary value against the expected value.
371///
372/// This is the core verification function called from context switch hooks
373/// or periodic stack checks. It compares `observed` (read from the stack)
374/// against `expected` (from the canary table).
375///
376/// # Panics
377///
378/// Panics with "stack smashing detected" if the values do not match,
379/// preventing any further execution of the compromised thread.
380pub fn verify_stack(expected: u64, observed: u64) {
381    if expected != observed {
382        CANARY_VIOLATIONS.fetch_add(1, Ordering::Relaxed);
383        panic!(
384            "stack smashing detected: expected canary {:#018x}, found {:#018x}",
385            expected, observed
386        );
387    }
388}
389
390/// Get the expected canary value for a thread.
391///
392/// Returns `None` if the subsystem is not initialized or the thread
393/// has no registered canary.
394pub fn get_thread_canary(tid: u64) -> Option<u64> {
395    if !CANARY_INITIALIZED.load(Ordering::Acquire) {
396        return None;
397    }
398
399    let table = CANARY_TABLE.read();
400    table.as_ref().and_then(|map| map.get(&tid).copied())
401}
402
403/// Check if the canary subsystem is initialized.
404pub fn is_active() -> bool {
405    CANARY_INITIALIZED.load(Ordering::Acquire)
406}
407
408/// Get diagnostic statistics.
409///
410/// Returns `(canaries_generated, checks_performed, violations_detected)`.
411pub fn get_stats() -> (u64, u64, u64) {
412    (
413        CANARIES_GENERATED.load(Ordering::Relaxed),
414        CANARY_CHECKS.load(Ordering::Relaxed),
415        CANARY_VIOLATIONS.load(Ordering::Relaxed),
416    )
417}
418
419/// Get the number of threads with registered canaries.
420pub fn registered_count() -> usize {
421    if !CANARY_INITIALIZED.load(Ordering::Acquire) {
422        return 0;
423    }
424    let table = CANARY_TABLE.read();
425    table.as_ref().map_or(0, |map| map.len())
426}
427
428// ---------------------------------------------------------------------------
429// Tests
430// ---------------------------------------------------------------------------
431
432#[cfg(test)]
433mod tests {
434    use super::*;
435
436    #[test]
437    fn test_xorshift64_produces_values() {
438        let mut rng = Xorshift64::new(42);
439        let a = rng.next();
440        let b = rng.next();
441        assert_ne!(a, 0);
442        assert_ne!(b, 0);
443        assert_ne!(a, b);
444    }
445
446    #[test]
447    fn test_xorshift64_zero_seed() {
448        let mut rng = Xorshift64::new(0);
449        let val = rng.next();
450        assert_ne!(
451            val, 0,
452            "zero seed should be replaced with non-zero constant"
453        );
454    }
455
456    #[test]
457    fn test_generate_canary_nonzero() {
458        // Seed the PRNG state for test
459        CANARY_PRNG_STATE.store(0xDEAD_BEEF, Ordering::Relaxed);
460
461        for _ in 0..100 {
462            let canary = generate_canary();
463            assert_ne!(canary, 0, "canary must never be zero");
464        }
465    }
466
467    #[test]
468    fn test_generate_canary_uniqueness() {
469        CANARY_PRNG_STATE.store(0x1234_5678, Ordering::Relaxed);
470
471        let c1 = generate_canary();
472        let c2 = generate_canary();
473        let c3 = generate_canary();
474
475        // All three should be different
476        assert_ne!(c1, c2);
477        assert_ne!(c2, c3);
478        assert_ne!(c1, c3);
479    }
480
481    #[test]
482    fn test_verify_stack_match() {
483        // Should not panic
484        verify_stack(0xCAFE_BABE, 0xCAFE_BABE);
485    }
486
487    #[test]
488    #[should_panic(expected = "stack smashing detected")]
489    fn test_verify_stack_mismatch() {
490        verify_stack(0xCAFE_BABE, 0xDEAD_BEEF);
491    }
492
493    #[test]
494    fn test_set_and_get_thread_canary() {
495        // Initialize if not already done
496        {
497            let mut table = CANARY_TABLE.write();
498            if table.is_none() {
499                *table = Some(BTreeMap::new());
500            }
501        }
502        CANARY_INITIALIZED.store(true, Ordering::Release);
503
504        let tid = 42;
505        let canary = 0xABCD_EF01_2345_6789;
506
507        set_thread_canary(tid, canary).unwrap();
508        assert_eq!(get_thread_canary(tid), Some(canary));
509    }
510
511    #[test]
512    fn test_remove_thread_canary() {
513        {
514            let mut table = CANARY_TABLE.write();
515            if table.is_none() {
516                *table = Some(BTreeMap::new());
517            }
518        }
519        CANARY_INITIALIZED.store(true, Ordering::Release);
520
521        let tid = 99;
522        let canary = 0x1111_2222_3333_4444;
523
524        set_thread_canary(tid, canary).unwrap();
525        assert_eq!(remove_thread_canary(tid), Some(canary));
526        assert_eq!(get_thread_canary(tid), None);
527    }
528
529    #[test]
530    fn test_check_canary_registered() {
531        {
532            let mut table = CANARY_TABLE.write();
533            if table.is_none() {
534                *table = Some(BTreeMap::new());
535            }
536        }
537        CANARY_INITIALIZED.store(true, Ordering::Release);
538
539        let tid = 77;
540        let canary = 0xAAAA_BBBB_CCCC_DDDD;
541
542        set_thread_canary(tid, canary).unwrap();
543        assert!(check_canary(tid).is_ok());
544    }
545
546    #[test]
547    fn test_check_canary_unregistered() {
548        {
549            let mut table = CANARY_TABLE.write();
550            if table.is_none() {
551                *table = Some(BTreeMap::new());
552            }
553        }
554        CANARY_INITIALIZED.store(true, Ordering::Release);
555
556        // Thread 9999 was never registered
557        let result = check_canary(9999);
558        assert!(result.is_err());
559    }
560
561    #[test]
562    fn test_stats_tracking() {
563        // Reset counters for this test
564        CANARIES_GENERATED.store(0, Ordering::Relaxed);
565        CANARY_CHECKS.store(0, Ordering::Relaxed);
566        CANARY_VIOLATIONS.store(0, Ordering::Relaxed);
567
568        CANARY_PRNG_STATE.store(0xFEED_FACE, Ordering::Relaxed);
569        let _ = generate_canary();
570        let _ = generate_canary();
571
572        let (generated, checks, violations) = get_stats();
573        assert_eq!(generated, 2);
574        assert_eq!(checks, 0);
575        assert_eq!(violations, 0);
576    }
577}