Skip to main content

strat9_kernel/
audit.rs

1//! Kernel audit log for security-sensitive operations.
2//!
3//! Records events like silo creation/destruction, privilege changes,
4//! capability violations, and syscall denials into a fixed-size ring
5//! buffer queryable via the `audit` shell command.
6
7use crate::sync::SpinLock;
8use alloc::{string::String, vec::Vec};
9
10const AUDIT_CAPACITY: usize = 512;
11
12/// Categories of auditable events.
13#[derive(Debug, Clone, Copy)]
14pub enum AuditCategory {
15    /// Silo lifecycle (create, destroy, suspend, resume).
16    Silo,
17    /// Capability grant, revoke, or violation.
18    Capability,
19    /// Syscall denied or restricted.
20    Syscall,
21    /// Process lifecycle (exec, fork, exit).
22    Process,
23    /// Security policy change (pledge, unveil, sandbox).
24    Security,
25}
26
27/// A single audit log entry.
28#[derive(Clone)]
29pub struct AuditEntry {
30    /// Monotonic sequence number.
31    pub seq: u64,
32    /// Kernel tick at which the event was recorded.
33    pub tick: u64,
34    /// PID of the task that triggered the event.
35    pub pid: u32,
36    /// Silo ID (0 if none).
37    pub silo_id: u32,
38    /// Event category.
39    pub category: AuditCategory,
40    /// Short human-readable description.
41    pub message: String,
42}
43
44struct AuditLog {
45    entries: [Option<AuditEntry>; AUDIT_CAPACITY],
46    head: usize,
47    count: usize,
48    next_seq: u64,
49}
50
51impl AuditLog {
52    const fn new() -> Self {
53        const NONE: Option<AuditEntry> = None;
54        Self {
55            entries: [NONE; AUDIT_CAPACITY],
56            head: 0,
57            count: 0,
58            next_seq: 1,
59        }
60    }
61
62    fn push(&mut self, category: AuditCategory, pid: u32, silo_id: u32, message: String) {
63        let tick = crate::process::scheduler::ticks();
64        let seq = self.next_seq;
65        self.next_seq += 1;
66
67        let entry = AuditEntry {
68            seq,
69            tick,
70            pid,
71            silo_id,
72            category,
73            message,
74        };
75        let idx = (self.head + self.count) % AUDIT_CAPACITY;
76        self.entries[idx] = Some(entry);
77        if self.count < AUDIT_CAPACITY {
78            self.count += 1;
79        } else {
80            self.head = (self.head + 1) % AUDIT_CAPACITY;
81        }
82    }
83
84    fn entries_newest(&self, n: usize) -> Vec<AuditEntry> {
85        let take = n.min(self.count);
86        let mut out = Vec::with_capacity(take);
87        let start = if self.count > take {
88            (self.head + self.count - take) % AUDIT_CAPACITY
89        } else {
90            self.head
91        };
92        for i in 0..take {
93            let idx = (start + i) % AUDIT_CAPACITY;
94            if let Some(e) = &self.entries[idx] {
95                out.push(e.clone());
96            }
97        }
98        out
99    }
100}
101
102static AUDIT: SpinLock<AuditLog> = SpinLock::new(AuditLog::new());
103
104/// Record an audit event.
105///
106/// Called from various kernel subsystems when security-relevant
107/// operations occur (silo creation, capability changes, etc.).
108pub fn log(category: AuditCategory, pid: u32, silo_id: u32, message: String) {
109    AUDIT.lock().push(category, pid, silo_id, message);
110}
111
112/// Retrieve the most recent `n` audit entries.
113pub fn recent(n: usize) -> Vec<AuditEntry> {
114    AUDIT.lock().entries_newest(n)
115}
116
117/// Return total number of audit events recorded since boot.
118pub fn total_count() -> u64 {
119    AUDIT.lock().next_seq - 1
120}