memf_linux/
user_ns_escalation.rs1use memf_core::object_reader::ObjectReader;
4use memf_format::PhysicalMemoryProvider;
5
6use crate::types::UserNsEscalationInfo;
7use crate::Result;
8
9pub fn is_escalation_suspicious(uid_in_parent: u32, uid_in_ns: u32) -> bool {
17 uid_in_ns == 0 && uid_in_parent != 0
18}
19
20pub fn scan_user_ns_escalation<P: PhysicalMemoryProvider>(
24 reader: &ObjectReader<P>,
25) -> Result<Vec<UserNsEscalationInfo>> {
26 let _ = reader;
27 Ok(vec![])
28}
29
30#[cfg(test)]
31mod tests {
32 use super::*;
33 use memf_core::test_builders::PageTableBuilder;
34 use memf_core::vas::{TranslationMode, VirtualAddressSpace};
35 use memf_symbols::isf::IsfResolver;
36 use memf_symbols::test_builders::IsfBuilder;
37
38 fn make_minimal_reader() -> ObjectReader<memf_core::test_builders::SyntheticPhysMem> {
39 let isf = IsfBuilder::new().build_json();
40 let resolver = IsfResolver::from_value(&isf).unwrap();
41 let (cr3, mem) = PageTableBuilder::new().build();
42 let vas = VirtualAddressSpace::new(mem, cr3, TranslationMode::X86_64FourLevel);
43 ObjectReader::new(vas, Box::new(resolver))
44 }
45
46 #[test]
47 fn empty_memory_returns_ok_empty() {
48 let reader = make_minimal_reader();
49 let result = scan_user_ns_escalation(&reader);
50 assert!(result.is_ok(), "should succeed with minimal reader");
51 assert!(
52 result.unwrap().is_empty(),
53 "empty memory → no user ns escalation hits"
54 );
55 }
56
57 #[test]
58 fn result_is_vec_of_user_ns_escalation_info() {
59 let reader = make_minimal_reader();
60 let result: Result<Vec<UserNsEscalationInfo>> = scan_user_ns_escalation(&reader);
61 assert!(result.is_ok());
62 }
63
64 #[test]
65 fn user_ns_escalation_info_fields_constructible() {
66 let info = UserNsEscalationInfo {
67 pid: 4242,
68 comm: "unshare".to_string(),
69 ns_depth: 2,
70 owner_uid: 1000,
71 process_uid: 0,
72 has_cap_sys_admin: true,
73 is_suspicious: true,
74 };
75 assert_eq!(info.pid, 4242);
76 assert_eq!(info.ns_depth, 2);
77 assert_eq!(info.owner_uid, 1000);
78 assert_eq!(info.process_uid, 0);
79 assert!(info.has_cap_sys_admin);
80 assert!(info.is_suspicious);
81 }
82
83 #[test]
84 fn user_ns_escalation_info_serializes() {
85 let info = UserNsEscalationInfo {
86 pid: 99,
87 comm: "exploit".to_string(),
88 ns_depth: 3,
89 owner_uid: 1001,
90 process_uid: 0,
91 has_cap_sys_admin: true,
92 is_suspicious: true,
93 };
94 let json = serde_json::to_string(&info).unwrap();
95 assert!(json.contains("\"pid\":99"));
96 assert!(json.contains("\"ns_depth\":3"));
97 assert!(json.contains("\"has_cap_sys_admin\":true"));
98 assert!(json.contains("\"is_suspicious\":true"));
99 }
100
101 #[test]
104 fn non_root_in_parent_but_root_in_ns_is_suspicious_escalation() {
105 assert!(is_escalation_suspicious(1000, 0));
107 }
108
109 #[test]
110 fn root_in_both_is_not_suspicious() {
111 assert!(!is_escalation_suspicious(0, 0));
112 }
113
114 #[test]
115 fn non_root_in_both_is_not_suspicious() {
116 assert!(!is_escalation_suspicious(1000, 1000));
117 }
118}