secmem_proc/
config.rs

1//! Module containing hardening configuration.
2use crate::components;
3use crate::error::Result;
4
5/// Configuration for the hardening procedure. The configuration allows to
6/// enable or disable certain features, such as filesystem access (e.g. for
7/// procfs), anti-tracing methods and to use a custom DACL on windows.
8pub struct Config {
9    anti_tracing: bool,
10    fs: Fs,
11    unstable: Unstable,
12    win_dacl: WinDacl,
13}
14
15impl Default for Config {
16    fn default() -> Self {
17        Self::DEFAULT
18    }
19}
20
21impl Config {
22    /// Default configuration.
23    pub const DEFAULT: Self = Self {
24        anti_tracing: true,
25        fs: Fs::DEFAULT,
26        unstable: Unstable::DEFAULT,
27        win_dacl: WinDacl::DEFAULT,
28    };
29
30    /// Create new default configuration, with anti-tracing set to
31    /// `anti_tracing`.
32    #[must_use]
33    pub const fn new_with_anti_tracing(anti_tracing: bool) -> Self {
34        Self {
35            anti_tracing,
36            fs: Fs::DEFAULT,
37            unstable: Unstable::DEFAULT,
38            win_dacl: WinDacl::DEFAULT,
39        }
40    }
41
42    /// Set anti-tracing to `b` (true means enabled).
43    pub fn set_anti_tracing(&mut self, b: bool) {
44        self.anti_tracing = b;
45    }
46
47    /// Set filesystem access to `b` (true means enabled).
48    pub fn set_fs(&mut self, b: bool) {
49        self.fs = match b {
50            true => Fs::TRUE,
51            false => Fs::FALSE,
52        };
53    }
54
55    /// Get mutable reference to filesystem access configuration, allowing to
56    /// modify it.
57    pub fn fs_mut(&mut self) -> &mut Fs {
58        &mut self.fs
59    }
60
61    /// Set procfs access to `b` (true means enabled).
62    pub fn set_fs_procfs(&mut self, b: bool) {
63        self.fs.set_procfs(b);
64    }
65
66    /// Set unstable hardening methods to `b` (true means enabled).
67    ///
68    /// Default is disabled (false). Note that the `unstable` crate feature is
69    /// required for this configuration to have any effect. Without that crate
70    /// feature, the value of this configuration is silently ignored, and
71    /// unstable hardening is not performed.
72    pub fn set_unstable(&mut self, b: bool) {
73        self.unstable = match b {
74            true => Unstable::TRUE,
75            false => Unstable::FALSE,
76        };
77    }
78
79    /// Get mutable reference to unstable hardening configuration, allowing to
80    /// modify it.
81    pub fn unstable_mut(&mut self) -> &mut Unstable {
82        &mut self.unstable
83    }
84
85    /// Set use of unstable windows native API to `b` (true means enabled).
86    ///
87    /// Default is disabled (false). Note that the `unstable` crate feature is
88    /// required for this configuration to have any effect. Without that crate
89    /// feature, the value of this configuration is silently ignored, and
90    /// unstable hardening is not performed.
91    pub fn set_unstable_win_ntapi(&mut self, b: bool) {
92        self.unstable.set_win_ntapi(b);
93    }
94
95    /// Set use of unstable windows hardening relying on shared kernel memory to
96    /// `b` (true means enabled).
97    ///
98    /// Default is disabled (false). Note that the `unstable` crate feature is
99    /// required for this configuration to have any effect. Without that crate
100    /// feature, the value of this configuration is silently ignored, and
101    /// unstable hardening is not performed.
102    pub fn set_unstable_win_kernelmem(&mut self, b: bool) {
103        self.unstable.set_win_kernelmem(b);
104    }
105
106    /// Configure a custom windows DACL `dacl` (for the process).
107    pub fn set_win_dacl(&mut self, dacl: WinDacl) {
108        self.win_dacl = dacl;
109    }
110
111    /// Configure the windows DAC (for the process)L as the default.
112    pub fn set_win_dacl_default(&mut self) {
113        self.set_win_dacl(WinDacl::Default);
114    }
115
116    /// Configure the windows DACL (for the process) as an empty DACL. This
117    /// means giving no access to any user at all. This is extremely strict. Use
118    /// with caution.
119    pub fn set_win_dacl_empty(&mut self) {
120        self.set_win_dacl(WinDacl::Empty);
121    }
122
123    /// Configure the windows DACL (for the process) as a DACL which gives
124    /// precisely the accesses specified by `access` to the current user, and no
125    /// access to any other user.
126    pub fn set_win_dacl_custom_user_perm(&mut self, access: WinDaclProcessAccess) {
127        self.set_win_dacl(WinDacl::CustomUserPerm(access));
128    }
129
130    /// Configure to, instead of setting a DACL (for the process) on windows,
131    /// call the function `fnptr`. This callback function `fnptr` can then be
132    /// used to set a custom DACL yourself, using the API in
133    /// [`crate::win_acl`].
134    pub fn set_win_dacl_custom_fn(&mut self, fnptr: fn() -> Result) {
135        self.set_win_dacl(WinDacl::CustomFn(fnptr));
136    }
137
138    /// Use the configuration `self` to harden the current process.
139    pub fn harden_process(self) -> Result {
140        // hide from debugger
141        {
142            if self.unstable.has_win_ntapi() {
143                #[cfg(all(windows, feature = "unstable"))]
144                components::hide_thread_from_debugger_ntapi()?;
145            }
146        }
147
148        // disable debugger attaching; set up memory security; don't dump
149        {
150            #[cfg(windows)]
151            self.win_dacl.call()?;
152
153            #[cfg(any(target_os = "linux", target_os = "freebsd", target_os = "macos"))]
154            components::disable_tracing_prctl()?;
155
156            #[cfg(unix)]
157            components::disable_core_dumps_rlimit()?;
158        }
159
160        // anti-tracing
161        if self.anti_tracing {
162            #[cfg(windows)]
163            components::check_tracer_winapi()?;
164
165            if self.unstable.has_win_kernelmem() {
166                #[cfg(all(windows, feature = "unstable"))]
167                components::check_tracer_unstable()?;
168            }
169
170            if self.fs.has_procfs() {
171                #[cfg(all(target_os = "linux", feature = "std"))]
172                components::check_tracer_procfs()?;
173            }
174
175            #[cfg(target_os = "freebsd")]
176            components::check_tracer_prctl()?;
177        }
178
179        Ok(())
180    }
181}
182
183/// Filesystem access configuration.
184#[derive(Clone, Debug, PartialEq)]
185pub struct Fs {
186    procfs: bool,
187}
188
189impl Default for Fs {
190    fn default() -> Self {
191        Self::DEFAULT
192    }
193}
194
195impl Fs {
196    /// Default filesystem configuration.
197    pub const DEFAULT: Self = Self::TRUE;
198    /// Disable all filesystem access.
199    pub const FALSE: Self = Self { procfs: false };
200    /// Enable all filesystem access.
201    pub const TRUE: Self = Self { procfs: true };
202
203    /// Set procfs access to `b` (true means enabled).
204    pub fn set_procfs(&mut self, b: bool) {
205        self.procfs = b;
206    }
207
208    /// Return whether procfs access is enabled.
209    const fn has_procfs(&self) -> bool {
210        self.procfs
211    }
212}
213
214/// Structure for configuring hardening methods which rely on undocumented or
215/// unstable target OS/platform details.
216///
217/// The default is to disable all. Note that the `unstable` crate feature is
218/// required for this configuration to have any effect. Without that crate
219/// feature, the value of this configuration is silently ignored.
220#[derive(Clone, Debug, PartialEq)]
221pub struct Unstable {
222    /// Windows native API.
223    win_ntapi: bool,
224    /// Windows shared kernel memory.
225    win_kernelmem: bool,
226}
227
228impl Default for Unstable {
229    fn default() -> Self {
230        Self::DEFAULT
231    }
232}
233
234impl Unstable {
235    /// Default unstable configuration. This is the same as [`Self::FALSE`].
236    pub const DEFAULT: Self = Self::FALSE;
237    /// Disable (all) unstable hardening methods.
238    pub const FALSE: Self = Self {
239        win_ntapi: false,
240        win_kernelmem: false,
241    };
242    /// Enable all unstable hardening methods.
243    pub const TRUE: Self = Self {
244        win_ntapi: true,
245        win_kernelmem: true,
246    };
247
248    /// Set unstable windows native API hardening methods to `b` (true means
249    /// enabled).
250    pub fn set_win_ntapi(&mut self, b: bool) {
251        self.win_ntapi = b;
252    }
253
254    /// Set unstable windows hardening methods relying on shared kernel memory
255    /// to `b` (true means enabled).
256    pub fn set_win_kernelmem(&mut self, b: bool) {
257        self.win_kernelmem = b;
258    }
259
260    /// Return whether windows native API methods are enabled.
261    const fn has_win_ntapi(&self) -> bool {
262        self.win_ntapi
263    }
264
265    /// Return whether shared kernel memory methods are enabled.
266    const fn has_win_kernelmem(&self) -> bool {
267        self.win_kernelmem
268    }
269}
270
271/// Custom windows DACL configuration.
272pub enum WinDacl {
273    /// The empty DACL. This means giving no access to any user at all. This is
274    /// extremely strict. Use with caution.
275    Empty,
276    /// The default DACL.
277    Default,
278    /// A DACL which gives precisely the accesses specified in the first tuple
279    /// position to the current user, and no access to any other user.
280    CustomUserPerm(WinDaclProcessAccess),
281    /// Don't set a DACL at all.
282    False,
283    /// Instead of setting a DACL, call the function in the first tuple
284    /// position. This callback function can then be used to set a custom DACL
285    /// yourself, using the API in [`crate::win_acl`].
286    CustomFn(fn() -> Result),
287}
288
289/// Cross-platform type for windows process access masks, used for setting a
290/// process DACL on windows.
291///
292/// Accesses can be added by bit-or-ing them together.
293///
294/// The type and all associated constants are available on all platforms, but
295/// only meaningful on windows.
296#[derive(Debug, Clone, Copy)]
297pub struct WinDaclProcessAccess(u32);
298
299impl core::ops::BitOr for WinDaclProcessAccess {
300    type Output = Self;
301
302    fn bitor(self, rhs: Self) -> Self::Output {
303        Self(self.0 | rhs.0)
304    }
305}
306
307impl core::ops::BitAnd for WinDaclProcessAccess {
308    type Output = Self;
309
310    fn bitand(self, rhs: Self) -> Self::Output {
311        Self(self.0 & rhs.0)
312    }
313}
314
315impl core::ops::BitOrAssign for WinDaclProcessAccess {
316    fn bitor_assign(&mut self, rhs: Self) {
317        self.0 |= rhs.0;
318    }
319}
320
321impl core::ops::BitAndAssign for WinDaclProcessAccess {
322    fn bitand_assign(&mut self, rhs: Self) {
323        self.0 &= rhs.0;
324    }
325}
326
327#[cfg(windows)]
328impl From<WinDaclProcessAccess> for windows::Win32::System::Threading::PROCESS_ACCESS_RIGHTS {
329    fn from(access: WinDaclProcessAccess) -> Self {
330        Self(access.0)
331    }
332}
333
334#[cfg(windows)]
335impl From<&WinDaclProcessAccess> for windows::Win32::System::Threading::PROCESS_ACCESS_RIGHTS {
336    fn from(access: &WinDaclProcessAccess) -> Self {
337        Self(access.0)
338    }
339}
340
341#[cfg(windows)]
342impl WinDaclProcessAccess {
343    pub const ALL_ACCESS: Self = Self::new(windows::Win32::System::Threading::PROCESS_ALL_ACCESS);
344    pub const CREATE_PROCESS: Self =
345        Self::new(windows::Win32::System::Threading::PROCESS_CREATE_PROCESS);
346    pub const CREATE_THREAD: Self =
347        Self::new(windows::Win32::System::Threading::PROCESS_CREATE_THREAD);
348    pub const DELETE: Self = Self::new(windows::Win32::System::Threading::PROCESS_DELETE);
349    pub const DUP_HANDLE: Self = Self::new(windows::Win32::System::Threading::PROCESS_DUP_HANDLE);
350    pub const QUERY_INFORMATION: Self =
351        Self::new(windows::Win32::System::Threading::PROCESS_QUERY_INFORMATION);
352    pub const QUERY_LIMITED_INFORMATION: Self =
353        Self::new(windows::Win32::System::Threading::PROCESS_QUERY_LIMITED_INFORMATION);
354    pub const READ_CONTROL: Self =
355        Self::new(windows::Win32::System::Threading::PROCESS_READ_CONTROL);
356    pub const SET_INFORMATION: Self =
357        Self::new(windows::Win32::System::Threading::PROCESS_SET_INFORMATION);
358    pub const SET_QUOTA: Self = Self::new(windows::Win32::System::Threading::PROCESS_SET_QUOTA);
359    pub const SUSPEND_RESUME: Self =
360        Self::new(windows::Win32::System::Threading::PROCESS_SUSPEND_RESUME);
361    pub const SYNCHRONIZE: Self = Self::new(windows::Win32::System::Threading::PROCESS_SYNCHRONIZE);
362    pub const TERMINATE: Self = Self::new(windows::Win32::System::Threading::PROCESS_TERMINATE);
363    pub const VM_OPERATION: Self =
364        Self::new(windows::Win32::System::Threading::PROCESS_VM_OPERATION);
365    pub const VM_READ: Self = Self::new(windows::Win32::System::Threading::PROCESS_VM_READ);
366    pub const VM_WRITE: Self = Self::new(windows::Win32::System::Threading::PROCESS_VM_WRITE);
367    pub const WRITE_DAC: Self = Self::new(windows::Win32::System::Threading::PROCESS_WRITE_DAC);
368    pub const WRITE_OWNER: Self = Self::new(windows::Win32::System::Threading::PROCESS_WRITE_OWNER);
369
370    const fn new(access: windows::Win32::System::Threading::PROCESS_ACCESS_RIGHTS) -> Self {
371        Self(access.0)
372    }
373}
374
375// on non-windows targets just a bunch of dummy constants
376#[cfg(not(windows))]
377impl WinDaclProcessAccess {
378    pub const ALL_ACCESS: Self = Self(0);
379    pub const CREATE_PROCESS: Self = Self(0);
380    pub const CREATE_THREAD: Self = Self(0);
381    pub const DELETE: Self = Self(0);
382    pub const DUP_HANDLE: Self = Self(0);
383    pub const QUERY_INFORMATION: Self = Self(0);
384    pub const QUERY_LIMITED_INFORMATION: Self = Self(0);
385    pub const READ_CONTROL: Self = Self(0);
386    pub const SET_INFORMATION: Self = Self(0);
387    pub const SET_QUOTA: Self = Self(0);
388    pub const SUSPEND_RESUME: Self = Self(0);
389    pub const SYNCHRONIZE: Self = Self(0);
390    pub const TERMINATE: Self = Self(0);
391    pub const VM_OPERATION: Self = Self(0);
392    pub const VM_READ: Self = Self(0);
393    pub const VM_WRITE: Self = Self(0);
394    pub const WRITE_DAC: Self = Self(0);
395    pub const WRITE_OWNER: Self = Self(0);
396}
397
398impl Default for WinDacl {
399    fn default() -> Self {
400        Self::DEFAULT
401    }
402}
403
404impl WinDacl {
405    /// Default DACL.
406    pub const DEFAULT: Self = Self::Default;
407
408    /// Set the DACL configured in `self`. Most users probably want to set this
409    /// DACL configuration in a [`Config`] using [`Config::set_win_dacl`], and
410    /// then harden the process using that configuration
411    /// ([`Config::harden_process`]) instead.
412    pub fn call(&self) -> Result {
413        #[cfg(windows)]
414        return match self {
415            Self::Empty => components::set_empty_dacl_winapi(),
416            Self::Default => components::set_default_dacl_winapi(),
417            Self::CustomUserPerm(access) => components::set_custom_dacl_winapi(access.into()),
418            Self::False => Ok(()),
419            Self::CustomFn(f) => f(),
420        };
421        #[cfg(not(windows))]
422        Ok(())
423    }
424}