use memf_core::object_reader::ObjectReader;
use memf_format::PhysicalMemoryProvider;
use serde::Serialize;
use crate::{vma_walker::for_each_task_vma, Result};
const VM_EXEC: u64 = 0x4;
#[derive(Debug, Clone, Serialize)]
pub struct MemfdInfo {
pub pid: u32,
pub comm: String,
pub memfd_name: String,
pub size_bytes: u64,
pub is_executable: bool,
pub is_suspicious: bool,
}
pub use crate::heuristics::classify_memfd;
pub fn walk_memfd_create<P: PhysicalMemoryProvider>(
reader: &ObjectReader<P>,
) -> Result<Vec<MemfdInfo>> {
let init_task_addr = match reader.symbols().symbol_address("init_task") {
Some(a) => a,
None => return Ok(vec![]),
};
let tasks_offset = match reader.symbols().field_offset("task_struct", "tasks") {
Some(o) => o,
None => return Ok(vec![]),
};
let head_vaddr = init_task_addr + tasks_offset;
let task_addrs = reader.walk_list(head_vaddr, "task_struct", "tasks")?;
let mut results: Vec<MemfdInfo> = Vec::new();
collect_memfd_for_task(reader, init_task_addr, &mut results);
for &task_addr in &task_addrs {
collect_memfd_for_task(reader, task_addr, &mut results);
}
results.sort_by_key(|r| r.pid);
Ok(results)
}
fn collect_memfd_for_task<P: PhysicalMemoryProvider>(
reader: &ObjectReader<P>,
task_addr: u64,
out: &mut Vec<MemfdInfo>,
) {
let pid: u32 = match reader.read_field(task_addr, "task_struct", "pid") {
Ok(v) => v,
Err(_) => return,
};
let comm = reader
.read_field_string(task_addr, "task_struct", "comm", 16)
.unwrap_or_default();
for_each_task_vma(reader, task_addr, &mut |e| {
if let Some(info) = try_read_memfd_vma(reader, pid, &comm, e.vma_addr) {
let existing = out
.iter_mut()
.find(|entry| entry.pid == info.pid && entry.memfd_name == info.memfd_name);
if let Some(existing) = existing {
existing.size_bytes += info.size_bytes;
existing.is_executable |= info.is_executable;
existing.is_suspicious =
classify_memfd(&existing.memfd_name, existing.is_executable);
} else {
out.push(info);
}
}
});
}
fn try_read_memfd_vma<P: PhysicalMemoryProvider>(
reader: &ObjectReader<P>,
pid: u32,
comm: &str,
vma_addr: u64,
) -> Option<MemfdInfo> {
let vm_file_ptr: u64 = reader
.read_field(vma_addr, "vm_area_struct", "vm_file")
.ok()?;
if vm_file_ptr == 0 {
return None;
}
let dentry_name = read_file_dentry_name(reader, vm_file_ptr)?;
let memfd_name = dentry_name.strip_prefix("memfd:")?;
let vm_start: u64 = reader
.read_field(vma_addr, "vm_area_struct", "vm_start")
.ok()?;
let vm_end: u64 = reader
.read_field(vma_addr, "vm_area_struct", "vm_end")
.ok()?;
let vm_flags: u64 = reader
.read_field(vma_addr, "vm_area_struct", "vm_flags")
.ok()?;
let size_bytes = vm_end.saturating_sub(vm_start);
let is_executable = (vm_flags & VM_EXEC) != 0;
let is_suspicious = classify_memfd(memfd_name, is_executable);
Some(MemfdInfo {
pid,
comm: comm.to_string(),
memfd_name: memfd_name.to_string(),
size_bytes,
is_executable,
is_suspicious,
})
}
fn read_file_dentry_name<P: PhysicalMemoryProvider>(
reader: &ObjectReader<P>,
file_ptr: u64,
) -> Option<String> {
let f_path_offset = reader.symbols().field_offset("file", "f_path")?;
let dentry_in_path = reader.symbols().field_offset("path", "dentry")?;
let d_name_offset = reader.symbols().field_offset("dentry", "d_name")?;
let name_in_qstr = reader.symbols().field_offset("qstr", "name")?;
let dentry_addr = file_ptr + f_path_offset + dentry_in_path;
let dentry_raw = reader.read_bytes(dentry_addr, 8).ok()?;
let dentry_ptr = u64::from_le_bytes(dentry_raw.try_into().ok()?);
if dentry_ptr == 0 {
return None;
}
let name_addr = dentry_ptr + d_name_offset + name_in_qstr;
let name_raw = reader.read_bytes(name_addr, 8).ok()?;
let name_ptr = u64::from_le_bytes(name_raw.try_into().ok()?);
if name_ptr == 0 {
return None;
}
reader.read_string(name_ptr, 256).ok()
}
#[cfg(test)]
mod tests {
use super::*;
use memf_core::object_reader::ObjectReader;
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;
#[test]
fn classify_memfd_executable_is_suspicious() {
assert!(
classify_memfd("harmless", true),
"an executable memfd mapping must always be suspicious"
);
}
#[test]
fn classify_memfd_shellcode_name_is_suspicious() {
assert!(
classify_memfd("shellcode", false),
"a memfd named 'shellcode' must be suspicious"
);
}
#[test]
fn classify_memfd_empty_name_is_suspicious() {
assert!(
classify_memfd("", false),
"an anonymous memfd with empty name must be suspicious (evasion)"
);
}
#[test]
fn classify_memfd_pulseaudio_benign() {
assert!(
!classify_memfd("pulseaudio-shm", false),
"a non-executable memfd named 'pulseaudio-shm' must not be suspicious"
);
}
#[test]
fn classify_memfd_payload_name_is_suspicious() {
assert!(
classify_memfd("payload", false),
"a memfd named 'payload' must be suspicious"
);
}
#[test]
fn classify_memfd_wayland_benign() {
assert!(
!classify_memfd("wayland-shm", false),
"a non-executable memfd named 'wayland-shm' must not be suspicious"
);
}
fn make_reader_no_init_task() -> 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_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);
ObjectReader::new(vas, Box::new(resolver))
}
fn make_reader_no_memfd() -> ObjectReader<SyntheticPhysMem> {
let vaddr: u64 = 0xFFFF_8000_0010_0000;
let paddr: u64 = 0x0080_0000;
let mut data = vec![0u8; 4096];
data[0..4].copy_from_slice(&1u32.to_le_bytes());
let tasks_next = vaddr + 16;
data[16..24].copy_from_slice(&tasks_next.to_le_bytes());
data[24..32].copy_from_slice(&tasks_next.to_le_bytes());
data[32..37].copy_from_slice(b"init\0");
data[48..56].copy_from_slice(&0u64.to_le_bytes());
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", "comm", 32, "char")
.add_field("task_struct", "mm", 48, "pointer")
.add_struct("list_head", 16)
.add_field("list_head", "next", 0, "pointer")
.add_field("list_head", "prev", 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_memfd_missing_init_task_returns_empty() {
let reader = make_reader_no_init_task();
let result = walk_memfd_create(&reader).expect("should not error");
assert!(
result.is_empty(),
"missing init_task symbol must yield empty result (graceful degradation)"
);
}
#[test]
fn walk_memfd_no_memfd_processes_returns_empty() {
let reader = make_reader_no_memfd();
let result = walk_memfd_create(&reader).expect("should not error");
assert!(
result.is_empty(),
"a kernel thread with mm==NULL must not produce any memfd results"
);
}
#[test]
fn walk_memfd_missing_tasks_offset_returns_empty() {
let isf = IsfBuilder::new()
.add_struct("task_struct", 128)
.add_field("task_struct", "pid", 0, "int")
.add_symbol("init_task", 0xFFFF_8000_0000_0000)
.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_memfd_create(&reader).expect("should not error");
assert!(
result.is_empty(),
"missing tasks offset must yield empty result"
);
}
#[test]
fn classify_memfd_shm_prefix_benign() {
assert!(
!classify_memfd("shm_region", false),
"shm prefix must be benign"
);
}
#[test]
fn classify_memfd_chrome_prefix_benign() {
assert!(
!classify_memfd("chrome_shared", false),
"chrome prefix must be benign"
);
}
#[test]
fn classify_memfd_firefox_prefix_benign() {
assert!(
!classify_memfd("firefox-ipc", false),
"firefox prefix must be benign"
);
}
#[test]
fn classify_memfd_v8_prefix_benign() {
assert!(
!classify_memfd("v8-heap", false),
"v8 prefix must be benign"
);
}
#[test]
fn classify_memfd_dbus_prefix_benign() {
assert!(
!classify_memfd("dbus-shm", false),
"dbus prefix must be benign"
);
}
#[test]
fn classify_memfd_stage_name_suspicious() {
assert!(
classify_memfd("stage2", false),
"stage substring must be suspicious"
);
}
#[test]
fn classify_memfd_loader_name_suspicious() {
assert!(
classify_memfd("loader", false),
"loader substring must be suspicious"
);
}
#[test]
fn classify_memfd_inject_name_suspicious() {
assert!(
classify_memfd("inject_hook", false),
"inject substring must be suspicious"
);
}
#[test]
fn classify_memfd_hack_name_suspicious() {
assert!(
classify_memfd("hack_tool", false),
"hack substring must be suspicious"
);
}
#[test]
fn classify_memfd_benign_non_prefix_non_suspicious_name() {
assert!(
!classify_memfd("my_normal_buffer", false),
"innocuous name must be benign"
);
}
#[test]
fn classify_memfd_case_insensitive_suspicious() {
assert!(
classify_memfd("PAYLOAD_EXEC", false),
"case-insensitive suspicious match"
);
}
#[test]
fn walk_memfd_symbol_present_empty_list() {
let sym_vaddr: u64 = 0xFFFF_8800_0020_0000;
let sym_paddr: u64 = 0x0030_0000;
let tasks_offset = 16u64;
let mut page = [0u8; 4096];
page[0..4].copy_from_slice(&1u32.to_le_bytes());
let list_self = sym_vaddr + tasks_offset;
page[tasks_offset as usize..tasks_offset as usize + 8]
.copy_from_slice(&list_self.to_le_bytes());
page[tasks_offset as usize + 8..tasks_offset as usize + 16]
.copy_from_slice(&list_self.to_le_bytes());
page[32..36].copy_from_slice(b"init");
page[48..56].copy_from_slice(&0u64.to_le_bytes());
let isf = IsfBuilder::new()
.add_struct("task_struct", 128)
.add_field("task_struct", "pid", 0, "unsigned int")
.add_field("task_struct", "tasks", 16, "pointer")
.add_field("task_struct", "comm", 32, "char")
.add_field("task_struct", "mm", 48, "pointer")
.add_symbol("init_task", sym_vaddr)
.build_json();
let resolver = IsfResolver::from_value(&isf).unwrap();
let (cr3, mem) = PageTableBuilder::new()
.map_4k(sym_vaddr, sym_paddr, flags::WRITABLE)
.write_phys(sym_paddr, &page)
.build();
let vas = VirtualAddressSpace::new(mem, cr3, TranslationMode::X86_64FourLevel);
let reader = ObjectReader::new(vas, Box::new(resolver));
let result = walk_memfd_create(&reader).unwrap_or_default();
assert!(
result.is_empty(),
"no memfd mappings expected for a kernel thread"
);
}
#[test]
fn memfd_info_serializes() {
let info = MemfdInfo {
pid: 999,
comm: "evil".to_string(),
memfd_name: "payload".to_string(),
size_bytes: 4096,
is_executable: true,
is_suspicious: true,
};
let json = serde_json::to_string(&info).unwrap();
assert!(json.contains("\"pid\":999"));
assert!(json.contains("\"is_suspicious\":true"));
assert!(json.contains("\"is_executable\":true"));
}
#[test]
fn walk_memfd_mm_nonzero_mmap_unreadable_returns_empty() {
let sym_vaddr: u64 = 0xFFFF_8800_0060_0000;
let sym_paddr: u64 = 0x0060_0000;
let tasks_offset: u64 = 16;
let mm_offset: u64 = 48;
let mm_vaddr: u64 = 0xFFFF_DEAD_BEEF_0000;
let mut page = [0u8; 4096];
page[0..4].copy_from_slice(&1u32.to_le_bytes());
let self_ptr = sym_vaddr + tasks_offset;
page[tasks_offset as usize..tasks_offset as usize + 8]
.copy_from_slice(&self_ptr.to_le_bytes());
page[32..36].copy_from_slice(b"proc");
page[mm_offset as usize..mm_offset as usize + 8].copy_from_slice(&mm_vaddr.to_le_bytes());
let isf = IsfBuilder::new()
.add_struct("list_head", 16)
.add_field("list_head", "next", 0, "pointer")
.add_struct("task_struct", 128)
.add_field("task_struct", "pid", 0, "unsigned int")
.add_field("task_struct", "tasks", tasks_offset, "pointer")
.add_field("task_struct", "comm", 32, "char")
.add_field("task_struct", "mm", mm_offset, "pointer")
.add_struct("mm_struct", 0x200)
.add_field("mm_struct", "mmap", 0x00, "pointer")
.add_symbol("init_task", sym_vaddr)
.build_json();
let resolver = IsfResolver::from_value(&isf).unwrap();
let (cr3, mem) = PageTableBuilder::new()
.map_4k(sym_vaddr, sym_paddr, flags::WRITABLE)
.write_phys(sym_paddr, &page)
.build();
let vas = VirtualAddressSpace::new(mem, cr3, TranslationMode::X86_64FourLevel);
let reader = ObjectReader::new(vas, Box::new(resolver));
let result = walk_memfd_create(&reader).expect("should not error");
assert!(
result.is_empty(),
"unreadable mm_struct → mmap unreadable → no memfd results"
);
}
#[test]
fn walk_memfd_mm_nonzero_mmap_zero_returns_empty() {
let sym_vaddr: u64 = 0xFFFF_8800_0061_0000;
let sym_paddr: u64 = 0x0061_0000;
let tasks_offset: u64 = 16;
let mm_offset: u64 = 48;
let mm_vaddr: u64 = 0xFFFF_8800_0062_0000;
let mm_paddr: u64 = 0x0062_0000;
let mut task_page = [0u8; 4096];
page_write_u32(&mut task_page, 0, 2u32); let self_ptr = sym_vaddr + tasks_offset;
task_page[tasks_offset as usize..tasks_offset as usize + 8]
.copy_from_slice(&self_ptr.to_le_bytes());
task_page[32..36].copy_from_slice(b"proc");
task_page[mm_offset as usize..mm_offset as usize + 8]
.copy_from_slice(&mm_vaddr.to_le_bytes());
let mm_page = [0u8; 4096];
let isf = IsfBuilder::new()
.add_struct("list_head", 16)
.add_field("list_head", "next", 0, "pointer")
.add_struct("task_struct", 128)
.add_field("task_struct", "pid", 0, "unsigned int")
.add_field("task_struct", "tasks", tasks_offset, "pointer")
.add_field("task_struct", "comm", 32, "char")
.add_field("task_struct", "mm", mm_offset, "pointer")
.add_struct("mm_struct", 0x200)
.add_field("mm_struct", "mmap", 0x00, "pointer")
.add_symbol("init_task", sym_vaddr)
.build_json();
let resolver = IsfResolver::from_value(&isf).unwrap();
let (cr3, mem) = PageTableBuilder::new()
.map_4k(sym_vaddr, sym_paddr, flags::WRITABLE)
.write_phys(sym_paddr, &task_page)
.map_4k(mm_vaddr, mm_paddr, flags::WRITABLE)
.write_phys(mm_paddr, &mm_page)
.build();
let vas = VirtualAddressSpace::new(mem, cr3, TranslationMode::X86_64FourLevel);
let reader = ObjectReader::new(vas, Box::new(resolver));
let result = walk_memfd_create(&reader).expect("should not error");
assert!(
result.is_empty(),
"mmap_ptr == 0 → no VMAs → no memfd results"
);
}
#[test]
fn walk_memfd_vma_vm_file_null_skipped() {
let sym_vaddr: u64 = 0xFFFF_8800_0063_0000;
let sym_paddr: u64 = 0x0063_0000;
let tasks_offset: u64 = 16;
let mm_offset: u64 = 48;
let mm_vaddr: u64 = 0xFFFF_8800_0064_0000;
let mm_paddr: u64 = 0x0064_0000;
let vma_vaddr: u64 = 0xFFFF_8800_0065_0000;
let vma_paddr: u64 = 0x0065_0000;
let mut task_page = [0u8; 4096];
page_write_u32(&mut task_page, 0, 3u32);
let self_ptr = sym_vaddr + tasks_offset;
task_page[tasks_offset as usize..tasks_offset as usize + 8]
.copy_from_slice(&self_ptr.to_le_bytes());
task_page[32..36].copy_from_slice(b"proc");
task_page[mm_offset as usize..mm_offset as usize + 8]
.copy_from_slice(&mm_vaddr.to_le_bytes());
let mut mm_page = [0u8; 4096];
mm_page[0..8].copy_from_slice(&vma_vaddr.to_le_bytes());
let vma_page = [0u8; 4096];
let isf = IsfBuilder::new()
.add_struct("list_head", 16)
.add_field("list_head", "next", 0, "pointer")
.add_struct("task_struct", 128)
.add_field("task_struct", "pid", 0, "unsigned int")
.add_field("task_struct", "tasks", tasks_offset, "pointer")
.add_field("task_struct", "comm", 32, "char")
.add_field("task_struct", "mm", mm_offset, "pointer")
.add_struct("mm_struct", 0x200)
.add_field("mm_struct", "mmap", 0x00, "pointer")
.add_struct("vm_area_struct", 0x100)
.add_field("vm_area_struct", "vm_next", 0x00, "pointer")
.add_field("vm_area_struct", "vm_file", 0x08, "pointer")
.add_field("vm_area_struct", "vm_start", 0x10, "unsigned long")
.add_field("vm_area_struct", "vm_end", 0x18, "unsigned long")
.add_field("vm_area_struct", "vm_flags", 0x20, "unsigned long")
.add_symbol("init_task", sym_vaddr)
.build_json();
let resolver = IsfResolver::from_value(&isf).unwrap();
let (cr3, mem) = PageTableBuilder::new()
.map_4k(sym_vaddr, sym_paddr, flags::WRITABLE)
.write_phys(sym_paddr, &task_page)
.map_4k(mm_vaddr, mm_paddr, flags::WRITABLE)
.write_phys(mm_paddr, &mm_page)
.map_4k(vma_vaddr, vma_paddr, flags::WRITABLE)
.write_phys(vma_paddr, &vma_page)
.build();
let vas = VirtualAddressSpace::new(mem, cr3, TranslationMode::X86_64FourLevel);
let reader = ObjectReader::new(vas, Box::new(resolver));
let result = walk_memfd_create(&reader).expect("should not error");
assert!(
result.is_empty(),
"vm_file = 0 → try_read_memfd_vma returns None → no entries"
);
}
fn page_write_u32(page: &mut [u8], offset: usize, val: u32) {
page[offset..offset + 4].copy_from_slice(&val.to_le_bytes());
}
fn page_write_u64(page: &mut [u8], offset: usize, val: u64) {
page[offset..offset + 8].copy_from_slice(&val.to_le_bytes());
}
#[test]
fn walk_memfd_second_task_pid_read_fails_skipped() {
let init_vaddr: u64 = 0xFFFF_8800_0080_0000;
let init_paddr: u64 = 0x0080_0000;
let tasks_offset: u64 = 16;
let task2_vaddr: u64 = 0xFFFF_DEAD_0000_0000;
let mut init_page = [0u8; 4096];
page_write_u32(&mut init_page, 0, 1); page_write_u64(
&mut init_page,
tasks_offset as usize,
task2_vaddr + tasks_offset,
);
page_write_u64(
&mut init_page,
tasks_offset as usize + 8,
task2_vaddr + tasks_offset,
);
init_page[32..36].copy_from_slice(b"init");
page_write_u64(&mut init_page, 48, 0);
let isf = IsfBuilder::new()
.add_struct("list_head", 16)
.add_field("list_head", "next", 0u64, "pointer")
.add_field("list_head", "prev", 8u64, "pointer")
.add_struct("task_struct", 128)
.add_field("task_struct", "pid", 0u64, "unsigned int")
.add_field("task_struct", "tasks", tasks_offset, "list_head")
.add_field("task_struct", "comm", 32u64, "char")
.add_field("task_struct", "mm", 48u64, "pointer")
.add_symbol("init_task", init_vaddr)
.build_json();
let resolver = IsfResolver::from_value(&isf).unwrap();
let (cr3, mem) = PageTableBuilder::new()
.map_4k(init_vaddr, init_paddr, flags::WRITABLE)
.write_phys(init_paddr, &init_page)
.build();
let vas = VirtualAddressSpace::new(mem, cr3, TranslationMode::X86_64FourLevel);
let reader = ObjectReader::new(vas, Box::new(resolver));
let result = walk_memfd_create(&reader);
let entries = result.unwrap_or_default();
assert!(entries.is_empty(), "unmapped task2 → no memfd entries");
}
#[test]
fn walk_memfd_full_path_memfd_vma_detected() {
let init_vaddr: u64 = 0xFFFF_8800_00A0_0000;
let init_paddr: u64 = 0x00A0_0000;
let mm_vaddr: u64 = 0xFFFF_8800_00A1_0000;
let mm_paddr: u64 = 0x00A1_0000;
let vma_vaddr: u64 = 0xFFFF_8800_00A2_0000;
let vma_paddr: u64 = 0x00A2_0000;
let file_vaddr: u64 = 0xFFFF_8800_00A3_0000;
let file_paddr: u64 = 0x00A3_0000;
let dentry_vaddr: u64 = 0xFFFF_8800_00A4_0000;
let dentry_paddr: u64 = 0x00A4_0000;
let name_vaddr: u64 = 0xFFFF_8800_00A5_0000;
let name_paddr: u64 = 0x00A5_0000;
let tasks_offset: u64 = 16;
let mm_offset: u64 = 48;
let mmap_offset: u64 = 0;
let f_path_off: u64 = 0x10;
let dentry_in_path: u64 = 0x00; let d_name_off: u64 = 0x08; let name_in_qstr: u64 = 0x00;
let mut init_page = [0u8; 4096];
page_write_u32(&mut init_page, 0, 7u32); let tasks_self = init_vaddr + tasks_offset;
page_write_u64(&mut init_page, tasks_offset as usize, tasks_self);
page_write_u64(&mut init_page, tasks_offset as usize + 8, tasks_self);
init_page[32..37].copy_from_slice(b"evil\0");
page_write_u64(&mut init_page, mm_offset as usize, mm_vaddr);
let mut mm_page = [0u8; 4096];
page_write_u64(&mut mm_page, mmap_offset as usize, vma_vaddr);
let mut vma_page = [0u8; 4096];
page_write_u64(&mut vma_page, 0x00, 0u64); page_write_u64(&mut vma_page, 0x08, file_vaddr); page_write_u64(&mut vma_page, 0x10, 0x1000u64); page_write_u64(&mut vma_page, 0x18, 0x2000u64); page_write_u64(&mut vma_page, 0x20, 4u64);
let mut file_page = [0u8; 4096];
page_write_u64(&mut file_page, 0x10, dentry_vaddr);
let mut dentry_page = [0u8; 4096];
page_write_u64(&mut dentry_page, 0x08, name_vaddr);
let mut name_page = [0u8; 4096];
name_page[..14].copy_from_slice(b"memfd:payload\0");
let isf = IsfBuilder::new()
.add_struct("list_head", 16)
.add_field("list_head", "next", 0u64, "pointer")
.add_field("list_head", "prev", 8u64, "pointer")
.add_struct("task_struct", 128)
.add_field("task_struct", "pid", 0u64, "unsigned int")
.add_field("task_struct", "tasks", tasks_offset, "list_head")
.add_field("task_struct", "comm", 32u64, "char")
.add_field("task_struct", "mm", mm_offset, "pointer")
.add_struct("mm_struct", 0x100)
.add_field("mm_struct", "mmap", mmap_offset, "pointer")
.add_struct("vm_area_struct", 0x100)
.add_field("vm_area_struct", "vm_next", 0x00u64, "pointer")
.add_field("vm_area_struct", "vm_file", 0x08u64, "pointer")
.add_field("vm_area_struct", "vm_start", 0x10u64, "unsigned long")
.add_field("vm_area_struct", "vm_end", 0x18u64, "unsigned long")
.add_field("vm_area_struct", "vm_flags", 0x20u64, "unsigned long")
.add_struct("file", 0x100)
.add_field("file", "f_path", f_path_off, "pointer")
.add_struct("path", 0x20)
.add_field("path", "dentry", dentry_in_path, "pointer")
.add_struct("dentry", 0x100)
.add_field("dentry", "d_name", d_name_off, "pointer")
.add_struct("qstr", 0x20)
.add_field("qstr", "name", name_in_qstr, "pointer")
.add_symbol("init_task", init_vaddr)
.build_json();
let resolver = IsfResolver::from_value(&isf).unwrap();
let (cr3, mem) = PageTableBuilder::new()
.map_4k(init_vaddr, init_paddr, flags::WRITABLE)
.write_phys(init_paddr, &init_page)
.map_4k(mm_vaddr, mm_paddr, flags::WRITABLE)
.write_phys(mm_paddr, &mm_page)
.map_4k(vma_vaddr, vma_paddr, flags::WRITABLE)
.write_phys(vma_paddr, &vma_page)
.map_4k(file_vaddr, file_paddr, flags::WRITABLE)
.write_phys(file_paddr, &file_page)
.map_4k(dentry_vaddr, dentry_paddr, flags::WRITABLE)
.write_phys(dentry_paddr, &dentry_page)
.map_4k(name_vaddr, name_paddr, flags::WRITABLE)
.write_phys(name_paddr, &name_page)
.build();
let vas = VirtualAddressSpace::new(mem, cr3, TranslationMode::X86_64FourLevel);
let reader = ObjectReader::new(vas, Box::new(resolver));
let result = walk_memfd_create(&reader).expect("should not error");
assert_eq!(result.len(), 1, "expected one memfd entry");
assert_eq!(result[0].pid, 7);
assert_eq!(result[0].comm, "evil");
assert_eq!(result[0].memfd_name, "payload");
assert_eq!(result[0].size_bytes, 0x1000);
assert!(result[0].is_executable, "VM_EXEC flag set → executable");
assert!(result[0].is_suspicious, "executable memfd → suspicious");
}
#[test]
fn walk_memfd_dentry_missing_isf_fields_no_entry() {
let init_vaddr: u64 = 0xFFFF_8800_00B0_0000;
let init_paddr: u64 = 0x00B0_0000;
let mm_vaddr: u64 = 0xFFFF_8800_00B1_0000;
let mm_paddr: u64 = 0x00B1_0000;
let vma_vaddr: u64 = 0xFFFF_8800_00B2_0000;
let vma_paddr: u64 = 0x00B2_0000;
let file_vaddr: u64 = 0xFFFF_8800_00B3_0000;
let tasks_offset: u64 = 16;
let mm_offset: u64 = 48;
let mut init_page = [0u8; 4096];
page_write_u32(&mut init_page, 0, 8u32);
let tasks_self = init_vaddr + tasks_offset;
page_write_u64(&mut init_page, tasks_offset as usize, tasks_self);
page_write_u64(&mut init_page, tasks_offset as usize + 8, tasks_self);
init_page[32..36].copy_from_slice(b"proc");
page_write_u64(&mut init_page, mm_offset as usize, mm_vaddr);
let mut mm_page = [0u8; 4096];
page_write_u64(&mut mm_page, 0, vma_vaddr);
let mut vma_page = [0u8; 4096];
page_write_u64(&mut vma_page, 0x00, 0u64); page_write_u64(&mut vma_page, 0x08, file_vaddr); page_write_u64(&mut vma_page, 0x10, 0x1000u64); page_write_u64(&mut vma_page, 0x18, 0x2000u64); page_write_u64(&mut vma_page, 0x20, 0u64);
let isf = IsfBuilder::new()
.add_struct("list_head", 16)
.add_field("list_head", "next", 0u64, "pointer")
.add_field("list_head", "prev", 8u64, "pointer")
.add_struct("task_struct", 128)
.add_field("task_struct", "pid", 0u64, "unsigned int")
.add_field("task_struct", "tasks", tasks_offset, "list_head")
.add_field("task_struct", "comm", 32u64, "char")
.add_field("task_struct", "mm", mm_offset, "pointer")
.add_struct("mm_struct", 0x100)
.add_field("mm_struct", "mmap", 0u64, "pointer")
.add_struct("vm_area_struct", 0x100)
.add_field("vm_area_struct", "vm_next", 0x00u64, "pointer")
.add_field("vm_area_struct", "vm_file", 0x08u64, "pointer")
.add_field("vm_area_struct", "vm_start", 0x10u64, "unsigned long")
.add_field("vm_area_struct", "vm_end", 0x18u64, "unsigned long")
.add_field("vm_area_struct", "vm_flags", 0x20u64, "unsigned long")
.add_symbol("init_task", init_vaddr)
.build_json();
let resolver = IsfResolver::from_value(&isf).unwrap();
let (cr3, mem) = PageTableBuilder::new()
.map_4k(init_vaddr, init_paddr, flags::WRITABLE)
.write_phys(init_paddr, &init_page)
.map_4k(mm_vaddr, mm_paddr, flags::WRITABLE)
.write_phys(mm_paddr, &mm_page)
.map_4k(vma_vaddr, vma_paddr, flags::WRITABLE)
.write_phys(vma_paddr, &vma_page)
.build();
let vas = VirtualAddressSpace::new(mem, cr3, TranslationMode::X86_64FourLevel);
let reader = ObjectReader::new(vas, Box::new(resolver));
let result = walk_memfd_create(&reader).expect("should not error");
assert!(
result.is_empty(),
"missing dentry ISF fields → read_file_dentry_name None → no entries"
);
}
#[test]
fn walk_memfd_two_vmas_same_name_merged() {
let init_vaddr: u64 = 0xFFFF_8800_00C0_0000;
let init_paddr: u64 = 0x00C0_0000;
let mm_vaddr: u64 = 0xFFFF_8800_00C1_0000;
let mm_paddr: u64 = 0x00C1_0000;
let vma1_vaddr: u64 = 0xFFFF_8800_00C2_0000;
let vma1_paddr: u64 = 0x00C2_0000;
let vma2_vaddr: u64 = 0xFFFF_8800_00C3_0000;
let vma2_paddr: u64 = 0x00C3_0000;
let file_vaddr: u64 = 0xFFFF_8800_00C4_0000;
let file_paddr: u64 = 0x00C4_0000;
let dentry_vaddr: u64 = 0xFFFF_8800_00C5_0000;
let dentry_paddr: u64 = 0x00C5_0000;
let name_vaddr: u64 = 0xFFFF_8800_00C6_0000;
let name_paddr: u64 = 0x00C6_0000;
let tasks_offset: u64 = 16;
let mm_offset: u64 = 48;
let f_path_off: u64 = 0x10;
let d_name_off: u64 = 0x08;
let mut init_page = [0u8; 4096];
page_write_u32(&mut init_page, 0, 9u32);
let tasks_self = init_vaddr + tasks_offset;
page_write_u64(&mut init_page, tasks_offset as usize, tasks_self);
page_write_u64(&mut init_page, tasks_offset as usize + 8, tasks_self);
init_page[32..40].copy_from_slice(b"malware\0");
page_write_u64(&mut init_page, mm_offset as usize, mm_vaddr);
let mut mm_page = [0u8; 4096];
page_write_u64(&mut mm_page, 0, vma1_vaddr);
let mut vma1_page = [0u8; 4096];
page_write_u64(&mut vma1_page, 0x00, vma2_vaddr); page_write_u64(&mut vma1_page, 0x08, file_vaddr); page_write_u64(&mut vma1_page, 0x10, 0x1000u64); page_write_u64(&mut vma1_page, 0x18, 0x2000u64); page_write_u64(&mut vma1_page, 0x20, 0u64);
let mut vma2_page = [0u8; 4096];
page_write_u64(&mut vma2_page, 0x00, 0u64); page_write_u64(&mut vma2_page, 0x08, file_vaddr); page_write_u64(&mut vma2_page, 0x10, 0x2000u64); page_write_u64(&mut vma2_page, 0x18, 0x3000u64); page_write_u64(&mut vma2_page, 0x20, 4u64);
let mut file_page = [0u8; 4096];
page_write_u64(&mut file_page, 0x10, dentry_vaddr);
let mut dentry_page = [0u8; 4096];
page_write_u64(&mut dentry_page, 0x08, name_vaddr);
let mut name_page = [0u8; 4096];
name_page[..14].copy_from_slice(b"memfd:payload\0");
let isf = IsfBuilder::new()
.add_struct("list_head", 16)
.add_field("list_head", "next", 0u64, "pointer")
.add_field("list_head", "prev", 8u64, "pointer")
.add_struct("task_struct", 128)
.add_field("task_struct", "pid", 0u64, "unsigned int")
.add_field("task_struct", "tasks", tasks_offset, "list_head")
.add_field("task_struct", "comm", 32u64, "char")
.add_field("task_struct", "mm", mm_offset, "pointer")
.add_struct("mm_struct", 0x100)
.add_field("mm_struct", "mmap", 0u64, "pointer")
.add_struct("vm_area_struct", 0x100)
.add_field("vm_area_struct", "vm_next", 0x00u64, "pointer")
.add_field("vm_area_struct", "vm_file", 0x08u64, "pointer")
.add_field("vm_area_struct", "vm_start", 0x10u64, "unsigned long")
.add_field("vm_area_struct", "vm_end", 0x18u64, "unsigned long")
.add_field("vm_area_struct", "vm_flags", 0x20u64, "unsigned long")
.add_struct("file", 0x100)
.add_field("file", "f_path", f_path_off, "pointer")
.add_struct("path", 0x20)
.add_field("path", "dentry", 0u64, "pointer")
.add_struct("dentry", 0x100)
.add_field("dentry", "d_name", d_name_off, "pointer")
.add_struct("qstr", 0x20)
.add_field("qstr", "name", 0u64, "pointer")
.add_symbol("init_task", init_vaddr)
.build_json();
let resolver = IsfResolver::from_value(&isf).unwrap();
let (cr3, mem) = PageTableBuilder::new()
.map_4k(init_vaddr, init_paddr, flags::WRITABLE)
.write_phys(init_paddr, &init_page)
.map_4k(mm_vaddr, mm_paddr, flags::WRITABLE)
.write_phys(mm_paddr, &mm_page)
.map_4k(vma1_vaddr, vma1_paddr, flags::WRITABLE)
.write_phys(vma1_paddr, &vma1_page)
.map_4k(vma2_vaddr, vma2_paddr, flags::WRITABLE)
.write_phys(vma2_paddr, &vma2_page)
.map_4k(file_vaddr, file_paddr, flags::WRITABLE)
.write_phys(file_paddr, &file_page)
.map_4k(dentry_vaddr, dentry_paddr, flags::WRITABLE)
.write_phys(dentry_paddr, &dentry_page)
.map_4k(name_vaddr, name_paddr, flags::WRITABLE)
.write_phys(name_paddr, &name_page)
.build();
let vas = VirtualAddressSpace::new(mem, cr3, TranslationMode::X86_64FourLevel);
let reader = ObjectReader::new(vas, Box::new(resolver));
let result = walk_memfd_create(&reader).expect("should not error");
assert_eq!(
result.len(),
1,
"two VMAs for same memfd → merged to 1 entry"
);
assert_eq!(result[0].memfd_name, "payload");
assert_eq!(result[0].size_bytes, 0x2000);
assert!(
result[0].is_executable,
"merged entry must be executable after vma2"
);
assert!(
result[0].is_suspicious,
"executable memfd:payload must be suspicious"
);
}
#[test]
fn walk_memfd_dentry_ptr_null_returns_none() {
let init_vaddr: u64 = 0xFFFF_8800_00D0_0000;
let init_paddr: u64 = 0x00D0_0000;
let mm_vaddr: u64 = 0xFFFF_8800_00D1_0000;
let mm_paddr: u64 = 0x00D1_0000;
let vma_vaddr: u64 = 0xFFFF_8800_00D2_0000;
let vma_paddr: u64 = 0x00D2_0000;
let file_vaddr: u64 = 0xFFFF_8800_00D3_0000;
let file_paddr: u64 = 0x00D3_0000;
let tasks_offset: u64 = 16;
let mm_offset: u64 = 48;
let f_path_off: u64 = 0x10;
let d_name_off: u64 = 0x08;
let mut init_page = [0u8; 4096];
page_write_u32(&mut init_page, 0, 11u32);
let tasks_self = init_vaddr + tasks_offset;
page_write_u64(&mut init_page, tasks_offset as usize, tasks_self);
page_write_u64(&mut init_page, tasks_offset as usize + 8, tasks_self);
init_page[32..36].copy_from_slice(b"proc");
page_write_u64(&mut init_page, mm_offset as usize, mm_vaddr);
let mut mm_page = [0u8; 4096];
page_write_u64(&mut mm_page, 0, vma_vaddr);
let mut vma_page = [0u8; 4096];
page_write_u64(&mut vma_page, 0x00, 0u64); page_write_u64(&mut vma_page, 0x08, file_vaddr); page_write_u64(&mut vma_page, 0x10, 0x1000u64);
page_write_u64(&mut vma_page, 0x18, 0x2000u64);
page_write_u64(&mut vma_page, 0x20, 0u64);
let mut file_page = [0u8; 4096];
page_write_u64(&mut file_page, 0x10, 0u64);
let isf = IsfBuilder::new()
.add_struct("list_head", 16)
.add_field("list_head", "next", 0u64, "pointer")
.add_field("list_head", "prev", 8u64, "pointer")
.add_struct("task_struct", 128)
.add_field("task_struct", "pid", 0u64, "unsigned int")
.add_field("task_struct", "tasks", tasks_offset, "list_head")
.add_field("task_struct", "comm", 32u64, "char")
.add_field("task_struct", "mm", mm_offset, "pointer")
.add_struct("mm_struct", 0x100)
.add_field("mm_struct", "mmap", 0u64, "pointer")
.add_struct("vm_area_struct", 0x100)
.add_field("vm_area_struct", "vm_next", 0x00u64, "pointer")
.add_field("vm_area_struct", "vm_file", 0x08u64, "pointer")
.add_field("vm_area_struct", "vm_start", 0x10u64, "unsigned long")
.add_field("vm_area_struct", "vm_end", 0x18u64, "unsigned long")
.add_field("vm_area_struct", "vm_flags", 0x20u64, "unsigned long")
.add_struct("file", 0x100)
.add_field("file", "f_path", f_path_off, "pointer")
.add_struct("path", 0x20)
.add_field("path", "dentry", 0u64, "pointer")
.add_struct("dentry", 0x100)
.add_field("dentry", "d_name", d_name_off, "pointer")
.add_struct("qstr", 0x20)
.add_field("qstr", "name", 0u64, "pointer")
.add_symbol("init_task", init_vaddr)
.build_json();
let resolver = IsfResolver::from_value(&isf).unwrap();
let (cr3, mem) = PageTableBuilder::new()
.map_4k(init_vaddr, init_paddr, flags::WRITABLE)
.write_phys(init_paddr, &init_page)
.map_4k(mm_vaddr, mm_paddr, flags::WRITABLE)
.write_phys(mm_paddr, &mm_page)
.map_4k(vma_vaddr, vma_paddr, flags::WRITABLE)
.write_phys(vma_paddr, &vma_page)
.map_4k(file_vaddr, file_paddr, flags::WRITABLE)
.write_phys(file_paddr, &file_page)
.build();
let vas = VirtualAddressSpace::new(mem, cr3, TranslationMode::X86_64FourLevel);
let reader = ObjectReader::new(vas, Box::new(resolver));
let result = walk_memfd_create(&reader).expect("should not error");
assert!(
result.is_empty(),
"dentry_ptr == 0 → read_file_dentry_name None → no entries"
);
}
#[test]
fn walk_memfd_name_ptr_null_returns_none() {
let init_vaddr: u64 = 0xFFFF_8800_00E0_0000;
let init_paddr: u64 = 0x00E0_0000;
let mm_vaddr: u64 = 0xFFFF_8800_00E1_0000;
let mm_paddr: u64 = 0x00E1_0000;
let vma_vaddr: u64 = 0xFFFF_8800_00E2_0000;
let vma_paddr: u64 = 0x00E2_0000;
let file_vaddr: u64 = 0xFFFF_8800_00E3_0000;
let file_paddr: u64 = 0x00E3_0000;
let dentry_vaddr: u64 = 0xFFFF_8800_00E4_0000;
let dentry_paddr: u64 = 0x00E4_0000;
let tasks_offset: u64 = 16;
let mm_offset: u64 = 48;
let f_path_off: u64 = 0x10;
let d_name_off: u64 = 0x08;
let mut init_page = [0u8; 4096];
page_write_u32(&mut init_page, 0, 12u32);
let tasks_self = init_vaddr + tasks_offset;
page_write_u64(&mut init_page, tasks_offset as usize, tasks_self);
page_write_u64(&mut init_page, tasks_offset as usize + 8, tasks_self);
init_page[32..36].copy_from_slice(b"proc");
page_write_u64(&mut init_page, mm_offset as usize, mm_vaddr);
let mut mm_page = [0u8; 4096];
page_write_u64(&mut mm_page, 0, vma_vaddr);
let mut vma_page = [0u8; 4096];
page_write_u64(&mut vma_page, 0x00, 0u64);
page_write_u64(&mut vma_page, 0x08, file_vaddr);
page_write_u64(&mut vma_page, 0x10, 0x1000u64);
page_write_u64(&mut vma_page, 0x18, 0x2000u64);
page_write_u64(&mut vma_page, 0x20, 0u64);
let mut file_page = [0u8; 4096];
page_write_u64(&mut file_page, 0x10, dentry_vaddr);
let mut dentry_page = [0u8; 4096];
page_write_u64(&mut dentry_page, 0x08, 0u64);
let isf = IsfBuilder::new()
.add_struct("list_head", 16)
.add_field("list_head", "next", 0u64, "pointer")
.add_field("list_head", "prev", 8u64, "pointer")
.add_struct("task_struct", 128)
.add_field("task_struct", "pid", 0u64, "unsigned int")
.add_field("task_struct", "tasks", tasks_offset, "list_head")
.add_field("task_struct", "comm", 32u64, "char")
.add_field("task_struct", "mm", mm_offset, "pointer")
.add_struct("mm_struct", 0x100)
.add_field("mm_struct", "mmap", 0u64, "pointer")
.add_struct("vm_area_struct", 0x100)
.add_field("vm_area_struct", "vm_next", 0x00u64, "pointer")
.add_field("vm_area_struct", "vm_file", 0x08u64, "pointer")
.add_field("vm_area_struct", "vm_start", 0x10u64, "unsigned long")
.add_field("vm_area_struct", "vm_end", 0x18u64, "unsigned long")
.add_field("vm_area_struct", "vm_flags", 0x20u64, "unsigned long")
.add_struct("file", 0x100)
.add_field("file", "f_path", f_path_off, "pointer")
.add_struct("path", 0x20)
.add_field("path", "dentry", 0u64, "pointer")
.add_struct("dentry", 0x100)
.add_field("dentry", "d_name", d_name_off, "pointer")
.add_struct("qstr", 0x20)
.add_field("qstr", "name", 0u64, "pointer")
.add_symbol("init_task", init_vaddr)
.build_json();
let resolver = IsfResolver::from_value(&isf).unwrap();
let (cr3, mem) = PageTableBuilder::new()
.map_4k(init_vaddr, init_paddr, flags::WRITABLE)
.write_phys(init_paddr, &init_page)
.map_4k(mm_vaddr, mm_paddr, flags::WRITABLE)
.write_phys(mm_paddr, &mm_page)
.map_4k(vma_vaddr, vma_paddr, flags::WRITABLE)
.write_phys(vma_paddr, &vma_page)
.map_4k(file_vaddr, file_paddr, flags::WRITABLE)
.write_phys(file_paddr, &file_page)
.map_4k(dentry_vaddr, dentry_paddr, flags::WRITABLE)
.write_phys(dentry_paddr, &dentry_page)
.build();
let vas = VirtualAddressSpace::new(mem, cr3, TranslationMode::X86_64FourLevel);
let reader = ObjectReader::new(vas, Box::new(resolver));
let result = walk_memfd_create(&reader).expect("should not error");
assert!(
result.is_empty(),
"name_ptr == 0 → read_file_dentry_name None → no entries"
);
}
#[test]
fn walk_memfd_vm_file_nonzero_non_memfd_name_skipped() {
let sym_vaddr: u64 = 0xFFFF_8800_0070_0000;
let sym_paddr: u64 = 0x0070_0000;
let tasks_offset: u64 = 16;
let mm_offset: u64 = 48;
let mm_vaddr: u64 = 0xFFFF_8800_0071_0000;
let mm_paddr: u64 = 0x0071_0000;
let vma_vaddr: u64 = 0xFFFF_8800_0072_0000;
let vma_paddr: u64 = 0x0072_0000;
let file_vaddr: u64 = 0xFFFF_8800_0073_0000;
let file_paddr: u64 = 0x0073_0000;
let dentry_vaddr: u64 = 0xFFFF_8800_0074_0000;
let dentry_paddr: u64 = 0x0074_0000;
let name_vaddr: u64 = 0xFFFF_8800_0075_0000;
let name_paddr: u64 = 0x0075_0000;
let f_path_off: u64 = 0x10; let dentry_in_path: u64 = 0x00; let d_name_off: u64 = 0x08; let name_in_qstr: u64 = 0x00;
let mut task_page = [0u8; 4096];
page_write_u32(&mut task_page, 0, 5u32); let self_ptr = sym_vaddr + tasks_offset;
task_page[tasks_offset as usize..tasks_offset as usize + 8]
.copy_from_slice(&self_ptr.to_le_bytes());
task_page[32..37].copy_from_slice(b"bash\0");
task_page[mm_offset as usize..mm_offset as usize + 8]
.copy_from_slice(&mm_vaddr.to_le_bytes());
let mut mm_page = [0u8; 4096];
mm_page[0..8].copy_from_slice(&vma_vaddr.to_le_bytes());
let mut vma_page = [0u8; 4096];
vma_page[0..8].copy_from_slice(&0u64.to_le_bytes()); vma_page[8..16].copy_from_slice(&file_vaddr.to_le_bytes()); vma_page[0x10..0x18].copy_from_slice(&0x1000u64.to_le_bytes()); vma_page[0x18..0x20].copy_from_slice(&0x2000u64.to_le_bytes()); vma_page[0x20..0x28].copy_from_slice(&0u64.to_le_bytes());
let mut file_page = [0u8; 4096];
file_page[0x10..0x18].copy_from_slice(&dentry_vaddr.to_le_bytes());
let mut dentry_page = [0u8; 4096];
dentry_page[0x08..0x10].copy_from_slice(&name_vaddr.to_le_bytes());
let mut name_page = [0u8; 4096];
name_page[..10].copy_from_slice(b"/dev/null\0");
let isf = IsfBuilder::new()
.add_struct("list_head", 16)
.add_field("list_head", "next", 0, "pointer")
.add_struct("task_struct", 128)
.add_field("task_struct", "pid", 0, "unsigned int")
.add_field("task_struct", "tasks", tasks_offset, "pointer")
.add_field("task_struct", "comm", 32, "char")
.add_field("task_struct", "mm", mm_offset, "pointer")
.add_struct("mm_struct", 0x200)
.add_field("mm_struct", "mmap", 0x00, "pointer")
.add_struct("vm_area_struct", 0x100)
.add_field("vm_area_struct", "vm_next", 0x00, "pointer")
.add_field("vm_area_struct", "vm_file", 0x08, "pointer")
.add_field("vm_area_struct", "vm_start", 0x10, "unsigned long")
.add_field("vm_area_struct", "vm_end", 0x18, "unsigned long")
.add_field("vm_area_struct", "vm_flags", 0x20, "unsigned long")
.add_struct("file", 0x200)
.add_field("file", "f_path", f_path_off, "pointer")
.add_struct("path", 0x20)
.add_field("path", "dentry", dentry_in_path, "pointer")
.add_struct("dentry", 0x200)
.add_field("dentry", "d_name", d_name_off, "pointer")
.add_struct("qstr", 0x20)
.add_field("qstr", "name", name_in_qstr, "pointer")
.add_symbol("init_task", sym_vaddr)
.build_json();
let resolver = IsfResolver::from_value(&isf).unwrap();
let (cr3, mem) = PageTableBuilder::new()
.map_4k(sym_vaddr, sym_paddr, flags::WRITABLE)
.write_phys(sym_paddr, &task_page)
.map_4k(mm_vaddr, mm_paddr, flags::WRITABLE)
.write_phys(mm_paddr, &mm_page)
.map_4k(vma_vaddr, vma_paddr, flags::WRITABLE)
.write_phys(vma_paddr, &vma_page)
.map_4k(file_vaddr, file_paddr, flags::WRITABLE)
.write_phys(file_paddr, &file_page)
.map_4k(dentry_vaddr, dentry_paddr, flags::WRITABLE)
.write_phys(dentry_paddr, &dentry_page)
.map_4k(name_vaddr, name_paddr, flags::WRITABLE)
.write_phys(name_paddr, &name_page)
.build();
let vas = VirtualAddressSpace::new(mem, cr3, TranslationMode::X86_64FourLevel);
let reader = ObjectReader::new(vas, Box::new(resolver));
let result = walk_memfd_create(&reader).expect("should not error");
assert!(
result.is_empty(),
"non-memfd dentry name → strip_prefix fails → no entries"
);
}
}