1use std::{
2 collections::BTreeMap,
3 fmt::Display,
4 sync::Arc,
5};
6
7use chrono::{
8 DateTime,
9 Local,
10};
11use enumflags2::BitFlags;
12use nix::{
13 errno::Errno,
14 libc::{
15 SIGRTMIN,
16 c_int,
17 },
18 unistd::{
19 Pid,
20 User,
21 },
22};
23use tokio::sync::mpsc::UnboundedSender;
24
25use crate::{
26 cli::{
27 args::{
28 LogModeArgs,
29 ModifierArgs,
30 PtraceArgs,
31 },
32 options::SeccompBpf,
33 },
34 elevate::EnvVars,
35 event::{
36 OutputMsg,
37 TracerEventDetailsKind,
38 TracerMessage,
39 },
40 printer::{
41 Printer,
42 PrinterArgs,
43 },
44 proc::{
45 BaselineInfo,
46 CgroupInfo,
47 Cred,
48 CredInspectError,
49 FileDescriptorInfoCollection,
50 Interpreter,
51 },
52 pty::UnixSlavePty,
53};
54
55pub type InspectError = Errno;
56
57#[derive(Debug, Clone, Copy, PartialEq, Eq)]
58pub enum Signal {
59 Standard(nix::sys::signal::Signal),
60 Realtime(u8), }
62
63impl Signal {
64 pub fn from_raw(raw: c_int) -> Self {
65 match nix::sys::signal::Signal::try_from(raw) {
66 Ok(sig) => Self::Standard(sig),
67 Err(_) => Self::Realtime(raw as u8),
71 }
72 }
73
74 pub fn as_raw(self) -> i32 {
75 match self {
76 Self::Standard(signal) => signal as i32,
77 Self::Realtime(raw) => raw as i32,
78 }
79 }
80}
81
82impl From<nix::sys::signal::Signal> for Signal {
83 fn from(value: nix::sys::signal::Signal) -> Self {
84 Self::Standard(value)
85 }
86}
87
88impl Display for Signal {
89 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
90 match self {
91 Self::Standard(signal) => signal.fmt(f),
92 Self::Realtime(sig) => {
93 let min = SIGRTMIN();
94 let delta = *sig as i32 - min;
95 match delta.signum() {
96 0 => write!(f, "SIGRTMIN"),
97 1 => write!(f, "SIGRTMIN+{delta}"),
98 -1 => write!(f, "SIGRTMIN{delta}"),
99 _ => unreachable!(),
100 }
101 }
102 }
103 }
104}
105
106#[derive(Default)]
107#[non_exhaustive]
108pub struct TracerBuilder {
109 pub user: Option<User>,
110 pub modifier: ModifierArgs,
111 pub mode: Option<TracerMode>,
112 pub filter: Option<BitFlags<TracerEventDetailsKind>>,
113 pub tx: Option<UnboundedSender<TracerMessage>>,
114 pub printer: Option<Printer>,
116 pub baseline: Option<Arc<BaselineInfo>>,
117 pub seccomp_bpf: SeccompBpf,
119 pub ptrace_polling_delay: Option<u64>,
120 pub ptrace_blocking: Option<bool>,
121 pub tracee_env: Option<EnvVars>,
122 pub tracexec_override_env: Option<EnvVars>,
123}
124
125#[allow(clippy::unwrap_used)]
126impl TracerBuilder {
127 pub fn new() -> Self {
129 Default::default()
130 }
131
132 pub fn ptrace_blocking(mut self, enable: bool) -> Self {
137 if self.ptrace_polling_delay.is_some() && enable {
138 panic!(
139 "Cannot enable blocking mode when ptrace polling delay implicitly specifys polling mode"
140 );
141 }
142 self.ptrace_blocking = Some(enable);
143 self
144 }
145
146 pub fn ptrace_polling_delay(mut self, ptrace_polling_delay: Option<u64>) -> Self {
151 if Some(true) == self.ptrace_blocking && ptrace_polling_delay.is_some() {
152 panic!("Cannot set ptrace_polling_delay when operating in blocking mode")
153 }
154 self.ptrace_polling_delay = ptrace_polling_delay;
155 self
156 }
157
158 pub fn ptrace_options(self, args: &PtraceArgs) -> Self {
160 self
161 .seccomp_bpf(args.seccomp_bpf)
162 .ptrace_blocking(args.polling_interval.is_none_or(|value| value < 0))
163 .ptrace_polling_delay(
164 args
165 .polling_interval
166 .filter(|&value| value > 0)
167 .map(|value| value as u64),
168 )
169 }
170
171 pub fn seccomp_bpf(mut self, seccomp_bpf: SeccompBpf) -> Self {
176 self.seccomp_bpf = seccomp_bpf;
177 self
178 }
179
180 pub fn user(mut self, user: Option<User>) -> Self {
184 self.user = user;
185 self
186 }
187
188 pub fn tracee_env(mut self, env: Option<EnvVars>) -> Self {
192 self.tracee_env = env;
193 self
194 }
195
196 pub fn tracexec_override_env(mut self, env: Option<EnvVars>) -> Self {
200 self.tracexec_override_env = env;
201 self
202 }
203
204 pub fn modifier(mut self, modifier: ModifierArgs) -> Self {
205 self.modifier = modifier;
206 self
207 }
208
209 pub fn mode(mut self, mode: TracerMode) -> Self {
211 self.mode = Some(mode);
212 self
213 }
214
215 pub fn filter(mut self, filter: BitFlags<TracerEventDetailsKind>) -> Self {
217 self.filter = Some(filter);
218 self
219 }
220
221 pub fn tracer_tx(mut self, tx: UnboundedSender<TracerMessage>) -> Self {
225 self.tx = Some(tx);
226 self
227 }
228
229 pub fn printer(mut self, printer: Printer) -> Self {
230 self.printer = Some(printer);
231 self
232 }
233
234 pub fn printer_from_cli(mut self, tracing_args: &LogModeArgs) -> Self {
238 self.printer = Some(Printer::new(
239 PrinterArgs::from_cli(tracing_args, &self.modifier),
240 self.baseline.clone().unwrap(),
241 ));
242 self
243 }
244
245 pub fn baseline(mut self, baseline: Arc<BaselineInfo>) -> Self {
246 self.baseline = Some(baseline);
247 self
248 }
249}
250
251#[derive(Debug)]
252pub struct ExecData {
253 pub exec_pid: Pid,
254 pub filename: OutputMsg,
255 pub argv: Arc<Result<Vec<OutputMsg>, InspectError>>,
256 pub envp: Arc<Result<BTreeMap<OutputMsg, OutputMsg>, InspectError>>,
257 pub has_dash_env: bool,
258 pub cred: Result<Cred, CredInspectError>,
259 pub cwd: OutputMsg,
260 pub interpreters: Option<Vec<Interpreter>>,
261 pub fdinfo: Arc<FileDescriptorInfoCollection>,
262 pub timestamp: DateTime<Local>,
263 pub cgroup: CgroupInfo,
264}
265
266impl ExecData {
267 #[allow(clippy::too_many_arguments)]
268 pub fn new(
269 exec_pid: Pid,
270 filename: OutputMsg,
271 argv: Result<Vec<OutputMsg>, InspectError>,
272 envp: Result<BTreeMap<OutputMsg, OutputMsg>, InspectError>,
273 has_dash_env: bool,
274 cred: Result<Cred, CredInspectError>,
275 cwd: OutputMsg,
276 interpreters: Option<Vec<Interpreter>>,
277 fdinfo: FileDescriptorInfoCollection,
278 timestamp: DateTime<Local>,
279 cgroup: CgroupInfo,
280 ) -> Self {
281 Self {
282 exec_pid,
283 filename,
284 argv: Arc::new(argv),
285 envp: Arc::new(envp),
286 has_dash_env,
287 cred,
288 cwd,
289 interpreters,
290 fdinfo: Arc::new(fdinfo),
291 timestamp,
292 cgroup,
293 }
294 }
295}
296
297#[derive(Debug)]
298pub enum TracerMode {
299 Tui(Option<UnixSlavePty>),
300 Log { foreground: bool },
301}
302
303impl PartialEq for TracerMode {
304 fn eq(&self, other: &Self) -> bool {
305 #[allow(clippy::match_like_matches_macro)]
307 match (self, other) {
308 (Self::Log { foreground: a }, Self::Log { foreground: b }) => a == b,
309 _ => false,
310 }
311 }
312}
313
314#[derive(Debug, Clone, Copy, PartialEq, Eq)]
315pub enum ProcessExit {
316 Code(i32),
317 Signal(Signal),
318}
319
320#[cfg(test)]
321mod tests {
322 use std::{
323 collections::BTreeMap,
324 sync::Arc,
325 };
326
327 use chrono::Local;
328 use nix::sys::signal::Signal as NixSignal;
329 use test_that::prelude::*;
330
331 use super::*;
332 use crate::event::OutputMsg;
333
334 #[test]
337 fn signal_from_raw_standard() {
338 let sig = Signal::from_raw(NixSignal::SIGINT as i32);
339 assert_eq!(sig, Signal::Standard(NixSignal::SIGINT));
340 assert_eq!(sig.as_raw(), NixSignal::SIGINT as i32);
341 }
342
343 #[test]
344 fn signal_from_raw_realtime() {
345 let raw = SIGRTMIN() + 3;
346 let sig = Signal::from_raw(raw);
347 assert_eq!(sig, Signal::Realtime(raw as u8));
348 assert_eq!(sig.as_raw(), raw);
349 }
350
351 #[test]
352 fn signal_display_standard() {
353 let sig = Signal::Standard(NixSignal::SIGTERM);
354 assert_eq!(sig.to_string(), "SIGTERM");
355 }
356
357 #[test]
358 fn signal_display_realtime_variants() {
359 let min = SIGRTMIN();
360
361 let sig_min = Signal::Realtime(min as u8);
362 assert_eq!(sig_min.to_string(), "SIGRTMIN");
363
364 let sig_plus = Signal::Realtime((min + 2) as u8);
365 assert_eq!(sig_plus.to_string(), "SIGRTMIN+2");
366
367 let sig_minus = Signal::Realtime((min - 1) as u8);
368 assert_eq!(sig_minus.to_string(), "SIGRTMIN-1");
369 }
370
371 #[test]
373 #[should_panic(expected = "Cannot enable blocking mode")]
374 fn tracer_builder_blocking_conflict_panics() {
375 TracerBuilder::new()
376 .ptrace_polling_delay(Some(10))
377 .ptrace_blocking(true);
378 }
379
380 #[test]
381 #[should_panic(expected = "Cannot set ptrace_polling_delay")]
382 fn tracer_builder_polling_conflict_panics() {
383 TracerBuilder::new()
384 .ptrace_blocking(true)
385 .ptrace_polling_delay(Some(10));
386 }
387
388 #[test]
389 fn tracer_builder_chaining_works() {
390 let builder = TracerBuilder::new()
391 .ptrace_blocking(false)
392 .ptrace_polling_delay(None)
393 .seccomp_bpf(SeccompBpf::Auto);
394
395 assert_eq!(builder.ptrace_blocking, Some(false));
396 assert_eq!(builder.ptrace_polling_delay, None);
397 }
398
399 #[test]
400 fn tracer_builder_applies_ptrace_cli_options() {
401 let blocking = TracerBuilder::new().ptrace_options(&PtraceArgs::default());
402 assert_eq!(blocking.ptrace_blocking, Some(true));
403 assert_eq!(blocking.ptrace_polling_delay, None);
404
405 let polling = TracerBuilder::new().ptrace_options(&PtraceArgs {
406 seccomp_bpf: SeccompBpf::Off,
407 polling_interval: Some(250),
408 });
409 assert_eq!(polling.seccomp_bpf, SeccompBpf::Off);
410 assert_eq!(polling.ptrace_blocking, Some(false));
411 assert_eq!(polling.ptrace_polling_delay, Some(250));
412
413 let no_delay = TracerBuilder::new().ptrace_options(&PtraceArgs {
414 polling_interval: Some(0),
415 ..Default::default()
416 });
417 assert_eq!(no_delay.ptrace_blocking, Some(false));
418 assert_eq!(no_delay.ptrace_polling_delay, None);
419 }
420
421 #[test]
424 fn exec_data_new_populates_fields() {
425 let filename = OutputMsg::Ok("bin".into());
426 let argv = Ok(vec![
427 OutputMsg::Ok("bin".into()),
428 OutputMsg::Ok("-h".into()),
429 ]);
430
431 let mut envp_map = BTreeMap::new();
432 envp_map.insert(OutputMsg::Ok("A".into()), OutputMsg::Ok("B".into()));
433 let envp = Ok(envp_map);
434
435 let cwd = OutputMsg::Ok("/".into());
436 let fdinfo = FileDescriptorInfoCollection::default();
437 let timestamp = Local::now();
438
439 let exec = ExecData::new(
440 Pid::from_raw(1234),
441 filename.clone(),
442 argv,
443 envp,
444 false,
445 Err(CredInspectError::Inspect),
446 cwd.clone(),
447 None,
448 fdinfo,
449 timestamp,
450 CgroupInfo::V2 {
451 path: "/".to_string(),
452 },
453 );
454
455 assert_eq!(exec.exec_pid, Pid::from_raw(1234));
456 assert_eq!(exec.filename, filename);
457 assert_eq!(exec.cwd, cwd);
458 assert_that!(exec.argv, points_to(ok(anything())));
459 assert_that!(exec.envp, points_to(ok(anything())));
460 assert!(!exec.has_dash_env);
461 assert_that!(exec.interpreters, none());
462 assert_that!(Arc::strong_count(&exec.argv), ge(1));
463 assert_that!(Arc::strong_count(&exec.envp), ge(1));
464 assert_that!(Arc::strong_count(&exec.fdinfo), ge(1));
465 }
466
467 #[test]
470 fn process_exit_equality() {
471 let a = ProcessExit::Code(0);
472 let b = ProcessExit::Code(0);
473 let c = ProcessExit::Code(1);
474
475 assert_eq!(a, b);
476 assert_ne!(a, c);
477
478 let s1 = ProcessExit::Signal(Signal::Standard(NixSignal::SIGKILL));
479 let s2 = ProcessExit::Signal(Signal::Standard(NixSignal::SIGKILL));
480 let s3 = ProcessExit::Signal(Signal::Standard(NixSignal::SIGTERM));
481
482 assert_eq!(s1, s2);
483 assert_ne!(s1, s3);
484 }
485}