tracexec_core/
tracer.rs

1use chrono::{DateTime, Local};
2use enumflags2::BitFlags;
3use nix::{
4  errno::Errno,
5  libc::{SIGRTMIN, c_int},
6  unistd::User,
7};
8use tokio::sync::mpsc::UnboundedSender;
9
10use crate::{
11  cli::{
12    args::{LogModeArgs, ModifierArgs},
13    options::SeccompBpf,
14  },
15  event::{TracerEventDetailsKind, TracerMessage},
16  printer::{Printer, PrinterArgs},
17  proc::{BaselineInfo, Cred, CredInspectError},
18  pty::UnixSlavePty,
19};
20use std::{collections::BTreeMap, fmt::Display, sync::Arc};
21
22use crate::{
23  event::OutputMsg,
24  proc::{FileDescriptorInfoCollection, Interpreter},
25};
26
27pub type InspectError = Errno;
28
29#[derive(Debug, Clone, Copy, PartialEq, Eq)]
30pub enum Signal {
31  Standard(nix::sys::signal::Signal),
32  Realtime(u8), // u8 is enough for Linux
33}
34
35impl Signal {
36  pub fn from_raw(raw: c_int) -> Self {
37    match nix::sys::signal::Signal::try_from(raw) {
38      Ok(sig) => Self::Standard(sig),
39      // libc might reserve some RT signals for itself.
40      // But from a tracer's perspective we don't need to care about it.
41      // So here no validation is done for the RT signal value.
42      Err(_) => Self::Realtime(raw as u8),
43    }
44  }
45
46  pub fn as_raw(self) -> i32 {
47    match self {
48      Self::Standard(signal) => signal as i32,
49      Self::Realtime(raw) => raw as i32,
50    }
51  }
52}
53
54impl From<nix::sys::signal::Signal> for Signal {
55  fn from(value: nix::sys::signal::Signal) -> Self {
56    Self::Standard(value)
57  }
58}
59
60impl Display for Signal {
61  fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
62    match self {
63      Self::Standard(signal) => signal.fmt(f),
64      Self::Realtime(sig) => {
65        let min = SIGRTMIN();
66        let delta = *sig as i32 - min;
67        match delta.signum() {
68          0 => write!(f, "SIGRTMIN"),
69          1 => write!(f, "SIGRTMIN+{delta}"),
70          -1 => write!(f, "SIGRTMIN{delta}"),
71          _ => unreachable!(),
72        }
73      }
74    }
75  }
76}
77
78#[derive(Default)]
79#[non_exhaustive]
80pub struct TracerBuilder {
81  pub user: Option<User>,
82  pub modifier: ModifierArgs,
83  pub mode: Option<TracerMode>,
84  pub filter: Option<BitFlags<TracerEventDetailsKind>>,
85  pub tx: Option<UnboundedSender<TracerMessage>>,
86  // TODO: remove this.
87  pub printer: Option<Printer>,
88  pub baseline: Option<Arc<BaselineInfo>>,
89  // --- ptrace specific ---
90  pub seccomp_bpf: SeccompBpf,
91  pub ptrace_polling_delay: Option<u64>,
92  pub ptrace_blocking: Option<bool>,
93}
94
95impl TracerBuilder {
96  /// Initialize a new [`TracerBuilder`]
97  pub fn new() -> Self {
98    Default::default()
99  }
100
101  /// Use blocking waitpid calls instead of polling.
102  ///
103  /// This mode conflicts with ptrace polling delay option
104  /// This option is not used in eBPF tracer.
105  pub fn ptrace_blocking(mut self, enable: bool) -> Self {
106    if self.ptrace_polling_delay.is_some() && enable {
107      panic!(
108        "Cannot enable blocking mode when ptrace polling delay implicitly specifys polling mode"
109      );
110    }
111    self.ptrace_blocking = Some(enable);
112    self
113  }
114
115  /// Sets ptrace polling delay (in microseconds)
116  /// This options conflicts with ptrace blocking mode.
117  ///
118  /// This option is not used in eBPF tracer.
119  pub fn ptrace_polling_delay(mut self, ptrace_polling_delay: Option<u64>) -> Self {
120    if Some(true) == self.ptrace_blocking && ptrace_polling_delay.is_some() {
121      panic!("Cannot set ptrace_polling_delay when operating in blocking mode")
122    }
123    self.ptrace_polling_delay = ptrace_polling_delay;
124    self
125  }
126
127  /// Sets seccomp-bpf mode for ptrace tracer
128  ///
129  /// Default to auto.
130  /// This option is not used in eBPF tracer.
131  pub fn seccomp_bpf(mut self, seccomp_bpf: SeccompBpf) -> Self {
132    self.seccomp_bpf = seccomp_bpf;
133    self
134  }
135
136  /// Sets the `User` used when spawning the command.
137  ///
138  /// Default to current user.
139  pub fn user(mut self, user: Option<User>) -> Self {
140    self.user = user;
141    self
142  }
143
144  pub fn modifier(mut self, modifier: ModifierArgs) -> Self {
145    self.modifier = modifier;
146    self
147  }
148
149  /// Sets the mode for the trace e.g. TUI or Log
150  pub fn mode(mut self, mode: TracerMode) -> Self {
151    self.mode = Some(mode);
152    self
153  }
154
155  /// Sets a filter for wanted tracer events.
156  pub fn filter(mut self, filter: BitFlags<TracerEventDetailsKind>) -> Self {
157    self.filter = Some(filter);
158    self
159  }
160
161  /// Passes the tx part of tracer event channel
162  ///
163  /// By default this is not set and tracer will not send events.
164  pub fn tracer_tx(mut self, tx: UnboundedSender<TracerMessage>) -> Self {
165    self.tx = Some(tx);
166    self
167  }
168
169  pub fn printer(mut self, printer: Printer) -> Self {
170    self.printer = Some(printer);
171    self
172  }
173
174  /// Create a printer from CLI options,
175  ///
176  /// Requires `modifier` and `baseline` to be set before calling.
177  pub fn printer_from_cli(mut self, tracing_args: &LogModeArgs) -> Self {
178    self.printer = Some(Printer::new(
179      PrinterArgs::from_cli(tracing_args, &self.modifier),
180      self.baseline.clone().unwrap(),
181    ));
182    self
183  }
184
185  pub fn baseline(mut self, baseline: Arc<BaselineInfo>) -> Self {
186    self.baseline = Some(baseline);
187    self
188  }
189}
190
191#[derive(Debug)]
192pub struct ExecData {
193  pub filename: OutputMsg,
194  pub argv: Arc<Result<Vec<OutputMsg>, InspectError>>,
195  pub envp: Arc<Result<BTreeMap<OutputMsg, OutputMsg>, InspectError>>,
196  pub has_dash_env: bool,
197  pub cred: Result<Cred, CredInspectError>,
198  pub cwd: OutputMsg,
199  pub interpreters: Option<Vec<Interpreter>>,
200  pub fdinfo: Arc<FileDescriptorInfoCollection>,
201  pub timestamp: DateTime<Local>,
202}
203
204impl ExecData {
205  #[allow(clippy::too_many_arguments)]
206  pub fn new(
207    filename: OutputMsg,
208    argv: Result<Vec<OutputMsg>, InspectError>,
209    envp: Result<BTreeMap<OutputMsg, OutputMsg>, InspectError>,
210    has_dash_env: bool,
211    cred: Result<Cred, CredInspectError>,
212    cwd: OutputMsg,
213    interpreters: Option<Vec<Interpreter>>,
214    fdinfo: FileDescriptorInfoCollection,
215    timestamp: DateTime<Local>,
216  ) -> Self {
217    Self {
218      filename,
219      argv: Arc::new(argv),
220      envp: Arc::new(envp),
221      has_dash_env,
222      cred,
223      cwd,
224      interpreters,
225      fdinfo: Arc::new(fdinfo),
226      timestamp,
227    }
228  }
229}
230
231pub enum TracerMode {
232  Tui(Option<UnixSlavePty>),
233  Log { foreground: bool },
234}
235
236impl PartialEq for TracerMode {
237  fn eq(&self, other: &Self) -> bool {
238    // I think a plain match is more readable here
239    #[allow(clippy::match_like_matches_macro)]
240    match (self, other) {
241      (Self::Log { foreground: a }, Self::Log { foreground: b }) => a == b,
242      _ => false,
243    }
244  }
245}
246
247#[derive(Debug, Clone, Copy, PartialEq, Eq)]
248pub enum ProcessExit {
249  Code(i32),
250  Signal(Signal),
251}