Skip to main content

memf_linux/
check_creds.rs

1//! Shared credential structure detection for privilege escalation analysis.
2//!
3//! In normal Linux operation each process has its own `struct cred` (or
4//! shares with parent/threads). When *unrelated* processes share the same
5//! `cred` pointer it is a strong indicator of privilege escalation — an
6//! exploit may have replaced a process's cred pointer with another
7//! process's (e.g. pointing to init's cred to gain root).
8
9use std::collections::HashMap;
10
11use memf_core::object_reader::ObjectReader;
12use memf_format::PhysicalMemoryProvider;
13
14use crate::Result;
15
16/// Information about a process whose `struct cred` is shared with other
17/// unrelated processes.
18#[derive(Debug, Clone, serde::Serialize)]
19pub struct SharedCredInfo {
20    /// Process ID.
21    pub pid: u32,
22    /// Process command name.
23    pub process_name: String,
24    /// UID from the credential structure.
25    pub uid: u32,
26    /// Virtual address of the `struct cred`.
27    pub cred_address: u64,
28    /// Other PIDs that share the same cred pointer.
29    pub shared_with_pids: Vec<u32>,
30    /// Whether this sharing pattern is suspicious.
31    pub is_suspicious: bool,
32}
33
34/// Classify whether shared credentials are suspicious.
35///
36/// Returns `true` (suspicious) when:
37/// - A non-kernel-thread process shares creds with init (pid 1)
38/// - Unrelated processes (not parent-child / not threads of the same
39///   process) share the same cred pointer
40///
41/// Returns `false` (benign) when:
42/// - Threads of the same process share creds (normal behaviour)
43/// - All uid-0 kernel threads share the kernel cred
44pub use crate::heuristics::classify_shared_creds;
45
46/// Walk all tasks and detect shared `struct cred` pointers.
47///
48/// Returns an entry for every process whose cred address is shared with
49/// at least one other process and where the sharing is suspicious.
50///
51/// Returns an empty `Vec` when symbols are missing (graceful degradation).
52pub fn walk_check_creds<P: PhysicalMemoryProvider>(
53    reader: &ObjectReader<P>,
54) -> Result<Vec<SharedCredInfo>> {
55    // --- Graceful degradation: bail with empty vec if symbols are absent ---
56    let init_task_addr = match reader.symbols().symbol_address("init_task") {
57        Some(addr) => addr,
58        None => return Ok(Vec::new()),
59    };
60
61    let tasks_offset = match reader.symbols().field_offset("task_struct", "tasks") {
62        Some(off) => off,
63        None => return Ok(Vec::new()),
64    };
65
66    // --- Step 1: Walk the task list -----------------------------------------
67    let head_vaddr = init_task_addr + tasks_offset;
68    let task_addrs = reader.walk_list(head_vaddr, "task_struct", "tasks")?;
69
70    // Collect (pid, tgid, name, cred_addr) for every task, including init_task.
71    let mut tasks: Vec<(u32, u32, String, u64)> = Vec::new();
72
73    // Helper closure to extract per-task info.
74    let collect_task = |addr: u64| -> Option<(u32, u32, String, u64)> {
75        let pid: u32 = reader.read_field(addr, "task_struct", "pid").ok()?;
76        let tgid: u32 = reader
77            .read_field(addr, "task_struct", "tgid")
78            .unwrap_or(pid);
79        let name = reader
80            .read_field_string(addr, "task_struct", "comm", 16)
81            .unwrap_or_else(|_| "<unknown>".to_string());
82        let cred_ptr: u64 = reader.read_field(addr, "task_struct", "cred").ok()?;
83        Some((pid, tgid, name, cred_ptr))
84    };
85
86    // Include init_task itself.
87    if let Some(info) = collect_task(init_task_addr) {
88        tasks.push(info);
89    }
90    for &task_addr in &task_addrs {
91        if let Some(info) = collect_task(task_addr) {
92            tasks.push(info);
93        }
94    }
95
96    // --- Step 2: Build cred_address → [(pid, tgid, name)] map ---------------
97    let mut cred_map: HashMap<u64, Vec<(u32, u32, String)>> = HashMap::new();
98    for (pid, tgid, name, cred_addr) in &tasks {
99        // Skip null cred pointers.
100        if *cred_addr == 0 {
101            continue;
102        }
103        cred_map
104            .entry(*cred_addr)
105            .or_default()
106            .push((*pid, *tgid, name.clone()));
107    }
108
109    // --- Step 3: For groups with >1 process, classify and emit results ------
110    let mut results = Vec::new();
111
112    for (cred_addr, group) in &cred_map {
113        if group.len() < 2 {
114            continue;
115        }
116
117        // Filter out thread-group siblings: tasks with the same tgid are
118        // threads of the same process and legitimately share creds.
119        // Group by tgid; only flag cross-tgid sharing.
120        let mut by_tgid: HashMap<u32, Vec<u32>> = HashMap::new();
121        for (pid, tgid, _) in group {
122            by_tgid.entry(*tgid).or_default().push(*pid);
123        }
124
125        // If every task in the group has the same tgid, it is pure
126        // thread sharing → benign, skip.
127        if by_tgid.len() < 2 {
128            continue;
129        }
130
131        // Read uid from the cred struct (best effort).
132        let uid: u32 = reader
133            .read_field(*cred_addr, "cred", "uid")
134            .unwrap_or(u32::MAX);
135
136        // Build per-process entries for cross-tgid participants.
137        for (pid, _tgid, name) in group {
138            let shared_with: Vec<u32> = group
139                .iter()
140                .filter(|(other_pid, _, _)| other_pid != pid)
141                .map(|(other_pid, _, _)| *other_pid)
142                .collect();
143
144            let is_suspicious = classify_shared_creds(*pid, &shared_with, uid);
145
146            if is_suspicious {
147                results.push(SharedCredInfo {
148                    pid: *pid,
149                    process_name: name.clone(),
150                    uid,
151                    cred_address: *cred_addr,
152                    shared_with_pids: shared_with,
153                    is_suspicious,
154                });
155            }
156        }
157    }
158
159    Ok(results)
160}
161
162#[cfg(test)]
163mod tests {
164    use super::*;
165    use memf_core::object_reader::ObjectReader;
166    use memf_core::test_builders::{flags, PageTableBuilder, SyntheticPhysMem};
167    use memf_core::vas::{TranslationMode, VirtualAddressSpace};
168    use memf_symbols::isf::IsfResolver;
169    use memf_symbols::test_builders::IsfBuilder;
170
171    // ---------------------------------------------------------------
172    // Classifier unit tests
173    // ---------------------------------------------------------------
174
175    #[test]
176    fn shared_with_init_suspicious() {
177        // A regular user-space process (uid=1000, pid=500) sharing
178        // creds with init (pid 1) → suspicious.
179        assert!(classify_shared_creds(500, &[1], 1000));
180    }
181
182    #[test]
183    fn unrelated_sharing_suspicious() {
184        // Two unrelated user-space processes sharing creds → suspicious.
185        assert!(classify_shared_creds(200, &[300], 1000));
186    }
187
188    #[test]
189    fn thread_sharing_benign() {
190        // Kernel thread (pid 2, uid 0) sharing with init → benign
191        // (kernel cred shared among kthreadd and init is expected).
192        assert!(!classify_shared_creds(2, &[1], 0));
193    }
194
195    #[test]
196    fn kernel_thread_benign() {
197        // A uid-0 kernel thread (pid 2) with no non-kernel sharing → benign.
198        assert!(!classify_shared_creds(2, &[1], 0));
199    }
200
201    #[test]
202    fn no_sharing_benign() {
203        // No shared PIDs at all → not suspicious.
204        assert!(!classify_shared_creds(100, &[], 1000));
205    }
206
207    // ---------------------------------------------------------------
208    // Walker integration test — missing symbol → empty Vec
209    // ---------------------------------------------------------------
210
211    #[test]
212    fn walk_check_creds_no_symbol_returns_empty() {
213        // Build a reader with task_struct defined but no init_task symbol.
214        let isf = IsfBuilder::new()
215            .add_struct("task_struct", 128)
216            .add_field("task_struct", "pid", 0, "int")
217            .add_field("task_struct", "tasks", 16, "list_head")
218            .add_field("task_struct", "comm", 32, "char")
219            .add_field("task_struct", "cred", 96, "pointer")
220            .add_field("task_struct", "real_cred", 104, "pointer")
221            .add_field("task_struct", "tgid", 112, "int")
222            .add_struct("list_head", 16)
223            .add_field("list_head", "next", 0, "pointer")
224            .add_field("list_head", "prev", 8, "pointer")
225            .add_struct("cred", 64)
226            .add_field("cred", "uid", 4, "unsigned int")
227            // NOTE: no "init_task" symbol registered
228            .build_json();
229
230        let resolver = IsfResolver::from_value(&isf).unwrap();
231        let (cr3, mem) = PageTableBuilder::new().build();
232        let vas = VirtualAddressSpace::new(mem, cr3, TranslationMode::X86_64FourLevel);
233        let reader = ObjectReader::new(vas, Box::new(resolver));
234
235        let result = walk_check_creds(&reader);
236        // Graceful degradation: missing symbol → empty vec, not an error.
237        assert!(result.is_ok());
238        assert!(result.unwrap().is_empty());
239    }
240
241    // ---------------------------------------------------------------
242    // is_likely_kernel_thread tests (via classify_shared_creds behaviour)
243    // ---------------------------------------------------------------
244
245    #[test]
246    fn is_likely_kernel_thread_pid_0_benign() {
247        // PID 0 (swapper/idle) is a kernel thread → uid-0 sharing is benign
248        assert!(!classify_shared_creds(0, &[2], 0));
249    }
250
251    #[test]
252    fn is_likely_kernel_thread_pid_1_shares_with_pid_2_suspicious() {
253        // PID 1 (init/systemd) is NOT a kernel thread (pid > 2), shares with pid 2
254        // uid=0, pid=1 → is_likely_kernel_thread(1) = true (pid <= 2)
255        // So this should be benign (kernel thread path)
256        assert!(!classify_shared_creds(1, &[2], 0));
257    }
258
259    #[test]
260    fn is_likely_kernel_thread_pid_3_uid_0_suspicious_when_sharing_non_init() {
261        // PID 3, uid=0 but pid > 2 → NOT a kernel thread, shares with pid 100
262        // is_likely_kernel_thread(3) = false → falls through to !shared_with.is_empty()
263        assert!(classify_shared_creds(3, &[100], 0));
264    }
265
266    #[test]
267    fn classify_sharing_with_pid_1_uid_0_kernel_thread_benign() {
268        // pid=2 (kthreadd), uid=0, shares with pid=1 → benign (kernel cred)
269        assert!(!classify_shared_creds(2, &[1], 0));
270    }
271
272    #[test]
273    fn classify_sharing_with_pid_1_uid_0_non_kernel_thread_suspicious() {
274        // pid=100, uid=0, shares with init (pid=1)
275        // is_likely_kernel_thread(100) = false → suspicious
276        assert!(classify_shared_creds(100, &[1], 0));
277    }
278
279    #[test]
280    fn classify_uid_0_kernel_thread_no_sharing_benign() {
281        // uid=0, pid=2, no other shared PIDs → benign (kernel thread path)
282        assert!(!classify_shared_creds(2, &[], 0));
283    }
284
285    #[test]
286    fn classify_uid_0_non_kernel_thread_sharing_suspicious() {
287        // uid=0, pid=50 (not kernel thread), shares with pid 60
288        // Falls through to !shared_with.is_empty() → suspicious
289        assert!(classify_shared_creds(50, &[60], 0));
290    }
291
292    #[test]
293    fn classify_is_pid_1_self_not_suspicious() {
294        // PID 1 checking shared_with containing no pid 1
295        // shared_with=[500], uid=0, pid=1 → is_likely_kernel_thread(1)=true → benign
296        assert!(!classify_shared_creds(1, &[500], 0));
297    }
298
299    // ---------------------------------------------------------------
300    // SharedCredInfo: Clone + Debug + Serialize
301    // ---------------------------------------------------------------
302
303    #[test]
304    fn shared_cred_info_clone_debug_serialize() {
305        let info = SharedCredInfo {
306            pid: 42,
307            process_name: "evil".to_string(),
308            uid: 0,
309            cred_address: 0xDEAD_BEEF,
310            shared_with_pids: vec![1],
311            is_suspicious: true,
312        };
313        let cloned = info.clone();
314        assert_eq!(cloned.pid, 42);
315        let dbg = format!("{cloned:?}");
316        assert!(dbg.contains("evil"));
317        let json = serde_json::to_string(&cloned).unwrap();
318        assert!(json.contains("\"pid\":42"));
319        assert!(json.contains("\"is_suspicious\":true"));
320    }
321
322    // ---------------------------------------------------------------
323    // walk_check_creds: symbol present + self-pointing list (walk body runs)
324    // ---------------------------------------------------------------
325
326    #[test]
327    fn walk_check_creds_symbol_present_single_task_no_sharing() {
328        // init_task present, tasks self-pointing (only one process).
329        // With a single process in the cred map, group.len() < 2 → no results.
330        let sym_vaddr: u64 = 0xFFFF_8800_0090_0000;
331        let sym_paddr: u64 = 0x00A0_0000;
332        let tasks_offset = 16u64;
333
334        let mut page = [0u8; 4096];
335        // pid = 1
336        page[0..4].copy_from_slice(&1u32.to_le_bytes());
337        // tgid = 1
338        page[4..8].copy_from_slice(&1u32.to_le_bytes());
339        // tasks: self-pointing
340        let list_self = sym_vaddr + tasks_offset;
341        page[tasks_offset as usize..tasks_offset as usize + 8]
342            .copy_from_slice(&list_self.to_le_bytes());
343        page[tasks_offset as usize + 8..tasks_offset as usize + 16]
344            .copy_from_slice(&list_self.to_le_bytes());
345        // comm = "systemd"
346        page[32..39].copy_from_slice(b"systemd");
347        // cred pointer = some non-zero value (unique to this task)
348        let cred_ptr: u64 = 0xFFFF_8800_DEAD_0000;
349        page[96..104].copy_from_slice(&cred_ptr.to_le_bytes());
350
351        let isf = IsfBuilder::new()
352            .add_struct("task_struct", 256)
353            .add_field("task_struct", "pid", 0, "unsigned int")
354            .add_field("task_struct", "tgid", 4, "unsigned int")
355            .add_field("task_struct", "tasks", 16, "pointer")
356            .add_field("task_struct", "comm", 32, "char")
357            .add_field("task_struct", "cred", 96, "pointer")
358            .add_struct("cred", 64)
359            .add_field("cred", "uid", 4, "unsigned int")
360            .add_symbol("init_task", sym_vaddr)
361            .build_json();
362
363        let resolver = IsfResolver::from_value(&isf).unwrap();
364        let (cr3, mem) = PageTableBuilder::new()
365            .map_4k(sym_vaddr, sym_paddr, flags::WRITABLE)
366            .write_phys(sym_paddr, &page)
367            .build();
368        let vas = VirtualAddressSpace::new(mem, cr3, TranslationMode::X86_64FourLevel);
369        let reader = ObjectReader::new(vas, Box::new(resolver));
370
371        let result = walk_check_creds(&reader).unwrap_or_default();
372        assert!(
373            result.is_empty(),
374            "single task with unique cred should not be flagged"
375        );
376    }
377
378    // ---------------------------------------------------------------
379    // walk_check_creds: symbol + list_head present, self-pointing list
380    // Exercises the full walk body: init_task info collected, group.len()<2
381    // since there is only one task → no results.
382    // ---------------------------------------------------------------
383
384    #[test]
385    fn walk_check_creds_with_list_head_single_task_no_sharing() {
386        let sym_vaddr: u64 = 0xFFFF_8800_0010_0000;
387        let sym_paddr: u64 = 0x0010_0000; // < 16 MB
388        let tasks_offset: u64 = 16;
389
390        let mut page = [0u8; 4096];
391        // pid = 42
392        page[0..4].copy_from_slice(&42u32.to_le_bytes());
393        // tgid = 42
394        page[4..8].copy_from_slice(&42u32.to_le_bytes());
395        // tasks: self-pointing list_head
396        let self_ptr = sym_vaddr + tasks_offset;
397        page[tasks_offset as usize..tasks_offset as usize + 8]
398            .copy_from_slice(&self_ptr.to_le_bytes());
399        page[tasks_offset as usize + 8..tasks_offset as usize + 16]
400            .copy_from_slice(&self_ptr.to_le_bytes());
401        // comm = "init"
402        page[32..36].copy_from_slice(b"init");
403        // cred pointer (unique non-zero)
404        let cred_ptr: u64 = 0xFFFF_8800_CAFE_0000;
405        page[96..104].copy_from_slice(&cred_ptr.to_le_bytes());
406
407        let isf = IsfBuilder::new()
408            .add_struct("list_head", 0x10)
409            .add_field("list_head", "next", 0x00, "pointer")
410            .add_field("list_head", "prev", 0x08, "pointer")
411            .add_struct("task_struct", 256)
412            .add_field("task_struct", "pid", 0, "unsigned int")
413            .add_field("task_struct", "tgid", 4, "unsigned int")
414            .add_field("task_struct", "tasks", 16, "pointer")
415            .add_field("task_struct", "comm", 32, "char")
416            .add_field("task_struct", "cred", 96, "pointer")
417            .add_struct("cred", 64)
418            .add_field("cred", "uid", 4, "unsigned int")
419            .add_symbol("init_task", sym_vaddr)
420            .build_json();
421
422        let resolver = IsfResolver::from_value(&isf).unwrap();
423        let (cr3, mem) = PageTableBuilder::new()
424            .map_4k(sym_vaddr, sym_paddr, flags::WRITABLE)
425            .write_phys(sym_paddr, &page)
426            .build();
427        let vas = VirtualAddressSpace::new(mem, cr3, TranslationMode::X86_64FourLevel);
428        let reader = ObjectReader::new(vas, Box::new(resolver));
429
430        let result = walk_check_creds(&reader).unwrap();
431        // Only one task → group.len() < 2 for every cred_addr → no suspicious entries.
432        assert!(
433            result.is_empty(),
434            "single task cannot share creds with another"
435        );
436    }
437
438    // ---------------------------------------------------------------
439    // walk_check_creds: TWO tasks with same cred pointer but different TGIDs
440    // Exercises the cred-sharing detection logic (lines 121-185):
441    //   - by_tgid.len() >= 2 → cross-tgid sharing detected → uid read → results pushed
442    // ---------------------------------------------------------------
443
444    #[test]
445    fn walk_check_creds_two_tasks_share_cred_different_tgids_flagged() {
446        // Memory layout:
447        //   init_task  @ init_vaddr / init_paddr
448        //     pid=100, tgid=100, cred=cred_vaddr, tasks.next → t2 list node
449        //   task2      @ t2_vaddr   / t2_paddr
450        //     pid=200, tgid=200, cred=cred_vaddr  (SAME cred, different tgid → suspicious)
451        //     tasks.next → init_vaddr + tasks_offset  (wraps back)
452        //   cred       @ cred_vaddr / cred_paddr
453        //     uid=1000  (non-zero, so sharing is suspicious)
454
455        let tasks_offset: u64 = 0x10;
456        let pid_offset: u64 = 0x00;
457        let tgid_offset: u64 = 0x04;
458        let comm_offset: u64 = 0x20;
459        let cred_offset: u64 = 0x60;
460        let uid_cred_off: u64 = 0x04;
461
462        let init_vaddr: u64 = 0xFFFF_8800_0090_0000;
463        let init_paddr: u64 = 0x0090_0000;
464        let t2_vaddr: u64 = 0xFFFF_8800_0091_0000;
465        let t2_paddr: u64 = 0x0091_0000;
466        let cred_vaddr: u64 = 0xFFFF_8800_0092_0000;
467        let cred_paddr: u64 = 0x0092_0000;
468
469        // init_task page
470        let mut init_page = [0u8; 4096];
471        init_page[pid_offset as usize..pid_offset as usize + 4]
472            .copy_from_slice(&100u32.to_le_bytes());
473        init_page[tgid_offset as usize..tgid_offset as usize + 4]
474            .copy_from_slice(&100u32.to_le_bytes());
475        let t2_list_node = t2_vaddr + tasks_offset;
476        init_page[tasks_offset as usize..tasks_offset as usize + 8]
477            .copy_from_slice(&t2_list_node.to_le_bytes());
478        init_page[tasks_offset as usize + 8..tasks_offset as usize + 16]
479            .copy_from_slice(&t2_list_node.to_le_bytes()); // prev
480        init_page[comm_offset as usize..comm_offset as usize + 5].copy_from_slice(b"evil1");
481        init_page[cred_offset as usize..cred_offset as usize + 8]
482            .copy_from_slice(&cred_vaddr.to_le_bytes());
483
484        // task2 page
485        let mut t2_page = [0u8; 4096];
486        t2_page[pid_offset as usize..pid_offset as usize + 4]
487            .copy_from_slice(&200u32.to_le_bytes());
488        t2_page[tgid_offset as usize..tgid_offset as usize + 4]
489            .copy_from_slice(&200u32.to_le_bytes()); // different tgid
490        let init_list_node = init_vaddr + tasks_offset;
491        t2_page[tasks_offset as usize..tasks_offset as usize + 8]
492            .copy_from_slice(&init_list_node.to_le_bytes()); // wraps back to init
493        t2_page[tasks_offset as usize + 8..tasks_offset as usize + 16]
494            .copy_from_slice(&init_list_node.to_le_bytes());
495        t2_page[comm_offset as usize..comm_offset as usize + 5].copy_from_slice(b"evil2");
496        t2_page[cred_offset as usize..cred_offset as usize + 8]
497            .copy_from_slice(&cred_vaddr.to_le_bytes()); // SAME cred pointer
498
499        // cred page: uid=1000 at uid_cred_off
500        let mut cred_page = [0u8; 4096];
501        cred_page[uid_cred_off as usize..uid_cred_off as usize + 4]
502            .copy_from_slice(&1000u32.to_le_bytes());
503
504        let isf = IsfBuilder::new()
505            .add_symbol("init_task", init_vaddr)
506            .add_struct("list_head", 0x10)
507            .add_field("list_head", "next", 0x00u64, "pointer")
508            .add_field("list_head", "prev", 0x08u64, "pointer")
509            .add_struct("task_struct", 0x200)
510            .add_field("task_struct", "pid", pid_offset, "unsigned int")
511            .add_field("task_struct", "tgid", tgid_offset, "unsigned int")
512            .add_field("task_struct", "tasks", tasks_offset, "pointer")
513            .add_field("task_struct", "comm", comm_offset, "char")
514            .add_field("task_struct", "cred", cred_offset, "pointer")
515            .add_struct("cred", 0x80)
516            .add_field("cred", "uid", uid_cred_off, "unsigned int")
517            .build_json();
518
519        let resolver = IsfResolver::from_value(&isf).unwrap();
520        let (cr3, mem) = PageTableBuilder::new()
521            .map_4k(init_vaddr, init_paddr, flags::WRITABLE)
522            .write_phys(init_paddr, &init_page)
523            .map_4k(t2_vaddr, t2_paddr, flags::WRITABLE)
524            .write_phys(t2_paddr, &t2_page)
525            .map_4k(cred_vaddr, cred_paddr, flags::WRITABLE)
526            .write_phys(cred_paddr, &cred_page)
527            .build();
528
529        let vas = VirtualAddressSpace::new(mem, cr3, TranslationMode::X86_64FourLevel);
530        let reader: ObjectReader<SyntheticPhysMem> = ObjectReader::new(vas, Box::new(resolver));
531
532        let result = walk_check_creds(&reader).unwrap();
533        // Both tasks share the same cred across different TGIDs with uid=1000 → suspicious
534        assert!(
535            !result.is_empty(),
536            "cross-tgid cred sharing should produce suspicious entries"
537        );
538        // Both tasks should appear (each is suspicious since uid=1000, non-kernel-thread)
539        assert_eq!(result.len(), 2, "both tasks should be flagged");
540        for entry in &result {
541            assert!(entry.is_suspicious);
542            assert_eq!(entry.cred_address, cred_vaddr);
543            assert_eq!(entry.uid, 1000);
544        }
545    }
546
547    // ---------------------------------------------------------------
548    // walk_check_creds: missing tasks field → empty Vec (graceful degradation)
549    // ---------------------------------------------------------------
550
551    #[test]
552    fn walk_check_creds_missing_tasks_field_returns_empty() {
553        // init_task symbol present but task_struct.tasks field absent → graceful empty
554        let isf = IsfBuilder::new()
555            .add_struct("task_struct", 128)
556            .add_field("task_struct", "pid", 0, "int")
557            // No "tasks" field → field_offset returns None → return Ok(empty)
558            .add_symbol("init_task", 0xFFFF_8000_0010_0000)
559            .build_json();
560
561        let resolver = IsfResolver::from_value(&isf).unwrap();
562        let (cr3, mem) = PageTableBuilder::new().build();
563        let vas = VirtualAddressSpace::new(mem, cr3, TranslationMode::X86_64FourLevel);
564        let reader: ObjectReader<SyntheticPhysMem> = ObjectReader::new(vas, Box::new(resolver));
565
566        let result = walk_check_creds(&reader);
567        assert!(result.is_ok());
568        assert!(result.unwrap().is_empty());
569    }
570}