use memf_core::object_reader::ObjectReader;
use memf_format::PhysicalMemoryProvider;
use crate::{ProcessInfo, Result};
#[derive(Debug, Clone, serde::Serialize)]
pub struct SeccompInfo {
pub pid: u64,
pub comm: String,
pub seccomp_mode: u8,
pub filter_count: u32,
pub is_unconfined: bool,
}
const MAX_FILTER_CHAIN: usize = 256;
pub fn walk_seccomp_profiles<P: PhysicalMemoryProvider>(
reader: &ObjectReader<P>,
processes: &[ProcessInfo],
) -> Result<Vec<SeccompInfo>> {
if processes.is_empty() {
return Ok(Vec::new());
}
let seccomp_offset = match reader.symbols().field_offset("task_struct", "seccomp") {
Some(off) => off,
None => return Ok(Vec::new()),
};
if reader.symbols().field_offset("seccomp", "mode").is_none() {
return Ok(Vec::new());
}
let filter_field_offset = reader.symbols().field_offset("seccomp", "filter");
let prev_field_offset = reader.symbols().field_offset("seccomp_filter", "prev");
let mut results = Vec::with_capacity(processes.len());
for proc in processes {
let seccomp_base = proc.vaddr + seccomp_offset;
let mode_raw: u32 = reader
.read_field(seccomp_base, "seccomp", "mode")
.unwrap_or(0);
let seccomp_mode = mode_raw as u8;
let filter_count = if seccomp_mode == 2 {
if let (Some(_filter_off), Some(_prev_off)) = (filter_field_offset, prev_field_offset) {
let filter_ptr: u64 = reader
.read_field(seccomp_base, "seccomp", "filter")
.unwrap_or(0);
count_filter_chain(reader, filter_ptr)
} else {
0
}
} else {
0
};
let is_unconfined = seccomp_mode == 0;
results.push(SeccompInfo {
pid: proc.pid,
comm: proc.comm.clone(),
seccomp_mode,
filter_count,
is_unconfined,
});
}
Ok(results)
}
fn count_filter_chain<P: PhysicalMemoryProvider>(
reader: &ObjectReader<P>,
first_filter: u64,
) -> u32 {
if first_filter == 0 {
return 0;
}
let mut count: u32 = 0;
let mut current = first_filter;
for _ in 0..MAX_FILTER_CHAIN {
if current == 0 {
break;
}
count += 1;
let prev: u64 = reader
.read_field(current, "seccomp_filter", "prev")
.unwrap_or(0);
current = prev;
}
count
}
#[cfg(test)]
mod tests {
use super::*;
use memf_core::test_builders::{flags, PageTableBuilder};
use memf_core::vas::{TranslationMode, VirtualAddressSpace};
use memf_symbols::isf::IsfResolver;
use memf_symbols::test_builders::IsfBuilder;
fn make_reader(
isf: &IsfBuilder,
ptb: PageTableBuilder,
) -> ObjectReader<memf_core::test_builders::SyntheticPhysMem> {
let json = isf.build_json();
let resolver = IsfResolver::from_value(&json).unwrap();
let (cr3, mem) = ptb.build();
let vas = VirtualAddressSpace::new(mem, cr3, TranslationMode::X86_64FourLevel);
ObjectReader::new(vas, Box::new(resolver))
}
fn fake_process(pid: u64, comm: &str, vaddr: u64) -> ProcessInfo {
ProcessInfo {
pid,
ppid: 1,
comm: comm.to_string(),
state: crate::types::ProcessState::Running,
vaddr,
cr3: None,
start_time: 0,
}
}
#[test]
fn walk_seccomp_empty() {
let isf = IsfBuilder::new();
let ptb = PageTableBuilder::new();
let reader = make_reader(&isf, ptb);
let result = walk_seccomp_profiles(&reader, &[]).unwrap();
assert!(
result.is_empty(),
"expected empty vec for empty process list"
);
}
#[test]
fn seccomp_mode_disabled() {
let task_vaddr: u64 = 0xFFFF_8000_0010_0000;
let task_paddr: u64 = 0x0080_0000;
let seccomp_offset: u64 = 2048;
let isf = IsfBuilder::new()
.add_struct("task_struct", 9024)
.add_field("task_struct", "seccomp", seccomp_offset, "seccomp")
.add_struct("seccomp", 16)
.add_field("seccomp", "mode", 0, "int")
.add_field("seccomp", "filter", 8, "pointer");
let ptb = PageTableBuilder::new()
.map_4k(task_vaddr, task_paddr, flags::WRITABLE)
.write_phys_u64(task_paddr + seccomp_offset, 0u64);
let reader = make_reader(&isf, ptb);
let procs = vec![fake_process(100, "nginx", task_vaddr)];
let result = walk_seccomp_profiles(&reader, &procs).unwrap();
assert_eq!(result.len(), 1);
assert_eq!(result[0].pid, 100);
assert_eq!(result[0].seccomp_mode, 0);
assert!(result[0].is_unconfined, "mode 0 should be unconfined");
assert_eq!(result[0].filter_count, 0);
}
#[test]
fn seccomp_mode_filter() {
let task_vaddr: u64 = 0xFFFF_8000_0020_0000;
let task_paddr: u64 = 0x0040_0000;
let filter_vaddr: u64 = 0xFFFF_8000_0030_0000;
let filter_paddr: u64 = 0x0060_0000;
let seccomp_offset: u64 = 2048;
let isf = IsfBuilder::new()
.add_struct("task_struct", 9024)
.add_field("task_struct", "seccomp", seccomp_offset, "seccomp")
.add_struct("seccomp", 16)
.add_field("seccomp", "mode", 0, "int")
.add_field("seccomp", "filter", 8, "pointer")
.add_struct("seccomp_filter", 16)
.add_field("seccomp_filter", "prev", 0, "pointer");
let ptb = PageTableBuilder::new()
.map_4k(task_vaddr, task_paddr, flags::WRITABLE)
.map_4k(filter_vaddr, filter_paddr, flags::WRITABLE)
.write_phys_u64(task_paddr + seccomp_offset, 2u64)
.write_phys_u64(task_paddr + seccomp_offset + 8, filter_vaddr)
.write_phys_u64(filter_paddr, 0u64);
let reader = make_reader(&isf, ptb);
let procs = vec![fake_process(200, "containerd", task_vaddr)];
let result = walk_seccomp_profiles(&reader, &procs).unwrap();
assert_eq!(result.len(), 1);
assert_eq!(result[0].pid, 200);
assert_eq!(result[0].comm, "containerd");
assert_eq!(result[0].seccomp_mode, 2);
assert!(!result[0].is_unconfined, "mode 2 should not be unconfined");
assert_eq!(result[0].filter_count, 1);
}
}