#![forbid(unsafe_code)]
use std::{
sync::{
atomic::{AtomicBool, AtomicI32},
Arc, OnceLock,
},
thread::Thread,
};
use ahash::HashMapExt;
use concurrent_queue::ConcurrentQueue;
use libseccomp::ScmpSyscall;
use nix::{errno::Errno, sys::socket::UnixAddr, unistd::Pid};
use serde::{ser::SerializeMap, Serializer};
use crate::{
config::{HASH_CACHE, PTRACE_RESP_CAPACITY, SYSBLOCK_CAPACITY, SYSQUEUE_CAPACITY},
confine::{ScmpNotifReq, SydArch, SydNotifReq, SydNotifResp},
expiry::ExpiringMap,
fd::SafeOwnedFd,
hash::{hash_pipe, SydHashMap, SydRandomState},
kernel::ptrace::mmap::MmapSyscall,
lookup::FileInfo,
path::XPathBuf,
sigset::SydSigSet,
};
pub(crate) type SysNotif = Arc<ConcurrentQueue<SydNotifReq>>;
pub(crate) type SysQueue = Arc<ConcurrentQueue<SydNotifReq>>;
pub(crate) type PtraceRespQueue = Arc<ConcurrentQueue<SydNotifResp>>;
#[derive(Debug)]
pub(crate) struct SysInterrupt {
pub(crate) handler: Pid,
pub(crate) tgid: Pid,
pub(crate) request: ScmpNotifReq,
pub(crate) status: Option<SafeOwnedFd>,
pub(crate) delete: bool,
pub(crate) signal: bool,
pub(crate) ignore_restart: bool,
}
pub(crate) type RestartMap = scc::HashMap<Pid, SydSigSet, SydRandomState>;
#[derive(Debug)]
pub(crate) struct SysInterruptMap {
pub(crate) sys_queue: Arc<ConcurrentQueue<SysInterrupt>>,
pub(crate) sys_delete: Arc<ConcurrentQueue<u64>>,
pub(crate) sys_signal: Arc<AtomicBool>,
pub(crate) int_thread: Arc<OnceLock<Thread>>,
pub(crate) not_tid: Arc<AtomicI32>,
pub(crate) sig_restart: Arc<RestartMap>,
}
pub(crate) type ErrorMap = scc::HashMap<Pid, Option<Errno>, SydRandomState>;
#[derive(Clone, Copy, Debug)]
pub(crate) struct ChdirEntry {
#[cfg_attr(not(feature = "kcov"), expect(dead_code))]
pub(crate) data: u16,
pub(crate) info: FileInfo,
}
pub(crate) type ChdirMap = scc::HashMap<Pid, ChdirEntry, SydRandomState>;
pub(crate) type MmapMap = scc::HashMap<Pid, MmapSyscall, SydRandomState>;
#[derive(Clone)]
pub(crate) struct UnixVal {
pub(crate) pid: Pid,
pub(crate) addr: Option<UnixAddr>,
pub(crate) peer: Option<UnixAddr>,
pub(crate) dest: Vec<(u32, u32)>,
}
impl Default for UnixVal {
fn default() -> Self {
Self {
pid: Pid::from_raw(0),
addr: None,
peer: None,
dest: Vec::new(),
}
}
}
pub(crate) type UnixMap = Arc<scc::HashMap<u64, UnixVal, SydRandomState>>;
pub(crate) type PtraceMap = Arc<scc::HashMap<Pid, Pid, SydRandomState>>;
pub(crate) type SegvGuardExpiryMap = Arc<ExpiringMap<XPathBuf, u8>>;
pub(crate) type SegvGuardSuspensionSet = Arc<ExpiringMap<XPathBuf, ()>>;
#[derive(Debug)]
pub(crate) struct SysResultMap {
pub(crate) trace_error: Arc<ErrorMap>,
pub(crate) trace_chdir: Arc<ChdirMap>,
pub(crate) trace_mmap: Arc<MmapMap>,
}
pub(crate) const SIG_NEST_MAX: usize = 128;
pub(crate) const SIG_NEST_DEEP: usize = 2;
#[derive(Clone, Copy, Debug, PartialEq, Eq)]
pub(crate) struct SigreturnTrampolineIP {
pub(crate) lo: u64,
pub(crate) hi: u64,
}
impl SigreturnTrampolineIP {
pub(crate) const DISTANCE: u64 = 16;
#[expect(clippy::arithmetic_side_effects)]
pub(crate) fn matches(self, ip: u64) -> bool {
let lo_ok = ip >= self.lo && ip - self.lo <= Self::DISTANCE;
let hi_ok = self.hi != self.lo && ip >= self.hi && ip - self.hi <= Self::DISTANCE;
lo_ok || hi_ok
}
}
#[derive(Clone, Debug)]
pub(crate) struct SighandleInfo {
pub(crate) depth: u8,
pub(crate) frames: [Option<()>; SIG_NEST_MAX],
pub(crate) in_sigreturn: bool,
pub(crate) in_singlestep: bool,
pub(crate) trampoline_ip: Option<SigreturnTrampolineIP>,
}
pub(crate) type SighandleMap = scc::HashMap<Pid, SighandleInfo, SydRandomState>;
#[derive(Debug)]
pub(crate) struct SignalMap {
pub(crate) sig_handle: Arc<SighandleMap>,
}
impl SysInterrupt {
pub(crate) fn new(
request: ScmpNotifReq,
handler: Pid,
tgid: Pid,
ignore_restart: bool,
) -> Result<Self, Errno> {
Ok(Self {
handler,
tgid,
request,
ignore_restart,
status: None,
delete: false,
signal: false,
})
}
pub(crate) fn delete(&mut self) -> bool {
if self.status.is_some() {
self.delete = true;
true } else {
false }
}
}
impl serde::Serialize for SysInterrupt {
fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
where
S: Serializer,
{
let mut map = serializer.serialize_map(Some(6))?;
let data = &self.request.data;
let syscall = ScmpSyscall::get_name_by_arch(data.syscall, data.arch)
.unwrap_or_else(|_| format!("{}", i32::from(data.syscall)));
let _ = map.serialize_entry("pid", &self.request.pid);
let _ = map.serialize_entry("sys", &syscall);
let _ = map.serialize_entry("arch", &SydArch::from(data.arch));
let _ = map.serialize_entry("args", &data.args);
let _ = map.serialize_entry("handler", &self.handler.as_raw());
let _ = map.serialize_entry("ignore_restart", &self.ignore_restart);
map.end()
}
}
pub(crate) fn unix_map_new() -> UnixMap {
Arc::new(scc::HashMap::with_hasher(SydRandomState::new()))
}
pub(crate) fn ptrace_map_new() -> PtraceMap {
Arc::new(scc::HashMap::with_hasher(SydRandomState::new()))
}
pub(crate) fn sys_interrupt_map_new() -> SysInterruptMap {
SysInterruptMap {
sys_queue: Arc::new(ConcurrentQueue::bounded(SYSBLOCK_CAPACITY)),
sys_delete: Arc::new(ConcurrentQueue::bounded(SYSBLOCK_CAPACITY)),
sys_signal: Arc::new(AtomicBool::new(false)),
int_thread: Arc::new(OnceLock::new()),
not_tid: Arc::new(AtomicI32::new(0)),
sig_restart: Arc::new(scc::HashMap::with_hasher(SydRandomState::new())),
}
}
pub(crate) fn sys_result_map_new() -> SysResultMap {
SysResultMap {
trace_error: Arc::new(scc::HashMap::with_hasher(SydRandomState::new())),
trace_chdir: Arc::new(scc::HashMap::with_hasher(SydRandomState::new())),
trace_mmap: Arc::new(scc::HashMap::with_hasher(SydRandomState::new())),
}
}
pub(crate) fn signal_map_new() -> SignalMap {
SignalMap {
sig_handle: Arc::new(scc::HashMap::with_hasher(SydRandomState::new())),
}
}
pub(crate) fn sys_queue_new() -> (SysNotif, SysQueue) {
let queue = Arc::new(ConcurrentQueue::bounded(SYSQUEUE_CAPACITY));
(Arc::clone(&queue), queue)
}
pub(crate) fn ptrace_resp_queue_new() -> PtraceRespQueue {
Arc::new(ConcurrentQueue::bounded(PTRACE_RESP_CAPACITY))
}
pub(crate) struct HashCache {
map: SydHashMap<String, Result<Vec<u8>, Errno>>,
}
impl HashCache {
pub(crate) fn new() -> Self {
Self {
map: SydHashMap::new(),
}
}
fn probe(&mut self, alg: &str) -> &Result<Vec<u8>, Errno> {
if !self.map.contains_key(alg) {
let result = hash_pipe(alg, None::<SafeOwnedFd>);
self.map.insert(alg.to_string(), result);
}
&self.map[alg]
}
pub(crate) fn is_supported(alg: &str) -> bool {
HASH_CACHE
.lock()
.unwrap_or_else(|err| err.into_inner())
.probe(alg)
.is_ok()
}
pub(crate) fn is_valid_checksum(alg: &str, key: &[u8]) -> bool {
match HASH_CACHE
.lock()
.unwrap_or_else(|err| err.into_inner())
.probe(alg)
{
Ok(sum) => key.len() == sum.len() && key != sum.as_slice(),
Err(_) => false,
}
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_unix_map_new() {
let map = unix_map_new();
assert!(map.is_empty());
}
#[test]
fn test_ptrace_map_new() {
let map = ptrace_map_new();
assert!(map.is_empty());
}
#[test]
fn test_sys_interrupt_map_new() {
let map = sys_interrupt_map_new();
assert!(map.sys_queue.is_empty());
assert!(map.sys_delete.is_empty());
assert!(!map.sys_signal.load(std::sync::atomic::Ordering::Relaxed));
assert!(map.sig_restart.is_empty());
}
#[test]
fn test_sys_result_map_new() {
let map = sys_result_map_new();
assert!(map.trace_error.is_empty());
assert!(map.trace_chdir.is_empty());
assert!(map.trace_mmap.is_empty());
}
#[test]
fn test_signal_map_new() {
let map = signal_map_new();
assert!(map.sig_handle.is_empty());
}
#[test]
fn test_hash_cache_1() {
let cache = HashCache::new();
assert!(cache.map.is_empty());
}
#[test]
fn test_hash_cache_2() {
if HashCache::is_supported("sha256") {
assert!(HashCache::is_supported("sha256"));
} else {
eprintln!("sha256 not supported by kernel, skipping.");
}
}
#[test]
fn test_hash_cache_3() {
assert!(!HashCache::is_supported("Pink Floyd"));
}
#[test]
fn test_hash_cache_4() {
assert!(!HashCache::is_valid_checksum("Pink Floyd", &[0u8; 32]));
if !HashCache::is_supported("sha256") {
eprintln!("sha256 not available, skipping checksum tests.");
return;
}
assert!(!HashCache::is_valid_checksum("sha256", &[0u8; 16]));
let empty = HASH_CACHE
.lock()
.unwrap()
.probe("sha256")
.as_ref()
.unwrap()
.clone();
assert!(!HashCache::is_valid_checksum("sha256", &empty));
let mut valid = vec![0xffu8; 32];
valid[0] ^= 0x01;
assert!(HashCache::is_valid_checksum("sha256", &valid));
}
#[test]
fn test_hash_cache_5() {
let first = {
HASH_CACHE
.lock()
.unwrap_or_else(|err| err.into_inner())
.probe("sha256")
.clone()
};
let second = {
HASH_CACHE
.lock()
.unwrap_or_else(|err| err.into_inner())
.probe("sha256")
.clone()
};
match (&first, &second) {
(Ok(a), Ok(b)) => assert_eq!(a, b),
(Err(a), Err(b)) => assert_eq!(a, b),
_ => panic!("probe returned different Result variants"),
}
}
}