use memf_core::object_reader::ObjectReader;
use memf_format::PhysicalMemoryProvider;
use crate::Result;
#[derive(Debug, Clone, serde::Serialize)]
pub struct IpcShmInfo {
pub key: u32,
pub shmid: u32,
pub size: u64,
pub owner_pid: u32,
pub creator_pid: u32,
pub permissions: u32,
pub num_attaches: u32,
}
#[derive(Debug, Clone, serde::Serialize)]
pub struct IpcSemInfo {
pub key: u32,
pub semid: u32,
pub num_sems: u32,
pub owner_pid: u32,
pub permissions: u32,
}
const MAX_IPC_IDS: usize = 32_768;
const MAX_XA_DEPTH: usize = 8;
const XA_NODE_SLOTS: usize = 64;
fn walk_xarray<P: memf_format::PhysicalMemoryProvider>(
reader: &ObjectReader<P>,
xa_head: u64,
depth: usize,
entries: &mut Vec<u64>,
) {
if xa_head == 0 || depth > MAX_XA_DEPTH || entries.len() >= MAX_IPC_IDS {
return;
}
if xa_head & 2 == 0 {
let ptr = xa_head & !3u64;
if ptr != 0 {
entries.push(ptr);
}
return;
}
let node_addr = xa_head & !3u64;
let slots_offset = reader
.symbols()
.field_offset("xa_node", "slots")
.unwrap_or(0);
let slots_vaddr = node_addr + slots_offset;
let raw = match reader.read_bytes(slots_vaddr, XA_NODE_SLOTS * 8) {
Ok(b) => b,
Err(_) => return,
};
for i in 0..XA_NODE_SLOTS {
if entries.len() >= MAX_IPC_IDS {
break;
}
let slot = raw[i * 8..(i + 1) * 8]
.try_into()
.map_or(0, u64::from_le_bytes);
if slot == 0 {
continue;
}
walk_xarray(reader, slot, depth + 1, entries);
}
}
pub fn walk_shm_segments<P: PhysicalMemoryProvider>(
reader: &ObjectReader<P>,
) -> Result<Vec<IpcShmInfo>> {
let shm_ids_addr = match reader.symbols().symbol_address("shm_ids") {
Some(addr) => addr,
None => return Ok(Vec::new()),
};
let in_use: u32 = match reader.read_field(shm_ids_addr, "ipc_ids", "in_use") {
Ok(v) => v,
Err(_) => return Ok(Vec::new()),
};
if in_use == 0 {
return Ok(Vec::new());
}
let ipcs_idr_offset = reader
.symbols()
.field_offset("ipc_ids", "ipcs_idr")
.unwrap_or(0);
let idr_rt_offset = reader.symbols().field_offset("idr", "idr_rt").unwrap_or(0);
let xa_head_offset = reader
.symbols()
.field_offset("radix_tree_root", "xa_head")
.unwrap_or(0);
let xa_head_addr = shm_ids_addr + ipcs_idr_offset + idr_rt_offset + xa_head_offset;
let first_entry: u64 = match reader.read_field(xa_head_addr, "radix_tree_root", "xa_head") {
Ok(v) => v,
Err(_) => return Ok(Vec::new()),
};
let mut segments = Vec::new();
if first_entry == 0 {
return Ok(segments);
}
let mut addrs = Vec::new();
walk_xarray(reader, first_entry, 0, &mut addrs);
let shm_perm_offset = reader
.symbols()
.field_offset("shmid_kernel", "shm_perm")
.unwrap_or(0);
for addr in addrs {
let perm_base = addr + shm_perm_offset;
let key: u32 = match reader.read_field(perm_base, "kern_ipc_perm", "key") {
Ok(v) => v,
Err(_) => continue,
};
let id: u32 = match reader.read_field(perm_base, "kern_ipc_perm", "id") {
Ok(v) => v,
Err(_) => continue,
};
let mode: u32 = match reader.read_field(perm_base, "kern_ipc_perm", "mode") {
Ok(v) => v,
Err(_) => continue,
};
let size: u64 = match reader.read_field(addr, "shmid_kernel", "shm_segsz") {
Ok(v) => v,
Err(_) => continue,
};
let cprid: u32 = match reader.read_field(addr, "shmid_kernel", "shm_cprid") {
Ok(v) => v,
Err(_) => continue,
};
let lprid: u32 = match reader.read_field(addr, "shmid_kernel", "shm_lprid") {
Ok(v) => v,
Err(_) => continue,
};
let nattch: u32 = match reader.read_field(addr, "shmid_kernel", "shm_nattch") {
Ok(v) => v,
Err(_) => continue,
};
segments.push(IpcShmInfo {
key,
shmid: id,
size,
owner_pid: lprid,
creator_pid: cprid,
permissions: mode,
num_attaches: nattch,
});
}
Ok(segments)
}
pub fn walk_semaphores<P: PhysicalMemoryProvider>(
reader: &ObjectReader<P>,
) -> Result<Vec<IpcSemInfo>> {
let sem_ids_addr = match reader.symbols().symbol_address("sem_ids") {
Some(addr) => addr,
None => return Ok(Vec::new()),
};
let in_use: u32 = match reader.read_field(sem_ids_addr, "ipc_ids", "in_use") {
Ok(v) => v,
Err(_) => return Ok(Vec::new()),
};
if in_use == 0 {
return Ok(Vec::new());
}
let ipcs_idr_offset = reader
.symbols()
.field_offset("ipc_ids", "ipcs_idr")
.unwrap_or(0);
let idr_rt_offset = reader.symbols().field_offset("idr", "idr_rt").unwrap_or(0);
let xa_head_offset = reader
.symbols()
.field_offset("radix_tree_root", "xa_head")
.unwrap_or(0);
let xa_head_addr = sem_ids_addr + ipcs_idr_offset + idr_rt_offset + xa_head_offset;
let first_entry: u64 = match reader.read_field(xa_head_addr, "radix_tree_root", "xa_head") {
Ok(v) => v,
Err(_) => return Ok(Vec::new()),
};
let mut semaphores = Vec::new();
if first_entry == 0 {
return Ok(semaphores);
}
let mut addrs = Vec::new();
walk_xarray(reader, first_entry, 0, &mut addrs);
let sem_perm_offset = reader
.symbols()
.field_offset("sem_array", "sem_perm")
.unwrap_or(0);
for addr in addrs {
let perm_base = addr + sem_perm_offset;
let key: u32 = match reader.read_field(perm_base, "kern_ipc_perm", "key") {
Ok(v) => v,
Err(_) => continue,
};
let id: u32 = match reader.read_field(perm_base, "kern_ipc_perm", "id") {
Ok(v) => v,
Err(_) => continue,
};
let mode: u32 = match reader.read_field(perm_base, "kern_ipc_perm", "mode") {
Ok(v) => v,
Err(_) => continue,
};
let nsems: u32 = match reader.read_field(addr, "sem_array", "sem_nsems") {
Ok(v) => v,
Err(_) => continue,
};
let owner_pid: u32 = reader
.read_field(addr, "sem_array", "sem_otime_high")
.unwrap_or(0);
semaphores.push(IpcSemInfo {
key,
semid: id,
num_sems: nsems,
owner_pid,
permissions: mode,
});
}
Ok(semaphores)
}
#[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_empty_reader() -> ObjectReader<SyntheticPhysMem> {
let isf = IsfBuilder::new().build_json();
let resolver = IsfResolver::from_value(&isf).unwrap();
let vaddr: u64 = 0xFFFF_8000_0010_0000;
let paddr: u64 = 0x0080_0000;
let data = vec![0u8; 4096];
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))
}
fn make_shm_reader(data: &[u8], vaddr: u64, paddr: u64) -> ObjectReader<SyntheticPhysMem> {
let isf = IsfBuilder::new()
.add_struct("ipc_ids", 64)
.add_field("ipc_ids", "in_use", 0, "unsigned int")
.add_field("ipc_ids", "ipcs_idr", 8, "idr")
.add_struct("idr", 32)
.add_field("idr", "idr_rt", 0, "radix_tree_root")
.add_struct("radix_tree_root", 16)
.add_field("radix_tree_root", "xa_head", 0, "pointer")
.add_struct("kern_ipc_perm", 64)
.add_field("kern_ipc_perm", "key", 0, "unsigned int")
.add_field("kern_ipc_perm", "id", 4, "unsigned int")
.add_field("kern_ipc_perm", "mode", 8, "unsigned int")
.add_struct("shmid_kernel", 128)
.add_field("shmid_kernel", "shm_perm", 0, "kern_ipc_perm")
.add_field("shmid_kernel", "shm_segsz", 64, "unsigned long")
.add_field("shmid_kernel", "shm_cprid", 72, "unsigned int")
.add_field("shmid_kernel", "shm_lprid", 76, "unsigned int")
.add_field("shmid_kernel", "shm_nattch", 80, "unsigned int")
.add_symbol("shm_ids", 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_shm_no_symbol() {
let reader = make_empty_reader();
let result = walk_shm_segments(&reader).unwrap();
assert!(
result.is_empty(),
"no shm_ids symbol should yield empty vec"
);
}
#[test]
fn walk_sem_no_symbol() {
let reader = make_empty_reader();
let result = walk_semaphores(&reader).unwrap();
assert!(
result.is_empty(),
"no sem_ids symbol should yield empty vec"
);
}
#[test]
fn walk_shm_in_use_zero_returns_empty() {
let vaddr: u64 = 0xFFFF_8000_0010_0000;
let paddr: u64 = 0x0080_0000;
let data = vec![0u8; 4096];
let _ = data[0];
let isf = IsfBuilder::new()
.add_struct("ipc_ids", 64)
.add_field("ipc_ids", "in_use", 0, "unsigned int")
.add_field("ipc_ids", "ipcs_idr", 8, "idr")
.add_struct("idr", 32)
.add_field("idr", "idr_rt", 0, "radix_tree_root")
.add_struct("radix_tree_root", 16)
.add_field("radix_tree_root", "xa_head", 0, "pointer")
.add_struct("kern_ipc_perm", 64)
.add_field("kern_ipc_perm", "key", 0, "unsigned int")
.add_field("kern_ipc_perm", "id", 4, "unsigned int")
.add_field("kern_ipc_perm", "mode", 8, "unsigned int")
.add_struct("shmid_kernel", 128)
.add_field("shmid_kernel", "shm_perm", 0, "kern_ipc_perm")
.add_field("shmid_kernel", "shm_segsz", 64, "unsigned long")
.add_field("shmid_kernel", "shm_cprid", 72, "unsigned int")
.add_field("shmid_kernel", "shm_lprid", 76, "unsigned int")
.add_field("shmid_kernel", "shm_nattch", 80, "unsigned int")
.add_symbol("shm_ids", 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);
let reader = ObjectReader::new(vas, Box::new(resolver));
let result = walk_shm_segments(&reader).unwrap();
assert!(result.is_empty(), "in_use == 0 should yield empty vec");
}
#[test]
fn walk_sem_in_use_zero_returns_empty() {
let vaddr: u64 = 0xFFFF_8000_0020_0000;
let paddr: u64 = 0x0090_0000;
let data = vec![0u8; 4096];
let isf = IsfBuilder::new()
.add_struct("ipc_ids", 64)
.add_field("ipc_ids", "in_use", 0, "unsigned int")
.add_field("ipc_ids", "ipcs_idr", 8, "idr")
.add_struct("idr", 32)
.add_field("idr", "idr_rt", 0, "radix_tree_root")
.add_struct("radix_tree_root", 16)
.add_field("radix_tree_root", "xa_head", 0, "pointer")
.add_struct("kern_ipc_perm", 64)
.add_field("kern_ipc_perm", "key", 0, "unsigned int")
.add_field("kern_ipc_perm", "id", 4, "unsigned int")
.add_field("kern_ipc_perm", "mode", 8, "unsigned int")
.add_struct("sem_array", 128)
.add_field("sem_array", "sem_perm", 0, "kern_ipc_perm")
.add_field("sem_array", "sem_nsems", 64, "unsigned int")
.add_field("sem_array", "sem_otime_high", 68, "unsigned int")
.add_symbol("sem_ids", 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);
let reader = ObjectReader::new(vas, Box::new(resolver));
let result = walk_semaphores(&reader).unwrap();
assert!(
result.is_empty(),
"in_use == 0 should yield empty vec for semaphores"
);
}
#[test]
fn walk_sem_in_use_nonzero_xa_head_zero_returns_empty() {
let vaddr: u64 = 0xFFFF_8800_00A0_0000;
let paddr: u64 = 0x00B0_0000;
let mut data = vec![0u8; 4096];
data[0..4].copy_from_slice(&1u32.to_le_bytes());
let isf = IsfBuilder::new()
.add_struct("ipc_ids", 64)
.add_field("ipc_ids", "in_use", 0, "unsigned int")
.add_field("ipc_ids", "ipcs_idr", 8, "idr")
.add_struct("idr", 32)
.add_field("idr", "idr_rt", 0, "radix_tree_root")
.add_struct("radix_tree_root", 16)
.add_field("radix_tree_root", "xa_head", 0, "pointer")
.add_struct("kern_ipc_perm", 64)
.add_field("kern_ipc_perm", "key", 0, "unsigned int")
.add_field("kern_ipc_perm", "id", 4, "unsigned int")
.add_field("kern_ipc_perm", "mode", 8, "unsigned int")
.add_struct("sem_array", 128)
.add_field("sem_array", "sem_perm", 0, "kern_ipc_perm")
.add_field("sem_array", "sem_nsems", 64, "unsigned int")
.add_field("sem_array", "sem_otime_high", 68, "unsigned int")
.add_symbol("sem_ids", 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);
let reader = ObjectReader::new(vas, Box::new(resolver));
let result = walk_semaphores(&reader).unwrap_or_default();
assert!(
result.is_empty(),
"xa_head==0 with in_use>0 should yield empty semaphore list"
);
}
#[test]
fn walk_shm_in_use_nonzero_xa_head_zero_returns_empty() {
let vaddr: u64 = 0xFFFF_8800_00C0_0000;
let paddr: u64 = 0x00D0_0000;
let mut data = vec![0u8; 4096];
data[0..4].copy_from_slice(&1u32.to_le_bytes());
let isf = IsfBuilder::new()
.add_struct("ipc_ids", 64)
.add_field("ipc_ids", "in_use", 0, "unsigned int")
.add_field("ipc_ids", "ipcs_idr", 8, "idr")
.add_struct("idr", 32)
.add_field("idr", "idr_rt", 0, "radix_tree_root")
.add_struct("radix_tree_root", 16)
.add_field("radix_tree_root", "xa_head", 0, "pointer")
.add_struct("kern_ipc_perm", 64)
.add_field("kern_ipc_perm", "key", 0, "unsigned int")
.add_field("kern_ipc_perm", "id", 4, "unsigned int")
.add_field("kern_ipc_perm", "mode", 8, "unsigned int")
.add_struct("shmid_kernel", 128)
.add_field("shmid_kernel", "shm_perm", 0, "kern_ipc_perm")
.add_field("shmid_kernel", "shm_segsz", 64, "unsigned long")
.add_field("shmid_kernel", "shm_cprid", 72, "unsigned int")
.add_field("shmid_kernel", "shm_lprid", 76, "unsigned int")
.add_field("shmid_kernel", "shm_nattch", 80, "unsigned int")
.add_symbol("shm_ids", 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);
let reader = ObjectReader::new(vas, Box::new(resolver));
let result = walk_shm_segments(&reader).unwrap_or_default();
assert!(
result.is_empty(),
"xa_head==0 with in_use>0 should yield empty shm list"
);
}
#[test]
fn walk_semaphores_single_semaphore_set() {
let vaddr: u64 = 0xFFFF_8800_00F0_0000;
let paddr: u64 = 0x00F0_0000;
let mut data = vec![0u8; 4096];
data[0..4].copy_from_slice(&1u32.to_le_bytes());
let sem_array_addr = vaddr + 0x200;
data[8..16].copy_from_slice(&sem_array_addr.to_le_bytes());
let base = 0x200usize;
data[base..base + 4].copy_from_slice(&0xBEEFu32.to_le_bytes());
data[base + 4..base + 8].copy_from_slice(&77u32.to_le_bytes());
data[base + 8..base + 12].copy_from_slice(&0o600u32.to_le_bytes());
data[base + 64..base + 68].copy_from_slice(&5u32.to_le_bytes());
data[base + 68..base + 72].copy_from_slice(&999u32.to_le_bytes());
let isf = IsfBuilder::new()
.add_struct("ipc_ids", 64)
.add_field("ipc_ids", "in_use", 0, "unsigned int")
.add_field("ipc_ids", "ipcs_idr", 8, "idr")
.add_struct("idr", 32)
.add_field("idr", "idr_rt", 0, "radix_tree_root")
.add_struct("radix_tree_root", 16)
.add_field("radix_tree_root", "xa_head", 0, "pointer")
.add_struct("kern_ipc_perm", 64)
.add_field("kern_ipc_perm", "key", 0, "unsigned int")
.add_field("kern_ipc_perm", "id", 4, "unsigned int")
.add_field("kern_ipc_perm", "mode", 8, "unsigned int")
.add_struct("sem_array", 128)
.add_field("sem_array", "sem_perm", 0, "kern_ipc_perm")
.add_field("sem_array", "sem_nsems", 64, "unsigned int")
.add_field("sem_array", "sem_otime_high", 68, "unsigned int")
.add_symbol("sem_ids", 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);
let reader = ObjectReader::new(vas, Box::new(resolver));
let result = walk_semaphores(&reader).unwrap();
assert_eq!(result.len(), 1, "should find one semaphore set");
assert_eq!(result[0].key, 0xBEEF);
assert_eq!(result[0].semid, 77);
assert_eq!(result[0].num_sems, 5);
assert_eq!(result[0].owner_pid, 999);
assert_eq!(result[0].permissions, 0o600);
}
#[test]
fn walk_shm_in_use_read_fails_returns_empty() {
let vaddr: u64 = 0xFFFF_8800_00F1_0000;
let paddr: u64 = 0x00F1_0000;
let data = vec![1u8; 4096];
let isf = IsfBuilder::new()
.add_struct("ipc_ids", 64)
.add_field("ipc_ids", "ipcs_idr", 8, "idr")
.add_symbol("shm_ids", 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);
let reader = ObjectReader::new(vas, Box::new(resolver));
let result = walk_shm_segments(&reader).unwrap();
assert!(result.is_empty(), "missing in_use field → Err → empty vec");
}
#[test]
fn walk_sem_in_use_read_fails_returns_empty() {
let vaddr: u64 = 0xFFFF_8800_00F2_0000;
let paddr: u64 = 0x00F2_0000;
let data = vec![1u8; 4096];
let isf = IsfBuilder::new()
.add_struct("ipc_ids", 64)
.add_field("ipc_ids", "ipcs_idr", 8, "idr")
.add_symbol("sem_ids", 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);
let reader = ObjectReader::new(vas, Box::new(resolver));
let result = walk_semaphores(&reader).unwrap();
assert!(result.is_empty(), "missing in_use field → Err → empty vec");
}
#[test]
fn ipc_shm_info_clone_debug_serialize() {
let info = IpcShmInfo {
key: 0xDEAD,
shmid: 42,
size: 65536,
owner_pid: 100,
creator_pid: 200,
permissions: 0o644,
num_attaches: 3,
};
let cloned = info.clone();
assert_eq!(cloned.key, 0xDEAD);
let dbg = format!("{cloned:?}");
assert!(dbg.contains("shmid"));
let json = serde_json::to_string(&cloned).unwrap();
assert!(json.contains("\"key\":57005"));
}
#[test]
fn ipc_sem_info_clone_debug_serialize() {
let info = IpcSemInfo {
key: 0xCAFE,
semid: 7,
num_sems: 4,
owner_pid: 99,
permissions: 0o755,
};
let cloned = info.clone();
assert_eq!(cloned.semid, 7);
let dbg = format!("{cloned:?}");
assert!(dbg.contains("num_sems"));
let json = serde_json::to_string(&cloned).unwrap();
assert!(json.contains("\"semid\":7"));
}
#[test]
fn walk_shm_in_use_gt1_returns_one_entry() {
let vaddr: u64 = 0xFFFF_8800_00E0_0000;
let paddr: u64 = 0x00E0_0000;
let mut data = vec![0u8; 4096];
data[0..4].copy_from_slice(&5u32.to_le_bytes());
let shm_kernel_addr = vaddr + 0x200;
data[8..16].copy_from_slice(&shm_kernel_addr.to_le_bytes());
let base = 0x200usize;
data[base..base + 4].copy_from_slice(&0x1234u32.to_le_bytes()); data[base + 4..base + 8].copy_from_slice(&99u32.to_le_bytes()); data[base + 8..base + 12].copy_from_slice(&0o644u32.to_le_bytes()); data[base + 64..base + 72].copy_from_slice(&8192u64.to_le_bytes()); data[base + 72..base + 76].copy_from_slice(&500u32.to_le_bytes()); data[base + 76..base + 80].copy_from_slice(&501u32.to_le_bytes()); data[base + 80..base + 84].copy_from_slice(&1u32.to_le_bytes());
let isf = IsfBuilder::new()
.add_struct("ipc_ids", 64)
.add_field("ipc_ids", "in_use", 0, "unsigned int")
.add_field("ipc_ids", "ipcs_idr", 8, "idr")
.add_struct("idr", 32)
.add_field("idr", "idr_rt", 0, "radix_tree_root")
.add_struct("radix_tree_root", 16)
.add_field("radix_tree_root", "xa_head", 0, "pointer")
.add_struct("kern_ipc_perm", 64)
.add_field("kern_ipc_perm", "key", 0, "unsigned int")
.add_field("kern_ipc_perm", "id", 4, "unsigned int")
.add_field("kern_ipc_perm", "mode", 8, "unsigned int")
.add_struct("shmid_kernel", 128)
.add_field("shmid_kernel", "shm_perm", 0, "kern_ipc_perm")
.add_field("shmid_kernel", "shm_segsz", 64, "unsigned long")
.add_field("shmid_kernel", "shm_cprid", 72, "unsigned int")
.add_field("shmid_kernel", "shm_lprid", 76, "unsigned int")
.add_field("shmid_kernel", "shm_nattch", 80, "unsigned int")
.add_symbol("shm_ids", 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);
let reader = ObjectReader::new(vas, Box::new(resolver));
let result = walk_shm_segments(&reader).unwrap();
assert_eq!(
result.len(),
1,
"in_use=5 but only first xa_head entry is recoverable"
);
assert_eq!(result[0].key, 0x1234);
}
#[test]
fn walk_sem_in_use_gt1_returns_one_entry() {
let vaddr: u64 = 0xFFFF_8800_00D0_0000;
let paddr: u64 = 0x00D8_0000;
let mut data = vec![0u8; 4096];
data[0..4].copy_from_slice(&3u32.to_le_bytes());
let sem_array_addr = vaddr + 0x200;
data[8..16].copy_from_slice(&sem_array_addr.to_le_bytes());
let base = 0x200usize;
data[base..base + 4].copy_from_slice(&0xABCDu32.to_le_bytes()); data[base + 4..base + 8].copy_from_slice(&55u32.to_le_bytes()); data[base + 8..base + 12].copy_from_slice(&0o700u32.to_le_bytes()); data[base + 64..base + 68].copy_from_slice(&2u32.to_le_bytes()); data[base + 68..base + 72].copy_from_slice(&0u32.to_le_bytes());
let isf = IsfBuilder::new()
.add_struct("ipc_ids", 64)
.add_field("ipc_ids", "in_use", 0, "unsigned int")
.add_field("ipc_ids", "ipcs_idr", 8, "idr")
.add_struct("idr", 32)
.add_field("idr", "idr_rt", 0, "radix_tree_root")
.add_struct("radix_tree_root", 16)
.add_field("radix_tree_root", "xa_head", 0, "pointer")
.add_struct("kern_ipc_perm", 64)
.add_field("kern_ipc_perm", "key", 0, "unsigned int")
.add_field("kern_ipc_perm", "id", 4, "unsigned int")
.add_field("kern_ipc_perm", "mode", 8, "unsigned int")
.add_struct("sem_array", 128)
.add_field("sem_array", "sem_perm", 0, "kern_ipc_perm")
.add_field("sem_array", "sem_nsems", 64, "unsigned int")
.add_field("sem_array", "sem_otime_high", 68, "unsigned int")
.add_symbol("sem_ids", 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);
let reader = ObjectReader::new(vas, Box::new(resolver));
let result = walk_semaphores(&reader).unwrap();
assert_eq!(
result.len(),
1,
"in_use=3 but only first xa_head entry is recoverable"
);
assert_eq!(result[0].key, 0xABCD);
assert_eq!(result[0].semid, 55);
}
fn make_isf_with_xa_node() -> serde_json::Value {
IsfBuilder::new()
.add_struct("ipc_ids", 64)
.add_field("ipc_ids", "in_use", 0, "unsigned int")
.add_field("ipc_ids", "ipcs_idr", 8, "idr")
.add_struct("idr", 32)
.add_field("idr", "idr_rt", 0, "radix_tree_root")
.add_struct("radix_tree_root", 16)
.add_field("radix_tree_root", "xa_head", 0, "pointer")
.add_struct("xa_node", 512)
.add_field("xa_node", "slots", 0, "pointer")
.add_struct("kern_ipc_perm", 64)
.add_field("kern_ipc_perm", "key", 0, "unsigned int")
.add_field("kern_ipc_perm", "id", 4, "unsigned int")
.add_field("kern_ipc_perm", "mode", 8, "unsigned int")
.add_struct("sem_array", 128)
.add_field("sem_array", "sem_perm", 0, "kern_ipc_perm")
.add_field("sem_array", "sem_nsems", 64, "unsigned int")
.add_field("sem_array", "sem_otime_high", 68, "unsigned int")
.add_struct("shmid_kernel", 128)
.add_field("shmid_kernel", "shm_perm", 0, "kern_ipc_perm")
.add_field("shmid_kernel", "shm_segsz", 64, "unsigned long")
.add_field("shmid_kernel", "shm_cprid", 72, "unsigned int")
.add_field("shmid_kernel", "shm_lprid", 76, "unsigned int")
.add_field("shmid_kernel", "shm_nattch", 80, "unsigned int")
.build_json()
}
#[test]
fn walk_semaphores_returns_multiple_entries() {
let vaddr_a: u64 = 0xFFFF_8800_0100_0000; let paddr_a: u64 = 0x00A0_0000;
let vaddr_b: u64 = 0xFFFF_8800_0101_0000; let paddr_b: u64 = 0x00A1_0000;
let vaddr_c: u64 = 0xFFFF_8800_0102_0000; let paddr_c: u64 = 0x00A2_0000;
let vaddr_d: u64 = 0xFFFF_8800_0103_0000; let paddr_d: u64 = 0x00A3_0000;
let vaddr_e: u64 = 0xFFFF_8800_0104_0000; let paddr_e: u64 = 0x00A4_0000;
let mut page_a = vec![0u8; 4096];
let mut page_b = vec![0u8; 4096];
let mut page_c = vec![0u8; 4096];
let mut page_d = vec![0u8; 4096];
let mut page_e = vec![0u8; 4096];
page_a[0..4].copy_from_slice(&3u32.to_le_bytes());
let xa_head_val = vaddr_b | 2;
page_a[8..16].copy_from_slice(&xa_head_val.to_le_bytes());
page_b[0..8].copy_from_slice(&vaddr_c.to_le_bytes());
page_b[8..16].copy_from_slice(&vaddr_d.to_le_bytes());
page_b[16..24].copy_from_slice(&vaddr_e.to_le_bytes());
page_c[0..4].copy_from_slice(&0x1001u32.to_le_bytes()); page_c[4..8].copy_from_slice(&10u32.to_le_bytes()); page_c[8..12].copy_from_slice(&0o600u32.to_le_bytes()); page_c[64..68].copy_from_slice(&3u32.to_le_bytes()); page_c[68..72].copy_from_slice(&100u32.to_le_bytes());
page_d[0..4].copy_from_slice(&0x1002u32.to_le_bytes());
page_d[4..8].copy_from_slice(&11u32.to_le_bytes());
page_d[8..12].copy_from_slice(&0o644u32.to_le_bytes());
page_d[64..68].copy_from_slice(&2u32.to_le_bytes());
page_d[68..72].copy_from_slice(&101u32.to_le_bytes());
page_e[0..4].copy_from_slice(&0x1003u32.to_le_bytes());
page_e[4..8].copy_from_slice(&12u32.to_le_bytes());
page_e[8..12].copy_from_slice(&0o755u32.to_le_bytes());
page_e[64..68].copy_from_slice(&1u32.to_le_bytes());
page_e[68..72].copy_from_slice(&102u32.to_le_bytes());
let mut isf = make_isf_with_xa_node();
isf["symbols"]["sem_ids"] = serde_json::json!({ "address": vaddr_a });
let resolver = IsfResolver::from_value(&isf).unwrap();
let (cr3, mem) = PageTableBuilder::new()
.map_4k(vaddr_a, paddr_a, flags::WRITABLE)
.write_phys(paddr_a, &page_a)
.map_4k(vaddr_b, paddr_b, flags::WRITABLE)
.write_phys(paddr_b, &page_b)
.map_4k(vaddr_c, paddr_c, flags::WRITABLE)
.write_phys(paddr_c, &page_c)
.map_4k(vaddr_d, paddr_d, flags::WRITABLE)
.write_phys(paddr_d, &page_d)
.map_4k(vaddr_e, paddr_e, flags::WRITABLE)
.write_phys(paddr_e, &page_e)
.build();
let vas = VirtualAddressSpace::new(mem, cr3, TranslationMode::X86_64FourLevel);
let reader = ObjectReader::new(vas, Box::new(resolver));
let result = walk_semaphores(&reader).unwrap();
assert_eq!(
result.len(),
3,
"XArray node with 3 slots must yield 3 semaphore sets"
);
let mut keys: Vec<u32> = result.iter().map(|s| s.key).collect();
keys.sort_unstable();
assert_eq!(keys, vec![0x1001, 0x1002, 0x1003]);
}
#[test]
fn walk_msgqueues_returns_multiple_entries() {
let vaddr_a: u64 = 0xFFFF_8800_0200_0000;
let paddr_a: u64 = 0x00B0_0000;
let vaddr_b: u64 = 0xFFFF_8800_0201_0000;
let paddr_b: u64 = 0x00B1_0000;
let vaddr_c: u64 = 0xFFFF_8800_0202_0000;
let paddr_c: u64 = 0x00B2_0000;
let vaddr_d: u64 = 0xFFFF_8800_0203_0000;
let paddr_d: u64 = 0x00B3_0000;
let vaddr_e: u64 = 0xFFFF_8800_0204_0000;
let paddr_e: u64 = 0x00B4_0000;
let mut page_a = vec![0u8; 4096];
let mut page_b = vec![0u8; 4096];
let mut page_c = vec![0u8; 4096];
let mut page_d = vec![0u8; 4096];
let mut page_e = vec![0u8; 4096];
page_a[0..4].copy_from_slice(&3u32.to_le_bytes()); let xa_head_val = vaddr_b | 2;
page_a[8..16].copy_from_slice(&xa_head_val.to_le_bytes());
page_b[0..8].copy_from_slice(&vaddr_c.to_le_bytes());
page_b[8..16].copy_from_slice(&vaddr_d.to_le_bytes());
page_b[16..24].copy_from_slice(&vaddr_e.to_le_bytes());
page_c[0..4].copy_from_slice(&0x2001u32.to_le_bytes());
page_c[4..8].copy_from_slice(&20u32.to_le_bytes());
page_c[8..12].copy_from_slice(&0o600u32.to_le_bytes());
page_c[64..68].copy_from_slice(&5u32.to_le_bytes());
page_d[0..4].copy_from_slice(&0x2002u32.to_le_bytes());
page_d[4..8].copy_from_slice(&21u32.to_le_bytes());
page_d[8..12].copy_from_slice(&0o644u32.to_le_bytes());
page_d[64..68].copy_from_slice(&4u32.to_le_bytes());
page_e[0..4].copy_from_slice(&0x2003u32.to_le_bytes());
page_e[4..8].copy_from_slice(&22u32.to_le_bytes());
page_e[8..12].copy_from_slice(&0o755u32.to_le_bytes());
page_e[64..68].copy_from_slice(&3u32.to_le_bytes());
let mut isf = make_isf_with_xa_node();
isf["symbols"]["sem_ids"] = serde_json::json!({ "address": vaddr_a });
let resolver = IsfResolver::from_value(&isf).unwrap();
let (cr3, mem) = PageTableBuilder::new()
.map_4k(vaddr_a, paddr_a, flags::WRITABLE)
.write_phys(paddr_a, &page_a)
.map_4k(vaddr_b, paddr_b, flags::WRITABLE)
.write_phys(paddr_b, &page_b)
.map_4k(vaddr_c, paddr_c, flags::WRITABLE)
.write_phys(paddr_c, &page_c)
.map_4k(vaddr_d, paddr_d, flags::WRITABLE)
.write_phys(paddr_d, &page_d)
.map_4k(vaddr_e, paddr_e, flags::WRITABLE)
.write_phys(paddr_e, &page_e)
.build();
let vas = VirtualAddressSpace::new(mem, cr3, TranslationMode::X86_64FourLevel);
let reader = ObjectReader::new(vas, Box::new(resolver));
let result = walk_semaphores(&reader).unwrap();
assert_eq!(
result.len(),
3,
"second XArray node with 3 slots must yield 3 entries"
);
let mut keys: Vec<u32> = result.iter().map(|s| s.key).collect();
keys.sort_unstable();
assert_eq!(keys, vec![0x2001, 0x2002, 0x2003]);
}
#[test]
fn walk_shm_single_segment() {
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 shm_kernel_addr = vaddr + 0x200;
data[8..16].copy_from_slice(&shm_kernel_addr.to_le_bytes());
let base = 0x200;
data[base..base + 4].copy_from_slice(&0xDEADu32.to_le_bytes());
data[base + 4..base + 8].copy_from_slice(&42u32.to_le_bytes());
data[base + 8..base + 12].copy_from_slice(&0x1B6u32.to_le_bytes());
data[base + 64..base + 72].copy_from_slice(&65536u64.to_le_bytes());
data[base + 72..base + 76].copy_from_slice(&1000u32.to_le_bytes());
data[base + 76..base + 80].copy_from_slice(&2000u32.to_le_bytes());
data[base + 80..base + 84].copy_from_slice(&3u32.to_le_bytes());
let reader = make_shm_reader(&data, vaddr, paddr);
let segments = walk_shm_segments(&reader).unwrap();
assert_eq!(segments.len(), 1);
assert_eq!(segments[0].key, 0xDEAD);
assert_eq!(segments[0].shmid, 42);
assert_eq!(segments[0].size, 65536);
assert_eq!(segments[0].creator_pid, 1000);
assert_eq!(segments[0].owner_pid, 2000);
assert_eq!(segments[0].permissions, 0x1B6);
assert_eq!(segments[0].num_attaches, 3);
}
}