endpoint_sec/
lib.rs

1//! Safe bindings for the [Endpoint Security Framework][esf] for Apple targets (macOS).
2//!
3//! The [`sys`] module contains the raw bindings since several types are publicly exported from there.
4//!
5//! At runtime, users should call [`version::set_runtime_version()`] before anything else, to indicate
6//! on which macOS version the app is running on.
7//!
8//! The entry point is the [`Client`] type, which is a wrapper around [`es_client_t`][sys::es_client_t],
9//! with the [`Client::new()`] method.
10//!
11//! After a `Client` has been created, [events][sys::es_event_type_t] can be subscribed to
12//! using [`Client::subscribe()`]. Each time Endpoint Security gets an event that is part of the
13//! subscribptions for your client, it will call the handler that was given to `Client::new()` with
14//! the [message][Message] associated to the event. Note that `AUTH` events have an associated
15//! deadline before which your handler must give a response else your client may be killed by macOS
16//! to avoid stalling for the user.
17//!
18//! [esf]: https://developer.apple.com/documentation/endpointsecurity
19
20#![cfg(target_os = "macos")]
21#![cfg_attr(docsrs, feature(doc_cfg, doc_auto_cfg))]
22#![allow(clippy::bool_comparison)]
23#![warn(
24    missing_docs,
25    unused_crate_dependencies,
26    clippy::missing_safety_doc,
27    unreachable_pub,
28    clippy::missing_docs_in_private_items,
29    rustdoc::bare_urls,
30    rustdoc::broken_intra_doc_links
31)]
32
33// Reexports [`endpoint_sec_sys`]
34pub use endpoint_sec_sys as sys;
35#[cfg(all(test, not(feature = "audit_token_from_pid")))]
36use sysinfo as _;
37#[cfg(test)]
38use trybuild as _;
39
40/// Our wrappers around Endpoint Security events cannot easily implement [`Debug`]: they contain
41/// a reference to the original value (and sometimes the version). Since the structs behind the
42/// references can change shape based on macOS' versions, we cannot rely on the one compiled in
43/// our Rust bindings. What's more, some fields are only available in some versions of Endpoint
44/// Security, others are behind pointers and associated with another (eg, array + len). This macro
45/// makes it easier to implement [`Debug`] by simply passing the type and the functions to use for
46/// the `Debug` impl. See examples of usage in the modules below.
47macro_rules! impl_debug_eq_hash_with_functions {
48    ($ty:ident$(<$lt: lifetime>)? $(with $version:ident)?; $($(#[$fmeta: meta])? $fname:ident),* $(,)?) =>  {
49        impl $(<$lt>)? ::core::fmt::Debug for $ty $(<$lt>)? {
50            fn fmt(&self, f: &mut ::core::fmt::Formatter<'_>) -> ::core::fmt::Result {
51                let mut d = f.debug_struct(::core::stringify!($ty));
52                $( d.field("version", &self.$version); )?
53                $( $(#[$fmeta])? d.field(::core::stringify!($fname), &self.$fname()); )*
54                d.finish()
55            }
56        }
57
58        impl $(<$lt>)? ::core::cmp::PartialEq for $ty $(<$lt>)? {
59            #[allow(unused_variables)]
60            fn eq(&self, other: &Self) -> bool {
61                $( if ::core::cmp::PartialEq::ne(&self.$version, &other.$version) { return false } )?
62                $( $(#[$fmeta])? if ::core::cmp::PartialEq::ne(&self.$fname(), &other.$fname()) { return false; } )*
63                true
64            }
65        }
66
67        impl $(<$lt>)? ::core::cmp::Eq for $ty $(<$lt>)? {}
68
69        impl $(<$lt>)? ::core::hash::Hash for $ty $(<$lt>)? {
70            #[allow(unused_variables)]
71            fn hash<H: ::core::hash::Hasher>(&self, state: &mut H) {
72                $( ::core::hash::Hash::hash(&self.$version, state); )?
73                $( $(#[$fmeta])? ::core::hash::Hash::hash(&self.$fname(), state); )*
74            }
75        }
76
77    };
78}
79
80/// Helper macro to generate all necessary version checks for a function call.
81macro_rules! versioned_call {
82    // It's not possible to use `feature = ::std::concat(...)` so we need to pass both forms.
83    (if cfg!($cfg: meta) && version >= ($major:literal, $minor:literal, $patch:literal) { $($if_tt:tt)* } $(else { $($else_tt:tt)* })?) => {
84        if ::std::cfg!($cfg) && $crate::version::is_version_or_more($major, $minor, $patch) {
85            #[cfg($cfg)]
86            { $($if_tt)* }
87            #[cfg(not($cfg))]
88            // Safety: the cfg was checked just above
89            unsafe { ::std::hint::unreachable_unchecked() }
90        } $( else {
91            $($else_tt)*
92        } )?
93    };
94}
95
96// Publicly reexported modules
97#[cfg(feature = "macos_10_15_1")]
98mod acl;
99mod action;
100mod audit;
101mod client;
102mod event;
103mod message;
104mod mute;
105// Not public
106mod utils;
107
108#[cfg(feature = "macos_10_15_1")]
109pub use acl::*;
110pub use action::*;
111pub use audit::*;
112pub use client::*;
113pub use event::*;
114pub use message::*;
115pub use mute::*;
116
117/// Helper module to avoid implementing version detection in this crate and make testing easier
118/// by telling the crate its on a lower version than the real one.
119pub mod version {
120    use std::sync::atomic::{AtomicU64, Ordering};
121
122    /// macOS major version
123    static MAJOR: AtomicU64 = AtomicU64::new(10);
124    /// macOS minor version
125    static MINOR: AtomicU64 = AtomicU64::new(15);
126    /// macOS patch version
127    static PATCH: AtomicU64 = AtomicU64::new(0);
128
129    /// Setup the runtime version of macOS, detected outside of this library.
130    ///
131    /// Conservatively, this library assumes the default is 10.15.0 and will refuse to use functions
132    /// that only became available in later version of macOS and Endpoint Security.
133    ///
134    /// Methods on [`Client`][super::Client] will check this version when calling a function only
135    /// available in macOS 11+ for example.
136    ///
137    /// # Panics
138    ///
139    /// Will panic if attempting to set a version below 10.15.0.
140    pub fn set_runtime_version(major: u64, minor: u64, patch: u64) {
141        if major < 10 || (major == 10 && minor < 15) {
142            panic!("Endpoint Security cannot run on versions inferiors to 10.15.0");
143        }
144
145        MAJOR.store(major, Ordering::Release);
146        MINOR.store(minor, Ordering::Release);
147        PATCH.store(patch, Ordering::Release);
148    }
149
150    /// `true` if the version setup in [`set_runtime_version()`] is at least the given
151    /// `major.minor.patch` here.
152    pub fn is_version_or_more(major: u64, minor: u64, patch: u64) -> bool {
153        let current_major = MAJOR.load(Ordering::Acquire);
154        let current_minor = MINOR.load(Ordering::Acquire);
155        let current_patch = PATCH.load(Ordering::Acquire);
156
157        (current_major, current_minor, current_patch) >= (major, minor, patch)
158    }
159
160    #[cfg(test)]
161    mod tests {
162        use super::*;
163
164        #[test]
165        #[should_panic(expected = "Endpoint Security cannot run on versions inferiors to 10.15.0")]
166        fn test_cannot_set_version_major_under_10() {
167            set_runtime_version(9, 16, 2);
168        }
169
170        #[test]
171        #[should_panic(expected = "Endpoint Security cannot run on versions inferiors to 10.15.0")]
172        fn test_cannot_set_version_minor_under_10_15() {
173            set_runtime_version(10, 14, 0);
174        }
175
176        #[test]
177        fn test_is_version_or_more_with_set_runtime() {
178            set_runtime_version(10, 15, 0);
179
180            assert!(is_version_or_more(10, 14, 99));
181            assert!(is_version_or_more(9, 15, 0));
182            assert!(is_version_or_more(9, 14, 1));
183            assert!(is_version_or_more(10, 15, 0));
184
185            assert!(!is_version_or_more(12, 3, 1));
186            assert!(!is_version_or_more(13, 3, 2));
187            assert!(!is_version_or_more(14, 5, 4));
188            assert!(!is_version_or_more(15, 0, 0));
189
190            set_runtime_version(13, 3, 2);
191
192            assert!(is_version_or_more(12, 3, 1));
193            assert!(is_version_or_more(13, 3, 2));
194            assert!(!is_version_or_more(14, 5, 4));
195            assert!(!is_version_or_more(15, 0, 0));
196        }
197    }
198}