#![forbid(unsafe_code)]
use std::sync::{Arc, Condvar, Mutex, RwLock};
use ahash::HashMapExt;
use libc::c_long;
use libseccomp::ScmpSyscall;
use nix::{errno::Errno, sys::socket::UnixAddr, unistd::Pid};
use serde::{ser::SerializeMap, Serializer};
use crate::{
config::HASH_CACHE,
confine::{ScmpNotifReq, SydArch},
fd::SafeOwnedFd,
hash::{hash_pipe, SydHashMap},
sigset::SydSigSet,
};
#[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 BlockVec = Vec<SysInterrupt>;
pub(crate) type RestartMap = SydHashMap<Pid, SydSigSet>;
#[derive(Debug)]
pub(crate) struct SysInterruptMap {
pub(crate) sys_block: Arc<(Mutex<BlockVec>, Condvar)>,
pub(crate) sig_restart: Arc<Mutex<RestartMap>>,
}
pub(crate) type ErrorMap = SydHashMap<Pid, Option<Errno>>;
pub(crate) type ChdirMap = SydHashMap<Pid, c_long>;
pub(crate) type MmapMap = SydHashMap<Pid, (c_long, [u64; 6])>;
#[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<RwLock<SydHashMap<u64, UnixVal>>>;
pub(crate) type PtraceMap = Arc<RwLock<SydHashMap<Pid, Pid>>>;
#[derive(Debug)]
pub(crate) struct SysResultMap {
pub(crate) trace_error: Arc<Mutex<ErrorMap>>,
pub(crate) trace_chdir: Arc<Mutex<ChdirMap>>,
pub(crate) trace_mmap: Arc<Mutex<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 = SydHashMap<Pid, SighandleInfo>;
#[derive(Debug)]
pub(crate) struct SignalMap {
pub(crate) sig_handle: Arc<Mutex<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(RwLock::new(SydHashMap::default()))
}
pub(crate) fn ptrace_map_new() -> PtraceMap {
Arc::new(RwLock::new(SydHashMap::default()))
}
pub(crate) fn sys_interrupt_map_new() -> SysInterruptMap {
SysInterruptMap {
sys_block: Arc::new((Mutex::new(BlockVec::new()), Condvar::new())),
sig_restart: Arc::new(Mutex::new(RestartMap::new())),
}
}
pub(crate) fn sys_result_map_new() -> SysResultMap {
SysResultMap {
trace_error: Arc::new(Mutex::new(ErrorMap::new())),
trace_chdir: Arc::new(Mutex::new(ChdirMap::new())),
trace_mmap: Arc::new(Mutex::new(MmapMap::new())),
}
}
pub(crate) fn signal_map_new() -> SignalMap {
SignalMap {
sig_handle: Arc::new(Mutex::new(SighandleMap::new())),
}
}
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.read().unwrap().is_empty());
}
#[test]
fn test_ptrace_map_new() {
let map = ptrace_map_new();
assert!(map.read().unwrap().is_empty());
}
#[test]
fn test_sys_interrupt_map_new() {
let map = sys_interrupt_map_new();
assert!(map.sys_block.0.lock().unwrap().is_empty());
assert!(map.sig_restart.lock().unwrap().is_empty());
}
#[test]
fn test_sys_result_map_new() {
let map = sys_result_map_new();
assert!(map.trace_error.lock().unwrap().is_empty());
assert!(map.trace_chdir.lock().unwrap().is_empty());
assert!(map.trace_mmap.lock().unwrap().is_empty());
}
#[test]
fn test_signal_map_new() {
let map = signal_map_new();
assert!(map.sig_handle.lock().unwrap().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"),
}
}
}