use std::{
collections::BTreeMap,
fmt::Display,
sync::Arc,
};
use chrono::{
DateTime,
Local,
};
use enumflags2::BitFlags;
use nix::{
errno::Errno,
libc::{
SIGRTMIN,
c_int,
},
unistd::{
Pid,
User,
},
};
use tokio::sync::mpsc::UnboundedSender;
use crate::{
cli::{
args::{
LogModeArgs,
ModifierArgs,
PtraceArgs,
},
options::SeccompBpf,
},
elevate::EnvVars,
event::{
OutputMsg,
TracerEventDetailsKind,
TracerMessage,
},
printer::{
Printer,
PrinterArgs,
},
proc::{
BaselineInfo,
CgroupInfo,
Cred,
CredInspectError,
FileDescriptorInfoCollection,
Interpreter,
},
pty::UnixSlavePty,
};
pub type InspectError = Errno;
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum Signal {
Standard(nix::sys::signal::Signal),
Realtime(u8), }
impl Signal {
pub fn from_raw(raw: c_int) -> Self {
match nix::sys::signal::Signal::try_from(raw) {
Ok(sig) => Self::Standard(sig),
Err(_) => Self::Realtime(raw as u8),
}
}
pub fn as_raw(self) -> i32 {
match self {
Self::Standard(signal) => signal as i32,
Self::Realtime(raw) => raw as i32,
}
}
}
impl From<nix::sys::signal::Signal> for Signal {
fn from(value: nix::sys::signal::Signal) -> Self {
Self::Standard(value)
}
}
impl Display for Signal {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
match self {
Self::Standard(signal) => signal.fmt(f),
Self::Realtime(sig) => {
let min = SIGRTMIN();
let delta = *sig as i32 - min;
match delta.signum() {
0 => write!(f, "SIGRTMIN"),
1 => write!(f, "SIGRTMIN+{delta}"),
-1 => write!(f, "SIGRTMIN{delta}"),
_ => unreachable!(),
}
}
}
}
}
#[derive(Default)]
#[non_exhaustive]
pub struct TracerBuilder {
pub user: Option<User>,
pub modifier: ModifierArgs,
pub mode: Option<TracerMode>,
pub filter: Option<BitFlags<TracerEventDetailsKind>>,
pub tx: Option<UnboundedSender<TracerMessage>>,
pub printer: Option<Printer>,
pub baseline: Option<Arc<BaselineInfo>>,
pub seccomp_bpf: SeccompBpf,
pub ptrace_polling_delay: Option<u64>,
pub ptrace_blocking: Option<bool>,
pub tracee_env: Option<EnvVars>,
pub tracexec_override_env: Option<EnvVars>,
}
#[allow(clippy::unwrap_used)]
impl TracerBuilder {
pub fn new() -> Self {
Default::default()
}
pub fn ptrace_blocking(mut self, enable: bool) -> Self {
if self.ptrace_polling_delay.is_some() && enable {
panic!(
"Cannot enable blocking mode when ptrace polling delay implicitly specifys polling mode"
);
}
self.ptrace_blocking = Some(enable);
self
}
pub fn ptrace_polling_delay(mut self, ptrace_polling_delay: Option<u64>) -> Self {
if Some(true) == self.ptrace_blocking && ptrace_polling_delay.is_some() {
panic!("Cannot set ptrace_polling_delay when operating in blocking mode")
}
self.ptrace_polling_delay = ptrace_polling_delay;
self
}
pub fn ptrace_options(self, args: &PtraceArgs) -> Self {
self
.seccomp_bpf(args.seccomp_bpf)
.ptrace_blocking(args.polling_interval.is_none_or(|value| value < 0))
.ptrace_polling_delay(
args
.polling_interval
.filter(|&value| value > 0)
.map(|value| value as u64),
)
}
pub fn seccomp_bpf(mut self, seccomp_bpf: SeccompBpf) -> Self {
self.seccomp_bpf = seccomp_bpf;
self
}
pub fn user(mut self, user: Option<User>) -> Self {
self.user = user;
self
}
pub fn tracee_env(mut self, env: Option<EnvVars>) -> Self {
self.tracee_env = env;
self
}
pub fn tracexec_override_env(mut self, env: Option<EnvVars>) -> Self {
self.tracexec_override_env = env;
self
}
pub fn modifier(mut self, modifier: ModifierArgs) -> Self {
self.modifier = modifier;
self
}
pub fn mode(mut self, mode: TracerMode) -> Self {
self.mode = Some(mode);
self
}
pub fn filter(mut self, filter: BitFlags<TracerEventDetailsKind>) -> Self {
self.filter = Some(filter);
self
}
pub fn tracer_tx(mut self, tx: UnboundedSender<TracerMessage>) -> Self {
self.tx = Some(tx);
self
}
pub fn printer(mut self, printer: Printer) -> Self {
self.printer = Some(printer);
self
}
pub fn printer_from_cli(mut self, tracing_args: &LogModeArgs) -> Self {
self.printer = Some(Printer::new(
PrinterArgs::from_cli(tracing_args, &self.modifier),
self.baseline.clone().unwrap(),
));
self
}
pub fn baseline(mut self, baseline: Arc<BaselineInfo>) -> Self {
self.baseline = Some(baseline);
self
}
}
#[derive(Debug)]
pub struct ExecData {
pub exec_pid: Pid,
pub filename: OutputMsg,
pub argv: Arc<Result<Vec<OutputMsg>, InspectError>>,
pub envp: Arc<Result<BTreeMap<OutputMsg, OutputMsg>, InspectError>>,
pub has_dash_env: bool,
pub cred: Result<Cred, CredInspectError>,
pub cwd: OutputMsg,
pub interpreters: Option<Vec<Interpreter>>,
pub fdinfo: Arc<FileDescriptorInfoCollection>,
pub timestamp: DateTime<Local>,
pub cgroup: CgroupInfo,
}
impl ExecData {
#[allow(clippy::too_many_arguments)]
pub fn new(
exec_pid: Pid,
filename: OutputMsg,
argv: Result<Vec<OutputMsg>, InspectError>,
envp: Result<BTreeMap<OutputMsg, OutputMsg>, InspectError>,
has_dash_env: bool,
cred: Result<Cred, CredInspectError>,
cwd: OutputMsg,
interpreters: Option<Vec<Interpreter>>,
fdinfo: FileDescriptorInfoCollection,
timestamp: DateTime<Local>,
cgroup: CgroupInfo,
) -> Self {
Self {
exec_pid,
filename,
argv: Arc::new(argv),
envp: Arc::new(envp),
has_dash_env,
cred,
cwd,
interpreters,
fdinfo: Arc::new(fdinfo),
timestamp,
cgroup,
}
}
}
#[derive(Debug)]
pub enum TracerMode {
Tui(Option<UnixSlavePty>),
Log { foreground: bool },
}
impl PartialEq for TracerMode {
fn eq(&self, other: &Self) -> bool {
#[allow(clippy::match_like_matches_macro)]
match (self, other) {
(Self::Log { foreground: a }, Self::Log { foreground: b }) => a == b,
_ => false,
}
}
}
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum ProcessExit {
Code(i32),
Signal(Signal),
}
#[cfg(test)]
mod tests {
use std::{
collections::BTreeMap,
sync::Arc,
};
use chrono::Local;
use nix::sys::signal::Signal as NixSignal;
use test_that::prelude::*;
use super::*;
use crate::event::OutputMsg;
#[test]
fn signal_from_raw_standard() {
let sig = Signal::from_raw(NixSignal::SIGINT as i32);
assert_eq!(sig, Signal::Standard(NixSignal::SIGINT));
assert_eq!(sig.as_raw(), NixSignal::SIGINT as i32);
}
#[test]
fn signal_from_raw_realtime() {
let raw = SIGRTMIN() + 3;
let sig = Signal::from_raw(raw);
assert_eq!(sig, Signal::Realtime(raw as u8));
assert_eq!(sig.as_raw(), raw);
}
#[test]
fn signal_display_standard() {
let sig = Signal::Standard(NixSignal::SIGTERM);
assert_eq!(sig.to_string(), "SIGTERM");
}
#[test]
fn signal_display_realtime_variants() {
let min = SIGRTMIN();
let sig_min = Signal::Realtime(min as u8);
assert_eq!(sig_min.to_string(), "SIGRTMIN");
let sig_plus = Signal::Realtime((min + 2) as u8);
assert_eq!(sig_plus.to_string(), "SIGRTMIN+2");
let sig_minus = Signal::Realtime((min - 1) as u8);
assert_eq!(sig_minus.to_string(), "SIGRTMIN-1");
}
#[test]
#[should_panic(expected = "Cannot enable blocking mode")]
fn tracer_builder_blocking_conflict_panics() {
TracerBuilder::new()
.ptrace_polling_delay(Some(10))
.ptrace_blocking(true);
}
#[test]
#[should_panic(expected = "Cannot set ptrace_polling_delay")]
fn tracer_builder_polling_conflict_panics() {
TracerBuilder::new()
.ptrace_blocking(true)
.ptrace_polling_delay(Some(10));
}
#[test]
fn tracer_builder_chaining_works() {
let builder = TracerBuilder::new()
.ptrace_blocking(false)
.ptrace_polling_delay(None)
.seccomp_bpf(SeccompBpf::Auto);
assert_eq!(builder.ptrace_blocking, Some(false));
assert_eq!(builder.ptrace_polling_delay, None);
}
#[test]
fn tracer_builder_applies_ptrace_cli_options() {
let blocking = TracerBuilder::new().ptrace_options(&PtraceArgs::default());
assert_eq!(blocking.ptrace_blocking, Some(true));
assert_eq!(blocking.ptrace_polling_delay, None);
let polling = TracerBuilder::new().ptrace_options(&PtraceArgs {
seccomp_bpf: SeccompBpf::Off,
polling_interval: Some(250),
});
assert_eq!(polling.seccomp_bpf, SeccompBpf::Off);
assert_eq!(polling.ptrace_blocking, Some(false));
assert_eq!(polling.ptrace_polling_delay, Some(250));
let no_delay = TracerBuilder::new().ptrace_options(&PtraceArgs {
polling_interval: Some(0),
..Default::default()
});
assert_eq!(no_delay.ptrace_blocking, Some(false));
assert_eq!(no_delay.ptrace_polling_delay, None);
}
#[test]
fn exec_data_new_populates_fields() {
let filename = OutputMsg::Ok("bin".into());
let argv = Ok(vec![
OutputMsg::Ok("bin".into()),
OutputMsg::Ok("-h".into()),
]);
let mut envp_map = BTreeMap::new();
envp_map.insert(OutputMsg::Ok("A".into()), OutputMsg::Ok("B".into()));
let envp = Ok(envp_map);
let cwd = OutputMsg::Ok("/".into());
let fdinfo = FileDescriptorInfoCollection::default();
let timestamp = Local::now();
let exec = ExecData::new(
Pid::from_raw(1234),
filename.clone(),
argv,
envp,
false,
Err(CredInspectError::Inspect),
cwd.clone(),
None,
fdinfo,
timestamp,
CgroupInfo::V2 {
path: "/".to_string(),
},
);
assert_eq!(exec.exec_pid, Pid::from_raw(1234));
assert_eq!(exec.filename, filename);
assert_eq!(exec.cwd, cwd);
assert_that!(exec.argv, points_to(ok(anything())));
assert_that!(exec.envp, points_to(ok(anything())));
assert!(!exec.has_dash_env);
assert_that!(exec.interpreters, none());
assert_that!(Arc::strong_count(&exec.argv), ge(1));
assert_that!(Arc::strong_count(&exec.envp), ge(1));
assert_that!(Arc::strong_count(&exec.fdinfo), ge(1));
}
#[test]
fn process_exit_equality() {
let a = ProcessExit::Code(0);
let b = ProcessExit::Code(0);
let c = ProcessExit::Code(1);
assert_eq!(a, b);
assert_ne!(a, c);
let s1 = ProcessExit::Signal(Signal::Standard(NixSignal::SIGKILL));
let s2 = ProcessExit::Signal(Signal::Standard(NixSignal::SIGKILL));
let s3 = ProcessExit::Signal(Signal::Standard(NixSignal::SIGTERM));
assert_eq!(s1, s2);
assert_ne!(s1, s3);
}
}