1use alloc::{format, string::String};
16use core::sync::atomic::{AtomicBool, AtomicU64, AtomicUsize, Ordering};
17
18use spin::Mutex;
19
20use crate::error::KernelError;
21
22#[derive(Debug, Clone, Copy, PartialEq, Eq)]
28#[repr(u8)]
29pub enum AuditEventType {
30 ProcessCreate = 0,
32 ProcessExit = 1,
34 FileAccess = 2,
36 NetworkConnect = 3,
38 AuthAttempt = 4,
40 PermissionDenied = 5,
42 Syscall = 6,
44 CapabilityOp = 7,
46 MacDecision = 8,
48 PrivilegeEscalation = 9,
50 SecurityConfigChange = 10,
52}
53
54impl AuditEventType {
55 pub fn to_flag(self) -> u32 {
57 1u32 << (self as u8)
58 }
59
60 pub fn as_str(self) -> &'static str {
62 match self {
63 Self::ProcessCreate => "PROCESS_CREATE",
64 Self::ProcessExit => "PROCESS_EXIT",
65 Self::FileAccess => "FILE_ACCESS",
66 Self::NetworkConnect => "NETWORK_CONNECT",
67 Self::AuthAttempt => "AUTH_ATTEMPT",
68 Self::PermissionDenied => "PERMISSION_DENIED",
69 Self::Syscall => "SYSCALL",
70 Self::CapabilityOp => "CAPABILITY_OP",
71 Self::MacDecision => "MAC_DECISION",
72 Self::PrivilegeEscalation => "PRIVILEGE_ESCALATION",
73 Self::SecurityConfigChange => "SECURITY_CONFIG_CHANGE",
74 }
75 }
76}
77
78#[derive(Debug, Clone, Copy, PartialEq, Eq)]
84pub enum AuditAction {
85 Create,
87 Exit,
89 Read,
91 Write,
93 Execute,
95 Delete,
97 Login,
99 Logout,
101 CapCreate,
103 CapRevoke,
105 CapDerive,
107 MacAllow,
109 MacDeny,
111 Escalate,
113 ConfigChange,
115 Other,
117}
118
119impl AuditAction {
120 pub fn as_str(self) -> &'static str {
122 match self {
123 Self::Create => "CREATE",
124 Self::Exit => "EXIT",
125 Self::Read => "READ",
126 Self::Write => "WRITE",
127 Self::Execute => "EXECUTE",
128 Self::Delete => "DELETE",
129 Self::Login => "LOGIN",
130 Self::Logout => "LOGOUT",
131 Self::CapCreate => "CAP_CREATE",
132 Self::CapRevoke => "CAP_REVOKE",
133 Self::CapDerive => "CAP_DERIVE",
134 Self::MacAllow => "MAC_ALLOW",
135 Self::MacDeny => "MAC_DENY",
136 Self::Escalate => "ESCALATE",
137 Self::ConfigChange => "CONFIG_CHANGE",
138 Self::Other => "OTHER",
139 }
140 }
141}
142
143#[derive(Debug, Clone)]
151pub struct AuditEvent {
152 pub event_type: AuditEventType,
154 pub timestamp: u64,
156 pub pid: u64,
158 pub uid: u32,
160 pub action: AuditAction,
162 pub target: String,
164 pub result: bool,
166 pub extra_data: String,
168}
169
170impl AuditEvent {
171 pub fn new(
173 event_type: AuditEventType,
174 pid: u64,
175 uid: u32,
176 action: AuditAction,
177 target: &str,
178 result: bool,
179 extra_data: &str,
180 ) -> Self {
181 Self {
182 event_type,
183 timestamp: get_audit_timestamp(),
184 pid,
185 uid,
186 action,
187 target: String::from(target),
188 result,
189 extra_data: String::from(extra_data),
190 }
191 }
192
193 pub fn from_legacy(
196 event_type: AuditEventType,
197 pid: u64,
198 uid: u32,
199 result_code: i32,
200 data: u64,
201 ) -> Self {
202 let action = match event_type {
203 AuditEventType::ProcessCreate => AuditAction::Create,
204 AuditEventType::ProcessExit => AuditAction::Exit,
205 AuditEventType::FileAccess => AuditAction::Read,
206 AuditEventType::AuthAttempt => AuditAction::Login,
207 AuditEventType::PermissionDenied => AuditAction::MacDeny,
208 AuditEventType::CapabilityOp => AuditAction::CapCreate,
209 _ => AuditAction::Other,
210 };
211
212 Self {
213 event_type,
214 timestamp: get_audit_timestamp(),
215 pid,
216 uid,
217 action,
218 target: format!("data:{:#x}", data),
219 result: result_code == 0,
220 extra_data: format!("result_code:{}", result_code),
221 }
222 }
223
224 pub fn serialize(&self) -> String {
228 format!(
229 "{}|{}|{}|{}|{}|{}|{}|{}\n",
230 self.timestamp,
231 self.event_type.as_str(),
232 self.pid,
233 self.uid,
234 self.action.as_str(),
235 self.target,
236 if self.result { "OK" } else { "FAIL" },
237 self.extra_data,
238 )
239 }
240}
241
242#[derive(Debug, Clone, Copy)]
251pub struct AuditFilter {
252 pub enabled_types: u32,
254}
255
256impl AuditFilter {
257 pub const fn allow_all() -> Self {
259 Self {
260 enabled_types: 0xFFFF_FFFF,
261 }
262 }
263
264 pub const fn deny_all() -> Self {
266 Self { enabled_types: 0 }
267 }
268
269 pub const fn from_mask(mask: u32) -> Self {
271 Self {
272 enabled_types: mask,
273 }
274 }
275
276 pub fn is_enabled(&self, event_type: AuditEventType) -> bool {
278 (self.enabled_types & event_type.to_flag()) != 0
279 }
280
281 pub fn enable(&mut self, event_type: AuditEventType) {
283 self.enabled_types |= event_type.to_flag();
284 }
285
286 pub fn disable(&mut self, event_type: AuditEventType) {
288 self.enabled_types &= !event_type.to_flag();
289 }
290}
291
292pub trait AlertCallback: Send + Sync {
302 fn on_alert(&self, event: &AuditEvent);
304}
305
306const MAX_ALERT_CALLBACKS: usize = 8;
308
309struct AuditStats {
315 total_events: AtomicU64,
317 filtered_events: AtomicU64,
319 persisted_events: AtomicU64,
321 alerts_triggered: AtomicU64,
323 per_type_counts: [AtomicU64; 16],
325}
326
327impl AuditStats {
328 const fn new() -> Self {
329 Self {
330 total_events: AtomicU64::new(0),
331 filtered_events: AtomicU64::new(0),
332 persisted_events: AtomicU64::new(0),
333 alerts_triggered: AtomicU64::new(0),
334 per_type_counts: [
335 AtomicU64::new(0),
336 AtomicU64::new(0),
337 AtomicU64::new(0),
338 AtomicU64::new(0),
339 AtomicU64::new(0),
340 AtomicU64::new(0),
341 AtomicU64::new(0),
342 AtomicU64::new(0),
343 AtomicU64::new(0),
344 AtomicU64::new(0),
345 AtomicU64::new(0),
346 AtomicU64::new(0),
347 AtomicU64::new(0),
348 AtomicU64::new(0),
349 AtomicU64::new(0),
350 AtomicU64::new(0),
351 ],
352 }
353 }
354
355 fn record_event(&self, event_type: AuditEventType) {
356 self.total_events.fetch_add(1, Ordering::Relaxed);
357 let idx = event_type as usize;
358 if idx < self.per_type_counts.len() {
359 self.per_type_counts[idx].fetch_add(1, Ordering::Relaxed);
360 }
361 }
362
363 fn record_filtered(&self) {
364 self.filtered_events.fetch_add(1, Ordering::Relaxed);
365 }
366
367 fn record_persisted(&self) {
368 self.persisted_events.fetch_add(1, Ordering::Relaxed);
369 }
370
371 fn record_alert(&self) {
372 self.alerts_triggered.fetch_add(1, Ordering::Relaxed);
373 }
374}
375
376const MAX_AUDIT_LOG: usize = 4096;
382
383static AUDIT_LOG: Mutex<[Option<AuditEvent>; MAX_AUDIT_LOG]> =
385 Mutex::new([const { None }; MAX_AUDIT_LOG]);
386static AUDIT_HEAD: AtomicUsize = AtomicUsize::new(0);
387static AUDIT_COUNT: AtomicUsize = AtomicUsize::new(0);
388static AUDIT_ENABLED: AtomicBool = AtomicBool::new(false);
389
390static AUDIT_FILTER: Mutex<AuditFilter> = Mutex::new(AuditFilter::allow_all());
392
393static ALERT_CALLBACKS: Mutex<[Option<&'static dyn AlertCallback>; MAX_ALERT_CALLBACKS]> =
395 Mutex::new([None; MAX_ALERT_CALLBACKS]);
396
397static AUDIT_STATS: AuditStats = AuditStats::new();
399
400const AUDIT_LOG_PATH: &str = "/var/log/audit.log";
402
403fn get_audit_timestamp() -> u64 {
409 #[cfg(any(
410 target_arch = "x86_64",
411 target_arch = "aarch64",
412 target_arch = "riscv64"
413 ))]
414 {
415 crate::arch::timer::get_timestamp_secs()
416 }
417 #[cfg(not(any(
418 target_arch = "x86_64",
419 target_arch = "aarch64",
420 target_arch = "riscv64"
421 )))]
422 {
423 0
424 }
425}
426
427pub fn log_event(event: AuditEvent) {
441 if !AUDIT_ENABLED.load(Ordering::Acquire) {
442 return;
443 }
444
445 let filter = match AUDIT_FILTER.try_lock() {
449 Some(f) => f,
450 None => {
451 AUDIT_STATS.record_filtered();
453 return;
454 }
455 };
456
457 if !filter.is_enabled(event.event_type) {
458 AUDIT_STATS.record_filtered();
459 return;
460 }
461 drop(filter); AUDIT_STATS.record_event(event.event_type);
465
466 if is_critical_event(&event) {
468 fire_alerts(&event);
469 }
470
471 persist_event(&event);
473
474 if let Some(mut log) = AUDIT_LOG.try_lock() {
477 let head = AUDIT_HEAD.load(Ordering::Relaxed);
478 log[head] = Some(event);
479 AUDIT_HEAD.store((head + 1) % MAX_AUDIT_LOG, Ordering::Relaxed);
480
481 let count = AUDIT_COUNT.load(Ordering::Relaxed);
482 if count < MAX_AUDIT_LOG {
483 AUDIT_COUNT.store(count + 1, Ordering::Relaxed);
484 }
485 }
486 }
488
489fn is_critical_event(event: &AuditEvent) -> bool {
491 matches!(
492 event.event_type,
493 AuditEventType::PermissionDenied
494 | AuditEventType::PrivilegeEscalation
495 | AuditEventType::SecurityConfigChange
496 ) || (event.event_type == AuditEventType::AuthAttempt && !event.result)
497}
498
499fn fire_alerts(event: &AuditEvent) {
501 let callbacks = ALERT_CALLBACKS.lock();
502 for cb in callbacks.iter().flatten() {
503 cb.on_alert(event);
504 AUDIT_STATS.record_alert();
505 }
506}
507
508fn persist_event(event: &AuditEvent) {
510 let serialized = event.serialize();
511 let bytes = serialized.as_bytes();
512
513 #[cfg(not(test))]
515 {
516 if crate::fs::append_file(AUDIT_LOG_PATH, bytes).is_ok() {
519 AUDIT_STATS.record_persisted();
520 }
521 }
522 #[cfg(test)]
523 let _ = bytes;
524}
525
526pub fn log_process_create(pid: u64, uid: u32, result: i32) {
532 log_event(AuditEvent::new(
533 AuditEventType::ProcessCreate,
534 pid,
535 uid,
536 AuditAction::Create,
537 "process",
538 result == 0,
539 "",
540 ));
541}
542
543pub fn log_process_exit(pid: u64, exit_code: i32) {
545 log_event(AuditEvent::new(
546 AuditEventType::ProcessExit,
547 pid,
548 0,
549 AuditAction::Exit,
550 "process",
551 true,
552 &format!("exit_code:{}", exit_code),
553 ));
554}
555
556pub fn log_file_access(pid: u64, uid: u32, path_hash: u64, access_type: u32) {
558 let action = match access_type {
559 0 => AuditAction::Read,
560 1 => AuditAction::Write,
561 2 => AuditAction::Execute,
562 _ => AuditAction::Other,
563 };
564 log_event(AuditEvent::new(
565 AuditEventType::FileAccess,
566 pid,
567 uid,
568 action,
569 &format!("file:{:#x}", path_hash),
570 true,
571 "",
572 ));
573}
574
575pub fn log_permission_denied(pid: u64, uid: u32, target: &str) {
577 log_event(AuditEvent::new(
578 AuditEventType::PermissionDenied,
579 pid,
580 uid,
581 AuditAction::MacDeny,
582 target,
583 false,
584 "denied",
585 ));
586}
587
588pub fn log_capability_op(pid: u64, cap_id: u64, result: i32) {
590 log_event(AuditEvent::new(
591 AuditEventType::CapabilityOp,
592 pid,
593 0,
594 AuditAction::CapCreate,
595 &format!("cap:{:#x}", cap_id),
596 result == 0,
597 "",
598 ));
599}
600
601pub fn log_syscall(pid: u64, uid: u32, syscall_nr: usize, result: bool) {
605 log_event(AuditEvent::new(
606 AuditEventType::Syscall,
607 pid,
608 uid,
609 AuditAction::Other,
610 &format!("syscall:{}", syscall_nr),
611 result,
612 "",
613 ));
614}
615
616pub fn log_capability(pid: u64, cap_id: u64, action: AuditAction, result: bool) {
618 log_event(AuditEvent::new(
619 AuditEventType::CapabilityOp,
620 pid,
621 0,
622 action,
623 &format!("cap:{:#x}", cap_id),
624 result,
625 "",
626 ));
627}
628
629pub fn log_mac_decision(
631 pid: u64,
632 uid: u32,
633 source_type: &str,
634 target_type: &str,
635 access: &str,
636 allowed: bool,
637) {
638 let action = if allowed {
639 AuditAction::MacAllow
640 } else {
641 AuditAction::MacDeny
642 };
643 log_event(AuditEvent::new(
644 AuditEventType::MacDecision,
645 pid,
646 uid,
647 action,
648 &format!("{}:{}", source_type, target_type),
649 allowed,
650 access,
651 ));
652}
653
654pub fn log_auth_attempt(pid: u64, uid: u32, username: &str, success: bool) {
656 log_event(AuditEvent::new(
657 AuditEventType::AuthAttempt,
658 pid,
659 uid,
660 AuditAction::Login,
661 username,
662 success,
663 if success {
664 "login_success"
665 } else {
666 "login_failure"
667 },
668 ));
669}
670
671pub fn register_alert_callback(callback: &'static dyn AlertCallback) -> Result<(), KernelError> {
682 let mut callbacks = ALERT_CALLBACKS.lock();
683 for slot in callbacks.iter_mut() {
684 if slot.is_none() {
685 *slot = Some(callback);
686 return Ok(());
687 }
688 }
689 Err(KernelError::ResourceExhausted {
690 resource: "audit alert callbacks",
691 })
692}
693
694pub fn set_filter(filter: AuditFilter) {
700 let mut f = AUDIT_FILTER.lock();
701 *f = filter;
702}
703
704pub fn get_filter() -> AuditFilter {
706 *AUDIT_FILTER.lock()
707}
708
709pub fn enable_event_type(event_type: AuditEventType) {
711 let mut f = AUDIT_FILTER.lock();
712 f.enable(event_type);
713}
714
715pub fn disable_event_type(event_type: AuditEventType) {
717 let mut f = AUDIT_FILTER.lock();
718 f.disable(event_type);
719}
720
721pub fn get_stats() -> (usize, usize) {
727 (AUDIT_COUNT.load(Ordering::Relaxed), MAX_AUDIT_LOG)
728}
729
730pub fn get_detailed_stats() -> AuditStatistics {
732 AuditStatistics {
733 total_events: AUDIT_STATS.total_events.load(Ordering::Relaxed),
734 filtered_events: AUDIT_STATS.filtered_events.load(Ordering::Relaxed),
735 persisted_events: AUDIT_STATS.persisted_events.load(Ordering::Relaxed),
736 alerts_triggered: AUDIT_STATS.alerts_triggered.load(Ordering::Relaxed),
737 buffer_count: AUDIT_COUNT.load(Ordering::Relaxed) as u64,
738 buffer_capacity: MAX_AUDIT_LOG as u64,
739 }
740}
741
742#[derive(Debug, Clone, Copy)]
744pub struct AuditStatistics {
745 pub total_events: u64,
747 pub filtered_events: u64,
749 pub persisted_events: u64,
751 pub alerts_triggered: u64,
753 pub buffer_count: u64,
755 pub buffer_capacity: u64,
757}
758
759pub fn enable() {
765 AUDIT_ENABLED.store(true, Ordering::Release);
766 println!("[AUDIT] Audit logging enabled");
767}
768
769pub fn disable() {
771 AUDIT_ENABLED.store(false, Ordering::Release);
772 println!("[AUDIT] Audit logging disabled");
773}
774
775pub fn init() -> Result<(), KernelError> {
781 println!("[AUDIT] Initializing audit framework...");
782
783 AUDIT_HEAD.store(0, Ordering::Relaxed);
785 AUDIT_COUNT.store(0, Ordering::Relaxed);
786
787 set_filter(AuditFilter::allow_all());
789
790 let _ = ensure_audit_log_dir();
794
795 enable();
797
798 println!("[AUDIT] Audit framework initialized");
799 Ok(())
800}
801
802fn ensure_audit_log_dir() -> Result<(), KernelError> {
804 if let Some(vfs_lock) = crate::fs::try_get_vfs() {
807 let vfs = vfs_lock.read();
808 let perms = crate::fs::Permissions::default();
809 let _ = vfs.mkdir("/var", perms);
810 let _ = vfs.mkdir("/var/log", perms);
811 }
812 Ok(())
813}
814
815fn _simple_hash(s: &str) -> u64 {
821 let mut hash = 0u64;
822 for byte in s.bytes() {
823 hash = hash.wrapping_mul(31).wrapping_add(byte as u64);
824 }
825 hash
826}
827
828#[cfg(test)]
833mod tests {
834 use super::*;
835
836 #[test]
837 fn test_audit_event() {
838 let event = AuditEvent::new(
839 AuditEventType::ProcessCreate,
840 123,
841 1000,
842 AuditAction::Create,
843 "process",
844 true,
845 "",
846 );
847 assert_eq!(event.pid, 123);
848 assert_eq!(event.uid, 1000);
849 assert!(event.result);
850 }
851
852 #[test]
853 fn test_log_event() {
854 enable(); log_process_create(456, 1000, 0);
856 let (count, _) = get_stats();
857 assert!(count > 0);
858 }
859
860 #[test]
861 fn test_audit_filter() {
862 let mut filter = AuditFilter::deny_all();
863 assert!(!filter.is_enabled(AuditEventType::ProcessCreate));
864
865 filter.enable(AuditEventType::ProcessCreate);
866 assert!(filter.is_enabled(AuditEventType::ProcessCreate));
867 assert!(!filter.is_enabled(AuditEventType::FileAccess));
868
869 filter.disable(AuditEventType::ProcessCreate);
870 assert!(!filter.is_enabled(AuditEventType::ProcessCreate));
871 }
872
873 #[test]
874 fn test_event_serialization() {
875 let event = AuditEvent::new(
876 AuditEventType::FileAccess,
877 42,
878 1000,
879 AuditAction::Read,
880 "/etc/passwd",
881 true,
882 "mode:0644",
883 );
884 let serialized = event.serialize();
885 assert!(serialized.contains("FILE_ACCESS"));
886 assert!(serialized.contains("42"));
887 assert!(serialized.contains("READ"));
888 assert!(serialized.contains("/etc/passwd"));
889 assert!(serialized.contains("OK"));
890 }
891}