tracexec_core/
tracer.rs

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::User,
19};
20use tokio::sync::mpsc::UnboundedSender;
21
22use crate::{
23  cli::{
24    args::{
25      LogModeArgs,
26      ModifierArgs,
27    },
28    options::SeccompBpf,
29  },
30  event::{
31    OutputMsg,
32    TracerEventDetailsKind,
33    TracerMessage,
34  },
35  printer::{
36    Printer,
37    PrinterArgs,
38  },
39  proc::{
40    BaselineInfo,
41    Cred,
42    CredInspectError,
43    FileDescriptorInfoCollection,
44    Interpreter,
45  },
46  pty::UnixSlavePty,
47};
48
49pub type InspectError = Errno;
50
51#[derive(Debug, Clone, Copy, PartialEq, Eq)]
52pub enum Signal {
53  Standard(nix::sys::signal::Signal),
54  Realtime(u8), // u8 is enough for Linux
55}
56
57impl Signal {
58  pub fn from_raw(raw: c_int) -> Self {
59    match nix::sys::signal::Signal::try_from(raw) {
60      Ok(sig) => Self::Standard(sig),
61      // libc might reserve some RT signals for itself.
62      // But from a tracer's perspective we don't need to care about it.
63      // So here no validation is done for the RT signal value.
64      Err(_) => Self::Realtime(raw as u8),
65    }
66  }
67
68  pub fn as_raw(self) -> i32 {
69    match self {
70      Self::Standard(signal) => signal as i32,
71      Self::Realtime(raw) => raw as i32,
72    }
73  }
74}
75
76impl From<nix::sys::signal::Signal> for Signal {
77  fn from(value: nix::sys::signal::Signal) -> Self {
78    Self::Standard(value)
79  }
80}
81
82impl Display for Signal {
83  fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
84    match self {
85      Self::Standard(signal) => signal.fmt(f),
86      Self::Realtime(sig) => {
87        let min = SIGRTMIN();
88        let delta = *sig as i32 - min;
89        match delta.signum() {
90          0 => write!(f, "SIGRTMIN"),
91          1 => write!(f, "SIGRTMIN+{delta}"),
92          -1 => write!(f, "SIGRTMIN{delta}"),
93          _ => unreachable!(),
94        }
95      }
96    }
97  }
98}
99
100#[derive(Default)]
101#[non_exhaustive]
102pub struct TracerBuilder {
103  pub user: Option<User>,
104  pub modifier: ModifierArgs,
105  pub mode: Option<TracerMode>,
106  pub filter: Option<BitFlags<TracerEventDetailsKind>>,
107  pub tx: Option<UnboundedSender<TracerMessage>>,
108  // TODO: remove this.
109  pub printer: Option<Printer>,
110  pub baseline: Option<Arc<BaselineInfo>>,
111  // --- ptrace specific ---
112  pub seccomp_bpf: SeccompBpf,
113  pub ptrace_polling_delay: Option<u64>,
114  pub ptrace_blocking: Option<bool>,
115}
116
117impl TracerBuilder {
118  /// Initialize a new [`TracerBuilder`]
119  pub fn new() -> Self {
120    Default::default()
121  }
122
123  /// Use blocking waitpid calls instead of polling.
124  ///
125  /// This mode conflicts with ptrace polling delay option
126  /// This option is not used in eBPF tracer.
127  pub fn ptrace_blocking(mut self, enable: bool) -> Self {
128    if self.ptrace_polling_delay.is_some() && enable {
129      panic!(
130        "Cannot enable blocking mode when ptrace polling delay implicitly specifys polling mode"
131      );
132    }
133    self.ptrace_blocking = Some(enable);
134    self
135  }
136
137  /// Sets ptrace polling delay (in microseconds)
138  /// This options conflicts with ptrace blocking mode.
139  ///
140  /// This option is not used in eBPF tracer.
141  pub fn ptrace_polling_delay(mut self, ptrace_polling_delay: Option<u64>) -> Self {
142    if Some(true) == self.ptrace_blocking && ptrace_polling_delay.is_some() {
143      panic!("Cannot set ptrace_polling_delay when operating in blocking mode")
144    }
145    self.ptrace_polling_delay = ptrace_polling_delay;
146    self
147  }
148
149  /// Sets seccomp-bpf mode for ptrace tracer
150  ///
151  /// Default to auto.
152  /// This option is not used in eBPF tracer.
153  pub fn seccomp_bpf(mut self, seccomp_bpf: SeccompBpf) -> Self {
154    self.seccomp_bpf = seccomp_bpf;
155    self
156  }
157
158  /// Sets the `User` used when spawning the command.
159  ///
160  /// Default to current user.
161  pub fn user(mut self, user: Option<User>) -> Self {
162    self.user = user;
163    self
164  }
165
166  pub fn modifier(mut self, modifier: ModifierArgs) -> Self {
167    self.modifier = modifier;
168    self
169  }
170
171  /// Sets the mode for the trace e.g. TUI or Log
172  pub fn mode(mut self, mode: TracerMode) -> Self {
173    self.mode = Some(mode);
174    self
175  }
176
177  /// Sets a filter for wanted tracer events.
178  pub fn filter(mut self, filter: BitFlags<TracerEventDetailsKind>) -> Self {
179    self.filter = Some(filter);
180    self
181  }
182
183  /// Passes the tx part of tracer event channel
184  ///
185  /// By default this is not set and tracer will not send events.
186  pub fn tracer_tx(mut self, tx: UnboundedSender<TracerMessage>) -> Self {
187    self.tx = Some(tx);
188    self
189  }
190
191  pub fn printer(mut self, printer: Printer) -> Self {
192    self.printer = Some(printer);
193    self
194  }
195
196  /// Create a printer from CLI options,
197  ///
198  /// Requires `modifier` and `baseline` to be set before calling.
199  pub fn printer_from_cli(mut self, tracing_args: &LogModeArgs) -> Self {
200    self.printer = Some(Printer::new(
201      PrinterArgs::from_cli(tracing_args, &self.modifier),
202      self.baseline.clone().unwrap(),
203    ));
204    self
205  }
206
207  pub fn baseline(mut self, baseline: Arc<BaselineInfo>) -> Self {
208    self.baseline = Some(baseline);
209    self
210  }
211}
212
213#[derive(Debug)]
214pub struct ExecData {
215  pub filename: OutputMsg,
216  pub argv: Arc<Result<Vec<OutputMsg>, InspectError>>,
217  pub envp: Arc<Result<BTreeMap<OutputMsg, OutputMsg>, InspectError>>,
218  pub has_dash_env: bool,
219  pub cred: Result<Cred, CredInspectError>,
220  pub cwd: OutputMsg,
221  pub interpreters: Option<Vec<Interpreter>>,
222  pub fdinfo: Arc<FileDescriptorInfoCollection>,
223  pub timestamp: DateTime<Local>,
224}
225
226impl ExecData {
227  #[allow(clippy::too_many_arguments)]
228  pub fn new(
229    filename: OutputMsg,
230    argv: Result<Vec<OutputMsg>, InspectError>,
231    envp: Result<BTreeMap<OutputMsg, OutputMsg>, InspectError>,
232    has_dash_env: bool,
233    cred: Result<Cred, CredInspectError>,
234    cwd: OutputMsg,
235    interpreters: Option<Vec<Interpreter>>,
236    fdinfo: FileDescriptorInfoCollection,
237    timestamp: DateTime<Local>,
238  ) -> Self {
239    Self {
240      filename,
241      argv: Arc::new(argv),
242      envp: Arc::new(envp),
243      has_dash_env,
244      cred,
245      cwd,
246      interpreters,
247      fdinfo: Arc::new(fdinfo),
248      timestamp,
249    }
250  }
251}
252
253pub enum TracerMode {
254  Tui(Option<UnixSlavePty>),
255  Log { foreground: bool },
256}
257
258impl PartialEq for TracerMode {
259  fn eq(&self, other: &Self) -> bool {
260    // I think a plain match is more readable here
261    #[allow(clippy::match_like_matches_macro)]
262    match (self, other) {
263      (Self::Log { foreground: a }, Self::Log { foreground: b }) => a == b,
264      _ => false,
265    }
266  }
267}
268
269#[derive(Debug, Clone, Copy, PartialEq, Eq)]
270pub enum ProcessExit {
271  Code(i32),
272  Signal(Signal),
273}
274
275#[cfg(test)]
276mod tests {
277  use std::{
278    collections::BTreeMap,
279    sync::Arc,
280  };
281
282  use chrono::Local;
283  use nix::sys::signal::Signal as NixSignal;
284
285  use super::*;
286  use crate::event::OutputMsg;
287
288  /* ---------------- Signal ---------------- */
289
290  #[test]
291  fn signal_from_raw_standard() {
292    let sig = Signal::from_raw(NixSignal::SIGINT as i32);
293    assert_eq!(sig, Signal::Standard(NixSignal::SIGINT));
294    assert_eq!(sig.as_raw(), NixSignal::SIGINT as i32);
295  }
296
297  #[test]
298  fn signal_from_raw_realtime() {
299    let raw = SIGRTMIN() + 3;
300    let sig = Signal::from_raw(raw);
301    assert_eq!(sig, Signal::Realtime(raw as u8));
302    assert_eq!(sig.as_raw(), raw);
303  }
304
305  #[test]
306  fn signal_display_standard() {
307    let sig = Signal::Standard(NixSignal::SIGTERM);
308    assert_eq!(sig.to_string(), "SIGTERM");
309  }
310
311  #[test]
312  fn signal_display_realtime_variants() {
313    let min = SIGRTMIN();
314
315    let sig_min = Signal::Realtime(min as u8);
316    assert_eq!(sig_min.to_string(), "SIGRTMIN");
317
318    let sig_plus = Signal::Realtime((min + 2) as u8);
319    assert_eq!(sig_plus.to_string(), "SIGRTMIN+2");
320
321    let sig_minus = Signal::Realtime((min - 1) as u8);
322    assert_eq!(sig_minus.to_string(), "SIGRTMIN-1");
323  }
324
325  /* ---------------- TracerBuilder ---------------- */
326  #[test]
327  #[should_panic(expected = "Cannot enable blocking mode")]
328  fn tracer_builder_blocking_conflict_panics() {
329    TracerBuilder::new()
330      .ptrace_polling_delay(Some(10))
331      .ptrace_blocking(true);
332  }
333
334  #[test]
335  #[should_panic(expected = "Cannot set ptrace_polling_delay")]
336  fn tracer_builder_polling_conflict_panics() {
337    TracerBuilder::new()
338      .ptrace_blocking(true)
339      .ptrace_polling_delay(Some(10));
340  }
341
342  #[test]
343  fn tracer_builder_chaining_works() {
344    let builder = TracerBuilder::new()
345      .ptrace_blocking(false)
346      .ptrace_polling_delay(None)
347      .seccomp_bpf(SeccompBpf::Auto);
348
349    assert_eq!(builder.ptrace_blocking, Some(false));
350    assert_eq!(builder.ptrace_polling_delay, None);
351  }
352
353  /* ---------------- ExecData ---------------- */
354
355  #[test]
356  fn exec_data_new_populates_fields() {
357    let filename = OutputMsg::Ok("bin".into());
358    let argv = Ok(vec![
359      OutputMsg::Ok("bin".into()),
360      OutputMsg::Ok("-h".into()),
361    ]);
362
363    let mut envp_map = BTreeMap::new();
364    envp_map.insert(OutputMsg::Ok("A".into()), OutputMsg::Ok("B".into()));
365    let envp = Ok(envp_map);
366
367    let cwd = OutputMsg::Ok("/".into());
368    let fdinfo = FileDescriptorInfoCollection::default();
369    let timestamp = Local::now();
370
371    let exec = ExecData::new(
372      filename.clone(),
373      argv,
374      envp,
375      false,
376      Err(CredInspectError::Inspect),
377      cwd.clone(),
378      None,
379      fdinfo,
380      timestamp,
381    );
382
383    assert_eq!(exec.filename, filename);
384    assert_eq!(exec.cwd, cwd);
385    assert!(exec.argv.is_ok());
386    assert!(exec.envp.is_ok());
387    assert!(!exec.has_dash_env);
388    assert!(exec.interpreters.is_none());
389    assert!(Arc::strong_count(&exec.argv) >= 1);
390    assert!(Arc::strong_count(&exec.envp) >= 1);
391    assert!(Arc::strong_count(&exec.fdinfo) >= 1);
392  }
393
394  /* ---------------- ProcessExit ---------------- */
395
396  #[test]
397  fn process_exit_equality() {
398    let a = ProcessExit::Code(0);
399    let b = ProcessExit::Code(0);
400    let c = ProcessExit::Code(1);
401
402    assert_eq!(a, b);
403    assert_ne!(a, c);
404
405    let s1 = ProcessExit::Signal(Signal::Standard(NixSignal::SIGKILL));
406    let s2 = ProcessExit::Signal(Signal::Standard(NixSignal::SIGKILL));
407    let s3 = ProcessExit::Signal(Signal::Standard(NixSignal::SIGTERM));
408
409    assert_eq!(s1, s2);
410    assert_ne!(s1, s3);
411  }
412}