1use memf_core::object_reader::ObjectReader;
8use memf_format::PhysicalMemoryProvider;
9
10use crate::{Error, Result, SyscallInfo};
11
12const DEFAULT_NR_SYSCALLS: u64 = 450;
14
15pub fn check_syscall_table<P: PhysicalMemoryProvider>(
21 reader: &ObjectReader<P>,
22) -> Result<Vec<SyscallInfo>> {
23 let table_addr = reader
24 .symbols()
25 .symbol_address("sys_call_table")
26 .ok_or_else(|| Error::MissingKernelSymbol {
27 name: "sys_call_table".into(),
28 })?;
29
30 let stext =
31 reader
32 .symbols()
33 .symbol_address("_stext")
34 .ok_or_else(|| Error::MissingKernelSymbol {
35 name: "_stext".into(),
36 })?;
37
38 let etext =
39 reader
40 .symbols()
41 .symbol_address("_etext")
42 .ok_or_else(|| Error::MissingKernelSymbol {
43 name: "_etext".into(),
44 })?;
45
46 let nr_syscalls = reader
48 .symbols()
49 .symbol_address("__NR_syscall_max")
50 .map_or(DEFAULT_NR_SYSCALLS, |max| max + 1);
51
52 let table_size = usize::try_from(nr_syscalls).unwrap_or(0) * 8;
54 let table_raw = reader.read_bytes(table_addr, table_size)?;
55
56 let mut entries = Vec::with_capacity(nr_syscalls as usize);
57
58 for i in 0..nr_syscalls {
59 let off = (i as usize) * 8;
60 let handler = table_raw[off..off + 8]
61 .try_into()
62 .map_or(0, u64::from_le_bytes);
63
64 let hooked = handler < stext || handler > etext;
65
66 entries.push(SyscallInfo {
67 number: i,
68 handler,
69 hooked,
70 expected_name: None,
71 });
72 }
73
74 Ok(entries)
75}
76
77#[cfg(test)]
78mod tests {
79 use super::*;
80 use memf_core::test_builders::{flags, PageTableBuilder, SyntheticPhysMem};
81 use memf_core::vas::{TranslationMode, VirtualAddressSpace};
82 use memf_symbols::isf::IsfResolver;
83 use memf_symbols::test_builders::IsfBuilder;
84
85 fn make_test_reader(
86 data: &[u8],
87 vaddr: u64,
88 paddr: u64,
89 nr_syscalls: u64,
90 stext: u64,
91 etext: u64,
92 ) -> ObjectReader<SyntheticPhysMem> {
93 let mut builder = IsfBuilder::new()
94 .add_struct("task_struct", 64)
95 .add_field("task_struct", "pid", 0, "int")
96 .add_symbol("sys_call_table", vaddr)
97 .add_symbol("_stext", stext)
98 .add_symbol("_etext", etext);
99
100 if nr_syscalls > 0 {
101 builder = builder.add_symbol("__NR_syscall_max", nr_syscalls - 1);
102 }
103
104 let isf = builder.build_json();
105 let resolver = IsfResolver::from_value(&isf).unwrap();
106 let (cr3, mem) = PageTableBuilder::new()
107 .map_4k(vaddr, paddr, flags::WRITABLE)
108 .write_phys(paddr, data)
109 .build();
110 let vas = VirtualAddressSpace::new(mem, cr3, TranslationMode::X86_64FourLevel);
111 ObjectReader::new(vas, Box::new(resolver))
112 }
113
114 #[test]
115 fn all_handlers_in_text_region() {
116 let vaddr: u64 = 0xFFFF_8000_0010_0000;
117 let paddr: u64 = 0x0080_0000;
118 let stext: u64 = 0xFFFF_8000_0000_0000;
119 let etext: u64 = 0xFFFF_8000_00FF_FFFF;
120 let mut data = vec![0u8; 4096];
121
122 let handler0: u64 = 0xFFFF_8000_0001_0000;
124 let handler1: u64 = 0xFFFF_8000_0002_0000;
125 let handler2: u64 = 0xFFFF_8000_0003_0000;
126 data[0..8].copy_from_slice(&handler0.to_le_bytes());
127 data[8..16].copy_from_slice(&handler1.to_le_bytes());
128 data[16..24].copy_from_slice(&handler2.to_le_bytes());
129
130 let reader = make_test_reader(&data, vaddr, paddr, 3, stext, etext);
131 let entries = check_syscall_table(&reader).unwrap();
132
133 assert_eq!(entries.len(), 3);
134 assert!(!entries[0].hooked);
135 assert!(!entries[1].hooked);
136 assert!(!entries[2].hooked);
137 assert_eq!(entries[0].number, 0);
138 assert_eq!(entries[1].number, 1);
139 assert_eq!(entries[2].number, 2);
140 assert_eq!(entries[0].handler, handler0);
141 }
142
143 #[test]
144 fn hooked_syscall_detected() {
145 let vaddr: u64 = 0xFFFF_8000_0010_0000;
146 let paddr: u64 = 0x0080_0000;
147 let stext: u64 = 0xFFFF_8000_0000_0000;
148 let etext: u64 = 0xFFFF_8000_00FF_FFFF;
149 let mut data = vec![0u8; 4096];
150
151 let normal: u64 = 0xFFFF_8000_0001_0000;
153 data[0..8].copy_from_slice(&normal.to_le_bytes());
154
155 let hooked: u64 = 0xFFFF_C900_1234_5678;
157 data[8..16].copy_from_slice(&hooked.to_le_bytes());
158
159 data[16..24].copy_from_slice(&normal.to_le_bytes());
161
162 let reader = make_test_reader(&data, vaddr, paddr, 3, stext, etext);
163 let entries = check_syscall_table(&reader).unwrap();
164
165 assert_eq!(entries.len(), 3);
166 assert!(!entries[0].hooked);
167 assert!(entries[1].hooked);
168 assert!(!entries[2].hooked);
169 assert_eq!(entries[1].handler, hooked);
170 }
171
172 #[test]
173 fn missing_sys_call_table_symbol() {
174 let isf = IsfBuilder::new()
175 .add_struct("task_struct", 64)
176 .add_field("task_struct", "pid", 0, "int")
177 .add_symbol("_stext", 0xFFFF_8000_0000_0000)
178 .add_symbol("_etext", 0xFFFF_8000_00FF_FFFF)
179 .build_json();
181
182 let resolver = IsfResolver::from_value(&isf).unwrap();
183 let (cr3, mem) = PageTableBuilder::new().build();
184 let vas = VirtualAddressSpace::new(mem, cr3, TranslationMode::X86_64FourLevel);
185 let reader = ObjectReader::new(vas, Box::new(resolver));
186
187 let result = check_syscall_table(&reader);
188 assert!(
189 matches!(result, Err(crate::Error::MissingKernelSymbol { ref name }) if name == "sys_call_table"),
190 "expected MissingKernelSymbol {{name: \"sys_call_table\"}}, got {result:?}"
191 );
192 }
193
194 #[test]
195 fn missing_stext_symbol_returns_missing_kernel_symbol() {
196 let isf = IsfBuilder::new()
197 .add_symbol("sys_call_table", 0xFFFF_8000_0010_0000)
198 .add_symbol("_etext", 0xFFFF_8000_00FF_FFFF)
200 .build_json();
201 let resolver = IsfResolver::from_value(&isf).unwrap();
202 let (cr3, mem) = PageTableBuilder::new().build();
203 let vas = VirtualAddressSpace::new(mem, cr3, TranslationMode::X86_64FourLevel);
204 let reader: ObjectReader<SyntheticPhysMem> = ObjectReader::new(vas, Box::new(resolver));
205 let result = check_syscall_table(&reader);
206 assert!(
207 matches!(result, Err(crate::Error::MissingKernelSymbol { ref name }) if name == "_stext"),
208 "expected MissingKernelSymbol {{name: \"_stext\"}}, got {result:?}"
209 );
210 }
211
212 #[test]
213 fn uses_default_count_without_nr_syscall_max() {
214 let vaddr: u64 = 0xFFFF_8000_0010_0000;
215 let paddr: u64 = 0x0080_0000;
216 let stext: u64 = 0xFFFF_8000_0000_0000;
217 let etext: u64 = 0xFFFF_8000_00FF_FFFF;
218
219 let mut data = vec![0u8; 4096];
221 let handler: u64 = 0xFFFF_8000_0001_0000;
222 for i in 0..512 {
223 let off = i * 8;
224 data[off..off + 8].copy_from_slice(&handler.to_le_bytes());
225 }
226
227 let reader = make_test_reader(&data, vaddr, paddr, 0, stext, etext);
229 let entries = check_syscall_table(&reader).unwrap();
230
231 assert_eq!(entries.len(), DEFAULT_NR_SYSCALLS as usize);
233 }
234}