1use memf_core::object_reader::ObjectReader;
7use memf_format::PhysicalMemoryProvider;
8
9use crate::Result;
10
11#[derive(Debug, Clone, serde::Serialize)]
17pub struct MountEntry {
18 pub mnt_id: u32,
20 pub parent_id: u32,
22 pub dev_name: String,
24 pub mnt_root: String,
26 pub mnt_flags: u32,
28 pub fs_type: String,
30 pub is_suspicious: bool,
32}
33
34pub use crate::heuristics::classify_mount;
40
41pub fn walk_mounts<P: PhysicalMemoryProvider>(reader: &ObjectReader<P>) -> Result<Vec<MountEntry>> {
45 let _ = reader;
46 Ok(Vec::new())
47}
48
49#[cfg(test)]
50mod tests {
51 use super::*;
52 use memf_core::test_builders::{PageTableBuilder, SyntheticPhysMem};
53 use memf_core::vas::{TranslationMode, VirtualAddressSpace};
54 use memf_symbols::isf::IsfResolver;
55 use memf_symbols::test_builders::IsfBuilder;
56
57 fn make_no_symbol_reader() -> ObjectReader<SyntheticPhysMem> {
58 let isf = IsfBuilder::new().build_json();
59 let resolver = IsfResolver::from_value(&isf).unwrap();
60 let (cr3, mem) = PageTableBuilder::new().build();
61 let vas = VirtualAddressSpace::new(mem, cr3, TranslationMode::X86_64FourLevel);
62 ObjectReader::new(vas, Box::new(resolver))
63 }
64
65 #[test]
66 fn no_symbol_returns_empty() {
67 let reader = make_no_symbol_reader();
68 let result = walk_mounts(&reader).unwrap();
69 assert!(result.is_empty(), "no init_task symbol → empty vec");
70 }
71
72 #[test]
73 fn classify_suspicious_tmpfs_mount() {
74 assert!(
76 classify_mount("tmpfs", "tmpfs", "/hidden"),
77 "tmpfs at /hidden should be suspicious"
78 );
79 assert!(
81 classify_mount("overlay", "overlay", "/mnt/secret"),
82 "overlay outside docker should be suspicious"
83 );
84 }
85
86 #[test]
87 fn classify_benign_proc_mount_not_flagged() {
88 assert!(
89 !classify_mount("proc", "proc", "/proc"),
90 "proc mount should not be suspicious"
91 );
92 assert!(
93 !classify_mount("tmpfs", "tmpfs", "/tmp"),
94 "tmpfs at /tmp should not be suspicious"
95 );
96 assert!(
97 !classify_mount("tmpfs", "tmpfs", "/run"),
98 "tmpfs at /run should not be suspicious"
99 );
100 assert!(
101 !classify_mount("overlay", "overlay", "/var/lib/docker/overlay2"),
102 "overlay inside docker should not be suspicious"
103 );
104 }
105
106 #[test]
107 fn classify_mount_tmpfs_benign_variants() {
108 assert!(
111 !classify_mount("tmpfs", "tmpfs", "/run/lock"),
112 "tmpfs at /run/lock must be benign"
113 );
114 assert!(
115 !classify_mount("tmpfs", "tmpfs", "/run/user"),
116 "tmpfs at /run/user must be benign"
117 );
118 assert!(
119 !classify_mount("tmpfs", "tmpfs", "/"),
120 "tmpfs at / must be benign (container rootfs)"
121 );
122 assert!(
123 !classify_mount("tmpfs", "tmpfs", "/run/some/sub"),
124 "tmpfs under /run/ must be benign"
125 );
126 assert!(
127 !classify_mount("tmpfs", "tmpfs", "/tmp/sub"),
128 "tmpfs under /tmp/ must be benign"
129 );
130 assert!(
131 !classify_mount("tmpfs", "tmpfs", "/dev/pts"),
132 "tmpfs under /dev/ must be benign"
133 );
134 assert!(
136 !classify_mount("ramfs", "ramfs", "/tmp"),
137 "ramfs at /tmp must be benign"
138 );
139 assert!(
140 classify_mount("ramfs", "ramfs", "/hidden"),
141 "ramfs at /hidden must be suspicious"
142 );
143 }
144
145 #[test]
146 fn classify_mount_overlay_containerd_benign() {
147 assert!(
149 !classify_mount("overlay", "overlay", "/var/lib/containerd/snapshotters"),
150 "overlay inside containerd must be benign"
151 );
152 assert!(
153 !classify_mount("overlayfs", "overlayfs", "/var/lib/containerd/overlay"),
154 "overlayfs inside containerd must be benign"
155 );
156 }
157
158 #[test]
159 fn classify_mount_other_fs_type_not_suspicious() {
160 assert!(
162 !classify_mount("ext4", "ext4", "/"),
163 "ext4 must not be suspicious"
164 );
165 assert!(
166 !classify_mount("nfs", "nfs", "/mnt/nfs"),
167 "nfs must not be suspicious"
168 );
169 assert!(
170 !classify_mount("sysfs", "sysfs", "/sys"),
171 "sysfs must not be suspicious"
172 );
173 }
174
175 #[test]
177 fn mount_info_struct_clone_debug_serialize() {
178 let info = MountEntry {
179 mnt_id: 1,
180 parent_id: 0,
181 dev_name: "/dev/sda1".to_string(),
182 mnt_root: "/".to_string(),
183 mnt_flags: 0x1000,
184 fs_type: "ext4".to_string(),
185 is_suspicious: false,
186 };
187 let cloned = info.clone();
188 let dbg = format!("{cloned:?}");
189 assert!(dbg.contains("ext4"));
190 let json = serde_json::to_string(&info).unwrap();
191 assert!(json.contains("\"mnt_id\":1"));
192 assert!(json.contains("\"is_suspicious\":false"));
193 assert!(json.contains("ext4"));
194 }
195
196 #[test]
197 fn mount_info_suspicious_struct() {
198 let info = MountEntry {
199 mnt_id: 42,
200 parent_id: 1,
201 dev_name: "none".to_string(),
202 mnt_root: "/hidden".to_string(),
203 mnt_flags: 0,
204 fs_type: "tmpfs".to_string(),
205 is_suspicious: true,
206 };
207 assert!(info.is_suspicious);
208 assert_eq!(info.mnt_id, 42);
209 assert_eq!(info.fs_type, "tmpfs");
210 }
211
212 #[test]
214 fn walk_mounts_with_symbol_returns_entries() {
215 use memf_core::test_builders::flags;
216
217 let init_task_vaddr: u64 = 0xFFFF_8000_0030_0000;
228 let init_task_paddr: u64 = 0x0084_0000;
229
230 let isf = IsfBuilder::new()
231 .add_symbol("init_task", init_task_vaddr)
232 .build_json();
233
234 let resolver = IsfResolver::from_value(&isf).unwrap();
235 let (cr3, mem) = PageTableBuilder::new()
236 .map_4k(
237 init_task_vaddr,
238 init_task_paddr,
239 flags::PRESENT | flags::WRITABLE,
240 )
241 .build();
242
243 let vas = VirtualAddressSpace::new(mem, cr3, TranslationMode::X86_64FourLevel);
244 let reader = ObjectReader::new(vas, Box::new(resolver));
245
246 let result = walk_mounts(&reader);
249 assert!(result.is_ok(), "walk_mounts should not error");
250 }
251}