use crate::{
core::{
architecture::Architecture,
error::{Error, ErrorKind, Result},
virtual_memory_reader::VirtualMemoryReader,
},
memory::{primitives::RawVirtualAddress, readable::Readable, virtual_address::VirtualAddress},
operating_system::linux::{
utils::get_struct_member_byte_offset, virtual_struct::VirtualStruct, xarray::XArray,
},
};
use {btfparse::TypeInformation, log::error};
use std::{collections::BTreeSet, sync::Arc};
const MAX_PID_NS_LEVEL: u32 = 32;
const PIDTYPE_PID: usize = 0;
pub struct PidNsIterator<'a> {
memory_dump: Arc<dyn Readable>,
architecture: Arc<dyn Architecture>,
kernel_type_info: &'a TypeInformation,
pid_links_offset: u64,
namespace_level: u32,
pid_entries: Vec<VirtualAddress>,
current_index: usize,
visited_tasks: BTreeSet<RawVirtualAddress>,
}
impl<'a> PidNsIterator<'a> {
pub fn new(
memory_dump: Arc<dyn Readable>,
architecture: Arc<dyn Architecture>,
kernel_type_info: &'a TypeInformation,
pid_ns_vaddr: VirtualAddress,
) -> Result<Self> {
let task_struct_tid = kernel_type_info.id_of("task_struct").ok_or_else(|| {
Error::new(
ErrorKind::TypeInformationError,
"Failed to find task_struct type",
)
})?;
let pid_links_offset =
get_struct_member_byte_offset(kernel_type_info, task_struct_tid, "pid_links")?;
let vmem_reader = VirtualMemoryReader::new(memory_dump.as_ref(), architecture.as_ref());
let pid_ns = VirtualStruct::from_name(
&vmem_reader,
kernel_type_info,
"pid_namespace",
&pid_ns_vaddr,
)?;
let namespace_level = pid_ns.traverse("level")?.read_u32()?;
let idr_rt_vaddr = pid_ns.traverse("idr.idr_rt")?.virtual_address();
let xarray = XArray::new(
memory_dump.as_ref(),
architecture.as_ref(),
kernel_type_info,
idr_rt_vaddr,
)?;
let total_xarray_entries = xarray.entries().len();
let pid_entries: Vec<_> = xarray
.entries()
.iter()
.filter(|addr| addr.is_in_high_canonical_space())
.copied()
.collect();
if total_xarray_entries != pid_entries.len() {
error!(
"PidNsIterator: xarray returned {} entries, {} passed kernel space filter, namespace at {:?}",
total_xarray_entries,
pid_entries.len(),
pid_ns_vaddr
);
}
Ok(Self {
memory_dump,
architecture,
kernel_type_info,
pid_links_offset,
namespace_level,
pid_entries,
current_index: 0,
visited_tasks: BTreeSet::new(),
})
}
fn pid_to_task_vaddr(&self, pid_vaddr: VirtualAddress) -> Option<VirtualAddress> {
let vmem_reader =
VirtualMemoryReader::new(self.memory_dump.as_ref(), self.architecture.as_ref());
let pid_struct =
VirtualStruct::from_name(&vmem_reader, self.kernel_type_info, "pid", &pid_vaddr)
.ok()?;
let level = pid_struct.traverse("level").ok()?.read_u32().ok()?;
if level >= MAX_PID_NS_LEVEL {
error!(
"Invalid pid.level ({}) at {:?}, expected < {}",
level, pid_vaddr, MAX_PID_NS_LEVEL
);
return None;
}
if level < self.namespace_level {
error!(
"pid.level ({}) < namespace_level ({}) at {:?}, struct pid should not be in this namespace",
level, self.namespace_level, pid_vaddr
);
return None;
}
let tasks_hlist_head = pid_struct
.traverse(&format!("tasks[{PIDTYPE_PID}]"))
.ok()?
.read_vaddr()
.ok()?;
if tasks_hlist_head.is_null() {
error!("Null tasks hlist_head for pid at {:?}", pid_vaddr);
return None;
}
if !tasks_hlist_head.is_in_high_canonical_space() {
error!(
"tasks hlist_head {:?} not in kernel space for pid at {:?}",
tasks_hlist_head, pid_vaddr
);
return None;
}
let task_vaddr = tasks_hlist_head - self.pid_links_offset;
if !task_vaddr.is_in_high_canonical_space() {
error!(
"task_struct address {:?} not in kernel space for pid at {:?}",
task_vaddr, pid_vaddr
);
return None;
}
Some(task_vaddr)
}
}
impl<'a> Iterator for PidNsIterator<'a> {
type Item = VirtualAddress;
fn next(&mut self) -> Option<Self::Item> {
while self.current_index < self.pid_entries.len() {
let pid_vaddr = self.pid_entries[self.current_index];
self.current_index += 1;
if let Some(task_vaddr) = self.pid_to_task_vaddr(pid_vaddr)
&& self.visited_tasks.insert(task_vaddr.value())
{
return Some(task_vaddr);
}
}
None
}
}