Skip to main content

memf_linux/
fuse_abuse.rs

1//! FUSE filesystem abuse detection.
2
3use memf_core::object_reader::ObjectReader;
4use memf_format::PhysicalMemoryProvider;
5
6use crate::types::FuseAbuseInfo;
7use crate::Result;
8
9/// Classify whether a FUSE mount is suspicious based on the daemon's UID and
10/// whether the mounted filesystem has the setuid bit honoured.
11///
12/// Returns `true` if the daemon is a non-root user (`uid != 0`) but the mount
13/// allows setuid execution — a privilege-escalation path.
14pub fn is_suspicious_fuse_mount(uid: u32, is_setuid: bool) -> bool {
15    uid != 0 && is_setuid
16}
17
18/// Scan for FUSE filesystem abuse (mounted over sensitive paths, root daemon with allow_other).
19///
20/// Returns `Ok(vec![])` as a stub until full implementation is added.
21pub fn scan_fuse_abuse<P: PhysicalMemoryProvider>(
22    reader: &ObjectReader<P>,
23) -> Result<Vec<FuseAbuseInfo>> {
24    let _ = reader;
25    Ok(vec![])
26}
27
28#[cfg(test)]
29mod tests {
30    use super::*;
31    use memf_core::test_builders::PageTableBuilder;
32    use memf_core::vas::{TranslationMode, VirtualAddressSpace};
33    use memf_symbols::isf::IsfResolver;
34    use memf_symbols::test_builders::IsfBuilder;
35
36    fn make_minimal_reader() -> ObjectReader<memf_core::test_builders::SyntheticPhysMem> {
37        let isf = IsfBuilder::new().build_json();
38        let resolver = IsfResolver::from_value(&isf).unwrap();
39        let (cr3, mem) = PageTableBuilder::new().build();
40        let vas = VirtualAddressSpace::new(mem, cr3, TranslationMode::X86_64FourLevel);
41        ObjectReader::new(vas, Box::new(resolver))
42    }
43
44    #[test]
45    fn empty_memory_returns_ok_empty() {
46        let reader = make_minimal_reader();
47        let result = scan_fuse_abuse(&reader);
48        assert!(result.is_ok(), "should succeed with minimal reader");
49        assert!(
50            result.unwrap().is_empty(),
51            "empty memory → no FUSE abuse hits"
52        );
53    }
54
55    #[test]
56    fn result_is_vec_of_fuse_abuse_info() {
57        let reader = make_minimal_reader();
58        let result: Result<Vec<FuseAbuseInfo>> = scan_fuse_abuse(&reader);
59        assert!(result.is_ok());
60    }
61
62    #[test]
63    fn fuse_abuse_info_fields_constructible() {
64        let info = FuseAbuseInfo {
65            pid: 333,
66            comm: "sshfs".to_string(),
67            mount_point: "/proc".to_string(),
68            is_over_sensitive_path: true,
69            daemon_is_root: true,
70            allow_other: true,
71        };
72        assert_eq!(info.pid, 333);
73        assert_eq!(info.mount_point, "/proc");
74        assert!(info.is_over_sensitive_path);
75        assert!(info.allow_other);
76    }
77
78    #[test]
79    fn fuse_abuse_info_serializes() {
80        let info = FuseAbuseInfo {
81            pid: 1,
82            comm: "fusermount".to_string(),
83            mount_point: "/etc".to_string(),
84            is_over_sensitive_path: true,
85            daemon_is_root: false,
86            allow_other: false,
87        };
88        let json = serde_json::to_string(&info).unwrap();
89        assert!(json.contains("\"pid\":1"));
90        assert!(json.contains("/etc"));
91        assert!(json.contains("\"is_over_sensitive_path\":true"));
92    }
93
94    // --- classifier helper tests (genuine RED: function does not exist yet) ---
95
96    #[test]
97    fn non_root_uid_with_setuid_is_suspicious_fuse_mount() {
98        // Non-root daemon with setuid flag → privilege escalation path
99        assert!(is_suspicious_fuse_mount(1000, true));
100    }
101
102    #[test]
103    fn root_uid_with_setuid_is_not_suspicious() {
104        assert!(!is_suspicious_fuse_mount(0, true));
105    }
106
107    #[test]
108    fn non_root_uid_without_setuid_is_not_suspicious() {
109        assert!(!is_suspicious_fuse_mount(1000, false));
110    }
111}