1use memf_core::object_reader::ObjectReader;
9use memf_format::PhysicalMemoryProvider;
10
11use std::collections::HashSet;
12
13use crate::{Error, Result};
14
15const MAX_PROCESSES: usize = 8192;
17
18const PAGE_SIZE: u64 = 4096;
20
21#[derive(Debug, Clone, serde::Serialize)]
23pub struct PsAuxInfo {
24 pub pid: u32,
26 pub ppid: u32,
28 pub uid: u32,
30 pub gid: u32,
32 pub comm: String,
34 pub state: String,
36 pub nice: i32,
38 pub vsize: u64,
40 pub rss: u64,
42 pub tty: String,
44 pub start_time: u64,
46 pub flags: u64,
48 pub is_suspicious: bool,
50}
51
52pub fn task_state_name(state: u64) -> String {
54 match state {
55 0 => "Running".to_string(),
56 1 => "Sleeping".to_string(),
57 2 => "DiskSleep".to_string(),
58 4 => "Stopped".to_string(),
59 8 => "Tracing".to_string(),
60 16 => "Zombie".to_string(),
61 32 => "Dead".to_string(),
62 64 => "Wakekill".to_string(),
63 128 => "Waking".to_string(),
64 256 => "Parked".to_string(),
65 _ => format!("Unknown({state})"),
66 }
67}
68
69pub use crate::heuristics::classify_psaux;
71
72pub fn walk_psaux<P: PhysicalMemoryProvider>(reader: &ObjectReader<P>) -> Result<Vec<PsAuxInfo>> {
74 let init_task_addr = match reader.symbols().symbol_address("init_task") {
75 Some(addr) => addr,
76 None => return Ok(Vec::new()),
77 };
78
79 let tasks_offset = reader
80 .symbols()
81 .field_offset("task_struct", "tasks")
82 .ok_or_else(|| Error::MissingField {
83 struct_name: "task_struct".into(),
84 field_name: "tasks".into(),
85 })?;
86
87 let head_vaddr = init_task_addr + tasks_offset;
88 let task_addrs = reader.walk_list(head_vaddr, "task_struct", "tasks")?;
89
90 let mut results = Vec::new();
91 let mut seen = HashSet::new();
92
93 if let Ok(info) = read_psaux_info(reader, init_task_addr) {
94 seen.insert(init_task_addr);
95 results.push(info);
96 }
97
98 for &task_addr in &task_addrs {
99 if results.len() >= MAX_PROCESSES {
100 break;
101 }
102 if !seen.insert(task_addr) {
103 break;
104 }
105 if let Ok(info) = read_psaux_info(reader, task_addr) {
106 results.push(info);
107 }
108 }
109
110 results.sort_by_key(|p| p.pid);
111 Ok(results)
112}
113
114fn read_psaux_info<P: PhysicalMemoryProvider>(
115 reader: &ObjectReader<P>,
116 task_addr: u64,
117) -> Result<PsAuxInfo> {
118 let pid: u32 = reader.read_field(task_addr, "task_struct", "pid")?;
119 let comm = reader.read_field_string(task_addr, "task_struct", "comm", 16)?;
120
121 #[allow(clippy::cast_sign_loss)]
122 let state: u64 = reader
123 .read_field::<i64>(task_addr, "task_struct", "state")
124 .map(|v| v as u64)
125 .unwrap_or(0);
126
127 let ppid = read_parent_pid(reader, task_addr).unwrap_or(0);
128 let (uid, gid) = read_cred_ids(reader, task_addr).unwrap_or((0, 0));
129
130 let nice: i32 = reader
131 .read_field::<i32>(task_addr, "task_struct", "static_prio")
132 .map(|prio| prio - 120)
133 .unwrap_or(0);
134
135 let flags: u64 = reader
136 .read_field::<u32>(task_addr, "task_struct", "flags")
137 .map(u64::from)
138 .unwrap_or(0);
139
140 let (vsize, rss) = read_mm_stats(reader, task_addr).unwrap_or((0, 0));
141 let tty = read_tty_name(reader, task_addr).unwrap_or_default();
142
143 let start_time: u64 = reader
144 .read_field(task_addr, "task_struct", "real_start_time")
145 .or_else(|_| reader.read_field(task_addr, "task_struct", "start_time"))
146 .unwrap_or(0);
147
148 let state_name = task_state_name(state);
149 let is_suspicious = classify_psaux(state, uid, flags, vsize);
150
151 Ok(PsAuxInfo {
152 pid,
153 ppid,
154 uid,
155 gid,
156 comm,
157 state: state_name,
158 nice,
159 vsize,
160 rss,
161 tty,
162 start_time,
163 flags,
164 is_suspicious,
165 })
166}
167
168fn read_parent_pid<P: PhysicalMemoryProvider>(
169 reader: &ObjectReader<P>,
170 task_addr: u64,
171) -> Result<u32> {
172 let parent_ptr: u64 = reader.read_field(task_addr, "task_struct", "real_parent")?;
173 if parent_ptr == 0 {
174 return Ok(0);
175 }
176 let ppid: u32 = reader.read_field(parent_ptr, "task_struct", "pid")?;
177 Ok(ppid)
178}
179
180fn read_cred_ids<P: PhysicalMemoryProvider>(
181 reader: &ObjectReader<P>,
182 task_addr: u64,
183) -> Result<(u32, u32)> {
184 let cred_ptr: u64 = reader.read_field(task_addr, "task_struct", "cred")?;
185 if cred_ptr == 0 {
186 return Ok((0, 0));
187 }
188 let uid: u32 = reader.read_field(cred_ptr, "cred", "uid").unwrap_or(0);
189 let gid: u32 = reader.read_field(cred_ptr, "cred", "gid").unwrap_or(0);
190 Ok((uid, gid))
191}
192
193fn read_mm_stats<P: PhysicalMemoryProvider>(
194 reader: &ObjectReader<P>,
195 task_addr: u64,
196) -> Result<(u64, u64)> {
197 let mm_ptr: u64 = reader.read_field(task_addr, "task_struct", "mm")?;
198 if mm_ptr == 0 {
199 return Ok((0, 0));
200 }
201 let total_vm: u64 = reader
202 .read_field::<u64>(mm_ptr, "mm_struct", "total_vm")
203 .unwrap_or(0);
204 let rss: u64 = reader
205 .read_field::<u64>(mm_ptr, "mm_struct", "rss_stat")
206 .unwrap_or(0);
207 Ok((total_vm * PAGE_SIZE, rss))
208}
209
210fn read_tty_name<P: PhysicalMemoryProvider>(
211 reader: &ObjectReader<P>,
212 task_addr: u64,
213) -> Result<String> {
214 let signal_ptr: u64 = reader.read_field(task_addr, "task_struct", "signal")?;
215 if signal_ptr == 0 {
216 return Ok(String::new());
217 }
218 let tty_ptr: u64 = reader
219 .read_field(signal_ptr, "signal_struct", "tty")
220 .unwrap_or(0);
221 if tty_ptr == 0 {
222 return Ok(String::new());
223 }
224 let name = reader
225 .read_field_string(tty_ptr, "tty_struct", "name", 64)
226 .unwrap_or_default();
227 Ok(name)
228}
229
230#[cfg(test)]
231mod tests {
232 use super::*;
233
234 const PF_KTHREAD: u64 = 0x0020_0000;
236 const VSIZE_ABUSE_THRESHOLD: u64 = 100 * 1024 * 1024 * 1024;
238
239 #[test]
240 fn state_running() {
241 assert_eq!(task_state_name(0), "Running");
242 }
243
244 #[test]
245 fn state_zombie() {
246 assert_eq!(task_state_name(16), "Zombie");
247 }
248
249 #[test]
250 fn state_unknown() {
251 assert_eq!(task_state_name(999), "Unknown(999)");
252 }
253
254 #[test]
255 fn state_sleeping() {
256 assert_eq!(task_state_name(1), "Sleeping");
257 }
258
259 #[test]
260 fn state_disk_sleep() {
261 assert_eq!(task_state_name(2), "DiskSleep");
262 }
263
264 #[test]
265 fn state_stopped() {
266 assert_eq!(task_state_name(4), "Stopped");
267 }
268
269 #[test]
270 fn state_dead() {
271 assert_eq!(task_state_name(32), "Dead");
272 }
273
274 #[test]
275 fn state_tracing() {
276 assert_eq!(task_state_name(8), "Tracing");
277 }
278
279 #[test]
280 fn state_wakekill() {
281 assert_eq!(task_state_name(64), "Wakekill");
282 }
283
284 #[test]
285 fn state_waking() {
286 assert_eq!(task_state_name(128), "Waking");
287 }
288
289 #[test]
290 fn state_parked() {
291 assert_eq!(task_state_name(256), "Parked");
292 }
293
294 #[test]
295 fn state_unknown_zero_based_checks() {
296 assert_eq!(task_state_name(3), "Unknown(3)");
297 assert_eq!(task_state_name(5), "Unknown(5)");
298 assert_eq!(task_state_name(512), "Unknown(512)");
299 assert_eq!(task_state_name(u64::MAX), format!("Unknown({})", u64::MAX));
300 }
301
302 #[test]
303 fn classify_root_zombie_suspicious() {
304 assert!(classify_psaux(16, 0, 0, 0));
305 }
306
307 #[test]
308 fn classify_fake_kthread_suspicious() {
309 assert!(classify_psaux(0, 1000, PF_KTHREAD, 0));
310 }
311
312 #[test]
313 fn classify_huge_vsize_suspicious() {
314 let huge = 200 * 1024 * 1024 * 1024;
315 assert!(classify_psaux(0, 1000, 0, huge));
316 }
317
318 #[test]
319 fn classify_exact_vsize_threshold_suspicious() {
320 let over = VSIZE_ABUSE_THRESHOLD + 1;
321 assert!(classify_psaux(0, 1000, 0, over));
322 }
323
324 #[test]
325 fn classify_exact_vsize_threshold_benign() {
326 assert!(!classify_psaux(0, 1000, 0, VSIZE_ABUSE_THRESHOLD));
327 }
328
329 #[test]
330 fn classify_normal_benign() {
331 assert!(!classify_psaux(1, 1000, 0, 1024 * 1024 * 1024));
332 }
333
334 #[test]
335 fn classify_root_kthread_benign() {
336 assert!(!classify_psaux(0, 0, PF_KTHREAD, 0));
337 }
338
339 #[test]
340 fn classify_nonroot_zombie_benign() {
341 assert!(!classify_psaux(16, 1000, 0, 0));
342 }
343
344 #[test]
345 fn classify_pf_kthread_uid_1_suspicious() {
346 assert!(classify_psaux(0, 1, PF_KTHREAD, 0));
347 }
348
349 #[test]
350 fn classify_multiple_flags_with_pf_kthread_nonroot_suspicious() {
351 let flags = PF_KTHREAD | 0x0001_0000;
352 assert!(classify_psaux(0, 500, flags, 0));
353 }
354
355 #[test]
356 fn ps_aux_info_serializes_to_json() {
357 let info = PsAuxInfo {
358 pid: 42,
359 ppid: 1,
360 uid: 1000,
361 gid: 1000,
362 comm: "bash".to_string(),
363 state: "Sleeping".to_string(),
364 nice: 0,
365 vsize: 4096,
366 rss: 2,
367 tty: "pts/0".to_string(),
368 start_time: 12345678,
369 flags: 0,
370 is_suspicious: false,
371 };
372 let json = serde_json::to_string(&info).unwrap();
373 assert!(json.contains("\"pid\":42"));
374 assert!(json.contains("\"comm\":\"bash\""));
375 assert!(json.contains("\"state\":\"Sleeping\""));
376 assert!(json.contains("\"is_suspicious\":false"));
377 assert!(json.contains("\"tty\":\"pts/0\""));
378 }
379
380 #[test]
381 fn ps_aux_info_clone_and_debug() {
382 let info = PsAuxInfo {
383 pid: 1,
384 ppid: 0,
385 uid: 0,
386 gid: 0,
387 comm: "systemd".to_string(),
388 state: "Running".to_string(),
389 nice: -5,
390 vsize: 0,
391 rss: 0,
392 tty: String::new(),
393 start_time: 0,
394 flags: 0,
395 is_suspicious: false,
396 };
397 let cloned = info.clone();
398 assert_eq!(cloned.pid, 1);
399 let debug_str = format!("{cloned:?}");
400 assert!(debug_str.contains("systemd"));
401 }
402
403 #[test]
404 fn walk_no_symbol_returns_empty() {
405 use memf_core::test_builders::{PageTableBuilder, SyntheticPhysMem};
406 use memf_core::vas::{TranslationMode, VirtualAddressSpace};
407 use memf_symbols::isf::IsfResolver;
408 use memf_symbols::test_builders::IsfBuilder;
409
410 let isf = IsfBuilder::new()
411 .add_struct("task_struct", 128)
412 .add_field("task_struct", "pid", 0, "int")
413 .add_field("task_struct", "tasks", 16, "list_head")
414 .add_struct("list_head", 16)
415 .add_field("list_head", "next", 0, "pointer")
416 .add_field("list_head", "prev", 8, "pointer")
417 .build_json();
418
419 let resolver = IsfResolver::from_value(&isf).unwrap();
420 let (cr3, mem) = PageTableBuilder::new().build();
421 let vas = VirtualAddressSpace::new(mem, cr3, TranslationMode::X86_64FourLevel);
422 let reader: ObjectReader<SyntheticPhysMem> = ObjectReader::new(vas, Box::new(resolver));
423
424 let result = walk_psaux(&reader).unwrap();
425 assert!(result.is_empty());
426 }
427
428 #[test]
429 fn walk_missing_tasks_field_returns_error() {
430 use memf_core::test_builders::{PageTableBuilder, SyntheticPhysMem};
431 use memf_core::vas::{TranslationMode, VirtualAddressSpace};
432 use memf_symbols::isf::IsfResolver;
433 use memf_symbols::test_builders::IsfBuilder;
434
435 let isf = IsfBuilder::new()
436 .add_struct("task_struct", 128)
437 .add_field("task_struct", "pid", 0, "int")
438 .add_symbol("init_task", 0xFFFF_8000_0010_0000)
439 .build_json();
440
441 let resolver = IsfResolver::from_value(&isf).unwrap();
442 let (cr3, mem) = PageTableBuilder::new().build();
443 let vas = VirtualAddressSpace::new(mem, cr3, TranslationMode::X86_64FourLevel);
444 let reader: ObjectReader<SyntheticPhysMem> = ObjectReader::new(vas, Box::new(resolver));
445
446 let result = walk_psaux(&reader);
447 assert!(
448 matches!(result, Err(crate::Error::MissingField { ref struct_name, ref field_name }) if struct_name == "task_struct" && field_name == "tasks"),
449 "expected MissingField task_struct.tasks, got {result:?}"
450 );
451 }
452
453 #[test]
454 fn walk_psaux_with_readable_parent_and_minimal_fields() {
455 use memf_core::test_builders::{flags as ptf, PageTableBuilder, SyntheticPhysMem};
456 use memf_core::vas::{TranslationMode, VirtualAddressSpace};
457 use memf_symbols::isf::IsfResolver;
458 use memf_symbols::test_builders::IsfBuilder;
459
460 let tasks_offset: u64 = 0x10;
461 let parent_offset: u64 = 0x40;
462 let cred_offset: u64 = 0x48;
463 let mm_offset: u64 = 0x50;
464 let signal_offset: u64 = 0x58;
465
466 let sym_vaddr: u64 = 0xFFFF_8800_00B0_0000;
467 let sym_paddr: u64 = 0x00B0_0000;
468 let parent_vaddr: u64 = 0xFFFF_8800_00B1_0000;
469 let parent_paddr: u64 = 0x00B1_0000;
470
471 let mut task_page = [0u8; 4096];
472 task_page[0..4].copy_from_slice(&42u32.to_le_bytes());
473 let self_ptr = sym_vaddr + tasks_offset;
474 task_page[tasks_offset as usize..tasks_offset as usize + 8]
475 .copy_from_slice(&self_ptr.to_le_bytes());
476 task_page[0x20..0x27].copy_from_slice(b"worker\0");
477 task_page[parent_offset as usize..parent_offset as usize + 8]
478 .copy_from_slice(&parent_vaddr.to_le_bytes());
479
480 let mut parent_page = [0u8; 4096];
481 parent_page[0..4].copy_from_slice(&1u32.to_le_bytes());
482
483 let isf = IsfBuilder::new()
484 .add_symbol("init_task", sym_vaddr)
485 .add_struct("list_head", 0x10)
486 .add_field("list_head", "next", 0x00, "pointer")
487 .add_struct("task_struct", 0x400)
488 .add_field("task_struct", "tasks", tasks_offset, "pointer")
489 .add_field("task_struct", "pid", 0x00, "unsigned int")
490 .add_field("task_struct", "comm", 0x20, "char")
491 .add_field("task_struct", "state", 0x08, "int")
492 .add_field("task_struct", "real_parent", parent_offset, "pointer")
493 .add_field("task_struct", "cred", cred_offset, "pointer")
494 .add_field("task_struct", "mm", mm_offset, "pointer")
495 .add_field("task_struct", "signal", signal_offset, "pointer")
496 .add_field("task_struct", "static_prio", 0x60, "int")
497 .add_field("task_struct", "flags", 0x64, "unsigned int")
498 .build_json();
499 let resolver = IsfResolver::from_value(&isf).unwrap();
500
501 let (cr3, mem) = PageTableBuilder::new()
502 .map_4k(sym_vaddr, sym_paddr, ptf::WRITABLE)
503 .write_phys(sym_paddr, &task_page)
504 .map_4k(parent_vaddr, parent_paddr, ptf::WRITABLE)
505 .write_phys(parent_paddr, &parent_page)
506 .build();
507
508 let vas = VirtualAddressSpace::new(mem, cr3, TranslationMode::X86_64FourLevel);
509 let reader: ObjectReader<SyntheticPhysMem> = ObjectReader::new(vas, Box::new(resolver));
510
511 let result = walk_psaux(&reader).unwrap();
512 assert_eq!(result.len(), 1);
513 assert_eq!(result[0].pid, 42);
514 assert_eq!(
515 result[0].ppid, 1,
516 "ppid should be read from real_parent.pid"
517 );
518 assert_eq!(result[0].uid, 0, "cred=null → uid defaults to 0");
519 assert_eq!(result[0].vsize, 0, "mm=null → vsize defaults to 0");
520 assert!(
521 result[0].tty.is_empty(),
522 "signal=null → tty defaults to empty"
523 );
524 }
525
526 #[test]
527 fn walk_psaux_with_two_tasks_and_full_chains() {
528 use memf_core::test_builders::{flags as ptf, PageTableBuilder, SyntheticPhysMem};
529 use memf_core::vas::{TranslationMode, VirtualAddressSpace};
530 use memf_symbols::isf::IsfResolver;
531 use memf_symbols::test_builders::IsfBuilder;
532
533 let tasks_offset: u64 = 0x10;
534 let pid_offset: u64 = 0x00;
535 let comm_offset: u64 = 0x20;
536 let state_offset: u64 = 0x08;
537 let real_parent_off: u64 = 0x40;
538 let cred_offset: u64 = 0x48;
539 let mm_offset: u64 = 0x50;
540 let signal_offset: u64 = 0x58;
541 let static_prio_off: u64 = 0x60;
542 let flags_offset: u64 = 0x64;
543
544 let cred_uid_off: u64 = 0x04;
545 let cred_gid_off: u64 = 0x08;
546 let total_vm_off: u64 = 0x00;
547 let rss_stat_off: u64 = 0x08;
548 let sig_tty_off: u64 = 0x00;
549 let tty_name_off: u64 = 0x00;
550
551 let init_vaddr: u64 = 0xFFFF_8800_00E0_0000;
552 let init_paddr: u64 = 0x00E0_0000;
553 let t2_vaddr: u64 = 0xFFFF_8800_00E1_0000;
554 let t2_paddr: u64 = 0x00E1_0000;
555 let cred_vaddr: u64 = 0xFFFF_8800_00E2_0000;
556 let cred_paddr: u64 = 0x00E2_0000;
557 let mm_vaddr: u64 = 0xFFFF_8800_00E3_0000;
558 let mm_paddr: u64 = 0x00E3_0000;
559 let sig_vaddr: u64 = 0xFFFF_8800_00E4_0000;
560 let sig_paddr: u64 = 0x00E4_0000;
561 let tty_vaddr: u64 = 0xFFFF_8800_00E5_0000;
562 let tty_paddr: u64 = 0x00E5_0000;
563
564 let mut init_page = [0u8; 4096];
565 let t2_list_node = t2_vaddr + tasks_offset;
566 init_page[tasks_offset as usize..tasks_offset as usize + 8]
567 .copy_from_slice(&t2_list_node.to_le_bytes());
568 init_page[comm_offset as usize..comm_offset as usize + 7].copy_from_slice(b"swapper");
569
570 let mut t2_page = [0u8; 4096];
571 t2_page[pid_offset as usize..pid_offset as usize + 4].copy_from_slice(&42u32.to_le_bytes());
572 t2_page[state_offset as usize..state_offset as usize + 4]
573 .copy_from_slice(&1u32.to_le_bytes());
574 let init_list_node = init_vaddr + tasks_offset;
575 t2_page[tasks_offset as usize..tasks_offset as usize + 8]
576 .copy_from_slice(&init_list_node.to_le_bytes());
577 t2_page[comm_offset as usize..comm_offset as usize + 4].copy_from_slice(b"bash");
578 t2_page[real_parent_off as usize..real_parent_off as usize + 8]
579 .copy_from_slice(&init_vaddr.to_le_bytes());
580 t2_page[cred_offset as usize..cred_offset as usize + 8]
581 .copy_from_slice(&cred_vaddr.to_le_bytes());
582 t2_page[mm_offset as usize..mm_offset as usize + 8]
583 .copy_from_slice(&mm_vaddr.to_le_bytes());
584 t2_page[signal_offset as usize..signal_offset as usize + 8]
585 .copy_from_slice(&sig_vaddr.to_le_bytes());
586 t2_page[static_prio_off as usize..static_prio_off as usize + 4]
587 .copy_from_slice(&120i32.to_le_bytes());
588
589 let mut cred_page = [0u8; 4096];
590 cred_page[cred_uid_off as usize..cred_uid_off as usize + 4]
591 .copy_from_slice(&1000u32.to_le_bytes());
592 cred_page[cred_gid_off as usize..cred_gid_off as usize + 4]
593 .copy_from_slice(&2000u32.to_le_bytes());
594
595 let mut mm_page = [0u8; 4096];
596 mm_page[total_vm_off as usize..total_vm_off as usize + 8]
597 .copy_from_slice(&256u64.to_le_bytes());
598 mm_page[rss_stat_off as usize..rss_stat_off as usize + 8]
599 .copy_from_slice(&128u64.to_le_bytes());
600
601 let mut sig_page = [0u8; 4096];
602 sig_page[sig_tty_off as usize..sig_tty_off as usize + 8]
603 .copy_from_slice(&tty_vaddr.to_le_bytes());
604
605 let mut tty_page = [0u8; 4096];
606 tty_page[tty_name_off as usize..tty_name_off as usize + 6].copy_from_slice(b"pts/0\0");
607
608 let isf = IsfBuilder::new()
609 .add_symbol("init_task", init_vaddr)
610 .add_struct("list_head", 0x10)
611 .add_field("list_head", "next", 0x00u64, "pointer")
612 .add_struct("task_struct", 0x400)
613 .add_field("task_struct", "tasks", tasks_offset, "pointer")
614 .add_field("task_struct", "pid", pid_offset, "unsigned int")
615 .add_field("task_struct", "comm", comm_offset, "char")
616 .add_field("task_struct", "state", state_offset, "int")
617 .add_field("task_struct", "real_parent", real_parent_off, "pointer")
618 .add_field("task_struct", "cred", cred_offset, "pointer")
619 .add_field("task_struct", "mm", mm_offset, "pointer")
620 .add_field("task_struct", "signal", signal_offset, "pointer")
621 .add_field("task_struct", "static_prio", static_prio_off, "int")
622 .add_field("task_struct", "flags", flags_offset, "unsigned int")
623 .add_struct("cred", 0x80)
624 .add_field("cred", "uid", cred_uid_off, "unsigned int")
625 .add_field("cred", "gid", cred_gid_off, "unsigned int")
626 .add_struct("mm_struct", 0x200)
627 .add_field("mm_struct", "total_vm", total_vm_off, "unsigned long")
628 .add_field("mm_struct", "rss_stat", rss_stat_off, "unsigned long")
629 .add_struct("signal_struct", 0x200)
630 .add_field("signal_struct", "tty", sig_tty_off, "pointer")
631 .add_struct("tty_struct", 0x200)
632 .add_field("tty_struct", "name", tty_name_off, "char")
633 .build_json();
634 let resolver = IsfResolver::from_value(&isf).unwrap();
635
636 let (cr3, mem) = PageTableBuilder::new()
637 .map_4k(init_vaddr, init_paddr, ptf::WRITABLE)
638 .write_phys(init_paddr, &init_page)
639 .map_4k(t2_vaddr, t2_paddr, ptf::WRITABLE)
640 .write_phys(t2_paddr, &t2_page)
641 .map_4k(cred_vaddr, cred_paddr, ptf::WRITABLE)
642 .write_phys(cred_paddr, &cred_page)
643 .map_4k(mm_vaddr, mm_paddr, ptf::WRITABLE)
644 .write_phys(mm_paddr, &mm_page)
645 .map_4k(sig_vaddr, sig_paddr, ptf::WRITABLE)
646 .write_phys(sig_paddr, &sig_page)
647 .map_4k(tty_vaddr, tty_paddr, ptf::WRITABLE)
648 .write_phys(tty_paddr, &tty_page)
649 .build();
650
651 let vas = VirtualAddressSpace::new(mem, cr3, TranslationMode::X86_64FourLevel);
652 let reader: ObjectReader<SyntheticPhysMem> = ObjectReader::new(vas, Box::new(resolver));
653
654 let result = walk_psaux(&reader).unwrap();
655 assert_eq!(result.len(), 2, "both tasks should appear");
656 let t2 = result.iter().find(|p| p.pid == 42).expect("task2 missing");
657 assert_eq!(t2.uid, 1000);
658 assert_eq!(t2.gid, 2000);
659 assert_eq!(t2.vsize, 256 * 4096, "vsize = total_vm * PAGE_SIZE");
660 assert_eq!(t2.rss, 128);
661 assert_eq!(t2.tty, "pts/0");
662 assert_eq!(t2.state, "Sleeping");
663 assert_eq!(t2.nice, 0);
664 }
665
666 #[test]
667 fn walk_psaux_symbol_present_self_pointing_list_returns_init_task() {
668 use memf_core::test_builders::{flags as ptf, PageTableBuilder, SyntheticPhysMem};
669 use memf_core::vas::{TranslationMode, VirtualAddressSpace};
670 use memf_symbols::isf::IsfResolver;
671 use memf_symbols::test_builders::IsfBuilder;
672
673 let tasks_offset: u64 = 0x10;
674 let sym_vaddr: u64 = 0xFFFF_8800_0060_0000;
675 let sym_paddr: u64 = 0x0060_0000;
676
677 let isf = IsfBuilder::new()
678 .add_symbol("init_task", sym_vaddr)
679 .add_struct("list_head", 0x10)
680 .add_field("list_head", "next", 0x00, "pointer")
681 .add_struct("task_struct", 0x400)
682 .add_field("task_struct", "tasks", tasks_offset, "pointer")
683 .add_field("task_struct", "pid", 0x00, "unsigned int")
684 .add_field("task_struct", "comm", 0x20, "char")
685 .add_field("task_struct", "state", 0x08, "int")
686 .build_json();
687 let resolver = IsfResolver::from_value(&isf).unwrap();
688
689 let mut page = [0u8; 4096];
690 let self_ptr = sym_vaddr + tasks_offset;
691 page[tasks_offset as usize..tasks_offset as usize + 8]
692 .copy_from_slice(&self_ptr.to_le_bytes());
693 page[0x20..0x28].copy_from_slice(b"swapper\0");
694
695 let (cr3, mem) = PageTableBuilder::new()
696 .map_4k(sym_vaddr, sym_paddr, ptf::WRITABLE)
697 .write_phys(sym_paddr, &page)
698 .build();
699
700 let vas = VirtualAddressSpace::new(mem, cr3, TranslationMode::X86_64FourLevel);
701 let reader: ObjectReader<SyntheticPhysMem> = ObjectReader::new(vas, Box::new(resolver));
702
703 let result = walk_psaux(&reader).unwrap();
704 assert_eq!(
705 result.len(),
706 1,
707 "only init_task should appear (self-pointing list)"
708 );
709 assert_eq!(result[0].pid, 0);
710 }
711}