use memf_core::object_reader::ObjectReader;
use memf_format::PhysicalMemoryProvider;
use crate::{Error, MountInfo, Result};
pub fn walk_filesystems<P: PhysicalMemoryProvider>(
reader: &ObjectReader<P>,
) -> Result<Vec<MountInfo>> {
let init_task_addr = reader
.symbols()
.symbol_address("init_task")
.ok_or_else(|| Error::MissingKernelSymbol {
name: "init_task".into(),
})?;
let nsproxy_ptr: u64 = reader.read_field(init_task_addr, "task_struct", "nsproxy")?;
if nsproxy_ptr == 0 {
return Err(Error::WalkFailed {
walker: "walk_filesystems",
reason: "init_task has NULL nsproxy".into(),
});
}
let mnt_ns_ptr: u64 = reader.read_field(nsproxy_ptr, "nsproxy", "mnt_ns")?;
if mnt_ns_ptr == 0 {
return Err(Error::WalkFailed {
walker: "walk_filesystems",
reason: "nsproxy has NULL mnt_ns".into(),
});
}
let mount_addrs = reader.walk_list(mnt_ns_ptr, "mount", "mnt_list")?;
let d_name_offset = reader
.symbols()
.field_offset("dentry", "d_name")
.ok_or_else(|| Error::MissingField {
struct_name: "dentry".into(),
field_name: "d_name".into(),
})?;
let name_in_qstr_offset = reader
.symbols()
.field_offset("qstr", "name")
.ok_or_else(|| Error::MissingField {
struct_name: "qstr".into(),
field_name: "name".into(),
})?;
let mnt_offset = reader
.symbols()
.field_offset("mount", "mnt")
.ok_or_else(|| Error::MissingField {
struct_name: "mount".into(),
field_name: "mnt".into(),
})?;
let mut mounts = Vec::new();
for &mount_addr in &mount_addrs {
let devname_ptr: u64 = reader.read_field(mount_addr, "mount", "mnt_devname")?;
let dev_name = if devname_ptr != 0 {
reader.read_string(devname_ptr, 256).unwrap_or_default()
} else {
String::new()
};
let mountpoint_ptr: u64 = reader.read_field(mount_addr, "mount", "mnt_mountpoint")?;
let mount_point = if mountpoint_ptr != 0 {
read_dentry_name(reader, mountpoint_ptr, d_name_offset, name_in_qstr_offset)
.unwrap_or_default()
} else {
String::new()
};
let sb_ptr: u64 = reader.read_field(mount_addr + mnt_offset, "vfsmount", "mnt_sb")?;
let fs_type = if sb_ptr != 0 {
read_fs_type_name(reader, sb_ptr).unwrap_or_default()
} else {
String::new()
};
mounts.push(MountInfo {
dev_name,
mount_point,
fs_type,
});
}
Ok(mounts)
}
fn read_dentry_name<P: PhysicalMemoryProvider>(
reader: &ObjectReader<P>,
dentry_ptr: u64,
d_name_offset: u64,
name_in_qstr_offset: u64,
) -> Result<String> {
let name_addr = dentry_ptr + d_name_offset + name_in_qstr_offset;
let name_raw = reader.read_bytes(name_addr, 8)?;
let name_ptr = name_raw.try_into().map_or(0, u64::from_le_bytes);
if name_ptr != 0 {
Ok(reader.read_string(name_ptr, 256)?)
} else {
Ok(String::new())
}
}
fn read_fs_type_name<P: PhysicalMemoryProvider>(
reader: &ObjectReader<P>,
sb_ptr: u64,
) -> Result<String> {
let s_type_ptr: u64 = reader.read_field(sb_ptr, "super_block", "s_type")?;
if s_type_ptr == 0 {
return Ok(String::new());
}
let name_ptr: u64 = reader.read_field(s_type_ptr, "file_system_type", "name")?;
if name_ptr == 0 {
return Ok(String::new());
}
Ok(reader.read_string(name_ptr, 64)?)
}
#[cfg(test)]
mod tests {
use super::*;
use memf_core::test_builders::{flags, PageTableBuilder, SyntheticPhysMem};
use memf_core::vas::{TranslationMode, VirtualAddressSpace};
use memf_symbols::isf::IsfResolver;
use memf_symbols::test_builders::IsfBuilder;
fn make_test_reader(data: &[u8], vaddr: u64, paddr: u64) -> ObjectReader<SyntheticPhysMem> {
let isf = IsfBuilder::new()
.add_struct("task_struct", 128)
.add_field("task_struct", "pid", 0, "int")
.add_field("task_struct", "tasks", 16, "list_head")
.add_field("task_struct", "nsproxy", 64, "pointer")
.add_struct("list_head", 16)
.add_field("list_head", "next", 0, "pointer")
.add_field("list_head", "prev", 8, "pointer")
.add_struct("nsproxy", 48)
.add_field("nsproxy", "mnt_ns", 16, "pointer")
.add_struct("mnt_namespace", 32)
.add_field("mnt_namespace", "list", 0, "list_head")
.add_struct("mount", 256)
.add_field("mount", "mnt_list", 0, "list_head")
.add_field("mount", "mnt_devname", 16, "pointer")
.add_field("mount", "mnt_mountpoint", 24, "pointer")
.add_field("mount", "mnt", 32, "vfsmount")
.add_struct("vfsmount", 32)
.add_field("vfsmount", "mnt_sb", 0, "pointer")
.add_struct("super_block", 64)
.add_field("super_block", "s_type", 0, "pointer")
.add_struct("file_system_type", 64)
.add_field("file_system_type", "name", 0, "pointer")
.add_struct("dentry", 64)
.add_field("dentry", "d_name", 0, "qstr")
.add_struct("qstr", 16)
.add_field("qstr", "name", 8, "pointer")
.add_symbol("init_task", vaddr)
.build_json();
let resolver = IsfResolver::from_value(&isf).unwrap();
let (cr3, mem) = PageTableBuilder::new()
.map_4k(vaddr, paddr, flags::WRITABLE)
.write_phys(paddr, data)
.build();
let vas = VirtualAddressSpace::new(mem, cr3, TranslationMode::X86_64FourLevel);
ObjectReader::new(vas, Box::new(resolver))
}
#[test]
fn walk_two_mounts() {
let vaddr: u64 = 0xFFFF_8000_0010_0000;
let paddr: u64 = 0x0080_0000;
let mut data = vec![0u8; 4096];
let nsproxy_addr = vaddr + 0x100;
data[64..72].copy_from_slice(&nsproxy_addr.to_le_bytes());
let mnt_ns_addr = vaddr + 0x180;
data[0x110..0x118].copy_from_slice(&mnt_ns_addr.to_le_bytes());
let ns_list_addr = vaddr + 0x180; let mount1_addr = vaddr + 0x200;
let mount2_addr = vaddr + 0x400;
let mount1_list = mount1_addr; let mount2_list = mount2_addr;
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;
data[0x210..0x218].copy_from_slice(&devname1_addr.to_le_bytes()); let dentry1_addr = vaddr + 0x340;
data[0x218..0x220].copy_from_slice(&dentry1_addr.to_le_bytes()); let sb1_addr = vaddr + 0x380;
data[0x220..0x228].copy_from_slice(&sb1_addr.to_le_bytes());
data[0x300..0x308].copy_from_slice(b"/dev/sda");
let dname1_addr = vaddr + 0x360;
data[0x348..0x350].copy_from_slice(&dname1_addr.to_le_bytes()); data[0x360..0x361].copy_from_slice(b"/");
let fstype1_addr = vaddr + 0x3C0;
data[0x380..0x388].copy_from_slice(&fstype1_addr.to_le_bytes());
let typename1_addr = vaddr + 0x3E0;
data[0x3C0..0x3C8].copy_from_slice(&typename1_addr.to_le_bytes());
data[0x3E0..0x3E4].copy_from_slice(b"ext4");
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;
data[0x410..0x418].copy_from_slice(&devname2_addr.to_le_bytes()); let dentry2_addr = vaddr + 0x540;
data[0x418..0x420].copy_from_slice(&dentry2_addr.to_le_bytes()); let sb2_addr = vaddr + 0x580;
data[0x420..0x428].copy_from_slice(&sb2_addr.to_le_bytes());
data[0x500..0x505].copy_from_slice(b"tmpfs");
let dname2_addr = vaddr + 0x560;
data[0x548..0x550].copy_from_slice(&dname2_addr.to_le_bytes());
data[0x560..0x564].copy_from_slice(b"/tmp");
let fstype2_addr = vaddr + 0x5C0;
data[0x580..0x588].copy_from_slice(&fstype2_addr.to_le_bytes());
let typename2_addr = vaddr + 0x5E0;
data[0x5C0..0x5C8].copy_from_slice(&typename2_addr.to_le_bytes());
data[0x5E0..0x5E5].copy_from_slice(b"tmpfs");
let reader = make_test_reader(&data, vaddr, paddr);
let mounts = walk_filesystems(&reader).unwrap();
assert_eq!(mounts.len(), 2);
assert_eq!(mounts[0].dev_name, "/dev/sda");
assert_eq!(mounts[0].mount_point, "/");
assert_eq!(mounts[0].fs_type, "ext4");
assert_eq!(mounts[1].dev_name, "tmpfs");
assert_eq!(mounts[1].mount_point, "/tmp");
assert_eq!(mounts[1].fs_type, "tmpfs");
}
#[test]
fn walk_filesystems_null_nsproxy() {
let vaddr: u64 = 0xFFFF_8000_0010_0000;
let paddr: u64 = 0x0080_0000;
let mut data = vec![0u8; 4096];
data[64..72].copy_from_slice(&0u64.to_le_bytes());
let reader = make_test_reader(&data, vaddr, paddr);
let result = walk_filesystems(&reader);
assert!(result.is_err());
}
#[test]
fn missing_init_task_symbol() {
let isf = IsfBuilder::new()
.add_struct("task_struct", 64)
.add_field("task_struct", "pid", 0, "int")
.add_field("task_struct", "tasks", 8, "list_head")
.add_struct("list_head", 16)
.add_field("list_head", "next", 0, "pointer")
.add_field("list_head", "prev", 8, "pointer")
.build_json();
let resolver = IsfResolver::from_value(&isf).unwrap();
let (cr3, mem) = PageTableBuilder::new().build();
let vas = VirtualAddressSpace::new(mem, cr3, TranslationMode::X86_64FourLevel);
let reader = ObjectReader::new(vas, Box::new(resolver));
let result = walk_filesystems(&reader);
assert!(result.is_err());
}
#[test]
fn walk_filesystems_null_mnt_ns_returns_error() {
let vaddr: u64 = 0xFFFF_8000_0010_0000;
let paddr: u64 = 0x0080_0000;
let mut data = vec![0u8; 4096];
let nsproxy_addr = vaddr + 0x100;
data[64..72].copy_from_slice(&nsproxy_addr.to_le_bytes());
data[0x110..0x118].copy_from_slice(&0u64.to_le_bytes());
let reader = make_test_reader(&data, vaddr, paddr);
let result = walk_filesystems(&reader);
assert!(result.is_err(), "null mnt_ns must return Err");
}
#[test]
fn walk_filesystems_all_null_ptrs_in_mount() {
let vaddr: u64 = 0xFFFF_8000_0010_0000;
let paddr: u64 = 0x0080_0000;
let mut data = vec![0u8; 4096];
let nsproxy_addr = vaddr + 0x100;
data[64..72].copy_from_slice(&nsproxy_addr.to_le_bytes());
let mnt_ns_addr = vaddr + 0x180;
data[0x110..0x118].copy_from_slice(&mnt_ns_addr.to_le_bytes());
let ns_list_addr = vaddr + 0x180;
let mount1_addr = vaddr + 0x200;
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());
data[0x218..0x220].copy_from_slice(&0u64.to_le_bytes());
data[0x220..0x228].copy_from_slice(&0u64.to_le_bytes());
let reader = make_test_reader(&data, vaddr, paddr);
let mounts = walk_filesystems(&reader).unwrap();
assert_eq!(mounts.len(), 1, "one mount entry expected");
assert_eq!(mounts[0].dev_name, "", "null devname_ptr → empty string");
assert_eq!(
mounts[0].mount_point, "",
"null mountpoint_ptr → empty string"
);
assert_eq!(mounts[0].fs_type, "", "null sb_ptr → empty string");
}
#[test]
fn walk_filesystems_null_s_type_gives_empty_fs_type() {
let vaddr: u64 = 0xFFFF_8000_0010_0000;
let paddr: u64 = 0x0080_0000;
let mut data = vec![0u8; 4096];
let nsproxy_addr = vaddr + 0x100;
data[64..72].copy_from_slice(&nsproxy_addr.to_le_bytes());
let mnt_ns_addr = vaddr + 0x180;
data[0x110..0x118].copy_from_slice(&mnt_ns_addr.to_le_bytes());
let ns_list_addr = vaddr + 0x180;
let mount1_addr = vaddr + 0x200;
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());
data[0x218..0x220].copy_from_slice(&0u64.to_le_bytes());
let sb_addr = vaddr + 0x380;
data[0x220..0x228].copy_from_slice(&sb_addr.to_le_bytes());
data[0x380..0x388].copy_from_slice(&0u64.to_le_bytes());
let reader = make_test_reader(&data, vaddr, paddr);
let mounts = walk_filesystems(&reader).unwrap();
assert_eq!(mounts.len(), 1);
assert_eq!(mounts[0].fs_type, "", "null s_type_ptr → empty fs_type");
}
#[test]
fn walk_filesystems_null_name_ptr_gives_empty_fs_type() {
let vaddr: u64 = 0xFFFF_8000_0010_0000;
let paddr: u64 = 0x0080_0000;
let mut data = vec![0u8; 4096];
let nsproxy_addr = vaddr + 0x100;
data[64..72].copy_from_slice(&nsproxy_addr.to_le_bytes());
let mnt_ns_addr = vaddr + 0x180;
data[0x110..0x118].copy_from_slice(&mnt_ns_addr.to_le_bytes());
let ns_list_addr = vaddr + 0x180;
let mount1_addr = vaddr + 0x200;
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()); data[0x218..0x220].copy_from_slice(&0u64.to_le_bytes()); let sb_addr = vaddr + 0x380;
data[0x220..0x228].copy_from_slice(&sb_addr.to_le_bytes());
let fstype_addr = vaddr + 0x3C0;
data[0x380..0x388].copy_from_slice(&fstype_addr.to_le_bytes()); data[0x3C0..0x3C8].copy_from_slice(&0u64.to_le_bytes());
let reader = make_test_reader(&data, vaddr, paddr);
let mounts = walk_filesystems(&reader).unwrap();
assert_eq!(mounts.len(), 1);
assert_eq!(
mounts[0].fs_type, "",
"null name_ptr in file_system_type → empty fs_type"
);
}
#[test]
fn mount_info_debug_clone() {
let m = MountInfo {
dev_name: "/dev/sda".to_string(),
mount_point: "/".to_string(),
fs_type: "ext4".to_string(),
};
let cloned = m.clone();
let dbg = format!("{cloned:?}");
assert!(dbg.contains("ext4"));
}
}