1use memf_core::object_reader::ObjectReader;
11use memf_format::PhysicalMemoryProvider;
12use serde::Serialize;
13
14use crate::{vma_walker::for_each_task_vma, Result};
15
16const VM_EXEC: u64 = 0x4;
18
19#[derive(Debug, Clone, Serialize)]
21pub struct MemfdInfo {
22 pub pid: u32,
24 pub comm: String,
26 pub memfd_name: String,
28 pub size_bytes: u64,
30 pub is_executable: bool,
32 pub is_suspicious: bool,
34}
35
36pub use crate::heuristics::classify_memfd;
45
46pub fn walk_memfd_create<P: PhysicalMemoryProvider>(
55 reader: &ObjectReader<P>,
56) -> Result<Vec<MemfdInfo>> {
57 let init_task_addr = match reader.symbols().symbol_address("init_task") {
59 Some(a) => a,
60 None => return Ok(vec![]),
61 };
62 let tasks_offset = match reader.symbols().field_offset("task_struct", "tasks") {
63 Some(o) => o,
64 None => return Ok(vec![]),
65 };
66
67 let head_vaddr = init_task_addr + tasks_offset;
68 let task_addrs = reader.walk_list(head_vaddr, "task_struct", "tasks")?;
69
70 let mut results: Vec<MemfdInfo> = Vec::new();
71
72 collect_memfd_for_task(reader, init_task_addr, &mut results);
73 for &task_addr in &task_addrs {
74 collect_memfd_for_task(reader, task_addr, &mut results);
75 }
76
77 results.sort_by_key(|r| r.pid);
78 Ok(results)
79}
80
81fn collect_memfd_for_task<P: PhysicalMemoryProvider>(
83 reader: &ObjectReader<P>,
84 task_addr: u64,
85 out: &mut Vec<MemfdInfo>,
86) {
87 let pid: u32 = match reader.read_field(task_addr, "task_struct", "pid") {
88 Ok(v) => v,
89 Err(_) => return,
90 };
91 let comm = reader
92 .read_field_string(task_addr, "task_struct", "comm", 16)
93 .unwrap_or_default();
94
95 for_each_task_vma(reader, task_addr, &mut |e| {
97 if let Some(info) = try_read_memfd_vma(reader, pid, &comm, e.vma_addr) {
98 let existing = out
100 .iter_mut()
101 .find(|entry| entry.pid == info.pid && entry.memfd_name == info.memfd_name);
102 if let Some(existing) = existing {
103 existing.size_bytes += info.size_bytes;
104 existing.is_executable |= info.is_executable;
105 existing.is_suspicious =
106 classify_memfd(&existing.memfd_name, existing.is_executable);
107 } else {
108 out.push(info);
109 }
110 }
111 });
112}
113
114fn try_read_memfd_vma<P: PhysicalMemoryProvider>(
118 reader: &ObjectReader<P>,
119 pid: u32,
120 comm: &str,
121 vma_addr: u64,
122) -> Option<MemfdInfo> {
123 let vm_file_ptr: u64 = reader
125 .read_field(vma_addr, "vm_area_struct", "vm_file")
126 .ok()?;
127 if vm_file_ptr == 0 {
128 return None;
129 }
130
131 let dentry_name = read_file_dentry_name(reader, vm_file_ptr)?;
133
134 let memfd_name = dentry_name.strip_prefix("memfd:")?;
136
137 let vm_start: u64 = reader
138 .read_field(vma_addr, "vm_area_struct", "vm_start")
139 .ok()?;
140 let vm_end: u64 = reader
141 .read_field(vma_addr, "vm_area_struct", "vm_end")
142 .ok()?;
143 let vm_flags: u64 = reader
144 .read_field(vma_addr, "vm_area_struct", "vm_flags")
145 .ok()?;
146
147 let size_bytes = vm_end.saturating_sub(vm_start);
148 let is_executable = (vm_flags & VM_EXEC) != 0;
149 let is_suspicious = classify_memfd(memfd_name, is_executable);
150
151 Some(MemfdInfo {
152 pid,
153 comm: comm.to_string(),
154 memfd_name: memfd_name.to_string(),
155 size_bytes,
156 is_executable,
157 is_suspicious,
158 })
159}
160
161fn read_file_dentry_name<P: PhysicalMemoryProvider>(
163 reader: &ObjectReader<P>,
164 file_ptr: u64,
165) -> Option<String> {
166 let f_path_offset = reader.symbols().field_offset("file", "f_path")?;
167 let dentry_in_path = reader.symbols().field_offset("path", "dentry")?;
168 let d_name_offset = reader.symbols().field_offset("dentry", "d_name")?;
169 let name_in_qstr = reader.symbols().field_offset("qstr", "name")?;
170
171 let dentry_addr = file_ptr + f_path_offset + dentry_in_path;
172 let dentry_raw = reader.read_bytes(dentry_addr, 8).ok()?;
173 let dentry_ptr = u64::from_le_bytes(dentry_raw.try_into().ok()?);
174 if dentry_ptr == 0 {
175 return None;
176 }
177
178 let name_addr = dentry_ptr + d_name_offset + name_in_qstr;
179 let name_raw = reader.read_bytes(name_addr, 8).ok()?;
180 let name_ptr = u64::from_le_bytes(name_raw.try_into().ok()?);
181 if name_ptr == 0 {
182 return None;
183 }
184
185 reader.read_string(name_ptr, 256).ok()
186}
187
188#[cfg(test)]
189mod tests {
190 use super::*;
191 use memf_core::object_reader::ObjectReader;
192 use memf_core::test_builders::{flags, PageTableBuilder, SyntheticPhysMem};
193 use memf_core::vas::{TranslationMode, VirtualAddressSpace};
194 use memf_symbols::isf::IsfResolver;
195 use memf_symbols::test_builders::IsfBuilder;
196
197 #[test]
202 fn classify_memfd_executable_is_suspicious() {
203 assert!(
204 classify_memfd("harmless", true),
205 "an executable memfd mapping must always be suspicious"
206 );
207 }
208
209 #[test]
210 fn classify_memfd_shellcode_name_is_suspicious() {
211 assert!(
212 classify_memfd("shellcode", false),
213 "a memfd named 'shellcode' must be suspicious"
214 );
215 }
216
217 #[test]
218 fn classify_memfd_empty_name_is_suspicious() {
219 assert!(
220 classify_memfd("", false),
221 "an anonymous memfd with empty name must be suspicious (evasion)"
222 );
223 }
224
225 #[test]
226 fn classify_memfd_pulseaudio_benign() {
227 assert!(
228 !classify_memfd("pulseaudio-shm", false),
229 "a non-executable memfd named 'pulseaudio-shm' must not be suspicious"
230 );
231 }
232
233 #[test]
234 fn classify_memfd_payload_name_is_suspicious() {
235 assert!(
236 classify_memfd("payload", false),
237 "a memfd named 'payload' must be suspicious"
238 );
239 }
240
241 #[test]
242 fn classify_memfd_wayland_benign() {
243 assert!(
244 !classify_memfd("wayland-shm", false),
245 "a non-executable memfd named 'wayland-shm' must not be suspicious"
246 );
247 }
248
249 fn make_reader_no_init_task() -> ObjectReader<SyntheticPhysMem> {
254 let isf = IsfBuilder::new()
255 .add_struct("task_struct", 128)
256 .add_field("task_struct", "pid", 0, "int")
257 .add_field("task_struct", "tasks", 16, "list_head")
258 .add_struct("list_head", 16)
259 .add_field("list_head", "next", 0, "pointer")
260 .add_field("list_head", "prev", 8, "pointer")
261 .build_json();
262
263 let resolver = IsfResolver::from_value(&isf).unwrap();
264 let (cr3, mem) = PageTableBuilder::new().build();
265 let vas = VirtualAddressSpace::new(mem, cr3, TranslationMode::X86_64FourLevel);
266 ObjectReader::new(vas, Box::new(resolver))
267 }
268
269 fn make_reader_no_memfd() -> ObjectReader<SyntheticPhysMem> {
270 let vaddr: u64 = 0xFFFF_8000_0010_0000;
271 let paddr: u64 = 0x0080_0000;
272
273 let mut data = vec![0u8; 4096];
274 data[0..4].copy_from_slice(&1u32.to_le_bytes());
275 let tasks_next = vaddr + 16;
276 data[16..24].copy_from_slice(&tasks_next.to_le_bytes());
277 data[24..32].copy_from_slice(&tasks_next.to_le_bytes());
278 data[32..37].copy_from_slice(b"init\0");
279 data[48..56].copy_from_slice(&0u64.to_le_bytes());
281
282 let isf = IsfBuilder::new()
283 .add_struct("task_struct", 128)
284 .add_field("task_struct", "pid", 0, "int")
285 .add_field("task_struct", "tasks", 16, "list_head")
286 .add_field("task_struct", "comm", 32, "char")
287 .add_field("task_struct", "mm", 48, "pointer")
288 .add_struct("list_head", 16)
289 .add_field("list_head", "next", 0, "pointer")
290 .add_field("list_head", "prev", 8, "pointer")
291 .add_symbol("init_task", vaddr)
292 .build_json();
293
294 let resolver = IsfResolver::from_value(&isf).unwrap();
295 let (cr3, mem) = PageTableBuilder::new()
296 .map_4k(vaddr, paddr, flags::WRITABLE)
297 .write_phys(paddr, &data)
298 .build();
299 let vas = VirtualAddressSpace::new(mem, cr3, TranslationMode::X86_64FourLevel);
300 ObjectReader::new(vas, Box::new(resolver))
301 }
302
303 #[test]
304 fn walk_memfd_missing_init_task_returns_empty() {
305 let reader = make_reader_no_init_task();
306 let result = walk_memfd_create(&reader).expect("should not error");
307 assert!(
308 result.is_empty(),
309 "missing init_task symbol must yield empty result (graceful degradation)"
310 );
311 }
312
313 #[test]
314 fn walk_memfd_no_memfd_processes_returns_empty() {
315 let reader = make_reader_no_memfd();
316 let result = walk_memfd_create(&reader).expect("should not error");
317 assert!(
318 result.is_empty(),
319 "a kernel thread with mm==NULL must not produce any memfd results"
320 );
321 }
322
323 #[test]
324 fn walk_memfd_missing_tasks_offset_returns_empty() {
325 let isf = IsfBuilder::new()
327 .add_struct("task_struct", 128)
328 .add_field("task_struct", "pid", 0, "int")
329 .add_symbol("init_task", 0xFFFF_8000_0000_0000)
331 .build_json();
332
333 let resolver = IsfResolver::from_value(&isf).unwrap();
334 let (cr3, mem) = PageTableBuilder::new().build();
335 let vas = VirtualAddressSpace::new(mem, cr3, TranslationMode::X86_64FourLevel);
336 let reader = ObjectReader::new(vas, Box::new(resolver));
337
338 let result = walk_memfd_create(&reader).expect("should not error");
339 assert!(
340 result.is_empty(),
341 "missing tasks offset must yield empty result"
342 );
343 }
344
345 #[test]
350 fn classify_memfd_shm_prefix_benign() {
351 assert!(
352 !classify_memfd("shm_region", false),
353 "shm prefix must be benign"
354 );
355 }
356
357 #[test]
358 fn classify_memfd_chrome_prefix_benign() {
359 assert!(
360 !classify_memfd("chrome_shared", false),
361 "chrome prefix must be benign"
362 );
363 }
364
365 #[test]
366 fn classify_memfd_firefox_prefix_benign() {
367 assert!(
368 !classify_memfd("firefox-ipc", false),
369 "firefox prefix must be benign"
370 );
371 }
372
373 #[test]
374 fn classify_memfd_v8_prefix_benign() {
375 assert!(
376 !classify_memfd("v8-heap", false),
377 "v8 prefix must be benign"
378 );
379 }
380
381 #[test]
382 fn classify_memfd_dbus_prefix_benign() {
383 assert!(
384 !classify_memfd("dbus-shm", false),
385 "dbus prefix must be benign"
386 );
387 }
388
389 #[test]
390 fn classify_memfd_stage_name_suspicious() {
391 assert!(
392 classify_memfd("stage2", false),
393 "stage substring must be suspicious"
394 );
395 }
396
397 #[test]
398 fn classify_memfd_loader_name_suspicious() {
399 assert!(
400 classify_memfd("loader", false),
401 "loader substring must be suspicious"
402 );
403 }
404
405 #[test]
406 fn classify_memfd_inject_name_suspicious() {
407 assert!(
408 classify_memfd("inject_hook", false),
409 "inject substring must be suspicious"
410 );
411 }
412
413 #[test]
414 fn classify_memfd_hack_name_suspicious() {
415 assert!(
416 classify_memfd("hack_tool", false),
417 "hack substring must be suspicious"
418 );
419 }
420
421 #[test]
422 fn classify_memfd_benign_non_prefix_non_suspicious_name() {
423 assert!(
425 !classify_memfd("my_normal_buffer", false),
426 "innocuous name must be benign"
427 );
428 }
429
430 #[test]
431 fn classify_memfd_case_insensitive_suspicious() {
432 assert!(
434 classify_memfd("PAYLOAD_EXEC", false),
435 "case-insensitive suspicious match"
436 );
437 }
438
439 #[test]
444 fn walk_memfd_symbol_present_empty_list() {
445 let sym_vaddr: u64 = 0xFFFF_8800_0020_0000;
448 let sym_paddr: u64 = 0x0030_0000;
449 let tasks_offset = 16u64;
450
451 let mut page = [0u8; 4096];
452 page[0..4].copy_from_slice(&1u32.to_le_bytes());
454 let list_self = sym_vaddr + tasks_offset;
456 page[tasks_offset as usize..tasks_offset as usize + 8]
457 .copy_from_slice(&list_self.to_le_bytes());
458 page[tasks_offset as usize + 8..tasks_offset as usize + 16]
459 .copy_from_slice(&list_self.to_le_bytes());
460 page[32..36].copy_from_slice(b"init");
462 page[48..56].copy_from_slice(&0u64.to_le_bytes());
464
465 let isf = IsfBuilder::new()
466 .add_struct("task_struct", 128)
467 .add_field("task_struct", "pid", 0, "unsigned int")
468 .add_field("task_struct", "tasks", 16, "pointer")
469 .add_field("task_struct", "comm", 32, "char")
470 .add_field("task_struct", "mm", 48, "pointer")
471 .add_symbol("init_task", sym_vaddr)
472 .build_json();
473
474 let resolver = IsfResolver::from_value(&isf).unwrap();
475 let (cr3, mem) = PageTableBuilder::new()
476 .map_4k(sym_vaddr, sym_paddr, flags::WRITABLE)
477 .write_phys(sym_paddr, &page)
478 .build();
479 let vas = VirtualAddressSpace::new(mem, cr3, TranslationMode::X86_64FourLevel);
480 let reader = ObjectReader::new(vas, Box::new(resolver));
481
482 let result = walk_memfd_create(&reader).unwrap_or_default();
483 assert!(
484 result.is_empty(),
485 "no memfd mappings expected for a kernel thread"
486 );
487 }
488
489 #[test]
490 fn memfd_info_serializes() {
491 let info = MemfdInfo {
492 pid: 999,
493 comm: "evil".to_string(),
494 memfd_name: "payload".to_string(),
495 size_bytes: 4096,
496 is_executable: true,
497 is_suspicious: true,
498 };
499 let json = serde_json::to_string(&info).unwrap();
500 assert!(json.contains("\"pid\":999"));
501 assert!(json.contains("\"is_suspicious\":true"));
502 assert!(json.contains("\"is_executable\":true"));
503 }
504
505 #[test]
508 fn walk_memfd_mm_nonzero_mmap_unreadable_returns_empty() {
509 let sym_vaddr: u64 = 0xFFFF_8800_0060_0000;
510 let sym_paddr: u64 = 0x0060_0000;
511 let tasks_offset: u64 = 16;
512 let mm_offset: u64 = 48;
513
514 let mm_vaddr: u64 = 0xFFFF_DEAD_BEEF_0000; let mut page = [0u8; 4096];
518 page[0..4].copy_from_slice(&1u32.to_le_bytes());
520 let self_ptr = sym_vaddr + tasks_offset;
522 page[tasks_offset as usize..tasks_offset as usize + 8]
523 .copy_from_slice(&self_ptr.to_le_bytes());
524 page[32..36].copy_from_slice(b"proc");
526 page[mm_offset as usize..mm_offset as usize + 8].copy_from_slice(&mm_vaddr.to_le_bytes());
528
529 let isf = IsfBuilder::new()
530 .add_struct("list_head", 16)
531 .add_field("list_head", "next", 0, "pointer")
532 .add_struct("task_struct", 128)
533 .add_field("task_struct", "pid", 0, "unsigned int")
534 .add_field("task_struct", "tasks", tasks_offset, "pointer")
535 .add_field("task_struct", "comm", 32, "char")
536 .add_field("task_struct", "mm", mm_offset, "pointer")
537 .add_struct("mm_struct", 0x200)
538 .add_field("mm_struct", "mmap", 0x00, "pointer")
539 .add_symbol("init_task", sym_vaddr)
540 .build_json();
541
542 let resolver = IsfResolver::from_value(&isf).unwrap();
543 let (cr3, mem) = PageTableBuilder::new()
544 .map_4k(sym_vaddr, sym_paddr, flags::WRITABLE)
545 .write_phys(sym_paddr, &page)
546 .build();
547 let vas = VirtualAddressSpace::new(mem, cr3, TranslationMode::X86_64FourLevel);
548 let reader = ObjectReader::new(vas, Box::new(resolver));
549
550 let result = walk_memfd_create(&reader).expect("should not error");
551 assert!(
552 result.is_empty(),
553 "unreadable mm_struct → mmap unreadable → no memfd results"
554 );
555 }
556
557 #[test]
560 fn walk_memfd_mm_nonzero_mmap_zero_returns_empty() {
561 let sym_vaddr: u64 = 0xFFFF_8800_0061_0000;
562 let sym_paddr: u64 = 0x0061_0000;
563 let tasks_offset: u64 = 16;
564 let mm_offset: u64 = 48;
565
566 let mm_vaddr: u64 = 0xFFFF_8800_0062_0000;
567 let mm_paddr: u64 = 0x0062_0000;
568
569 let mut task_page = [0u8; 4096];
570 page_write_u32(&mut task_page, 0, 2u32); let self_ptr = sym_vaddr + tasks_offset;
572 task_page[tasks_offset as usize..tasks_offset as usize + 8]
573 .copy_from_slice(&self_ptr.to_le_bytes());
574 task_page[32..36].copy_from_slice(b"proc");
575 task_page[mm_offset as usize..mm_offset as usize + 8]
576 .copy_from_slice(&mm_vaddr.to_le_bytes());
577
578 let mm_page = [0u8; 4096];
580
581 let isf = IsfBuilder::new()
582 .add_struct("list_head", 16)
583 .add_field("list_head", "next", 0, "pointer")
584 .add_struct("task_struct", 128)
585 .add_field("task_struct", "pid", 0, "unsigned int")
586 .add_field("task_struct", "tasks", tasks_offset, "pointer")
587 .add_field("task_struct", "comm", 32, "char")
588 .add_field("task_struct", "mm", mm_offset, "pointer")
589 .add_struct("mm_struct", 0x200)
590 .add_field("mm_struct", "mmap", 0x00, "pointer")
591 .add_symbol("init_task", sym_vaddr)
592 .build_json();
593
594 let resolver = IsfResolver::from_value(&isf).unwrap();
595 let (cr3, mem) = PageTableBuilder::new()
596 .map_4k(sym_vaddr, sym_paddr, flags::WRITABLE)
597 .write_phys(sym_paddr, &task_page)
598 .map_4k(mm_vaddr, mm_paddr, flags::WRITABLE)
599 .write_phys(mm_paddr, &mm_page)
600 .build();
601 let vas = VirtualAddressSpace::new(mem, cr3, TranslationMode::X86_64FourLevel);
602 let reader = ObjectReader::new(vas, Box::new(resolver));
603
604 let result = walk_memfd_create(&reader).expect("should not error");
605 assert!(
606 result.is_empty(),
607 "mmap_ptr == 0 → no VMAs → no memfd results"
608 );
609 }
610
611 #[test]
615 fn walk_memfd_vma_vm_file_null_skipped() {
616 let sym_vaddr: u64 = 0xFFFF_8800_0063_0000;
617 let sym_paddr: u64 = 0x0063_0000;
618 let tasks_offset: u64 = 16;
619 let mm_offset: u64 = 48;
620
621 let mm_vaddr: u64 = 0xFFFF_8800_0064_0000;
622 let mm_paddr: u64 = 0x0064_0000;
623
624 let vma_vaddr: u64 = 0xFFFF_8800_0065_0000;
625 let vma_paddr: u64 = 0x0065_0000;
626
627 let mut task_page = [0u8; 4096];
628 page_write_u32(&mut task_page, 0, 3u32);
629 let self_ptr = sym_vaddr + tasks_offset;
630 task_page[tasks_offset as usize..tasks_offset as usize + 8]
631 .copy_from_slice(&self_ptr.to_le_bytes());
632 task_page[32..36].copy_from_slice(b"proc");
633 task_page[mm_offset as usize..mm_offset as usize + 8]
634 .copy_from_slice(&mm_vaddr.to_le_bytes());
635
636 let mut mm_page = [0u8; 4096];
638 mm_page[0..8].copy_from_slice(&vma_vaddr.to_le_bytes());
639
640 let vma_page = [0u8; 4096]; let isf = IsfBuilder::new()
645 .add_struct("list_head", 16)
646 .add_field("list_head", "next", 0, "pointer")
647 .add_struct("task_struct", 128)
648 .add_field("task_struct", "pid", 0, "unsigned int")
649 .add_field("task_struct", "tasks", tasks_offset, "pointer")
650 .add_field("task_struct", "comm", 32, "char")
651 .add_field("task_struct", "mm", mm_offset, "pointer")
652 .add_struct("mm_struct", 0x200)
653 .add_field("mm_struct", "mmap", 0x00, "pointer")
654 .add_struct("vm_area_struct", 0x100)
655 .add_field("vm_area_struct", "vm_next", 0x00, "pointer")
656 .add_field("vm_area_struct", "vm_file", 0x08, "pointer")
657 .add_field("vm_area_struct", "vm_start", 0x10, "unsigned long")
658 .add_field("vm_area_struct", "vm_end", 0x18, "unsigned long")
659 .add_field("vm_area_struct", "vm_flags", 0x20, "unsigned long")
660 .add_symbol("init_task", sym_vaddr)
661 .build_json();
662
663 let resolver = IsfResolver::from_value(&isf).unwrap();
664 let (cr3, mem) = PageTableBuilder::new()
665 .map_4k(sym_vaddr, sym_paddr, flags::WRITABLE)
666 .write_phys(sym_paddr, &task_page)
667 .map_4k(mm_vaddr, mm_paddr, flags::WRITABLE)
668 .write_phys(mm_paddr, &mm_page)
669 .map_4k(vma_vaddr, vma_paddr, flags::WRITABLE)
670 .write_phys(vma_paddr, &vma_page)
671 .build();
672 let vas = VirtualAddressSpace::new(mem, cr3, TranslationMode::X86_64FourLevel);
673 let reader = ObjectReader::new(vas, Box::new(resolver));
674
675 let result = walk_memfd_create(&reader).expect("should not error");
676 assert!(
677 result.is_empty(),
678 "vm_file = 0 → try_read_memfd_vma returns None → no entries"
679 );
680 }
681
682 fn page_write_u32(page: &mut [u8], offset: usize, val: u32) {
684 page[offset..offset + 4].copy_from_slice(&val.to_le_bytes());
685 }
686
687 fn page_write_u64(page: &mut [u8], offset: usize, val: u64) {
689 page[offset..offset + 8].copy_from_slice(&val.to_le_bytes());
690 }
691
692 #[test]
694 fn walk_memfd_second_task_pid_read_fails_skipped() {
695 let init_vaddr: u64 = 0xFFFF_8800_0080_0000;
698 let init_paddr: u64 = 0x0080_0000;
699 let tasks_offset: u64 = 16;
700
701 let task2_vaddr: u64 = 0xFFFF_DEAD_0000_0000; let mut init_page = [0u8; 4096];
705 page_write_u32(&mut init_page, 0, 1); page_write_u64(
708 &mut init_page,
709 tasks_offset as usize,
710 task2_vaddr + tasks_offset,
711 );
712 page_write_u64(
713 &mut init_page,
714 tasks_offset as usize + 8,
715 task2_vaddr + tasks_offset,
716 );
717 init_page[32..36].copy_from_slice(b"init");
718 page_write_u64(&mut init_page, 48, 0); let isf = IsfBuilder::new()
721 .add_struct("list_head", 16)
722 .add_field("list_head", "next", 0u64, "pointer")
723 .add_field("list_head", "prev", 8u64, "pointer")
724 .add_struct("task_struct", 128)
725 .add_field("task_struct", "pid", 0u64, "unsigned int")
726 .add_field("task_struct", "tasks", tasks_offset, "list_head")
727 .add_field("task_struct", "comm", 32u64, "char")
728 .add_field("task_struct", "mm", 48u64, "pointer")
729 .add_symbol("init_task", init_vaddr)
730 .build_json();
731
732 let resolver = IsfResolver::from_value(&isf).unwrap();
733 let (cr3, mem) = PageTableBuilder::new()
734 .map_4k(init_vaddr, init_paddr, flags::WRITABLE)
735 .write_phys(init_paddr, &init_page)
736 .build();
737 let vas = VirtualAddressSpace::new(mem, cr3, TranslationMode::X86_64FourLevel);
738 let reader = ObjectReader::new(vas, Box::new(resolver));
739
740 let result = walk_memfd_create(&reader);
742 let entries = result.unwrap_or_default();
745 assert!(entries.is_empty(), "unmapped task2 → no memfd entries");
746 }
747
748 #[test]
751 fn walk_memfd_full_path_memfd_vma_detected() {
752 let init_vaddr: u64 = 0xFFFF_8800_00A0_0000;
757 let init_paddr: u64 = 0x00A0_0000;
758 let mm_vaddr: u64 = 0xFFFF_8800_00A1_0000;
759 let mm_paddr: u64 = 0x00A1_0000;
760 let vma_vaddr: u64 = 0xFFFF_8800_00A2_0000;
761 let vma_paddr: u64 = 0x00A2_0000;
762 let file_vaddr: u64 = 0xFFFF_8800_00A3_0000;
763 let file_paddr: u64 = 0x00A3_0000;
764 let dentry_vaddr: u64 = 0xFFFF_8800_00A4_0000;
765 let dentry_paddr: u64 = 0x00A4_0000;
766 let name_vaddr: u64 = 0xFFFF_8800_00A5_0000;
767 let name_paddr: u64 = 0x00A5_0000;
768
769 let tasks_offset: u64 = 16;
770 let mm_offset: u64 = 48;
771 let mmap_offset: u64 = 0; let f_path_off: u64 = 0x10;
775 let dentry_in_path: u64 = 0x00; let d_name_off: u64 = 0x08; let name_in_qstr: u64 = 0x00; let mut init_page = [0u8; 4096];
781 page_write_u32(&mut init_page, 0, 7u32); let tasks_self = init_vaddr + tasks_offset;
783 page_write_u64(&mut init_page, tasks_offset as usize, tasks_self);
784 page_write_u64(&mut init_page, tasks_offset as usize + 8, tasks_self);
785 init_page[32..37].copy_from_slice(b"evil\0");
786 page_write_u64(&mut init_page, mm_offset as usize, mm_vaddr); let mut mm_page = [0u8; 4096];
790 page_write_u64(&mut mm_page, mmap_offset as usize, vma_vaddr);
791
792 let mut vma_page = [0u8; 4096];
799 page_write_u64(&mut vma_page, 0x00, 0u64); page_write_u64(&mut vma_page, 0x08, file_vaddr); page_write_u64(&mut vma_page, 0x10, 0x1000u64); page_write_u64(&mut vma_page, 0x18, 0x2000u64); page_write_u64(&mut vma_page, 0x20, 4u64); let mut file_page = [0u8; 4096];
807 page_write_u64(&mut file_page, 0x10, dentry_vaddr);
808
809 let mut dentry_page = [0u8; 4096];
811 page_write_u64(&mut dentry_page, 0x08, name_vaddr);
812
813 let mut name_page = [0u8; 4096];
815 name_page[..14].copy_from_slice(b"memfd:payload\0");
816
817 let isf = IsfBuilder::new()
818 .add_struct("list_head", 16)
819 .add_field("list_head", "next", 0u64, "pointer")
820 .add_field("list_head", "prev", 8u64, "pointer")
821 .add_struct("task_struct", 128)
822 .add_field("task_struct", "pid", 0u64, "unsigned int")
823 .add_field("task_struct", "tasks", tasks_offset, "list_head")
824 .add_field("task_struct", "comm", 32u64, "char")
825 .add_field("task_struct", "mm", mm_offset, "pointer")
826 .add_struct("mm_struct", 0x100)
827 .add_field("mm_struct", "mmap", mmap_offset, "pointer")
828 .add_struct("vm_area_struct", 0x100)
829 .add_field("vm_area_struct", "vm_next", 0x00u64, "pointer")
830 .add_field("vm_area_struct", "vm_file", 0x08u64, "pointer")
831 .add_field("vm_area_struct", "vm_start", 0x10u64, "unsigned long")
832 .add_field("vm_area_struct", "vm_end", 0x18u64, "unsigned long")
833 .add_field("vm_area_struct", "vm_flags", 0x20u64, "unsigned long")
834 .add_struct("file", 0x100)
835 .add_field("file", "f_path", f_path_off, "pointer")
836 .add_struct("path", 0x20)
837 .add_field("path", "dentry", dentry_in_path, "pointer")
838 .add_struct("dentry", 0x100)
839 .add_field("dentry", "d_name", d_name_off, "pointer")
840 .add_struct("qstr", 0x20)
841 .add_field("qstr", "name", name_in_qstr, "pointer")
842 .add_symbol("init_task", init_vaddr)
843 .build_json();
844
845 let resolver = IsfResolver::from_value(&isf).unwrap();
846 let (cr3, mem) = PageTableBuilder::new()
847 .map_4k(init_vaddr, init_paddr, flags::WRITABLE)
848 .write_phys(init_paddr, &init_page)
849 .map_4k(mm_vaddr, mm_paddr, flags::WRITABLE)
850 .write_phys(mm_paddr, &mm_page)
851 .map_4k(vma_vaddr, vma_paddr, flags::WRITABLE)
852 .write_phys(vma_paddr, &vma_page)
853 .map_4k(file_vaddr, file_paddr, flags::WRITABLE)
854 .write_phys(file_paddr, &file_page)
855 .map_4k(dentry_vaddr, dentry_paddr, flags::WRITABLE)
856 .write_phys(dentry_paddr, &dentry_page)
857 .map_4k(name_vaddr, name_paddr, flags::WRITABLE)
858 .write_phys(name_paddr, &name_page)
859 .build();
860 let vas = VirtualAddressSpace::new(mem, cr3, TranslationMode::X86_64FourLevel);
861 let reader = ObjectReader::new(vas, Box::new(resolver));
862
863 let result = walk_memfd_create(&reader).expect("should not error");
864 assert_eq!(result.len(), 1, "expected one memfd entry");
865 assert_eq!(result[0].pid, 7);
866 assert_eq!(result[0].comm, "evil");
867 assert_eq!(result[0].memfd_name, "payload");
868 assert_eq!(result[0].size_bytes, 0x1000);
869 assert!(result[0].is_executable, "VM_EXEC flag set → executable");
870 assert!(result[0].is_suspicious, "executable memfd → suspicious");
871 }
872
873 #[test]
875 fn walk_memfd_dentry_missing_isf_fields_no_entry() {
876 let init_vaddr: u64 = 0xFFFF_8800_00B0_0000;
878 let init_paddr: u64 = 0x00B0_0000;
879 let mm_vaddr: u64 = 0xFFFF_8800_00B1_0000;
880 let mm_paddr: u64 = 0x00B1_0000;
881 let vma_vaddr: u64 = 0xFFFF_8800_00B2_0000;
882 let vma_paddr: u64 = 0x00B2_0000;
883 let file_vaddr: u64 = 0xFFFF_8800_00B3_0000;
884
885 let tasks_offset: u64 = 16;
886 let mm_offset: u64 = 48;
887
888 let mut init_page = [0u8; 4096];
889 page_write_u32(&mut init_page, 0, 8u32);
890 let tasks_self = init_vaddr + tasks_offset;
891 page_write_u64(&mut init_page, tasks_offset as usize, tasks_self);
892 page_write_u64(&mut init_page, tasks_offset as usize + 8, tasks_self);
893 init_page[32..36].copy_from_slice(b"proc");
894 page_write_u64(&mut init_page, mm_offset as usize, mm_vaddr);
895
896 let mut mm_page = [0u8; 4096];
897 page_write_u64(&mut mm_page, 0, vma_vaddr); let mut vma_page = [0u8; 4096];
900 page_write_u64(&mut vma_page, 0x00, 0u64); page_write_u64(&mut vma_page, 0x08, file_vaddr); page_write_u64(&mut vma_page, 0x10, 0x1000u64); page_write_u64(&mut vma_page, 0x18, 0x2000u64); page_write_u64(&mut vma_page, 0x20, 0u64); let isf = IsfBuilder::new()
907 .add_struct("list_head", 16)
908 .add_field("list_head", "next", 0u64, "pointer")
909 .add_field("list_head", "prev", 8u64, "pointer")
910 .add_struct("task_struct", 128)
911 .add_field("task_struct", "pid", 0u64, "unsigned int")
912 .add_field("task_struct", "tasks", tasks_offset, "list_head")
913 .add_field("task_struct", "comm", 32u64, "char")
914 .add_field("task_struct", "mm", mm_offset, "pointer")
915 .add_struct("mm_struct", 0x100)
916 .add_field("mm_struct", "mmap", 0u64, "pointer")
917 .add_struct("vm_area_struct", 0x100)
918 .add_field("vm_area_struct", "vm_next", 0x00u64, "pointer")
919 .add_field("vm_area_struct", "vm_file", 0x08u64, "pointer")
920 .add_field("vm_area_struct", "vm_start", 0x10u64, "unsigned long")
921 .add_field("vm_area_struct", "vm_end", 0x18u64, "unsigned long")
922 .add_field("vm_area_struct", "vm_flags", 0x20u64, "unsigned long")
923 .add_symbol("init_task", init_vaddr)
925 .build_json();
926
927 let resolver = IsfResolver::from_value(&isf).unwrap();
928 let (cr3, mem) = PageTableBuilder::new()
929 .map_4k(init_vaddr, init_paddr, flags::WRITABLE)
930 .write_phys(init_paddr, &init_page)
931 .map_4k(mm_vaddr, mm_paddr, flags::WRITABLE)
932 .write_phys(mm_paddr, &mm_page)
933 .map_4k(vma_vaddr, vma_paddr, flags::WRITABLE)
934 .write_phys(vma_paddr, &vma_page)
935 .build();
936 let vas = VirtualAddressSpace::new(mem, cr3, TranslationMode::X86_64FourLevel);
937 let reader = ObjectReader::new(vas, Box::new(resolver));
938
939 let result = walk_memfd_create(&reader).expect("should not error");
940 assert!(
941 result.is_empty(),
942 "missing dentry ISF fields → read_file_dentry_name None → no entries"
943 );
944 }
945
946 #[test]
948 fn walk_memfd_two_vmas_same_name_merged() {
949 let init_vaddr: u64 = 0xFFFF_8800_00C0_0000;
951 let init_paddr: u64 = 0x00C0_0000;
952 let mm_vaddr: u64 = 0xFFFF_8800_00C1_0000;
953 let mm_paddr: u64 = 0x00C1_0000;
954 let vma1_vaddr: u64 = 0xFFFF_8800_00C2_0000;
955 let vma1_paddr: u64 = 0x00C2_0000;
956 let vma2_vaddr: u64 = 0xFFFF_8800_00C3_0000;
957 let vma2_paddr: u64 = 0x00C3_0000;
958 let file_vaddr: u64 = 0xFFFF_8800_00C4_0000;
959 let file_paddr: u64 = 0x00C4_0000;
960 let dentry_vaddr: u64 = 0xFFFF_8800_00C5_0000;
961 let dentry_paddr: u64 = 0x00C5_0000;
962 let name_vaddr: u64 = 0xFFFF_8800_00C6_0000;
963 let name_paddr: u64 = 0x00C6_0000;
964
965 let tasks_offset: u64 = 16;
966 let mm_offset: u64 = 48;
967 let f_path_off: u64 = 0x10;
968 let d_name_off: u64 = 0x08;
969
970 let mut init_page = [0u8; 4096];
971 page_write_u32(&mut init_page, 0, 9u32);
972 let tasks_self = init_vaddr + tasks_offset;
973 page_write_u64(&mut init_page, tasks_offset as usize, tasks_self);
974 page_write_u64(&mut init_page, tasks_offset as usize + 8, tasks_self);
975 init_page[32..40].copy_from_slice(b"malware\0");
976 page_write_u64(&mut init_page, mm_offset as usize, mm_vaddr);
977
978 let mut mm_page = [0u8; 4096];
980 page_write_u64(&mut mm_page, 0, vma1_vaddr);
981
982 let mut vma1_page = [0u8; 4096];
984 page_write_u64(&mut vma1_page, 0x00, vma2_vaddr); page_write_u64(&mut vma1_page, 0x08, file_vaddr); page_write_u64(&mut vma1_page, 0x10, 0x1000u64); page_write_u64(&mut vma1_page, 0x18, 0x2000u64); page_write_u64(&mut vma1_page, 0x20, 0u64); let mut vma2_page = [0u8; 4096];
992 page_write_u64(&mut vma2_page, 0x00, 0u64); page_write_u64(&mut vma2_page, 0x08, file_vaddr); page_write_u64(&mut vma2_page, 0x10, 0x2000u64); page_write_u64(&mut vma2_page, 0x18, 0x3000u64); page_write_u64(&mut vma2_page, 0x20, 4u64); let mut file_page = [0u8; 4096];
1000 page_write_u64(&mut file_page, 0x10, dentry_vaddr);
1001
1002 let mut dentry_page = [0u8; 4096];
1004 page_write_u64(&mut dentry_page, 0x08, name_vaddr);
1005
1006 let mut name_page = [0u8; 4096];
1008 name_page[..14].copy_from_slice(b"memfd:payload\0");
1009
1010 let isf = IsfBuilder::new()
1011 .add_struct("list_head", 16)
1012 .add_field("list_head", "next", 0u64, "pointer")
1013 .add_field("list_head", "prev", 8u64, "pointer")
1014 .add_struct("task_struct", 128)
1015 .add_field("task_struct", "pid", 0u64, "unsigned int")
1016 .add_field("task_struct", "tasks", tasks_offset, "list_head")
1017 .add_field("task_struct", "comm", 32u64, "char")
1018 .add_field("task_struct", "mm", mm_offset, "pointer")
1019 .add_struct("mm_struct", 0x100)
1020 .add_field("mm_struct", "mmap", 0u64, "pointer")
1021 .add_struct("vm_area_struct", 0x100)
1022 .add_field("vm_area_struct", "vm_next", 0x00u64, "pointer")
1023 .add_field("vm_area_struct", "vm_file", 0x08u64, "pointer")
1024 .add_field("vm_area_struct", "vm_start", 0x10u64, "unsigned long")
1025 .add_field("vm_area_struct", "vm_end", 0x18u64, "unsigned long")
1026 .add_field("vm_area_struct", "vm_flags", 0x20u64, "unsigned long")
1027 .add_struct("file", 0x100)
1028 .add_field("file", "f_path", f_path_off, "pointer")
1029 .add_struct("path", 0x20)
1030 .add_field("path", "dentry", 0u64, "pointer")
1031 .add_struct("dentry", 0x100)
1032 .add_field("dentry", "d_name", d_name_off, "pointer")
1033 .add_struct("qstr", 0x20)
1034 .add_field("qstr", "name", 0u64, "pointer")
1035 .add_symbol("init_task", init_vaddr)
1036 .build_json();
1037
1038 let resolver = IsfResolver::from_value(&isf).unwrap();
1039 let (cr3, mem) = PageTableBuilder::new()
1040 .map_4k(init_vaddr, init_paddr, flags::WRITABLE)
1041 .write_phys(init_paddr, &init_page)
1042 .map_4k(mm_vaddr, mm_paddr, flags::WRITABLE)
1043 .write_phys(mm_paddr, &mm_page)
1044 .map_4k(vma1_vaddr, vma1_paddr, flags::WRITABLE)
1045 .write_phys(vma1_paddr, &vma1_page)
1046 .map_4k(vma2_vaddr, vma2_paddr, flags::WRITABLE)
1047 .write_phys(vma2_paddr, &vma2_page)
1048 .map_4k(file_vaddr, file_paddr, flags::WRITABLE)
1049 .write_phys(file_paddr, &file_page)
1050 .map_4k(dentry_vaddr, dentry_paddr, flags::WRITABLE)
1051 .write_phys(dentry_paddr, &dentry_page)
1052 .map_4k(name_vaddr, name_paddr, flags::WRITABLE)
1053 .write_phys(name_paddr, &name_page)
1054 .build();
1055 let vas = VirtualAddressSpace::new(mem, cr3, TranslationMode::X86_64FourLevel);
1056 let reader = ObjectReader::new(vas, Box::new(resolver));
1057
1058 let result = walk_memfd_create(&reader).expect("should not error");
1059 assert_eq!(
1060 result.len(),
1061 1,
1062 "two VMAs for same memfd → merged to 1 entry"
1063 );
1064 assert_eq!(result[0].memfd_name, "payload");
1065 assert_eq!(result[0].size_bytes, 0x2000);
1067 assert!(
1069 result[0].is_executable,
1070 "merged entry must be executable after vma2"
1071 );
1072 assert!(
1073 result[0].is_suspicious,
1074 "executable memfd:payload must be suspicious"
1075 );
1076 }
1077
1078 #[test]
1080 fn walk_memfd_dentry_ptr_null_returns_none() {
1081 let init_vaddr: u64 = 0xFFFF_8800_00D0_0000;
1083 let init_paddr: u64 = 0x00D0_0000;
1084 let mm_vaddr: u64 = 0xFFFF_8800_00D1_0000;
1085 let mm_paddr: u64 = 0x00D1_0000;
1086 let vma_vaddr: u64 = 0xFFFF_8800_00D2_0000;
1087 let vma_paddr: u64 = 0x00D2_0000;
1088 let file_vaddr: u64 = 0xFFFF_8800_00D3_0000;
1089 let file_paddr: u64 = 0x00D3_0000;
1090
1091 let tasks_offset: u64 = 16;
1092 let mm_offset: u64 = 48;
1093 let f_path_off: u64 = 0x10;
1094 let d_name_off: u64 = 0x08;
1095
1096 let mut init_page = [0u8; 4096];
1097 page_write_u32(&mut init_page, 0, 11u32);
1098 let tasks_self = init_vaddr + tasks_offset;
1099 page_write_u64(&mut init_page, tasks_offset as usize, tasks_self);
1100 page_write_u64(&mut init_page, tasks_offset as usize + 8, tasks_self);
1101 init_page[32..36].copy_from_slice(b"proc");
1102 page_write_u64(&mut init_page, mm_offset as usize, mm_vaddr);
1103
1104 let mut mm_page = [0u8; 4096];
1105 page_write_u64(&mut mm_page, 0, vma_vaddr);
1106
1107 let mut vma_page = [0u8; 4096];
1108 page_write_u64(&mut vma_page, 0x00, 0u64); page_write_u64(&mut vma_page, 0x08, file_vaddr); page_write_u64(&mut vma_page, 0x10, 0x1000u64);
1111 page_write_u64(&mut vma_page, 0x18, 0x2000u64);
1112 page_write_u64(&mut vma_page, 0x20, 0u64);
1113
1114 let mut file_page = [0u8; 4096];
1116 page_write_u64(&mut file_page, 0x10, 0u64); let isf = IsfBuilder::new()
1119 .add_struct("list_head", 16)
1120 .add_field("list_head", "next", 0u64, "pointer")
1121 .add_field("list_head", "prev", 8u64, "pointer")
1122 .add_struct("task_struct", 128)
1123 .add_field("task_struct", "pid", 0u64, "unsigned int")
1124 .add_field("task_struct", "tasks", tasks_offset, "list_head")
1125 .add_field("task_struct", "comm", 32u64, "char")
1126 .add_field("task_struct", "mm", mm_offset, "pointer")
1127 .add_struct("mm_struct", 0x100)
1128 .add_field("mm_struct", "mmap", 0u64, "pointer")
1129 .add_struct("vm_area_struct", 0x100)
1130 .add_field("vm_area_struct", "vm_next", 0x00u64, "pointer")
1131 .add_field("vm_area_struct", "vm_file", 0x08u64, "pointer")
1132 .add_field("vm_area_struct", "vm_start", 0x10u64, "unsigned long")
1133 .add_field("vm_area_struct", "vm_end", 0x18u64, "unsigned long")
1134 .add_field("vm_area_struct", "vm_flags", 0x20u64, "unsigned long")
1135 .add_struct("file", 0x100)
1136 .add_field("file", "f_path", f_path_off, "pointer")
1137 .add_struct("path", 0x20)
1138 .add_field("path", "dentry", 0u64, "pointer")
1139 .add_struct("dentry", 0x100)
1140 .add_field("dentry", "d_name", d_name_off, "pointer")
1141 .add_struct("qstr", 0x20)
1142 .add_field("qstr", "name", 0u64, "pointer")
1143 .add_symbol("init_task", init_vaddr)
1144 .build_json();
1145
1146 let resolver = IsfResolver::from_value(&isf).unwrap();
1147 let (cr3, mem) = PageTableBuilder::new()
1148 .map_4k(init_vaddr, init_paddr, flags::WRITABLE)
1149 .write_phys(init_paddr, &init_page)
1150 .map_4k(mm_vaddr, mm_paddr, flags::WRITABLE)
1151 .write_phys(mm_paddr, &mm_page)
1152 .map_4k(vma_vaddr, vma_paddr, flags::WRITABLE)
1153 .write_phys(vma_paddr, &vma_page)
1154 .map_4k(file_vaddr, file_paddr, flags::WRITABLE)
1155 .write_phys(file_paddr, &file_page)
1156 .build();
1157 let vas = VirtualAddressSpace::new(mem, cr3, TranslationMode::X86_64FourLevel);
1158 let reader = ObjectReader::new(vas, Box::new(resolver));
1159
1160 let result = walk_memfd_create(&reader).expect("should not error");
1161 assert!(
1162 result.is_empty(),
1163 "dentry_ptr == 0 → read_file_dentry_name None → no entries"
1164 );
1165 }
1166
1167 #[test]
1169 fn walk_memfd_name_ptr_null_returns_none() {
1170 let init_vaddr: u64 = 0xFFFF_8800_00E0_0000;
1172 let init_paddr: u64 = 0x00E0_0000;
1173 let mm_vaddr: u64 = 0xFFFF_8800_00E1_0000;
1174 let mm_paddr: u64 = 0x00E1_0000;
1175 let vma_vaddr: u64 = 0xFFFF_8800_00E2_0000;
1176 let vma_paddr: u64 = 0x00E2_0000;
1177 let file_vaddr: u64 = 0xFFFF_8800_00E3_0000;
1178 let file_paddr: u64 = 0x00E3_0000;
1179 let dentry_vaddr: u64 = 0xFFFF_8800_00E4_0000;
1180 let dentry_paddr: u64 = 0x00E4_0000;
1181
1182 let tasks_offset: u64 = 16;
1183 let mm_offset: u64 = 48;
1184 let f_path_off: u64 = 0x10;
1185 let d_name_off: u64 = 0x08;
1186
1187 let mut init_page = [0u8; 4096];
1188 page_write_u32(&mut init_page, 0, 12u32);
1189 let tasks_self = init_vaddr + tasks_offset;
1190 page_write_u64(&mut init_page, tasks_offset as usize, tasks_self);
1191 page_write_u64(&mut init_page, tasks_offset as usize + 8, tasks_self);
1192 init_page[32..36].copy_from_slice(b"proc");
1193 page_write_u64(&mut init_page, mm_offset as usize, mm_vaddr);
1194
1195 let mut mm_page = [0u8; 4096];
1196 page_write_u64(&mut mm_page, 0, vma_vaddr);
1197
1198 let mut vma_page = [0u8; 4096];
1199 page_write_u64(&mut vma_page, 0x00, 0u64);
1200 page_write_u64(&mut vma_page, 0x08, file_vaddr);
1201 page_write_u64(&mut vma_page, 0x10, 0x1000u64);
1202 page_write_u64(&mut vma_page, 0x18, 0x2000u64);
1203 page_write_u64(&mut vma_page, 0x20, 0u64);
1204
1205 let mut file_page = [0u8; 4096];
1206 page_write_u64(&mut file_page, 0x10, dentry_vaddr); let mut dentry_page = [0u8; 4096];
1210 page_write_u64(&mut dentry_page, 0x08, 0u64); let isf = IsfBuilder::new()
1213 .add_struct("list_head", 16)
1214 .add_field("list_head", "next", 0u64, "pointer")
1215 .add_field("list_head", "prev", 8u64, "pointer")
1216 .add_struct("task_struct", 128)
1217 .add_field("task_struct", "pid", 0u64, "unsigned int")
1218 .add_field("task_struct", "tasks", tasks_offset, "list_head")
1219 .add_field("task_struct", "comm", 32u64, "char")
1220 .add_field("task_struct", "mm", mm_offset, "pointer")
1221 .add_struct("mm_struct", 0x100)
1222 .add_field("mm_struct", "mmap", 0u64, "pointer")
1223 .add_struct("vm_area_struct", 0x100)
1224 .add_field("vm_area_struct", "vm_next", 0x00u64, "pointer")
1225 .add_field("vm_area_struct", "vm_file", 0x08u64, "pointer")
1226 .add_field("vm_area_struct", "vm_start", 0x10u64, "unsigned long")
1227 .add_field("vm_area_struct", "vm_end", 0x18u64, "unsigned long")
1228 .add_field("vm_area_struct", "vm_flags", 0x20u64, "unsigned long")
1229 .add_struct("file", 0x100)
1230 .add_field("file", "f_path", f_path_off, "pointer")
1231 .add_struct("path", 0x20)
1232 .add_field("path", "dentry", 0u64, "pointer")
1233 .add_struct("dentry", 0x100)
1234 .add_field("dentry", "d_name", d_name_off, "pointer")
1235 .add_struct("qstr", 0x20)
1236 .add_field("qstr", "name", 0u64, "pointer")
1237 .add_symbol("init_task", init_vaddr)
1238 .build_json();
1239
1240 let resolver = IsfResolver::from_value(&isf).unwrap();
1241 let (cr3, mem) = PageTableBuilder::new()
1242 .map_4k(init_vaddr, init_paddr, flags::WRITABLE)
1243 .write_phys(init_paddr, &init_page)
1244 .map_4k(mm_vaddr, mm_paddr, flags::WRITABLE)
1245 .write_phys(mm_paddr, &mm_page)
1246 .map_4k(vma_vaddr, vma_paddr, flags::WRITABLE)
1247 .write_phys(vma_paddr, &vma_page)
1248 .map_4k(file_vaddr, file_paddr, flags::WRITABLE)
1249 .write_phys(file_paddr, &file_page)
1250 .map_4k(dentry_vaddr, dentry_paddr, flags::WRITABLE)
1251 .write_phys(dentry_paddr, &dentry_page)
1252 .build();
1253 let vas = VirtualAddressSpace::new(mem, cr3, TranslationMode::X86_64FourLevel);
1254 let reader = ObjectReader::new(vas, Box::new(resolver));
1255
1256 let result = walk_memfd_create(&reader).expect("should not error");
1257 assert!(
1258 result.is_empty(),
1259 "name_ptr == 0 → read_file_dentry_name None → no entries"
1260 );
1261 }
1262
1263 #[test]
1267 fn walk_memfd_vm_file_nonzero_non_memfd_name_skipped() {
1268 let sym_vaddr: u64 = 0xFFFF_8800_0070_0000;
1269 let sym_paddr: u64 = 0x0070_0000;
1270 let tasks_offset: u64 = 16;
1271 let mm_offset: u64 = 48;
1272
1273 let mm_vaddr: u64 = 0xFFFF_8800_0071_0000;
1274 let mm_paddr: u64 = 0x0071_0000;
1275
1276 let vma_vaddr: u64 = 0xFFFF_8800_0072_0000;
1277 let vma_paddr: u64 = 0x0072_0000;
1278
1279 let file_vaddr: u64 = 0xFFFF_8800_0073_0000;
1281 let file_paddr: u64 = 0x0073_0000;
1282 let dentry_vaddr: u64 = 0xFFFF_8800_0074_0000;
1283 let dentry_paddr: u64 = 0x0074_0000;
1284 let name_vaddr: u64 = 0xFFFF_8800_0075_0000;
1285 let name_paddr: u64 = 0x0075_0000;
1286
1287 let f_path_off: u64 = 0x10; let dentry_in_path: u64 = 0x00; let d_name_off: u64 = 0x08; let name_in_qstr: u64 = 0x00; let mut task_page = [0u8; 4096];
1294 page_write_u32(&mut task_page, 0, 5u32); let self_ptr = sym_vaddr + tasks_offset;
1296 task_page[tasks_offset as usize..tasks_offset as usize + 8]
1297 .copy_from_slice(&self_ptr.to_le_bytes());
1298 task_page[32..37].copy_from_slice(b"bash\0");
1299 task_page[mm_offset as usize..mm_offset as usize + 8]
1300 .copy_from_slice(&mm_vaddr.to_le_bytes());
1301
1302 let mut mm_page = [0u8; 4096];
1304 mm_page[0..8].copy_from_slice(&vma_vaddr.to_le_bytes());
1305
1306 let mut vma_page = [0u8; 4096];
1308 vma_page[0..8].copy_from_slice(&0u64.to_le_bytes()); vma_page[8..16].copy_from_slice(&file_vaddr.to_le_bytes()); vma_page[0x10..0x18].copy_from_slice(&0x1000u64.to_le_bytes()); vma_page[0x18..0x20].copy_from_slice(&0x2000u64.to_le_bytes()); vma_page[0x20..0x28].copy_from_slice(&0u64.to_le_bytes()); let mut file_page = [0u8; 4096];
1316 file_page[0x10..0x18].copy_from_slice(&dentry_vaddr.to_le_bytes());
1317
1318 let mut dentry_page = [0u8; 4096];
1320 dentry_page[0x08..0x10].copy_from_slice(&name_vaddr.to_le_bytes());
1321
1322 let mut name_page = [0u8; 4096];
1324 name_page[..10].copy_from_slice(b"/dev/null\0");
1325
1326 let isf = IsfBuilder::new()
1327 .add_struct("list_head", 16)
1328 .add_field("list_head", "next", 0, "pointer")
1329 .add_struct("task_struct", 128)
1330 .add_field("task_struct", "pid", 0, "unsigned int")
1331 .add_field("task_struct", "tasks", tasks_offset, "pointer")
1332 .add_field("task_struct", "comm", 32, "char")
1333 .add_field("task_struct", "mm", mm_offset, "pointer")
1334 .add_struct("mm_struct", 0x200)
1335 .add_field("mm_struct", "mmap", 0x00, "pointer")
1336 .add_struct("vm_area_struct", 0x100)
1337 .add_field("vm_area_struct", "vm_next", 0x00, "pointer")
1338 .add_field("vm_area_struct", "vm_file", 0x08, "pointer")
1339 .add_field("vm_area_struct", "vm_start", 0x10, "unsigned long")
1340 .add_field("vm_area_struct", "vm_end", 0x18, "unsigned long")
1341 .add_field("vm_area_struct", "vm_flags", 0x20, "unsigned long")
1342 .add_struct("file", 0x200)
1343 .add_field("file", "f_path", f_path_off, "pointer")
1344 .add_struct("path", 0x20)
1345 .add_field("path", "dentry", dentry_in_path, "pointer")
1346 .add_struct("dentry", 0x200)
1347 .add_field("dentry", "d_name", d_name_off, "pointer")
1348 .add_struct("qstr", 0x20)
1349 .add_field("qstr", "name", name_in_qstr, "pointer")
1350 .add_symbol("init_task", sym_vaddr)
1351 .build_json();
1352
1353 let resolver = IsfResolver::from_value(&isf).unwrap();
1354 let (cr3, mem) = PageTableBuilder::new()
1355 .map_4k(sym_vaddr, sym_paddr, flags::WRITABLE)
1356 .write_phys(sym_paddr, &task_page)
1357 .map_4k(mm_vaddr, mm_paddr, flags::WRITABLE)
1358 .write_phys(mm_paddr, &mm_page)
1359 .map_4k(vma_vaddr, vma_paddr, flags::WRITABLE)
1360 .write_phys(vma_paddr, &vma_page)
1361 .map_4k(file_vaddr, file_paddr, flags::WRITABLE)
1362 .write_phys(file_paddr, &file_page)
1363 .map_4k(dentry_vaddr, dentry_paddr, flags::WRITABLE)
1364 .write_phys(dentry_paddr, &dentry_page)
1365 .map_4k(name_vaddr, name_paddr, flags::WRITABLE)
1366 .write_phys(name_paddr, &name_page)
1367 .build();
1368
1369 let vas = VirtualAddressSpace::new(mem, cr3, TranslationMode::X86_64FourLevel);
1370 let reader = ObjectReader::new(vas, Box::new(resolver));
1371
1372 let result = walk_memfd_create(&reader).expect("should not error");
1373 assert!(
1374 result.is_empty(),
1375 "non-memfd dentry name → strip_prefix fails → no entries"
1376 );
1377 }
1378}