#![forbid(unsafe_code)]
use std::{
ops::Deref,
sync::{
atomic::{AtomicBool, AtomicI32},
Arc, OnceLock,
},
thread::Thread,
};
use concurrent_queue::ConcurrentQueue;
use libseccomp::ScmpSyscall;
use nix::{errno::Errno, sys::socket::UnixAddr, unistd::Pid};
use serde::{ser::SerializeMap, Serializer};
use crate::{
config::{SYSBLOCK_CAPACITY, UNIX_MAP_VACUUM_MIN},
confine::{ScmpNotifReq, SydArch},
expiry::ExpiringMap,
fd::SafeOwnedFd,
hash::SydRandomState,
kernel::ptrace::mmap::MmapSyscall,
lookup::FileInfo,
path::XPathBuf,
proc::proc_unix_inodes,
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 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(Debug)]
pub(crate) struct ChdirEntry {
#[cfg_attr(not(feature = "kcov"), expect(dead_code))]
pub(crate) data: u16,
pub(crate) info: FileInfo,
pub(crate) path: Option<XPathBuf>,
}
pub(crate) type ChdirMap = scc::HashMap<Pid, ChdirEntry, SydRandomState>;
#[derive(Debug)]
pub(crate) struct MmapEntry {
pub(crate) sys: MmapSyscall,
pub(crate) path: Option<XPathBuf>,
}
pub(crate) type MmapMap = scc::HashMap<Pid, MmapEntry, SydRandomState>;
#[derive(Clone, Default)]
pub(crate) struct UnixVal {
pub(crate) self_pid: Option<Pid>,
pub(crate) peer_pid: Option<Pid>,
pub(crate) addr: Option<UnixAddr>,
pub(crate) peer: Option<UnixAddr>,
pub(crate) dest: Vec<(u32, u32)>,
}
#[derive(Clone)]
pub(crate) struct UnixMap(Arc<scc::HashMap<u64, UnixVal, SydRandomState>>);
impl Deref for UnixMap {
type Target = scc::HashMap<u64, UnixVal, SydRandomState>;
fn deref(&self) -> &Self::Target {
&self.0
}
}
impl UnixMap {
pub(crate) fn vacuum(&self, pid: Pid) {
if self.len() < UNIX_MAP_VACUUM_MIN {
return;
}
if let Ok(live) = proc_unix_inodes(pid) {
self.retain_sync(|inode, _| live.contains(inode));
}
}
}
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>,
}
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 {
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())),
}
}
#[cfg(test)]
mod tests {
use std::os::{
linux::net::SocketAddrExt,
unix::net::{SocketAddr, UnixListener},
};
use nix::fcntl::OFlag;
use super::*;
use crate::{
compat::{fstatx, STATX_INO},
fd::open_static_proc,
};
#[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_unix_map_vacuum() {
let _ = open_static_proc(OFlag::O_PATH);
let map = unix_map_new();
for inode in 0u64..=UNIX_MAP_VACUUM_MIN as u64 {
let _ = map.insert_sync(inode, UnixVal::default());
}
let addr = SocketAddr::from_abstract_name(b"syd-vacuum-unix").unwrap();
let sock = UnixListener::bind_addr(&addr).unwrap();
let live = fstatx(&sock, STATX_INO).map(|s| s.stx_ino).unwrap();
let _ = map.insert_sync(live, UnixVal::default());
map.vacuum(Pid::this());
assert!(map.read_sync(&0, |_, _| ()).is_none());
assert!(map.read_sync(&live, |_, _| ()).is_some());
}
}