Skip to main content

memf_linux/
timerfd_signalfd.rs

1//! Timer/signal FD abuse detection.
2
3use memf_core::object_reader::ObjectReader;
4use memf_format::PhysicalMemoryProvider;
5
6use crate::types::FdAbuseInfo;
7use crate::Result;
8
9/// Classify whether a process has a suspicious number of timer/signal/event
10/// file descriptors by comparing against a threshold.
11///
12/// Returns `true` if `count > threshold`, indicating an abnormal accumulation
13/// of special FDs which can be used as covert inter-process signalling
14/// channels or timing oracles.
15pub fn is_suspicious_fd_count(count: usize, threshold: usize) -> bool {
16    count > threshold
17}
18
19/// Scan for timerfd/signalfd/eventfd abuse patterns.
20///
21/// Returns `Ok(vec![])` as a stub until full implementation is added.
22pub fn scan_fd_abuse<P: PhysicalMemoryProvider>(
23    reader: &ObjectReader<P>,
24) -> Result<Vec<FdAbuseInfo>> {
25    let _ = reader;
26    Ok(vec![])
27}
28
29#[cfg(test)]
30mod tests {
31    use super::*;
32    use crate::types::FdAbuseType;
33    use memf_core::test_builders::PageTableBuilder;
34    use memf_core::vas::{TranslationMode, VirtualAddressSpace};
35    use memf_symbols::isf::IsfResolver;
36    use memf_symbols::test_builders::IsfBuilder;
37
38    fn make_minimal_reader() -> ObjectReader<memf_core::test_builders::SyntheticPhysMem> {
39        let isf = IsfBuilder::new().build_json();
40        let resolver = IsfResolver::from_value(&isf).unwrap();
41        let (cr3, mem) = PageTableBuilder::new().build();
42        let vas = VirtualAddressSpace::new(mem, cr3, TranslationMode::X86_64FourLevel);
43        ObjectReader::new(vas, Box::new(resolver))
44    }
45
46    #[test]
47    fn empty_memory_returns_ok_empty() {
48        let reader = make_minimal_reader();
49        let result = scan_fd_abuse(&reader);
50        assert!(result.is_ok(), "should succeed with minimal reader");
51        assert!(
52            result.unwrap().is_empty(),
53            "empty memory → no fd abuse hits"
54        );
55    }
56
57    #[test]
58    fn result_is_vec_of_fd_abuse_info() {
59        let reader = make_minimal_reader();
60        let result: Result<Vec<FdAbuseInfo>> = scan_fd_abuse(&reader);
61        assert!(result.is_ok());
62    }
63
64    #[test]
65    fn fd_abuse_info_timerfd_constructible() {
66        let info = FdAbuseInfo {
67            pid: 200,
68            comm: "evil_timer".to_string(),
69            fd_type: FdAbuseType::TimerFd,
70            signal_mask: 0,
71            interval_ns: 1_000_000_000,
72            is_cross_process_shared: false,
73        };
74        assert_eq!(info.pid, 200);
75        assert_eq!(info.fd_type, FdAbuseType::TimerFd);
76        assert_eq!(info.interval_ns, 1_000_000_000);
77    }
78
79    #[test]
80    fn fd_abuse_info_signalfd_constructible() {
81        let info = FdAbuseInfo {
82            pid: 300,
83            comm: "sigmon".to_string(),
84            fd_type: FdAbuseType::SignalFd,
85            signal_mask: 0b1100,
86            interval_ns: 0,
87            is_cross_process_shared: true,
88        };
89        assert_eq!(info.fd_type, FdAbuseType::SignalFd);
90        assert_eq!(info.signal_mask, 0b1100);
91        assert!(info.is_cross_process_shared);
92    }
93
94    #[test]
95    fn fd_abuse_info_serializes() {
96        let info = FdAbuseInfo {
97            pid: 7,
98            comm: "efd".to_string(),
99            fd_type: FdAbuseType::EventFd,
100            signal_mask: 0,
101            interval_ns: 0,
102            is_cross_process_shared: false,
103        };
104        let json = serde_json::to_string(&info).unwrap();
105        assert!(json.contains("\"pid\":7"));
106        assert!(json.contains("EventFd"));
107    }
108
109    // --- classifier helper tests (genuine RED: function does not exist yet) ---
110
111    #[test]
112    fn fd_count_above_threshold_is_suspicious() {
113        assert!(is_suspicious_fd_count(101, 100));
114    }
115
116    #[test]
117    fn fd_count_at_threshold_is_not_suspicious() {
118        assert!(!is_suspicious_fd_count(100, 100));
119    }
120
121    #[test]
122    fn fd_count_below_threshold_is_not_suspicious() {
123        assert!(!is_suspicious_fd_count(5, 100));
124    }
125}