Skip to main content

endpoint_sec/
audit.rs

1//! Expose a wrapper around [`audit_token_t`]: [`AuditToken`]
2
3use std::fmt;
4#[cfg(feature = "audit_token_from_pid")]
5use std::mem;
6
7use endpoint_sec_sys::{
8    au_asid_t, audit_token_t, audit_token_to_asid, audit_token_to_auid, audit_token_to_egid, audit_token_to_euid,
9    audit_token_to_pid, audit_token_to_pidversion, audit_token_to_rgid, audit_token_to_ruid, gid_t, pid_t, uid_t,
10};
11#[cfg(feature = "audit_token_from_pid")]
12use libc::{c_int, KERN_SUCCESS};
13#[cfg(feature = "audit_token_from_pid")]
14use mach2::kern_return::kern_return_t;
15#[cfg(feature = "audit_token_from_pid")]
16use mach2::port::mach_port_name_t;
17#[cfg(feature = "audit_token_from_pid")]
18use mach2::task_info::TASK_AUDIT_TOKEN;
19
20/// A wrapper around an [`audit_token_t`].
21#[derive(Clone, Copy)]
22#[doc(alias = "audit_token_t")]
23#[repr(transparent)]
24pub struct AuditToken(pub audit_token_t);
25
26impl fmt::LowerHex for AuditToken {
27    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
28        for v in self.0.val {
29            fmt::LowerHex::fmt(&v, f)?;
30        }
31
32        Ok(())
33    }
34}
35
36impl fmt::UpperHex for AuditToken {
37    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
38        for v in self.0.val {
39            fmt::UpperHex::fmt(&v, f)?;
40        }
41
42        Ok(())
43    }
44}
45
46/// Endpoint Security wrappers and test helpers
47impl AuditToken {
48    /// Get the [`AuditToken`] for the given PID, if it exists.
49    ///
50    /// Endpoint Security does not currently provide a way to get the audit tokens of processes
51    /// already existing when first connecting a client. However, it is relatively easy to list
52    /// the PIDs of the current processes. This function therefore enables to bridge this gap.
53    ///
54    /// Errors from the underlying system calls are returned directly. Although it is almost
55    /// certain that only the catch-all `KERN_FAILURE` (5) will ever be observed in practice, this
56    /// should still be useful in order to emphasize that the unexpected case should be accounted
57    /// for instead of just discarded, for example to then log.
58    ///
59    /// ## Implementation details
60    ///
61    /// Currently this method is implemented following the method [described here][method], with
62    /// calls to `task_name_for_pid` and `task_info(_, TASK_AUDIT_TOKEN, _, _)` but the first
63    /// function is marked as *obsolete* in the header containing it in macOS's SDK.
64    ///
65    /// Other possibilities could be `task_for_pid()` or `task_inspect_for_pid()`. For now, the
66    /// current implementation is the most backwards and forwards compatible considering
67    /// `task_for_pid()` now concretely requires SIP to be disabled, which thus makes it pretty
68    /// much unusable. If you find a bug/need us to use a more recent method, please signal it.
69    ///
70    /// [method]: https://developer.apple.com/forums/thread/652363
71    #[cfg(feature = "audit_token_from_pid")]
72    pub fn from_pid(pid: pid_t) -> Result<Self, kern_return_t> {
73        Ok(Self(mach_task_audit_token(mach_task_name(pid)?)?))
74    }
75
76    /// Raw underlying audit token.
77    #[inline]
78    pub fn raw_token(&self) -> &audit_token_t {
79        &self.0
80    }
81
82    /// The audit user ID.
83    ///
84    /// **NOTE**: Used to identify Mach tasks and senders of Mach messages as subjects of the audit system.
85    #[inline(always)]
86    pub fn auid(&self) -> uid_t {
87        // Safety: The audit_token_t is owned by self.
88        unsafe { audit_token_to_auid(self.0) }
89    }
90
91    /// The effective user ID.
92    ///
93    /// **NOTE**: Used to identify Mach tasks and senders of Mach messages as subjects of the audit system.
94    #[inline(always)]
95    pub fn euid(&self) -> uid_t {
96        // Safety: The audit_token_t is owned by self.
97        unsafe { audit_token_to_euid(self.0) }
98    }
99
100    /// The effective group ID.
101    ///
102    /// **NOTE**: Used to identify Mach tasks and senders of Mach messages as subjects of the audit system.
103    #[inline(always)]
104    pub fn egid(&self) -> gid_t {
105        // Safety: The audit_token_t is owned by self.
106        unsafe { audit_token_to_egid(self.0) }
107    }
108
109    /// The real user ID.
110    ///
111    /// **NOTE**: Used to identify Mach tasks and senders of Mach messages as subjects of the audit system.
112    #[inline(always)]
113    pub fn ruid(&self) -> uid_t {
114        // Safety: The audit_token_t is owned by self.
115        unsafe { audit_token_to_ruid(self.0) }
116    }
117
118    /// The real group ID.
119    ///
120    /// **NOTE**: Used to identify Mach tasks and senders of Mach messages as subjects of the audit system.
121    #[inline(always)]
122    pub fn rgid(&self) -> gid_t {
123        // Safety: The audit_token_t is owned by self.
124        unsafe { audit_token_to_rgid(self.0) }
125    }
126
127    /// The process ID.
128    ///
129    /// **NOTE**: Used to identify Mach tasks and senders of Mach messages as subjects of the audit system.
130    #[inline(always)]
131    pub fn pid(&self) -> pid_t {
132        // Safety: The audit_token_t is owned by self.
133        unsafe { audit_token_to_pid(self.0) }
134    }
135
136    /// The audit session ID.
137    ///
138    /// **NOTE**: Used to identify Mach tasks and senders of Mach messages as subjects of the audit system.
139    #[inline(always)]
140    pub fn asid(&self) -> au_asid_t {
141        // Safety: The audit_token_t is owned by self.
142        unsafe { audit_token_to_asid(self.0) }
143    }
144
145    /// The process ID version.
146    ///
147    /// **NOTE**: Used to identify Mach tasks and senders of Mach messages as subjects of the audit system.
148    #[inline(always)]
149    pub fn pidversion(&self) -> i32 {
150        // Safety: The audit_token_t is owned by self.
151        unsafe { audit_token_to_pidversion(self.0) }
152    }
153}
154
155/// Crate-private methods
156impl AuditToken {
157    /// Create a new [`AuditToken`] from [`audit_token_t`].
158    #[inline(always)]
159    pub(crate) fn new(token: audit_token_t) -> Self {
160        AuditToken(token)
161    }
162
163    /// Allow to grab a reference out of the stored token.
164    #[inline(always)]
165    pub(crate) fn get_raw_ref(&self) -> &audit_token_t {
166        &self.0
167    }
168}
169
170#[cfg(feature = "static_assertions")]
171static_assertions::assert_impl_all!(AuditToken: Send);
172
173impl_debug_eq_hash_with_functions!(
174    AuditToken;
175    auid,
176    euid,
177    egid,
178    ruid,
179    rgid,
180    pid,
181    asid,
182    pidversion,
183);
184
185/// Safe wrapper around [`task_name_for_pid`].
186#[cfg(feature = "audit_token_from_pid")]
187fn mach_task_name(pid: pid_t) -> Result<mach_port_name_t, kern_return_t> {
188    let mut task_name = mach_port_name_t::default();
189
190    // SAFETY:
191    //  * `mach_task_self` is always safe to call: resolves a static variable;
192    //  * `task_name` is mutable and of the correct type so the reference is
193    //    aligned and points to initialized memory;
194    //  * errors are checked for below;
195    let res = unsafe { task_name_for_pid(mach2::traps::mach_task_self(), pid, &mut task_name) };
196
197    if res == KERN_SUCCESS {
198        Ok(task_name)
199    } else {
200        Err(res)
201    }
202}
203
204/// Safe wrapper around [`libc::task_info`] specialized for [`TASK_AUDIT_TOKEN`].
205#[cfg(feature = "audit_token_from_pid")]
206fn mach_task_audit_token(task_name: mach_port_name_t) -> Result<audit_token_t, kern_return_t> {
207    let mut audit_token = audit_token_t::default();
208    let mut audit_token_size = mem::size_of_val(&audit_token.val) as u32;
209
210    // SAFETY:
211    //  * `task_name` is initialized;
212    //  * `audit_token` is mutable and of the correct type so the reference
213    //    is aligned and points to initialized memory, its type is in sync
214    //    with `TASK_AUDIT_TOKEN` and `audit_token_size` is its size in bytes;
215    //  * errors are checked for below;
216    let res = unsafe {
217        libc::task_info(
218            task_name,
219            TASK_AUDIT_TOKEN,
220            audit_token.val.as_mut_ptr().cast(),
221            &mut audit_token_size,
222        )
223    };
224
225    if res == KERN_SUCCESS {
226        Ok(audit_token)
227    } else {
228        Err(res)
229    }
230}
231
232#[cfg(feature = "audit_token_from_pid")]
233extern "C" {
234    // TODO: Replace with the one from `mach2::traps` when
235    // https://github.com/JohnTitor/mach2/pull/71 is merged and released.
236    fn task_name_for_pid(target_tport: mach_port_name_t, pid: c_int, tn: *mut mach_port_name_t) -> kern_return_t;
237}
238
239#[cfg(test)]
240#[cfg(feature = "audit_token_from_pid")]
241mod test {
242    use sysinfo::{PidExt, ProcessExt, ProcessRefreshKind, RefreshKind, System, SystemExt};
243
244    use super::*;
245
246    #[test]
247    fn audit_token_from_pid() {
248        let s = System::new_with_specifics(RefreshKind::new().with_processes(ProcessRefreshKind::everything()));
249
250        for (pid, process) in s.processes() {
251            let audit_token = AuditToken::from_pid(pid.as_u32() as pid_t).unwrap();
252
253            assert_eq!(process.user_id().map_or(0, |x| **x), audit_token.euid());
254            assert_eq!(process.group_id().map_or(0, |x| *x), audit_token.egid());
255            assert_eq!(process.pid().as_u32(), audit_token.pid() as u32);
256        }
257    }
258}