1use memf_core::object_reader::ObjectReader;
8use memf_format::PhysicalMemoryProvider;
9
10use crate::{ProcessInfo, Result};
11
12#[derive(Debug, Clone, serde::Serialize)]
14pub struct NamespaceInfo {
15 pub pid: u64,
17 pub image_name: String,
19 pub uts_ns_addr: u64,
21 pub pid_ns_addr: u64,
23 pub net_ns_addr: u64,
25 pub mnt_ns_addr: u64,
27 pub ipc_ns_addr: u64,
29 pub cgroup_ns_addr: u64,
31 pub is_root_ns: bool,
37}
38
39pub fn walk_namespaces<P: PhysicalMemoryProvider>(
48 reader: &ObjectReader<P>,
49 processes: &[ProcessInfo],
50) -> Result<Vec<NamespaceInfo>> {
51 if processes.is_empty() {
52 return Ok(Vec::new());
53 }
54
55 let mut results = Vec::with_capacity(processes.len());
56
57 for proc in processes {
59 if let Ok(ns) = read_namespace_info(reader, proc) {
60 results.push(ns);
61 }
62 }
63
64 let root_ns = results
68 .iter()
69 .find(|n| n.pid == 1)
70 .or_else(|| results.first());
71
72 if let Some(root) = root_ns {
73 let root_uts = root.uts_ns_addr;
74 let root_ipc = root.ipc_ns_addr;
75 let root_mnt = root.mnt_ns_addr;
76 let root_pid = root.pid_ns_addr;
77 let root_net = root.net_ns_addr;
78 let root_cgroup = root.cgroup_ns_addr;
79
80 for ns in &mut results {
81 ns.is_root_ns = ns.uts_ns_addr == root_uts
82 && ns.ipc_ns_addr == root_ipc
83 && ns.mnt_ns_addr == root_mnt
84 && ns.pid_ns_addr == root_pid
85 && ns.net_ns_addr == root_net
86 && ns.cgroup_ns_addr == root_cgroup;
87 }
88 }
89
90 Ok(results)
91}
92
93fn read_namespace_info<P: PhysicalMemoryProvider>(
95 reader: &ObjectReader<P>,
96 proc: &ProcessInfo,
97) -> Result<NamespaceInfo> {
98 let nsproxy_ptr: u64 = reader.read_pointer(proc.vaddr, "task_struct", "nsproxy")?;
99
100 if nsproxy_ptr == 0 {
101 return Err(crate::Error::WalkFailed {
102 walker: "read_namespace_info",
103 reason: format!("PID {} has null nsproxy (zombie/dead process)", proc.pid),
104 });
105 }
106
107 let uts_ns_addr: u64 = reader.read_pointer(nsproxy_ptr, "nsproxy", "uts_ns")?;
108 let ipc_ns_addr: u64 = reader.read_pointer(nsproxy_ptr, "nsproxy", "ipc_ns")?;
109 let mnt_ns_addr: u64 = reader.read_pointer(nsproxy_ptr, "nsproxy", "mnt_ns")?;
110 let pid_ns_addr: u64 = reader.read_pointer(nsproxy_ptr, "nsproxy", "pid_ns_for_children")?;
111 let net_ns_addr: u64 = reader.read_pointer(nsproxy_ptr, "nsproxy", "net_ns")?;
112 let cgroup_ns_addr: u64 = reader.read_pointer(nsproxy_ptr, "nsproxy", "cgroup_ns")?;
113
114 Ok(NamespaceInfo {
115 pid: proc.pid,
116 image_name: proc.comm.clone(),
117 uts_ns_addr,
118 pid_ns_addr,
119 net_ns_addr,
120 mnt_ns_addr,
121 ipc_ns_addr,
122 cgroup_ns_addr,
123 is_root_ns: false, })
125}
126
127#[cfg(test)]
128mod tests {
129 use super::*;
130 use memf_core::test_builders::{flags, PageTableBuilder};
131 use memf_core::vas::{TranslationMode, VirtualAddressSpace};
132 use memf_symbols::isf::IsfResolver;
133 use memf_symbols::test_builders::IsfBuilder;
134
135 use crate::ProcessState;
136
137 const TASK_SIZE: u64 = 160;
152 const NSPROXY_SIZE: u64 = 64;
153
154 const NSPROXY_UTS_OFF: usize = 0;
156 const NSPROXY_IPC_OFF: usize = 8;
157 const NSPROXY_MNT_OFF: usize = 16;
158 const NSPROXY_PID_OFF: usize = 24;
159 const NSPROXY_NET_OFF: usize = 32;
160 const NSPROXY_CGROUP_OFF: usize = 40;
161
162 const TASK_PID_OFF: usize = 0;
164 const TASK_COMM_OFF: usize = 4;
165 const TASK_NSPROXY_OFF: usize = 24;
166
167 fn build_isf() -> serde_json::Value {
168 IsfBuilder::new()
169 .add_struct("task_struct", TASK_SIZE)
170 .add_field("task_struct", "pid", TASK_PID_OFF as u64, "int")
171 .add_field("task_struct", "comm", TASK_COMM_OFF as u64, "char")
172 .add_field("task_struct", "nsproxy", TASK_NSPROXY_OFF as u64, "pointer")
173 .add_struct("nsproxy", NSPROXY_SIZE)
174 .add_field("nsproxy", "uts_ns", NSPROXY_UTS_OFF as u64, "pointer")
175 .add_field("nsproxy", "ipc_ns", NSPROXY_IPC_OFF as u64, "pointer")
176 .add_field("nsproxy", "mnt_ns", NSPROXY_MNT_OFF as u64, "pointer")
177 .add_field(
178 "nsproxy",
179 "pid_ns_for_children",
180 NSPROXY_PID_OFF as u64,
181 "pointer",
182 )
183 .add_field("nsproxy", "net_ns", NSPROXY_NET_OFF as u64, "pointer")
184 .add_field("nsproxy", "cgroup_ns", NSPROXY_CGROUP_OFF as u64, "pointer")
185 .build_json()
186 }
187
188 fn write_task(
190 ptb: PageTableBuilder,
191 vaddr: u64,
192 paddr: u64,
193 pid: u32,
194 comm: &str,
195 nsproxy_vaddr: u64,
196 ) -> PageTableBuilder {
197 let mut data = vec![0u8; TASK_SIZE as usize];
198 data[TASK_PID_OFF..TASK_PID_OFF + 4].copy_from_slice(&pid.to_le_bytes());
199
200 let comm_bytes = comm.as_bytes();
201 let len = comm_bytes.len().min(15);
202 data[TASK_COMM_OFF..TASK_COMM_OFF + len].copy_from_slice(&comm_bytes[..len]);
203 data[TASK_COMM_OFF + len] = 0; data[TASK_NSPROXY_OFF..TASK_NSPROXY_OFF + 8].copy_from_slice(&nsproxy_vaddr.to_le_bytes());
206
207 ptb.map_4k(vaddr, paddr, flags::WRITABLE)
208 .write_phys(paddr, &data)
209 }
210
211 #[allow(clippy::too_many_arguments)] fn write_nsproxy(
214 ptb: PageTableBuilder,
215 vaddr: u64,
216 paddr: u64,
217 uts: u64,
218 ipc: u64,
219 mnt: u64,
220 pid_ns: u64,
221 net: u64,
222 cgroup: u64,
223 ) -> PageTableBuilder {
224 let mut data = vec![0u8; NSPROXY_SIZE as usize];
225 data[NSPROXY_UTS_OFF..NSPROXY_UTS_OFF + 8].copy_from_slice(&uts.to_le_bytes());
226 data[NSPROXY_IPC_OFF..NSPROXY_IPC_OFF + 8].copy_from_slice(&ipc.to_le_bytes());
227 data[NSPROXY_MNT_OFF..NSPROXY_MNT_OFF + 8].copy_from_slice(&mnt.to_le_bytes());
228 data[NSPROXY_PID_OFF..NSPROXY_PID_OFF + 8].copy_from_slice(&pid_ns.to_le_bytes());
229 data[NSPROXY_NET_OFF..NSPROXY_NET_OFF + 8].copy_from_slice(&net.to_le_bytes());
230 data[NSPROXY_CGROUP_OFF..NSPROXY_CGROUP_OFF + 8].copy_from_slice(&cgroup.to_le_bytes());
231
232 ptb.map_4k(vaddr, paddr, flags::WRITABLE)
233 .write_phys(paddr, &data)
234 }
235
236 fn make_process(pid: u64, comm: &str, vaddr: u64) -> ProcessInfo {
237 ProcessInfo {
238 pid,
239 ppid: 0,
240 comm: comm.to_string(),
241 state: ProcessState::Running,
242 vaddr,
243 cr3: None,
244 start_time: 0,
245 }
246 }
247
248 #[test]
249 fn walk_namespaces_empty() {
250 let isf = build_isf();
252 let resolver = IsfResolver::from_value(&isf).unwrap();
253 let (cr3, mem) = PageTableBuilder::new().build();
254 let vas = VirtualAddressSpace::new(mem, cr3, TranslationMode::X86_64FourLevel);
255 let reader = ObjectReader::new(vas, Box::new(resolver));
256
257 let result = walk_namespaces(&reader, &[]).unwrap();
258 assert!(result.is_empty());
259 }
260
261 #[test]
262 fn walk_namespaces_root_ns() {
263 let isf = build_isf();
265 let resolver = IsfResolver::from_value(&isf).unwrap();
266
267 let task1_vaddr: u64 = 0xFFFF_8000_0010_0000;
269 let task1_paddr: u64 = 0x0010_0000; let nsproxy1_vaddr: u64 = 0xFFFF_8000_0020_0000;
272 let nsproxy1_paddr: u64 = 0x0020_0000; let root_uts: u64 = 0xFFFF_8000_00A0_0000;
276 let root_ipc: u64 = 0xFFFF_8000_00A1_0000;
277 let root_mnt: u64 = 0xFFFF_8000_00A2_0000;
278 let root_pid: u64 = 0xFFFF_8000_00A3_0000;
279 let root_net: u64 = 0xFFFF_8000_00A4_0000;
280 let root_cgroup: u64 = 0xFFFF_8000_00A5_0000;
281
282 let ptb = PageTableBuilder::new();
283 let ptb = write_task(ptb, task1_vaddr, task1_paddr, 1, "systemd", nsproxy1_vaddr);
284 let ptb = write_nsproxy(
285 ptb,
286 nsproxy1_vaddr,
287 nsproxy1_paddr,
288 root_uts,
289 root_ipc,
290 root_mnt,
291 root_pid,
292 root_net,
293 root_cgroup,
294 );
295
296 let (cr3, mem) = ptb.build();
297 let vas = VirtualAddressSpace::new(mem, cr3, TranslationMode::X86_64FourLevel);
298 let reader = ObjectReader::new(vas, Box::new(resolver));
299
300 let procs = vec![make_process(1, "systemd", task1_vaddr)];
301 let result = walk_namespaces(&reader, &procs).unwrap();
302
303 assert_eq!(result.len(), 1);
304 let ns = &result[0];
305 assert_eq!(ns.pid, 1);
306 assert_eq!(ns.image_name, "systemd");
307 assert_eq!(ns.uts_ns_addr, root_uts);
308 assert_eq!(ns.pid_ns_addr, root_pid);
309 assert_eq!(ns.net_ns_addr, root_net);
310 assert_eq!(ns.mnt_ns_addr, root_mnt);
311 assert_eq!(ns.ipc_ns_addr, root_ipc);
312 assert_eq!(ns.cgroup_ns_addr, root_cgroup);
313 assert!(ns.is_root_ns, "PID 1 must be in root namespace");
314 }
315
316 #[test]
317 fn walk_namespaces_container() {
318 let isf = build_isf();
321 let resolver = IsfResolver::from_value(&isf).unwrap();
322
323 let task1_vaddr: u64 = 0xFFFF_8000_0010_0000;
325 let task1_paddr: u64 = 0x0010_0000;
326
327 let nsproxy1_vaddr: u64 = 0xFFFF_8000_0020_0000;
328 let nsproxy1_paddr: u64 = 0x0020_0000;
329
330 let task2_vaddr: u64 = 0xFFFF_8000_0030_0000;
332 let task2_paddr: u64 = 0x0030_0000;
333
334 let nsproxy2_vaddr: u64 = 0xFFFF_8000_0040_0000;
335 let nsproxy2_paddr: u64 = 0x0040_0000;
336
337 let root_uts: u64 = 0xFFFF_8000_00A0_0000;
339 let root_ipc: u64 = 0xFFFF_8000_00A1_0000;
340 let root_mnt: u64 = 0xFFFF_8000_00A2_0000;
341 let root_pid: u64 = 0xFFFF_8000_00A3_0000;
342 let root_net: u64 = 0xFFFF_8000_00A4_0000;
343 let root_cgroup: u64 = 0xFFFF_8000_00A5_0000;
344
345 let container_net: u64 = 0xFFFF_8000_00B0_0000;
347
348 let ptb = PageTableBuilder::new();
349
350 let ptb = write_task(ptb, task1_vaddr, task1_paddr, 1, "systemd", nsproxy1_vaddr);
352 let ptb = write_nsproxy(
353 ptb,
354 nsproxy1_vaddr,
355 nsproxy1_paddr,
356 root_uts,
357 root_ipc,
358 root_mnt,
359 root_pid,
360 root_net,
361 root_cgroup,
362 );
363
364 let ptb = write_task(ptb, task2_vaddr, task2_paddr, 42, "nginx", nsproxy2_vaddr);
366 let ptb = write_nsproxy(
367 ptb,
368 nsproxy2_vaddr,
369 nsproxy2_paddr,
370 root_uts,
371 root_ipc,
372 root_mnt,
373 root_pid,
374 container_net, root_cgroup,
376 );
377
378 let (cr3, mem) = ptb.build();
379 let vas = VirtualAddressSpace::new(mem, cr3, TranslationMode::X86_64FourLevel);
380 let reader = ObjectReader::new(vas, Box::new(resolver));
381
382 let procs = vec![
383 make_process(1, "systemd", task1_vaddr),
384 make_process(42, "nginx", task2_vaddr),
385 ];
386
387 let result = walk_namespaces(&reader, &procs).unwrap();
388
389 assert_eq!(result.len(), 2);
390
391 let init_ns = result.iter().find(|n| n.pid == 1).unwrap();
393 assert!(init_ns.is_root_ns, "PID 1 must be in root namespace");
394 assert_eq!(init_ns.net_ns_addr, root_net);
395
396 let container_ns = result.iter().find(|n| n.pid == 42).unwrap();
398 assert!(
399 !container_ns.is_root_ns,
400 "Container process must not be in root namespace"
401 );
402 assert_eq!(container_ns.net_ns_addr, container_net);
403 assert_eq!(container_ns.image_name, "nginx");
404
405 assert_eq!(container_ns.uts_ns_addr, root_uts);
407 assert_eq!(container_ns.ipc_ns_addr, root_ipc);
408 assert_eq!(container_ns.mnt_ns_addr, root_mnt);
409 assert_eq!(container_ns.pid_ns_addr, root_pid);
410 assert_eq!(container_ns.cgroup_ns_addr, root_cgroup);
411 }
412}