Skip to main content

endpoint_sec/event/
event_exec.rs

1//! [`EventExec`]
2
3use std::ffi::OsStr;
4
5#[cfg(feature = "macos_13_0_0")]
6use endpoint_sec_sys::{cpu_subtype_t, cpu_type_t};
7use endpoint_sec_sys::{es_event_exec_t, es_exec_arg, es_exec_arg_count, es_exec_env, es_exec_env_count};
8#[cfg(feature = "macos_11_0_0")]
9use endpoint_sec_sys::{es_exec_fd, es_exec_fd_count, es_fd_t, ShouldNotBeNull};
10
11#[cfg(feature = "macos_10_15_1")]
12use crate::File;
13use crate::Process;
14
15/// A process execution event.
16#[doc(alias = "es_event_exec_t")]
17pub struct EventExec<'a> {
18    /// The raw reference.
19    pub(crate) raw: &'a es_event_exec_t,
20
21    /// The version of the message.
22    pub(crate) version: u32,
23}
24
25/// Describe an open file descriptor.
26#[doc(alias = "es_fd_t")]
27#[cfg(feature = "macos_11_0_0")]
28pub struct Fd<'a>(pub(crate) &'a es_fd_t);
29
30impl<'a> EventExec<'a> {
31    /// The new process that is being executed.
32    #[inline(always)]
33    pub fn target(&self) -> Process<'a> {
34        // Safety: 'a tied to self, object obtained through ES
35        Process::new(unsafe { self.raw.target() }, self.version)
36    }
37
38    /// The exec path passed up to dyld, before symlink resolution. This is the path argument
39    /// to `execve(2)` or `posix_spawn(2)`, or the interpreter from the shebang line for scripts run
40    /// through the shell script image activator.
41    ///
42    /// Present on version 7 and later.
43    #[inline(always)]
44    #[cfg(feature = "macos_13_3_0")]
45    pub fn dyld_exec_path(&self) -> Option<&'a OsStr> {
46        if self.version >= 7 {
47            // Safety: 'a tied to self, object obtained through ES
48            Some(unsafe { self.raw.dyld_exec_path.as_os_str() })
49        } else {
50            None
51        }
52    }
53
54    /// Script being executed by interpreter (if present) on version 2 and later, otherwise None.
55    ///
56    /// **Warning**: This only work if a script was executed directly and not as an argument to the
57    /// interpreter (e.g. `./foo.sh` not `/bin/sh ./foo.sh`)
58    #[cfg(feature = "macos_10_15_1")]
59    #[inline(always)]
60    pub fn script(&self) -> Option<File<'a>> {
61        if self.version >= 2 {
62            // Safety: Safe as we check the version before accessing the field.
63            let script_ptr = unsafe { self.raw.anon_0.anon_0.script };
64
65            // Safety: Safe as File cannot outlive self and ES is supposed to give us an aligned
66            // and valid pointer if non-null.
67            Some(File::new(unsafe { script_ptr.as_ref()? }))
68        } else {
69            None
70        }
71    }
72
73    /// Current working directory at exec time (if present) on version 3 and later, otherwise None.
74    #[inline(always)]
75    #[cfg(feature = "macos_10_15_4")]
76    pub fn cwd(&self) -> Option<File<'a>> {
77        if self.version >= 3 {
78            // Safety: Safe as File cannot outlive self and as we check the version before accessing the field.
79            Some(File::new(unsafe { self.raw.anon_0.anon_0.cwd.as_ref() }))
80        } else {
81            None
82        }
83    }
84
85    /// Highest open file descriptor after the exec completed (if present) on version 4 and later, otherwise None.
86    #[inline(always)]
87    #[cfg(feature = "macos_11_0_0")]
88    pub fn last_fd(&self) -> Option<i32> {
89        if self.version >= 4 {
90            // Safety: Safe as we check the version before accessing the field.
91            Some(unsafe { self.raw.anon_0.anon_0.last_fd })
92        } else {
93            None
94        }
95    }
96
97    /// Get the number of arguments associated to the [`EventExec`].
98    #[inline(always)]
99    pub fn arg_count(&self) -> u32 {
100        // Safety: Safe as raw is a reference and therefore cannot be null and the data comes from
101        // ES: if it's not valid, there isn't anything we can do to detect it
102        unsafe { es_exec_arg_count(self.raw) }
103    }
104
105    /// Get the number of environment variables associated to the [`EventExec`].
106    #[inline(always)]
107    pub fn env_count(&self) -> u32 {
108        // Safety: Safe as raw is a reference and therefore cannot be null and the data comes from
109        // ES: if it's not valid, there isn't anything we can do to detect it
110        unsafe { es_exec_env_count(self.raw) }
111    }
112
113    /// Get the number of file descriptors associated to the [`EventExec`].
114    #[inline(always)]
115    #[cfg(feature = "macos_11_0_0")]
116    pub fn fd_count(&self) -> u32 {
117        // Safety: Safe as raw is a reference and therefore cannot be null and the data comes from
118        // ES: if it's not valid, there isn't anything we can do to detect it
119        unsafe { es_exec_fd_count(self.raw) }
120    }
121
122    /// Get the argument at the specified position on the associated [`EventExec`].
123    #[inline(always)]
124    pub fn arg(&self, index: u32) -> Option<&'a OsStr> {
125        self.args().nth(index as _)
126    }
127
128    /// Get the environment variable at the specified position on the associated [`EventExec`].
129    #[inline(always)]
130    pub fn env(&self, index: u32) -> Option<&'a OsStr> {
131        self.envs().nth(index as _)
132    }
133
134    /// Get the file descriptor at the specified position on the associated [`EventExec`].
135    #[inline(always)]
136    #[cfg(feature = "macos_11_0_0")]
137    pub fn fd(&self, index: u32) -> Option<Fd<'a>> {
138        self.fds().nth(index as _)
139    }
140
141    /// Iterator over the arguments
142    #[inline(always)]
143    pub fn args<'event>(&'event self) -> ExecArgs<'event, 'a> {
144        ExecArgs::new(self)
145    }
146
147    /// Iterator over the environment
148    #[inline(always)]
149    pub fn envs<'event>(&'event self) -> ExecEnvs<'event, 'a> {
150        ExecEnvs::new(self)
151    }
152
153    /// Iterator over the file descriptors
154    #[inline(always)]
155    #[cfg(feature = "macos_11_0_0")]
156    pub fn fds<'event>(&'event self) -> ExecFds<'event, 'a> {
157        ExecFds::new(self)
158    }
159
160    /// CPU type of the executable image which is being executed, present on version 6 or later.
161    #[inline(always)]
162    #[cfg(feature = "macos_13_0_0")]
163    pub fn image_cputype(&self) -> Option<cpu_type_t> {
164        if self.version >= 6 {
165            // Safety: Safe as we check the version before accessing the field.
166            Some(unsafe { self.raw.anon_0.anon_0.image_cputype })
167        } else {
168            None
169        }
170    }
171
172    /// CPU subtype of the executable image, present on version 6 or later.
173    #[inline(always)]
174    #[cfg(feature = "macos_13_0_0")]
175    pub fn image_cpusubtype(&self) -> Option<cpu_subtype_t> {
176        if self.version >= 6 {
177            // Safety: Safe as we check the version before accessing the field.
178            Some(unsafe { self.raw.anon_0.anon_0.image_cpusubtype })
179        } else {
180            None
181        }
182    }
183
184    /// Collect the argument for debug
185    fn all_args(&self) -> Vec<&'a OsStr> {
186        self.args().collect()
187    }
188}
189
190// Safety: safe to send across threads: does not contain any interior mutability nor depend on current thread state
191unsafe impl Send for EventExec<'_> {}
192// Safety: safe to share across threads: does not contain any interior mutability nor depend on current thread state
193unsafe impl Sync for EventExec<'_> {}
194
195// This will expose all arguments, env variables and file descriptors.
196impl_debug_eq_hash_with_functions!(EventExec<'a> with version;
197    #[cfg(feature = "macos_11_0_0")]
198    cwd,
199    all_args,
200    #[cfg(feature = "macos_10_15_1")]
201    script,
202    target,
203    #[cfg(feature = "macos_11_0_0")]
204    last_fd,
205    arg_count,
206    env_count,
207    #[cfg(feature = "macos_11_0_0")]
208    fd_count,
209    #[cfg(feature = "macos_13_0_0")]
210    image_cputype,
211    #[cfg(feature = "macos_13_0_0")]
212    image_cpusubtype,
213);
214
215#[cfg(feature = "macos_11_0_0")]
216impl Fd<'_> {
217    /// File descriptor number
218    #[inline(always)]
219    pub fn fd(&self) -> i32 {
220        self.0.fd
221    }
222
223    /// File descriptor type, as libproc fdtype
224    #[inline(always)]
225    pub fn fdtype(&self) -> u32 {
226        self.0.fdtype
227    }
228
229    /// Unique id of the pipe for correlation with other file descriptors pointing to the same or
230    /// other end of the same pipe.
231    ///
232    /// **Note**: This is only valid when `fdtype == PROX_FDTYPE_PIPE`, otherwise this return None.
233    #[inline(always)]
234    pub fn pipe_id(&self) -> Option<u64> {
235        // Safety: 'a tied to self, object obtained through ES
236        unsafe { self.0.pipe() }.map(|x| x.pipe_id)
237    }
238}
239
240#[cfg(feature = "macos_11_0_0")]
241// Safety: safe to send across threads: does not contain any interior mutability nor depend on current thread state
242unsafe impl Send for Fd<'_> {}
243
244#[cfg(feature = "macos_11_0_0")]
245impl_debug_eq_hash_with_functions!(Fd<'a>; fd, fdtype, pipe_id);
246
247make_event_data_iterator!(
248    EventExec;
249    /// Iterator over the arguments of an [`EventExec`]
250    ExecArgs with arg_count (u32);
251    &'raw OsStr;
252    es_exec_arg,
253    super::as_os_str,
254);
255
256make_event_data_iterator!(
257    EventExec;
258    /// Iterator over the environment of an [`EventExec`]
259    ExecEnvs with env_count (u32);
260    &'raw OsStr;
261    es_exec_env,
262    super::as_os_str,
263);
264
265/// Helper to declare the [`ExecFds`] iterator
266///
267/// Safety: safe if `fd` is aligned, non-null of the correct type
268#[cfg(feature = "macos_11_0_0")]
269unsafe fn make_fd<'a>(fd: ShouldNotBeNull<es_fd_t>) -> Fd<'a> {
270    // Safety: see above
271    unsafe { Fd(fd.as_ref()) }
272}
273
274#[cfg(feature = "macos_11_0_0")]
275make_event_data_iterator!(
276    EventExec;
277    /// Iterator over the file descriptors of an [`EventExec`]
278    ExecFds with fd_count (u32);
279    Fd<'raw>;
280    es_exec_fd,
281    make_fd,
282);