use memf_core::object_reader::ObjectReader;
use memf_format::PhysicalMemoryProvider;
use crate::{Error, Result, SyscallInfo};
const DEFAULT_NR_SYSCALLS: u64 = 450;
pub fn check_syscall_table<P: PhysicalMemoryProvider>(
reader: &ObjectReader<P>,
) -> Result<Vec<SyscallInfo>> {
let table_addr = reader
.symbols()
.symbol_address("sys_call_table")
.ok_or_else(|| Error::MissingKernelSymbol {
name: "sys_call_table".into(),
})?;
let stext =
reader
.symbols()
.symbol_address("_stext")
.ok_or_else(|| Error::MissingKernelSymbol {
name: "_stext".into(),
})?;
let etext =
reader
.symbols()
.symbol_address("_etext")
.ok_or_else(|| Error::MissingKernelSymbol {
name: "_etext".into(),
})?;
let nr_syscalls = reader
.symbols()
.symbol_address("__NR_syscall_max")
.map_or(DEFAULT_NR_SYSCALLS, |max| max + 1);
let table_size = usize::try_from(nr_syscalls).unwrap_or(0) * 8;
let table_raw = reader.read_bytes(table_addr, table_size)?;
let mut entries = Vec::with_capacity(nr_syscalls as usize);
for i in 0..nr_syscalls {
let off = (i as usize) * 8;
let handler = table_raw[off..off + 8]
.try_into()
.map_or(0, u64::from_le_bytes);
let hooked = handler < stext || handler > etext;
entries.push(SyscallInfo {
number: i,
handler,
hooked,
expected_name: None,
});
}
Ok(entries)
}
#[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,
nr_syscalls: u64,
stext: u64,
etext: u64,
) -> ObjectReader<SyntheticPhysMem> {
let mut builder = IsfBuilder::new()
.add_struct("task_struct", 64)
.add_field("task_struct", "pid", 0, "int")
.add_symbol("sys_call_table", vaddr)
.add_symbol("_stext", stext)
.add_symbol("_etext", etext);
if nr_syscalls > 0 {
builder = builder.add_symbol("__NR_syscall_max", nr_syscalls - 1);
}
let isf = builder.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 all_handlers_in_text_region() {
let vaddr: u64 = 0xFFFF_8000_0010_0000;
let paddr: u64 = 0x0080_0000;
let stext: u64 = 0xFFFF_8000_0000_0000;
let etext: u64 = 0xFFFF_8000_00FF_FFFF;
let mut data = vec![0u8; 4096];
let handler0: u64 = 0xFFFF_8000_0001_0000;
let handler1: u64 = 0xFFFF_8000_0002_0000;
let handler2: u64 = 0xFFFF_8000_0003_0000;
data[0..8].copy_from_slice(&handler0.to_le_bytes());
data[8..16].copy_from_slice(&handler1.to_le_bytes());
data[16..24].copy_from_slice(&handler2.to_le_bytes());
let reader = make_test_reader(&data, vaddr, paddr, 3, stext, etext);
let entries = check_syscall_table(&reader).unwrap();
assert_eq!(entries.len(), 3);
assert!(!entries[0].hooked);
assert!(!entries[1].hooked);
assert!(!entries[2].hooked);
assert_eq!(entries[0].number, 0);
assert_eq!(entries[1].number, 1);
assert_eq!(entries[2].number, 2);
assert_eq!(entries[0].handler, handler0);
}
#[test]
fn hooked_syscall_detected() {
let vaddr: u64 = 0xFFFF_8000_0010_0000;
let paddr: u64 = 0x0080_0000;
let stext: u64 = 0xFFFF_8000_0000_0000;
let etext: u64 = 0xFFFF_8000_00FF_FFFF;
let mut data = vec![0u8; 4096];
let normal: u64 = 0xFFFF_8000_0001_0000;
data[0..8].copy_from_slice(&normal.to_le_bytes());
let hooked: u64 = 0xFFFF_C900_1234_5678;
data[8..16].copy_from_slice(&hooked.to_le_bytes());
data[16..24].copy_from_slice(&normal.to_le_bytes());
let reader = make_test_reader(&data, vaddr, paddr, 3, stext, etext);
let entries = check_syscall_table(&reader).unwrap();
assert_eq!(entries.len(), 3);
assert!(!entries[0].hooked);
assert!(entries[1].hooked);
assert!(!entries[2].hooked);
assert_eq!(entries[1].handler, hooked);
}
#[test]
fn missing_sys_call_table_symbol() {
let isf = IsfBuilder::new()
.add_struct("task_struct", 64)
.add_field("task_struct", "pid", 0, "int")
.add_symbol("_stext", 0xFFFF_8000_0000_0000)
.add_symbol("_etext", 0xFFFF_8000_00FF_FFFF)
.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 = check_syscall_table(&reader);
assert!(
matches!(result, Err(crate::Error::MissingKernelSymbol { ref name }) if name == "sys_call_table"),
"expected MissingKernelSymbol {{name: \"sys_call_table\"}}, got {result:?}"
);
}
#[test]
fn missing_stext_symbol_returns_missing_kernel_symbol() {
let isf = IsfBuilder::new()
.add_symbol("sys_call_table", 0xFFFF_8000_0010_0000)
.add_symbol("_etext", 0xFFFF_8000_00FF_FFFF)
.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<SyntheticPhysMem> = ObjectReader::new(vas, Box::new(resolver));
let result = check_syscall_table(&reader);
assert!(
matches!(result, Err(crate::Error::MissingKernelSymbol { ref name }) if name == "_stext"),
"expected MissingKernelSymbol {{name: \"_stext\"}}, got {result:?}"
);
}
#[test]
fn uses_default_count_without_nr_syscall_max() {
let vaddr: u64 = 0xFFFF_8000_0010_0000;
let paddr: u64 = 0x0080_0000;
let stext: u64 = 0xFFFF_8000_0000_0000;
let etext: u64 = 0xFFFF_8000_00FF_FFFF;
let mut data = vec![0u8; 4096];
let handler: u64 = 0xFFFF_8000_0001_0000;
for i in 0..512 {
let off = i * 8;
data[off..off + 8].copy_from_slice(&handler.to_le_bytes());
}
let reader = make_test_reader(&data, vaddr, paddr, 0, stext, etext);
let entries = check_syscall_table(&reader).unwrap();
assert_eq!(entries.len(), DEFAULT_NR_SYSCALLS as usize);
}
}