Skip to main content

memf_linux/
netlink_audit.rs

1//! Audit rule suppression / netlink audit tamper detection.
2
3use memf_core::object_reader::ObjectReader;
4use memf_format::PhysicalMemoryProvider;
5
6use crate::types::AuditTamperInfo;
7use crate::Result;
8
9/// Classify whether the kernel audit subsystem has been tampered with by
10/// comparing the expected audit daemon PID against the PID that actually
11/// owns the audit netlink socket.
12///
13/// Returns `true` if `expected_pid != actual_pid`, indicating that the
14/// netlink socket has been hijacked or the audit daemon has been replaced.
15pub fn is_audit_tampered(expected_pid: u32, actual_pid: u32) -> bool {
16    expected_pid != actual_pid
17}
18
19/// Scan for audit subsystem tampering.
20///
21/// Returns `Ok(vec![])` as a stub until full implementation is added.
22pub fn scan_audit_tampering<P: PhysicalMemoryProvider>(
23    reader: &ObjectReader<P>,
24) -> Result<Vec<AuditTamperInfo>> {
25    let _ = reader;
26    Ok(vec![])
27}
28
29#[cfg(test)]
30mod tests {
31    use super::*;
32    use memf_core::test_builders::PageTableBuilder;
33    use memf_core::vas::{TranslationMode, VirtualAddressSpace};
34    use memf_symbols::isf::IsfResolver;
35    use memf_symbols::test_builders::IsfBuilder;
36
37    fn make_minimal_reader() -> ObjectReader<memf_core::test_builders::SyntheticPhysMem> {
38        let isf = IsfBuilder::new().build_json();
39        let resolver = IsfResolver::from_value(&isf).unwrap();
40        let (cr3, mem) = PageTableBuilder::new().build();
41        let vas = VirtualAddressSpace::new(mem, cr3, TranslationMode::X86_64FourLevel);
42        ObjectReader::new(vas, Box::new(resolver))
43    }
44
45    #[test]
46    fn empty_memory_returns_ok_empty() {
47        let reader = make_minimal_reader();
48        let result = scan_audit_tampering(&reader);
49        assert!(result.is_ok(), "should succeed with minimal reader");
50        assert!(
51            result.unwrap().is_empty(),
52            "empty memory → no audit tampering hits"
53        );
54    }
55
56    #[test]
57    fn result_is_vec_of_audit_tamper_info() {
58        let reader = make_minimal_reader();
59        let result: Result<Vec<AuditTamperInfo>> = scan_audit_tampering(&reader);
60        assert!(result.is_ok());
61    }
62
63    #[test]
64    fn audit_tamper_info_fields_constructible() {
65        let info = AuditTamperInfo {
66            audit_enabled: false,
67            backlog_limit: 8192,
68            suppressed_pids: vec![1337, 1338],
69            suppressed_uids: vec![0],
70            audit_globally_disabled: true,
71        };
72        assert!(!info.audit_enabled);
73        assert_eq!(info.backlog_limit, 8192);
74        assert_eq!(info.suppressed_pids.len(), 2);
75        assert!(info.audit_globally_disabled);
76    }
77
78    #[test]
79    fn audit_tamper_info_serializes() {
80        let info = AuditTamperInfo {
81            audit_enabled: true,
82            backlog_limit: 64,
83            suppressed_pids: vec![],
84            suppressed_uids: vec![],
85            audit_globally_disabled: false,
86        };
87        let json = serde_json::to_string(&info).unwrap();
88        assert!(json.contains("\"audit_enabled\":true"));
89        assert!(json.contains("\"backlog_limit\":64"));
90        assert!(json.contains("\"audit_globally_disabled\":false"));
91    }
92
93    // --- classifier helper tests (genuine RED: function does not exist yet) ---
94
95    #[test]
96    fn mismatched_audit_pid_is_tampered() {
97        // auditd PID 1234 but netlink socket owned by PID 9999 → tampered
98        assert!(is_audit_tampered(1234, 9999));
99    }
100
101    #[test]
102    fn matching_audit_pid_is_clean() {
103        assert!(!is_audit_tampered(1234, 1234));
104    }
105}