use std::collections::HashSet;
use memf_core::object_reader::ObjectReader;
use memf_format::PhysicalMemoryProvider;
use crate::types::VmaFlags;
const MAX_VMAS: usize = 1_000_000;
#[derive(Debug, Clone)]
pub struct VmaEntry {
pub vma_addr: u64,
pub start: u64,
pub end: u64,
pub flags: VmaFlags,
pub file_ptr: u64,
}
pub fn for_each_task_vma<P, F>(reader: &ObjectReader<P>, task_addr: u64, callback: &mut F)
where
P: PhysicalMemoryProvider,
F: FnMut(VmaEntry),
{
let mm_ptr: u64 = match reader.read_field(task_addr, "task_struct", "mm") {
Ok(v) => v,
Err(_) => return,
};
if mm_ptr == 0 {
return;
}
let mmap_ptr: u64 = match reader.read_field(mm_ptr, "mm_struct", "mmap") {
Ok(v) => v,
Err(_) => return,
};
let mut seen: HashSet<u64> = HashSet::new();
let mut vma_addr = mmap_ptr;
while vma_addr != 0 {
if seen.len() >= MAX_VMAS || !seen.insert(vma_addr) {
break;
}
let start: u64 = match reader.read_field(vma_addr, "vm_area_struct", "vm_start") {
Ok(v) => v,
Err(_) => break,
};
let end: u64 = match reader.read_field(vma_addr, "vm_area_struct", "vm_end") {
Ok(v) => v,
Err(_) => break,
};
let raw_flags: u64 = reader
.read_field(vma_addr, "vm_area_struct", "vm_flags")
.unwrap_or(0);
let file_ptr: u64 = reader
.read_field(vma_addr, "vm_area_struct", "vm_file")
.unwrap_or(0);
callback(VmaEntry {
vma_addr,
start,
end,
flags: VmaFlags::from_raw(raw_flags),
file_ptr,
});
vma_addr = reader
.read_field(vma_addr, "vm_area_struct", "vm_next")
.unwrap_or(0);
}
}
#[cfg(test)]
mod tests {
use super::*;
use memf_core::test_builders::{flags as ptflags, PageTableBuilder};
use memf_symbols::test_builders::IsfBuilder;
fn make_reader_with_isf(
isf: &IsfBuilder,
ptb: PageTableBuilder,
) -> memf_core::object_reader::ObjectReader<memf_core::test_builders::SyntheticPhysMem> {
let json = isf.build_json();
let resolver = memf_symbols::isf::IsfResolver::from_value(&json).unwrap();
let (cr3, mem) = ptb.build();
let vas = memf_core::vas::VirtualAddressSpace::new(
mem,
cr3,
memf_core::vas::TranslationMode::X86_64FourLevel,
);
memf_core::object_reader::ObjectReader::new(vas, Box::new(resolver))
}
#[test]
fn task_with_null_mm_produces_no_vmas() {
let isf = IsfBuilder::new().add_struct("task_struct", 32).add_field(
"task_struct",
"mm",
0,
"pointer",
);
let task_vaddr: u64 = 0xFFFF_8000_0010_0000;
let task_paddr: u64 = 0x0010_0000;
let mut page = [0u8; 4096];
page[0..8].copy_from_slice(&0u64.to_le_bytes());
let ptb = PageTableBuilder::new()
.map_4k(task_vaddr, task_paddr, ptflags::WRITABLE)
.write_phys(task_paddr, &page);
let reader = make_reader_with_isf(&isf, ptb);
let mut entries: Vec<VmaEntry> = Vec::new();
for_each_task_vma(&reader, task_vaddr, &mut |e| entries.push(e));
assert!(
entries.is_empty(),
"kernel thread (mm=0) should yield no VMAs"
);
}
#[test]
fn unreadable_mm_field_produces_no_vmas() {
let isf = IsfBuilder::new().add_struct("task_struct", 32).add_field(
"task_struct",
"mm",
0,
"pointer",
);
let reader = memf_core::test_builders::make_reader(&isf);
let mut entries: Vec<VmaEntry> = Vec::new();
for_each_task_vma(&reader, 0xDEAD_BEEF_0000_0000, &mut |e| entries.push(e));
assert!(
entries.is_empty(),
"unreadable task_struct should yield no VMAs"
);
}
#[test]
fn single_vma_yields_correct_entry() {
let task_vaddr: u64 = 0xFFFF_8000_0001_0000;
let task_paddr: u64 = 0x0001_0000;
let mm_vaddr: u64 = 0xFFFF_8000_0002_0000;
let mm_paddr: u64 = 0x0002_0000;
let vma_vaddr: u64 = 0xFFFF_8000_0003_0000;
let vma_paddr: u64 = 0x0003_0000;
let isf = IsfBuilder::new()
.add_struct("task_struct", 16)
.add_field("task_struct", "mm", 0, "pointer")
.add_struct("mm_struct", 16)
.add_field("mm_struct", "mmap", 0, "pointer")
.add_struct("vm_area_struct", 48)
.add_field("vm_area_struct", "vm_start", 0, "unsigned long")
.add_field("vm_area_struct", "vm_end", 8, "unsigned long")
.add_field("vm_area_struct", "vm_flags", 16, "unsigned long")
.add_field("vm_area_struct", "vm_file", 24, "pointer")
.add_field("vm_area_struct", "vm_next", 32, "pointer");
let mut task_page = [0u8; 4096];
task_page[0..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 vm_start: u64 = 0x0000_7FFF_0000_0000;
let vm_end: u64 = 0x0000_7FFF_0001_0000;
let vm_flags: u64 = 0x3; let vm_file: u64 = 0; let vm_next: u64 = 0;
let mut vma_page = [0u8; 4096];
vma_page[0..8].copy_from_slice(&vm_start.to_le_bytes());
vma_page[8..16].copy_from_slice(&vm_end.to_le_bytes());
vma_page[16..24].copy_from_slice(&vm_flags.to_le_bytes());
vma_page[24..32].copy_from_slice(&vm_file.to_le_bytes());
vma_page[32..40].copy_from_slice(&vm_next.to_le_bytes());
let ptb = PageTableBuilder::new()
.map_4k(task_vaddr, task_paddr, ptflags::WRITABLE)
.write_phys(task_paddr, &task_page)
.map_4k(mm_vaddr, mm_paddr, ptflags::WRITABLE)
.write_phys(mm_paddr, &mm_page)
.map_4k(vma_vaddr, vma_paddr, ptflags::WRITABLE)
.write_phys(vma_paddr, &vma_page);
let reader = make_reader_with_isf(&isf, ptb);
let mut entries: Vec<VmaEntry> = Vec::new();
for_each_task_vma(&reader, task_vaddr, &mut |e| entries.push(e));
assert_eq!(entries.len(), 1, "expected exactly one VMA");
let e = &entries[0];
assert_eq!(e.start, vm_start);
assert_eq!(e.end, vm_end);
assert_eq!(e.file_ptr, 0, "anonymous mapping");
assert!(e.flags.read, "read flag should be set");
assert!(e.flags.write, "write flag should be set");
assert!(!e.flags.exec, "exec flag should not be set");
}
#[test]
fn cyclic_vm_next_terminates_after_one_visit() {
let task_vaddr: u64 = 0xFFFF_8000_0001_0000;
let task_paddr: u64 = 0x0001_0000;
let mm_vaddr: u64 = 0xFFFF_8000_0002_0000;
let mm_paddr: u64 = 0x0002_0000;
let vma_vaddr: u64 = 0xFFFF_8000_0003_0000;
let vma_paddr: u64 = 0x0003_0000;
let isf = IsfBuilder::new()
.add_struct("task_struct", 16)
.add_field("task_struct", "mm", 0, "pointer")
.add_struct("mm_struct", 16)
.add_field("mm_struct", "mmap", 0, "pointer")
.add_struct("vm_area_struct", 48)
.add_field("vm_area_struct", "vm_start", 0, "unsigned long")
.add_field("vm_area_struct", "vm_end", 8, "unsigned long")
.add_field("vm_area_struct", "vm_flags", 16, "unsigned long")
.add_field("vm_area_struct", "vm_file", 24, "pointer")
.add_field("vm_area_struct", "vm_next", 32, "pointer");
let mut task_page = [0u8; 4096];
task_page[0..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(&0x0000_7FFF_0000_0000u64.to_le_bytes()); vma_page[8..16].copy_from_slice(&0x0000_7FFF_0001_0000u64.to_le_bytes()); vma_page[16..24].copy_from_slice(&3u64.to_le_bytes()); vma_page[24..32].copy_from_slice(&0u64.to_le_bytes()); vma_page[32..40].copy_from_slice(&vma_vaddr.to_le_bytes());
let ptb = PageTableBuilder::new()
.map_4k(task_vaddr, task_paddr, ptflags::WRITABLE)
.write_phys(task_paddr, &task_page)
.map_4k(mm_vaddr, mm_paddr, ptflags::WRITABLE)
.write_phys(mm_paddr, &mm_page)
.map_4k(vma_vaddr, vma_paddr, ptflags::WRITABLE)
.write_phys(vma_paddr, &vma_page);
let reader = make_reader_with_isf(&isf, ptb);
let mut count = 0usize;
for_each_task_vma(&reader, task_vaddr, &mut |_| count += 1);
assert_eq!(
count, 1,
"self-referencing vm_next must be visited once, then the walk stops"
);
}
#[test]
fn two_vmas_chained_via_vm_next() {
let task_vaddr: u64 = 0xFFFF_8000_0001_0000;
let task_paddr: u64 = 0x0001_0000;
let mm_vaddr: u64 = 0xFFFF_8000_0002_0000;
let mm_paddr: u64 = 0x0002_0000;
let vma1_vaddr: u64 = 0xFFFF_8000_0003_0000;
let vma1_paddr: u64 = 0x0003_0000;
let vma2_vaddr: u64 = 0xFFFF_8000_0004_0000;
let vma2_paddr: u64 = 0x0004_0000;
let isf = IsfBuilder::new()
.add_struct("task_struct", 16)
.add_field("task_struct", "mm", 0, "pointer")
.add_struct("mm_struct", 16)
.add_field("mm_struct", "mmap", 0, "pointer")
.add_struct("vm_area_struct", 48)
.add_field("vm_area_struct", "vm_start", 0, "unsigned long")
.add_field("vm_area_struct", "vm_end", 8, "unsigned long")
.add_field("vm_area_struct", "vm_flags", 16, "unsigned long")
.add_field("vm_area_struct", "vm_file", 24, "pointer")
.add_field("vm_area_struct", "vm_next", 32, "pointer");
let mut task_page = [0u8; 4096];
task_page[0..8].copy_from_slice(&mm_vaddr.to_le_bytes());
let mut mm_page = [0u8; 4096];
mm_page[0..8].copy_from_slice(&vma1_vaddr.to_le_bytes());
let mut vma1_page = [0u8; 4096];
vma1_page[0..8].copy_from_slice(&0x0000_7FFF_0000_0000u64.to_le_bytes());
vma1_page[8..16].copy_from_slice(&0x0000_7FFF_0001_0000u64.to_le_bytes());
vma1_page[16..24].copy_from_slice(&3u64.to_le_bytes()); vma1_page[24..32].copy_from_slice(&0u64.to_le_bytes());
vma1_page[32..40].copy_from_slice(&vma2_vaddr.to_le_bytes());
let fake_file_ptr: u64 = 0xFFFF_8888_0000_0000;
let mut vma2_page = [0u8; 4096];
vma2_page[0..8].copy_from_slice(&0x0000_7FFF_0010_0000u64.to_le_bytes());
vma2_page[8..16].copy_from_slice(&0x0000_7FFF_0020_0000u64.to_le_bytes());
vma2_page[16..24].copy_from_slice(&5u64.to_le_bytes()); vma2_page[24..32].copy_from_slice(&fake_file_ptr.to_le_bytes());
vma2_page[32..40].copy_from_slice(&0u64.to_le_bytes());
let ptb = PageTableBuilder::new()
.map_4k(task_vaddr, task_paddr, ptflags::WRITABLE)
.write_phys(task_paddr, &task_page)
.map_4k(mm_vaddr, mm_paddr, ptflags::WRITABLE)
.write_phys(mm_paddr, &mm_page)
.map_4k(vma1_vaddr, vma1_paddr, ptflags::WRITABLE)
.write_phys(vma1_paddr, &vma1_page)
.map_4k(vma2_vaddr, vma2_paddr, ptflags::WRITABLE)
.write_phys(vma2_paddr, &vma2_page);
let reader = make_reader_with_isf(&isf, ptb);
let mut entries: Vec<VmaEntry> = Vec::new();
for_each_task_vma(&reader, task_vaddr, &mut |e| entries.push(e));
assert_eq!(entries.len(), 2, "expected two VMAs");
assert_eq!(entries[0].file_ptr, 0, "vma1 is anonymous");
assert_eq!(entries[1].file_ptr, fake_file_ptr, "vma2 is file-backed");
assert!(entries[1].flags.read && entries[1].flags.exec, "vma2 is rx");
}
}