veridian_kernel/security/
stack_canary.rs1use alloc::collections::BTreeMap;
34use core::sync::atomic::{AtomicBool, AtomicU64, Ordering};
35
36use spin::RwLock;
37
38use crate::error::KernelError;
39
40struct Xorshift64 {
50 state: u64,
51}
52
53impl Xorshift64 {
54 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 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
76fn get_canary_entropy() -> u64 {
82 #[cfg(target_arch = "x86_64")]
83 {
84 if rdrand_available() {
86 if let Some(val) = rdrand64() {
87 return val;
88 }
89 }
90 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 0xBAAD_F00D_DEAD_BEEF
112 }
113}
114
115#[cfg(target_arch = "x86_64")]
118fn rdrand_available() -> bool {
119 let ecx: u32;
120 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 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 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#[cfg(target_arch = "aarch64")]
177fn read_cntpct() -> u64 {
178 let cnt: u64;
179 unsafe {
181 core::arch::asm!(
182 "mrs {}, cntpct_el0",
183 out(reg) cnt,
184 options(nomem, nostack),
185 );
186 }
187 cnt
188}
189
190#[cfg(target_arch = "riscv64")]
193fn read_cycle() -> u64 {
194 let cycles: u64;
195 unsafe {
197 core::arch::asm!(
198 "rdcycle {}",
199 out(reg) cycles,
200 options(nomem, nostack),
201 );
202 }
203 cycles
204}
205
206static CANARY_TABLE: RwLock<Option<BTreeMap<u64, u64>>> = RwLock::new(None);
212
213static CANARY_INITIALIZED: AtomicBool = AtomicBool::new(false);
215
216static CANARY_PRNG_STATE: AtomicU64 = AtomicU64::new(0);
219
220static CANARIES_GENERATED: AtomicU64 = AtomicU64::new(0);
222
223static CANARY_CHECKS: AtomicU64 = AtomicU64::new(0);
225
226static CANARY_VIOLATIONS: AtomicU64 = AtomicU64::new(0);
228
229pub 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 let seed = get_canary_entropy();
247 let mut rng = Xorshift64::new(seed);
248 let _ = rng.next();
250 let _ = rng.next();
251 CANARY_PRNG_STATE.store(rng.next(), Ordering::Release);
252
253 {
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
264pub fn generate_canary() -> u64 {
270 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 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 return if value == 0 {
285 0xDEAD_BEEF_CAFE_BABE
286 } else {
287 value
288 };
289 }
290 }
292}
293
294pub 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
318pub 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
331pub 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 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
370pub 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
390pub 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
403pub fn is_active() -> bool {
405 CANARY_INITIALIZED.load(Ordering::Acquire)
406}
407
408pub 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
419pub 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#[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 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 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 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 {
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 let result = check_canary(9999);
558 assert!(result.is_err());
559 }
560
561 #[test]
562 fn test_stats_tracking() {
563 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}