use memf_core::object_reader::ObjectReader;
use memf_format::PhysicalMemoryProvider;
use crate::Result;
#[derive(Debug, Clone, serde::Serialize)]
pub struct MountEntry {
pub mnt_id: u32,
pub parent_id: u32,
pub dev_name: String,
pub mnt_root: String,
pub mnt_flags: u32,
pub fs_type: String,
pub is_suspicious: bool,
}
pub use crate::heuristics::classify_mount;
pub fn walk_mounts<P: PhysicalMemoryProvider>(reader: &ObjectReader<P>) -> Result<Vec<MountEntry>> {
let _ = reader;
Ok(Vec::new())
}
#[cfg(test)]
mod tests {
use super::*;
use memf_core::test_builders::{PageTableBuilder, SyntheticPhysMem};
use memf_core::vas::{TranslationMode, VirtualAddressSpace};
use memf_symbols::isf::IsfResolver;
use memf_symbols::test_builders::IsfBuilder;
fn make_no_symbol_reader() -> ObjectReader<SyntheticPhysMem> {
let isf = IsfBuilder::new().build_json();
let resolver = IsfResolver::from_value(&isf).unwrap();
let (cr3, mem) = PageTableBuilder::new().build();
let vas = VirtualAddressSpace::new(mem, cr3, TranslationMode::X86_64FourLevel);
ObjectReader::new(vas, Box::new(resolver))
}
#[test]
fn no_symbol_returns_empty() {
let reader = make_no_symbol_reader();
let result = walk_mounts(&reader).unwrap();
assert!(result.is_empty(), "no init_task symbol → empty vec");
}
#[test]
fn classify_suspicious_tmpfs_mount() {
assert!(
classify_mount("tmpfs", "tmpfs", "/hidden"),
"tmpfs at /hidden should be suspicious"
);
assert!(
classify_mount("overlay", "overlay", "/mnt/secret"),
"overlay outside docker should be suspicious"
);
}
#[test]
fn classify_benign_proc_mount_not_flagged() {
assert!(
!classify_mount("proc", "proc", "/proc"),
"proc mount should not be suspicious"
);
assert!(
!classify_mount("tmpfs", "tmpfs", "/tmp"),
"tmpfs at /tmp should not be suspicious"
);
assert!(
!classify_mount("tmpfs", "tmpfs", "/run"),
"tmpfs at /run should not be suspicious"
);
assert!(
!classify_mount("overlay", "overlay", "/var/lib/docker/overlay2"),
"overlay inside docker should not be suspicious"
);
}
#[test]
fn classify_mount_tmpfs_benign_variants() {
assert!(
!classify_mount("tmpfs", "tmpfs", "/run/lock"),
"tmpfs at /run/lock must be benign"
);
assert!(
!classify_mount("tmpfs", "tmpfs", "/run/user"),
"tmpfs at /run/user must be benign"
);
assert!(
!classify_mount("tmpfs", "tmpfs", "/"),
"tmpfs at / must be benign (container rootfs)"
);
assert!(
!classify_mount("tmpfs", "tmpfs", "/run/some/sub"),
"tmpfs under /run/ must be benign"
);
assert!(
!classify_mount("tmpfs", "tmpfs", "/tmp/sub"),
"tmpfs under /tmp/ must be benign"
);
assert!(
!classify_mount("tmpfs", "tmpfs", "/dev/pts"),
"tmpfs under /dev/ must be benign"
);
assert!(
!classify_mount("ramfs", "ramfs", "/tmp"),
"ramfs at /tmp must be benign"
);
assert!(
classify_mount("ramfs", "ramfs", "/hidden"),
"ramfs at /hidden must be suspicious"
);
}
#[test]
fn classify_mount_overlay_containerd_benign() {
assert!(
!classify_mount("overlay", "overlay", "/var/lib/containerd/snapshotters"),
"overlay inside containerd must be benign"
);
assert!(
!classify_mount("overlayfs", "overlayfs", "/var/lib/containerd/overlay"),
"overlayfs inside containerd must be benign"
);
}
#[test]
fn classify_mount_other_fs_type_not_suspicious() {
assert!(
!classify_mount("ext4", "ext4", "/"),
"ext4 must not be suspicious"
);
assert!(
!classify_mount("nfs", "nfs", "/mnt/nfs"),
"nfs must not be suspicious"
);
assert!(
!classify_mount("sysfs", "sysfs", "/sys"),
"sysfs must not be suspicious"
);
}
#[test]
fn mount_info_struct_clone_debug_serialize() {
let info = MountEntry {
mnt_id: 1,
parent_id: 0,
dev_name: "/dev/sda1".to_string(),
mnt_root: "/".to_string(),
mnt_flags: 0x1000,
fs_type: "ext4".to_string(),
is_suspicious: false,
};
let cloned = info.clone();
let dbg = format!("{cloned:?}");
assert!(dbg.contains("ext4"));
let json = serde_json::to_string(&info).unwrap();
assert!(json.contains("\"mnt_id\":1"));
assert!(json.contains("\"is_suspicious\":false"));
assert!(json.contains("ext4"));
}
#[test]
fn mount_info_suspicious_struct() {
let info = MountEntry {
mnt_id: 42,
parent_id: 1,
dev_name: "none".to_string(),
mnt_root: "/hidden".to_string(),
mnt_flags: 0,
fs_type: "tmpfs".to_string(),
is_suspicious: true,
};
assert!(info.is_suspicious);
assert_eq!(info.mnt_id, 42);
assert_eq!(info.fs_type, "tmpfs");
}
#[test]
fn walk_mounts_with_symbol_returns_entries() {
use memf_core::test_builders::flags;
let init_task_vaddr: u64 = 0xFFFF_8000_0030_0000;
let init_task_paddr: u64 = 0x0084_0000;
let isf = IsfBuilder::new()
.add_symbol("init_task", init_task_vaddr)
.build_json();
let resolver = IsfResolver::from_value(&isf).unwrap();
let (cr3, mem) = PageTableBuilder::new()
.map_4k(
init_task_vaddr,
init_task_paddr,
flags::PRESENT | flags::WRITABLE,
)
.build();
let vas = VirtualAddressSpace::new(mem, cr3, TranslationMode::X86_64FourLevel);
let reader = ObjectReader::new(vas, Box::new(resolver));
let result = walk_mounts(&reader);
assert!(result.is_ok(), "walk_mounts should not error");
}
}