1use memf_core::object_reader::ObjectReader;
9use memf_format::PhysicalMemoryProvider;
10
11use crate::{Error, MountInfo, Result};
12
13pub fn walk_filesystems<P: PhysicalMemoryProvider>(
18 reader: &ObjectReader<P>,
19) -> Result<Vec<MountInfo>> {
20 let init_task_addr = reader
21 .symbols()
22 .symbol_address("init_task")
23 .ok_or_else(|| Error::MissingKernelSymbol {
24 name: "init_task".into(),
25 })?;
26
27 let nsproxy_ptr: u64 = reader.read_field(init_task_addr, "task_struct", "nsproxy")?;
28 if nsproxy_ptr == 0 {
29 return Err(Error::WalkFailed {
30 walker: "walk_filesystems",
31 reason: "init_task has NULL nsproxy".into(),
32 });
33 }
34
35 let mnt_ns_ptr: u64 = reader.read_field(nsproxy_ptr, "nsproxy", "mnt_ns")?;
36 if mnt_ns_ptr == 0 {
37 return Err(Error::WalkFailed {
38 walker: "walk_filesystems",
39 reason: "nsproxy has NULL mnt_ns".into(),
40 });
41 }
42
43 let mount_addrs = reader.walk_list(mnt_ns_ptr, "mount", "mnt_list")?;
45
46 let d_name_offset = reader
47 .symbols()
48 .field_offset("dentry", "d_name")
49 .ok_or_else(|| Error::MissingField {
50 struct_name: "dentry".into(),
51 field_name: "d_name".into(),
52 })?;
53
54 let name_in_qstr_offset = reader
55 .symbols()
56 .field_offset("qstr", "name")
57 .ok_or_else(|| Error::MissingField {
58 struct_name: "qstr".into(),
59 field_name: "name".into(),
60 })?;
61
62 let mnt_offset = reader
63 .symbols()
64 .field_offset("mount", "mnt")
65 .ok_or_else(|| Error::MissingField {
66 struct_name: "mount".into(),
67 field_name: "mnt".into(),
68 })?;
69
70 let mut mounts = Vec::new();
71
72 for &mount_addr in &mount_addrs {
73 let devname_ptr: u64 = reader.read_field(mount_addr, "mount", "mnt_devname")?;
75 let dev_name = if devname_ptr != 0 {
76 reader.read_string(devname_ptr, 256).unwrap_or_default()
77 } else {
78 String::new()
79 };
80
81 let mountpoint_ptr: u64 = reader.read_field(mount_addr, "mount", "mnt_mountpoint")?;
83 let mount_point = if mountpoint_ptr != 0 {
84 read_dentry_name(reader, mountpoint_ptr, d_name_offset, name_in_qstr_offset)
85 .unwrap_or_default()
86 } else {
87 String::new()
88 };
89
90 let sb_ptr: u64 = reader.read_field(mount_addr + mnt_offset, "vfsmount", "mnt_sb")?;
92 let fs_type = if sb_ptr != 0 {
93 read_fs_type_name(reader, sb_ptr).unwrap_or_default()
94 } else {
95 String::new()
96 };
97
98 mounts.push(MountInfo {
99 dev_name,
100 mount_point,
101 fs_type,
102 });
103 }
104
105 Ok(mounts)
106}
107
108fn read_dentry_name<P: PhysicalMemoryProvider>(
110 reader: &ObjectReader<P>,
111 dentry_ptr: u64,
112 d_name_offset: u64,
113 name_in_qstr_offset: u64,
114) -> Result<String> {
115 let name_addr = dentry_ptr + d_name_offset + name_in_qstr_offset;
116 let name_raw = reader.read_bytes(name_addr, 8)?;
117 let name_ptr = name_raw.try_into().map_or(0, u64::from_le_bytes);
118 if name_ptr != 0 {
119 Ok(reader.read_string(name_ptr, 256)?)
120 } else {
121 Ok(String::new())
122 }
123}
124
125fn read_fs_type_name<P: PhysicalMemoryProvider>(
127 reader: &ObjectReader<P>,
128 sb_ptr: u64,
129) -> Result<String> {
130 let s_type_ptr: u64 = reader.read_field(sb_ptr, "super_block", "s_type")?;
131 if s_type_ptr == 0 {
132 return Ok(String::new());
133 }
134 let name_ptr: u64 = reader.read_field(s_type_ptr, "file_system_type", "name")?;
135 if name_ptr == 0 {
136 return Ok(String::new());
137 }
138 Ok(reader.read_string(name_ptr, 64)?)
139}
140
141#[cfg(test)]
142mod tests {
143 use super::*;
144 use memf_core::test_builders::{flags, PageTableBuilder, SyntheticPhysMem};
145 use memf_core::vas::{TranslationMode, VirtualAddressSpace};
146 use memf_symbols::isf::IsfResolver;
147 use memf_symbols::test_builders::IsfBuilder;
148
149 fn make_test_reader(data: &[u8], vaddr: u64, paddr: u64) -> ObjectReader<SyntheticPhysMem> {
150 let isf = IsfBuilder::new()
151 .add_struct("task_struct", 128)
153 .add_field("task_struct", "pid", 0, "int")
154 .add_field("task_struct", "tasks", 16, "list_head")
155 .add_field("task_struct", "nsproxy", 64, "pointer")
156 .add_struct("list_head", 16)
158 .add_field("list_head", "next", 0, "pointer")
159 .add_field("list_head", "prev", 8, "pointer")
160 .add_struct("nsproxy", 48)
162 .add_field("nsproxy", "mnt_ns", 16, "pointer")
163 .add_struct("mnt_namespace", 32)
165 .add_field("mnt_namespace", "list", 0, "list_head")
166 .add_struct("mount", 256)
168 .add_field("mount", "mnt_list", 0, "list_head")
169 .add_field("mount", "mnt_devname", 16, "pointer")
170 .add_field("mount", "mnt_mountpoint", 24, "pointer")
171 .add_field("mount", "mnt", 32, "vfsmount")
172 .add_struct("vfsmount", 32)
174 .add_field("vfsmount", "mnt_sb", 0, "pointer")
175 .add_struct("super_block", 64)
177 .add_field("super_block", "s_type", 0, "pointer")
178 .add_struct("file_system_type", 64)
180 .add_field("file_system_type", "name", 0, "pointer")
181 .add_struct("dentry", 64)
183 .add_field("dentry", "d_name", 0, "qstr")
184 .add_struct("qstr", 16)
186 .add_field("qstr", "name", 8, "pointer")
187 .add_symbol("init_task", vaddr)
189 .build_json();
190
191 let resolver = IsfResolver::from_value(&isf).unwrap();
192 let (cr3, mem) = PageTableBuilder::new()
193 .map_4k(vaddr, paddr, flags::WRITABLE)
194 .write_phys(paddr, data)
195 .build();
196 let vas = VirtualAddressSpace::new(mem, cr3, TranslationMode::X86_64FourLevel);
197 ObjectReader::new(vas, Box::new(resolver))
198 }
199
200 #[test]
201 fn walk_two_mounts() {
202 let vaddr: u64 = 0xFFFF_8000_0010_0000;
203 let paddr: u64 = 0x0080_0000;
204 let mut data = vec![0u8; 4096];
205
206 let nsproxy_addr = vaddr + 0x100;
208 data[64..72].copy_from_slice(&nsproxy_addr.to_le_bytes());
209
210 let mnt_ns_addr = vaddr + 0x180;
212 data[0x110..0x118].copy_from_slice(&mnt_ns_addr.to_le_bytes());
213
214 let ns_list_addr = vaddr + 0x180; let mount1_addr = vaddr + 0x200;
218 let mount2_addr = vaddr + 0x400;
219 let mount1_list = mount1_addr; let mount2_list = mount2_addr;
221 data[0x180..0x188].copy_from_slice(&mount1_list.to_le_bytes()); data[0x188..0x190].copy_from_slice(&mount2_list.to_le_bytes()); data[0x200..0x208].copy_from_slice(&mount2_list.to_le_bytes()); data[0x208..0x210].copy_from_slice(&ns_list_addr.to_le_bytes()); let devname1_addr = vaddr + 0x300;
228 data[0x210..0x218].copy_from_slice(&devname1_addr.to_le_bytes()); let dentry1_addr = vaddr + 0x340;
230 data[0x218..0x220].copy_from_slice(&dentry1_addr.to_le_bytes()); let sb1_addr = vaddr + 0x380;
233 data[0x220..0x228].copy_from_slice(&sb1_addr.to_le_bytes()); data[0x300..0x308].copy_from_slice(b"/dev/sda");
237
238 let dname1_addr = vaddr + 0x360;
240 data[0x348..0x350].copy_from_slice(&dname1_addr.to_le_bytes()); data[0x360..0x361].copy_from_slice(b"/");
242
243 let fstype1_addr = vaddr + 0x3C0;
245 data[0x380..0x388].copy_from_slice(&fstype1_addr.to_le_bytes());
246
247 let typename1_addr = vaddr + 0x3E0;
249 data[0x3C0..0x3C8].copy_from_slice(&typename1_addr.to_le_bytes());
250 data[0x3E0..0x3E4].copy_from_slice(b"ext4");
251
252 data[0x400..0x408].copy_from_slice(&ns_list_addr.to_le_bytes()); data[0x408..0x410].copy_from_slice(&mount1_list.to_le_bytes()); let devname2_addr = vaddr + 0x500;
256 data[0x410..0x418].copy_from_slice(&devname2_addr.to_le_bytes()); let dentry2_addr = vaddr + 0x540;
258 data[0x418..0x420].copy_from_slice(&dentry2_addr.to_le_bytes()); let sb2_addr = vaddr + 0x580;
260 data[0x420..0x428].copy_from_slice(&sb2_addr.to_le_bytes()); data[0x500..0x505].copy_from_slice(b"tmpfs");
264
265 let dname2_addr = vaddr + 0x560;
267 data[0x548..0x550].copy_from_slice(&dname2_addr.to_le_bytes());
268 data[0x560..0x564].copy_from_slice(b"/tmp");
269
270 let fstype2_addr = vaddr + 0x5C0;
272 data[0x580..0x588].copy_from_slice(&fstype2_addr.to_le_bytes());
273
274 let typename2_addr = vaddr + 0x5E0;
276 data[0x5C0..0x5C8].copy_from_slice(&typename2_addr.to_le_bytes());
277 data[0x5E0..0x5E5].copy_from_slice(b"tmpfs");
278
279 let reader = make_test_reader(&data, vaddr, paddr);
280 let mounts = walk_filesystems(&reader).unwrap();
281
282 assert_eq!(mounts.len(), 2);
283
284 assert_eq!(mounts[0].dev_name, "/dev/sda");
285 assert_eq!(mounts[0].mount_point, "/");
286 assert_eq!(mounts[0].fs_type, "ext4");
287
288 assert_eq!(mounts[1].dev_name, "tmpfs");
289 assert_eq!(mounts[1].mount_point, "/tmp");
290 assert_eq!(mounts[1].fs_type, "tmpfs");
291 }
292
293 #[test]
294 fn walk_filesystems_null_nsproxy() {
295 let vaddr: u64 = 0xFFFF_8000_0010_0000;
296 let paddr: u64 = 0x0080_0000;
297 let mut data = vec![0u8; 4096];
298
299 data[64..72].copy_from_slice(&0u64.to_le_bytes());
301
302 let reader = make_test_reader(&data, vaddr, paddr);
303 let result = walk_filesystems(&reader);
304 assert!(result.is_err());
305 }
306
307 #[test]
308 fn missing_init_task_symbol() {
309 let isf = IsfBuilder::new()
310 .add_struct("task_struct", 64)
311 .add_field("task_struct", "pid", 0, "int")
312 .add_field("task_struct", "tasks", 8, "list_head")
313 .add_struct("list_head", 16)
314 .add_field("list_head", "next", 0, "pointer")
315 .add_field("list_head", "prev", 8, "pointer")
316 .build_json();
317
318 let resolver = IsfResolver::from_value(&isf).unwrap();
319 let (cr3, mem) = PageTableBuilder::new().build();
320 let vas = VirtualAddressSpace::new(mem, cr3, TranslationMode::X86_64FourLevel);
321 let reader = ObjectReader::new(vas, Box::new(resolver));
322
323 let result = walk_filesystems(&reader);
324 assert!(result.is_err());
325 }
326
327 #[test]
329 fn walk_filesystems_null_mnt_ns_returns_error() {
330 let vaddr: u64 = 0xFFFF_8000_0010_0000;
331 let paddr: u64 = 0x0080_0000;
332 let mut data = vec![0u8; 4096];
333
334 let nsproxy_addr = vaddr + 0x100;
336 data[64..72].copy_from_slice(&nsproxy_addr.to_le_bytes());
337 data[0x110..0x118].copy_from_slice(&0u64.to_le_bytes());
339
340 let reader = make_test_reader(&data, vaddr, paddr);
341 let result = walk_filesystems(&reader);
342 assert!(result.is_err(), "null mnt_ns must return Err");
343 }
344
345 #[test]
348 fn walk_filesystems_all_null_ptrs_in_mount() {
349 let vaddr: u64 = 0xFFFF_8000_0010_0000;
350 let paddr: u64 = 0x0080_0000;
351 let mut data = vec![0u8; 4096];
352
353 let nsproxy_addr = vaddr + 0x100;
355 data[64..72].copy_from_slice(&nsproxy_addr.to_le_bytes());
356
357 let mnt_ns_addr = vaddr + 0x180;
359 data[0x110..0x118].copy_from_slice(&mnt_ns_addr.to_le_bytes());
360
361 let ns_list_addr = vaddr + 0x180;
364 let mount1_addr = vaddr + 0x200;
365 data[0x180..0x188].copy_from_slice(&mount1_addr.to_le_bytes()); data[0x188..0x190].copy_from_slice(&mount1_addr.to_le_bytes()); data[0x200..0x208].copy_from_slice(&ns_list_addr.to_le_bytes()); data[0x208..0x210].copy_from_slice(&ns_list_addr.to_le_bytes()); data[0x210..0x218].copy_from_slice(&0u64.to_le_bytes());
373 data[0x218..0x220].copy_from_slice(&0u64.to_le_bytes());
375 data[0x220..0x228].copy_from_slice(&0u64.to_le_bytes());
377
378 let reader = make_test_reader(&data, vaddr, paddr);
379 let mounts = walk_filesystems(&reader).unwrap();
380
381 assert_eq!(mounts.len(), 1, "one mount entry expected");
382 assert_eq!(mounts[0].dev_name, "", "null devname_ptr → empty string");
383 assert_eq!(
384 mounts[0].mount_point, "",
385 "null mountpoint_ptr → empty string"
386 );
387 assert_eq!(mounts[0].fs_type, "", "null sb_ptr → empty string");
388 }
389
390 #[test]
392 fn walk_filesystems_null_s_type_gives_empty_fs_type() {
393 let vaddr: u64 = 0xFFFF_8000_0010_0000;
394 let paddr: u64 = 0x0080_0000;
395 let mut data = vec![0u8; 4096];
396
397 let nsproxy_addr = vaddr + 0x100;
398 data[64..72].copy_from_slice(&nsproxy_addr.to_le_bytes());
399
400 let mnt_ns_addr = vaddr + 0x180;
401 data[0x110..0x118].copy_from_slice(&mnt_ns_addr.to_le_bytes());
402
403 let ns_list_addr = vaddr + 0x180;
404 let mount1_addr = vaddr + 0x200;
405 data[0x180..0x188].copy_from_slice(&mount1_addr.to_le_bytes());
406 data[0x188..0x190].copy_from_slice(&mount1_addr.to_le_bytes());
407
408 data[0x200..0x208].copy_from_slice(&ns_list_addr.to_le_bytes());
409 data[0x208..0x210].copy_from_slice(&ns_list_addr.to_le_bytes());
410 data[0x210..0x218].copy_from_slice(&0u64.to_le_bytes());
412 data[0x218..0x220].copy_from_slice(&0u64.to_le_bytes());
413 let sb_addr = vaddr + 0x380;
415 data[0x220..0x228].copy_from_slice(&sb_addr.to_le_bytes());
416 data[0x380..0x388].copy_from_slice(&0u64.to_le_bytes());
418
419 let reader = make_test_reader(&data, vaddr, paddr);
420 let mounts = walk_filesystems(&reader).unwrap();
421
422 assert_eq!(mounts.len(), 1);
423 assert_eq!(mounts[0].fs_type, "", "null s_type_ptr → empty fs_type");
424 }
425
426 #[test]
428 fn walk_filesystems_null_name_ptr_gives_empty_fs_type() {
429 let vaddr: u64 = 0xFFFF_8000_0010_0000;
430 let paddr: u64 = 0x0080_0000;
431 let mut data = vec![0u8; 4096];
432
433 let nsproxy_addr = vaddr + 0x100;
434 data[64..72].copy_from_slice(&nsproxy_addr.to_le_bytes());
435
436 let mnt_ns_addr = vaddr + 0x180;
437 data[0x110..0x118].copy_from_slice(&mnt_ns_addr.to_le_bytes());
438
439 let ns_list_addr = vaddr + 0x180;
440 let mount1_addr = vaddr + 0x200;
441 data[0x180..0x188].copy_from_slice(&mount1_addr.to_le_bytes());
442 data[0x188..0x190].copy_from_slice(&mount1_addr.to_le_bytes());
443
444 data[0x200..0x208].copy_from_slice(&ns_list_addr.to_le_bytes());
445 data[0x208..0x210].copy_from_slice(&ns_list_addr.to_le_bytes());
446 data[0x210..0x218].copy_from_slice(&0u64.to_le_bytes()); data[0x218..0x220].copy_from_slice(&0u64.to_le_bytes()); let sb_addr = vaddr + 0x380;
450 data[0x220..0x228].copy_from_slice(&sb_addr.to_le_bytes());
451 let fstype_addr = vaddr + 0x3C0;
452 data[0x380..0x388].copy_from_slice(&fstype_addr.to_le_bytes()); data[0x3C0..0x3C8].copy_from_slice(&0u64.to_le_bytes());
455
456 let reader = make_test_reader(&data, vaddr, paddr);
457 let mounts = walk_filesystems(&reader).unwrap();
458
459 assert_eq!(mounts.len(), 1);
460 assert_eq!(
461 mounts[0].fs_type, "",
462 "null name_ptr in file_system_type → empty fs_type"
463 );
464 }
465
466 #[test]
468 fn mount_info_debug_clone() {
469 let m = MountInfo {
470 dev_name: "/dev/sda".to_string(),
471 mount_point: "/".to_string(),
472 fs_type: "ext4".to_string(),
473 };
474 let cloned = m.clone();
475 let dbg = format!("{cloned:?}");
476 assert!(dbg.contains("ext4"));
477 }
478}