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