1 //! Bindings for the FreeBSD `procctl` system call.
2 //!
3 //! There are similarities (but also differences) with Linux's `prctl` system
4 //! call, whose interface is located in the `prctl.rs` file.
5 
6 #![allow(unsafe_code)]
7 
8 #[cfg(feature = "alloc")]
9 use alloc::{vec, vec::Vec};
10 use core::mem::MaybeUninit;
11 use core::ptr;
12 
13 use bitflags::bitflags;
14 
15 use crate::backend::c::{c_int, c_uint, c_void};
16 use crate::backend::process::syscalls;
17 use crate::backend::process::types::RawId;
18 use crate::io;
19 use crate::process::{Pid, RawPid};
20 use crate::signal::Signal;
21 use crate::utils::{as_mut_ptr, as_ptr};
22 
23 //
24 // Helper functions.
25 //
26 
27 /// Subset of `idtype_t` C enum, with only the values allowed by `procctl`.
28 #[repr(i32)]
29 pub enum IdType {
30     /// Process id.
31     Pid = 0,
32     /// Process group id.
33     Pgid = 2,
34 }
35 
36 /// A process selector for use with the `procctl` interface.
37 ///
38 /// `None` represents the current process. `Some((IdType::Pid, pid))`
39 /// represents the process with pid `pid`. `Some((IdType::Pgid, pgid))`
40 /// represents the control processes belonging to the process group with id
41 /// `pgid`.
42 pub type ProcSelector = Option<(IdType, Pid)>;
proc_selector_to_raw(selector: ProcSelector) -> (IdType, RawPid)43 fn proc_selector_to_raw(selector: ProcSelector) -> (IdType, RawPid) {
44     match selector {
45         Some((idtype, id)) => (idtype, id.as_raw_nonzero().get()),
46         None => (IdType::Pid, 0),
47     }
48 }
49 
50 #[inline]
procctl( option: c_int, process: ProcSelector, data: *mut c_void, ) -> io::Result<()>51 pub(crate) unsafe fn procctl(
52     option: c_int,
53     process: ProcSelector,
54     data: *mut c_void,
55 ) -> io::Result<()> {
56     let (idtype, id) = proc_selector_to_raw(process);
57     syscalls::procctl(idtype as c_uint, id as RawId, option, data)
58 }
59 
60 #[inline]
procctl_set<P>( option: c_int, process: ProcSelector, data: &P, ) -> io::Result<()>61 pub(crate) unsafe fn procctl_set<P>(
62     option: c_int,
63     process: ProcSelector,
64     data: &P,
65 ) -> io::Result<()> {
66     procctl(option, process, (as_ptr(data) as *mut P).cast())
67 }
68 
69 #[inline]
procctl_get_optional<P>( option: c_int, process: ProcSelector, ) -> io::Result<P>70 pub(crate) unsafe fn procctl_get_optional<P>(
71     option: c_int,
72     process: ProcSelector,
73 ) -> io::Result<P> {
74     let mut value: MaybeUninit<P> = MaybeUninit::uninit();
75     procctl(option, process, value.as_mut_ptr().cast())?;
76     Ok(value.assume_init())
77 }
78 
79 //
80 // PROC_PDEATHSIG_STATUS/PROC_PDEATHSIG_CTL
81 //
82 
83 const PROC_PDEATHSIG_STATUS: c_int = 12;
84 
85 /// Get the current value of the parent process death signal.
86 ///
87 /// # References
88 ///  - [Linux: `prctl(PR_GET_PDEATHSIG,...)`]
89 ///  - [FreeBSD: `procctl(PROC_PDEATHSIG_STATUS,...)`]
90 ///
91 /// [Linux: `prctl(PR_GET_PDEATHSIG,...)`]: https://man7.org/linux/man-pages/man2/prctl.2.html
92 /// [FreeBSD: `procctl(PROC_PDEATHSIG_STATUS,...)`]: https://man.freebsd.org/cgi/man.cgi?query=procctl&sektion=2
93 #[inline]
parent_process_death_signal() -> io::Result<Option<Signal>>94 pub fn parent_process_death_signal() -> io::Result<Option<Signal>> {
95     unsafe { procctl_get_optional::<c_int>(PROC_PDEATHSIG_STATUS, None) }.map(Signal::from_raw)
96 }
97 
98 const PROC_PDEATHSIG_CTL: c_int = 11;
99 
100 /// Set the parent-death signal of the calling process.
101 ///
102 /// # References
103 ///  - [Linux: `prctl(PR_SET_PDEATHSIG,...)`]
104 ///  - [FreeBSD: `procctl(PROC_PDEATHSIG_CTL,...)`]
105 ///
106 /// [Linux: `prctl(PR_SET_PDEATHSIG,...)`]: https://man7.org/linux/man-pages/man2/prctl.2.html
107 /// [FreeBSD: `procctl(PROC_PDEATHSIG_CTL,...)`]: https://man.freebsd.org/cgi/man.cgi?query=procctl&sektion=2
108 #[inline]
set_parent_process_death_signal(signal: Option<Signal>) -> io::Result<()>109 pub fn set_parent_process_death_signal(signal: Option<Signal>) -> io::Result<()> {
110     let signal = signal.map_or(0, |signal| signal as c_int);
111     unsafe { procctl_set::<c_int>(PROC_PDEATHSIG_CTL, None, &signal) }
112 }
113 
114 //
115 // PROC_TRACE_CTL
116 //
117 
118 const PROC_TRACE_CTL: c_int = 7;
119 
120 const PROC_TRACE_CTL_ENABLE: i32 = 1;
121 const PROC_TRACE_CTL_DISABLE: i32 = 2;
122 const PROC_TRACE_CTL_DISABLE_EXEC: i32 = 3;
123 
124 /// `PROC_TRACE_CTL_*`.
125 #[derive(Copy, Clone, Debug, Eq, PartialEq)]
126 #[repr(i32)]
127 pub enum DumpableBehavior {
128     /// Not dumpable.
129     NotDumpable = PROC_TRACE_CTL_DISABLE,
130     /// Dumpable.
131     Dumpable = PROC_TRACE_CTL_ENABLE,
132     /// Not dumpable, and this behaviour is preserved across `execve` calls.
133     NotDumpableExecPreserved = PROC_TRACE_CTL_DISABLE_EXEC,
134 }
135 
136 /// Set the state of the `dumpable` attribute for the process indicated by
137 /// `idtype` and `id`. This determines whether the process can be traced and
138 /// whether core dumps are produced for the process upon delivery of a signal
139 /// whose default behavior is to produce a core dump.
140 ///
141 /// This is similar to `set_dumpable_behavior` on Linux, with the exception
142 /// that on FreeBSD there is an extra argument `process`. When `process` is set
143 /// to `None`, the operation is performed for the current process, like on
144 /// Linux.
145 ///
146 /// # References
147 ///  - [FreeBSD `procctl(PROC_TRACE_CTL,...)`]
148 ///
149 /// [FreeBSD `procctl(PROC_TRACE_CTL,...)`]: https://man.freebsd.org/cgi/man.cgi?query=procctl&sektion=2
150 #[inline]
set_dumpable_behavior(process: ProcSelector, config: DumpableBehavior) -> io::Result<()>151 pub fn set_dumpable_behavior(process: ProcSelector, config: DumpableBehavior) -> io::Result<()> {
152     unsafe { procctl(PROC_TRACE_CTL, process, config as usize as *mut _) }
153 }
154 
155 //
156 // PROC_TRACE_STATUS
157 //
158 
159 const PROC_TRACE_STATUS: c_int = 8;
160 
161 /// Tracing status as returned by [`trace_status`].
162 #[derive(Copy, Clone, Debug, Eq, PartialEq)]
163 pub enum TracingStatus {
164     /// Tracing is disabled for the process.
165     NotTraceble,
166     /// Tracing is not disabled for the process, but not debugger/tracer is
167     /// attached.
168     Tracable,
169     /// The process is being traced by the process whose pid is stored in the
170     /// first component of this variant.
171     BeingTraced(Pid),
172 }
173 
174 /// Get the tracing status of the process indicated by `idtype` and `id`.
175 ///
176 /// # References
177 ///  - [FreeBSD `procctl(PROC_TRACE_STATUS,...)`]
178 ///
179 /// [FreeBSD `procctl(PROC_TRACE_STATUS,...)`]: https://man.freebsd.org/cgi/man.cgi?query=procctl&sektion=2
180 #[inline]
trace_status(process: ProcSelector) -> io::Result<TracingStatus>181 pub fn trace_status(process: ProcSelector) -> io::Result<TracingStatus> {
182     let val = unsafe { procctl_get_optional::<c_int>(PROC_TRACE_STATUS, process) }?;
183     match val {
184         -1 => Ok(TracingStatus::NotTraceble),
185         0 => Ok(TracingStatus::Tracable),
186         pid => {
187             let pid = Pid::from_raw(pid as RawPid).ok_or(io::Errno::RANGE)?;
188             Ok(TracingStatus::BeingTraced(pid))
189         }
190     }
191 }
192 
193 //
194 // PROC_REAP_*
195 //
196 
197 const PROC_REAP_ACQUIRE: c_int = 2;
198 const PROC_REAP_RELEASE: c_int = 3;
199 
200 /// Acquire or release the reaper status of the calling process.
201 ///
202 /// # References
203 ///  - [FreeBSD: `procctl(PROC_REAP_ACQUIRE/RELEASE,...)`]
204 ///
205 /// [FreeBSD: `procctl(PROC_REAP_ACQUIRE/RELEASE,...)`]: https://man.freebsd.org/cgi/man.cgi?query=procctl&sektion=2
206 #[inline]
set_reaper_status(reaper: bool) -> io::Result<()>207 pub fn set_reaper_status(reaper: bool) -> io::Result<()> {
208     unsafe {
209         procctl(
210             if reaper {
211                 PROC_REAP_ACQUIRE
212             } else {
213                 PROC_REAP_RELEASE
214             },
215             None,
216             ptr::null_mut(),
217         )
218     }
219 }
220 
221 const PROC_REAP_STATUS: c_int = 4;
222 
223 bitflags! {
224     /// `REAPER_STATUS_*`.
225     #[repr(transparent)]
226     #[derive(Copy, Clone, Eq, PartialEq, Hash, Debug)]
227     pub struct ReaperStatusFlags: c_uint {
228         /// The process has acquired reaper status.
229         const OWNED = 1;
230         /// The process is the root of the reaper tree (pid 1).
231         const REALINIT = 2;
232 
233         /// <https://docs.rs/bitflags/*/bitflags/#externally-defined-flags>
234         const _ = !0;
235     }
236 }
237 
238 #[repr(C)]
239 struct procctl_reaper_status {
240     rs_flags: c_uint,
241     rs_children: c_uint,
242     rs_descendants: c_uint,
243     rs_reaper: RawPid,
244     rs_pid: RawPid,
245     rs_pad0: [c_uint; 15],
246 }
247 
248 /// Reaper status as returned by [`get_reaper_status`].
249 #[derive(Copy, Clone, Debug, Eq, PartialEq)]
250 pub struct ReaperStatus {
251     /// The flags.
252     pub flags: ReaperStatusFlags,
253     /// The number of children of the reaper among the descendants.
254     pub children: usize,
255     /// The total number of descendants of the reaper(s), not counting
256     /// descendants of the reaper in the subtree.
257     pub descendants: usize,
258     /// The pid of the reaper for the specified process id.
259     pub reaper: Pid,
260     /// The pid of one reaper child if there are any descendants.
261     pub pid: Option<Pid>,
262 }
263 
264 /// Get information about the reaper of the specified process (or the process
265 /// itself if it is a reaper).
266 ///
267 /// # References
268 ///  - [FreeBSD: `procctl(PROC_REAP_STATUS,...)`]
269 ///
270 /// [FreeBSD: `procctl(PROC_REAP_STATUS,...)`]: https://man.freebsd.org/cgi/man.cgi?query=procctl&sektion=2
271 #[inline]
get_reaper_status(process: ProcSelector) -> io::Result<ReaperStatus>272 pub fn get_reaper_status(process: ProcSelector) -> io::Result<ReaperStatus> {
273     let raw = unsafe { procctl_get_optional::<procctl_reaper_status>(PROC_REAP_STATUS, process) }?;
274     Ok(ReaperStatus {
275         flags: ReaperStatusFlags::from_bits_retain(raw.rs_flags),
276         children: raw.rs_children as _,
277         descendants: raw.rs_descendants as _,
278         reaper: Pid::from_raw(raw.rs_reaper).ok_or(io::Errno::RANGE)?,
279         pid: if raw.rs_pid == -1 {
280             None
281         } else {
282             Some(Pid::from_raw(raw.rs_pid).ok_or(io::Errno::RANGE)?)
283         },
284     })
285 }
286 
287 const PROC_REAP_GETPIDS: c_int = 5;
288 
289 bitflags! {
290     /// `REAPER_PIDINFO_*`.
291     #[repr(transparent)]
292     #[derive(Copy, Clone, Eq, PartialEq, Hash, Debug)]
293     pub struct PidInfoFlags: c_uint {
294         /// This structure was filled by the kernel.
295         const VALID = 1;
296         /// The pid field identifies a direct child of the reaper.
297         const CHILD = 2;
298         /// The reported process is itself a reaper. Descendants of a
299         /// subordinate reaper are not reported.
300         const REAPER = 4;
301         /// The reported process is in the zombie state.
302         const ZOMBIE = 8;
303         /// The reported process is stopped by
304         /// [`Signal::Stop`]/[`Signal::Tstp`].
305         const STOPPED = 16;
306         /// The reported process is in the process of exiting.
307         const EXITING = 32;
308     }
309 }
310 
311 #[repr(C)]
312 #[derive(Default, Clone)]
313 struct procctl_reaper_pidinfo {
314     pi_pid: RawPid,
315     pi_subtree: RawPid,
316     pi_flags: c_uint,
317     pi_pad0: [c_uint; 15],
318 }
319 
320 #[repr(C)]
321 struct procctl_reaper_pids {
322     rp_count: c_uint,
323     rp_pad0: [c_uint; 15],
324     rp_pids: *mut procctl_reaper_pidinfo,
325 }
326 
327 /// A child process of a reaper.
328 #[derive(Copy, Clone, Debug, Eq, PartialEq)]
329 pub struct PidInfo {
330     /// The flags of the process.
331     pub flags: PidInfoFlags,
332     /// The pid of the process.
333     pub pid: Pid,
334     /// The pid of the child of the reaper which is the (grand-..)parent of the
335     /// process.
336     pub subtree: Pid,
337 }
338 
339 /// Get the list of descendants of the specified reaper process.
340 ///
341 /// # References
342 ///  - [FreeBSD: `procctl(PROC_REAP_GETPIDS,...)`]
343 ///
344 /// [FreeBSD: `procctl(PROC_REAP_GETPIDS,...)`]: https://man.freebsd.org/cgi/man.cgi?query=procctl&sektion=2
345 #[cfg(feature = "alloc")]
get_reaper_pids(process: ProcSelector) -> io::Result<Vec<PidInfo>>346 pub fn get_reaper_pids(process: ProcSelector) -> io::Result<Vec<PidInfo>> {
347     // Sadly no better way to guarantee that we get all the results than to
348     // allocate ~8MB of memory..
349     const PID_MAX: usize = 99999;
350     let mut pids: Vec<procctl_reaper_pidinfo> = vec![Default::default(); PID_MAX];
351     let mut pinfo = procctl_reaper_pids {
352         rp_count: PID_MAX as _,
353         rp_pad0: [0; 15],
354         rp_pids: pids.as_mut_slice().as_mut_ptr(),
355     };
356     unsafe { procctl(PROC_REAP_GETPIDS, process, as_mut_ptr(&mut pinfo).cast())? };
357     let mut result = Vec::new();
358     for raw in pids.into_iter() {
359         let flags = PidInfoFlags::from_bits_retain(raw.pi_flags);
360         if !flags.contains(PidInfoFlags::VALID) {
361             break;
362         }
363         result.push(PidInfo {
364             flags,
365             subtree: Pid::from_raw(raw.pi_subtree).ok_or(io::Errno::RANGE)?,
366             pid: Pid::from_raw(raw.pi_pid).ok_or(io::Errno::RANGE)?,
367         });
368     }
369     Ok(result)
370 }
371 
372 const PROC_REAP_KILL: c_int = 6;
373 
374 bitflags! {
375     /// `REAPER_KILL_*`.
376     #[repr(transparent)]
377     #[derive(Copy, Clone, Eq, PartialEq, Hash, Debug)]
378     struct KillFlags: c_uint {
379         const CHILDREN = 1;
380         const SUBTREE = 2;
381     }
382 }
383 
384 #[repr(C)]
385 struct procctl_reaper_kill {
386     rk_sig: c_int,
387     rk_flags: c_uint,
388     rk_subtree: RawPid,
389     rk_killed: c_uint,
390     rk_fpid: RawPid,
391     rk_pad0: [c_uint; 15],
392 }
393 
394 /// Reaper status as returned by [`get_reaper_status`].
395 #[derive(Copy, Clone, Debug, Eq, PartialEq)]
396 pub struct KillResult {
397     /// The number of processes that were signalled.
398     pub killed: usize,
399     /// The pid of the first process that wasn't successfully signalled.
400     pub first_failed: Option<Pid>,
401 }
402 
403 /// Deliver a signal to some subset of
404 ///
405 /// # References
406 ///  - [FreeBSD: `procctl(PROC_REAP_KILL,...)`]
407 ///
408 /// [FreeBSD: `procctl(PROC_REAP_KILL,...)`]: https://man.freebsd.org/cgi/man.cgi?query=procctl&sektion=2
reaper_kill( process: ProcSelector, signal: Signal, direct_children: bool, subtree: Option<Pid>, ) -> io::Result<KillResult>409 pub fn reaper_kill(
410     process: ProcSelector,
411     signal: Signal,
412     direct_children: bool,
413     subtree: Option<Pid>,
414 ) -> io::Result<KillResult> {
415     let mut flags = KillFlags::empty();
416     flags.set(KillFlags::CHILDREN, direct_children);
417     flags.set(KillFlags::SUBTREE, subtree.is_some());
418     let mut req = procctl_reaper_kill {
419         rk_sig: signal as c_int,
420         rk_flags: flags.bits(),
421         rk_subtree: subtree.map(|p| p.as_raw_nonzero().into()).unwrap_or(0),
422         rk_killed: 0,
423         rk_fpid: 0,
424         rk_pad0: [0; 15],
425     };
426     unsafe { procctl(PROC_REAP_KILL, process, as_mut_ptr(&mut req).cast())? };
427     Ok(KillResult {
428         killed: req.rk_killed as _,
429         first_failed: Pid::from_raw(req.rk_fpid),
430     })
431 }
432 
433 //
434 // PROC_TRAPCAP_STATUS/PROC_TRAPCAP_CTL
435 //
436 
437 const PROC_TRAPCAP_CTL: c_int = 9;
438 
439 const PROC_TRAPCAP_CTL_ENABLE: i32 = 1;
440 const PROC_TRAPCAP_CTL_DISABLE: i32 = 2;
441 
442 /// `PROC_TRAPCAP_CTL_*`.
443 #[derive(Copy, Clone, Debug, Eq, PartialEq)]
444 #[repr(i32)]
445 pub enum TrapCapBehavior {
446     /// Disable the [`Signal::Trap`] signal delivery on capability mode access
447     /// violations.
448     Disable = PROC_TRAPCAP_CTL_DISABLE,
449     /// Enable the [`Signal::Trap`] signal delivery on capability mode access
450     /// violations.
451     Enable = PROC_TRAPCAP_CTL_ENABLE,
452 }
453 
454 /// Set the current value of the capability mode violation trapping behavior.
455 /// If this behavior is enabled, the kernel would deliver a [`Signal::Trap`]
456 /// signal on any return from a system call that would result in a
457 /// [`io::Errno::NOTCAPABLE`]` or [`io::Errno::CAPMODE`] error.
458 ///
459 /// This behavior is inherited by the children of the process and is kept
460 /// across `execve` calls.
461 ///
462 /// # References
463 ///  - [FreeBSD: `procctl(PROC_TRAPCAP_CTL,...)`]
464 ///
465 /// [FreeBSD: `procctl(PROC_TRAPCAP_CTL,...)`]: https://man.freebsd.org/cgi/man.cgi?query=procctl&sektion=2
466 #[inline]
set_trap_cap_behavior(process: ProcSelector, config: TrapCapBehavior) -> io::Result<()>467 pub fn set_trap_cap_behavior(process: ProcSelector, config: TrapCapBehavior) -> io::Result<()> {
468     let config = config as c_int;
469     unsafe { procctl_set::<c_int>(PROC_TRAPCAP_CTL, process, &config) }
470 }
471 
472 const PROC_TRAPCAP_STATUS: c_int = 10;
473 
474 /// Get the current value of the capability mode violation trapping behavior.
475 ///
476 /// # References
477 ///  - [FreeBSD: `procctl(PROC_TRAPCAP_STATUS,...)`]
478 ///
479 /// [FreeBSD: `procctl(PROC_TRAPCAP_STATUS,...)`]: https://man.freebsd.org/cgi/man.cgi?query=procctl&sektion=2
480 #[inline]
trap_cap_behavior(process: ProcSelector) -> io::Result<TrapCapBehavior>481 pub fn trap_cap_behavior(process: ProcSelector) -> io::Result<TrapCapBehavior> {
482     let val = unsafe { procctl_get_optional::<c_int>(PROC_TRAPCAP_STATUS, process) }?;
483     match val {
484         PROC_TRAPCAP_CTL_DISABLE => Ok(TrapCapBehavior::Disable),
485         PROC_TRAPCAP_CTL_ENABLE => Ok(TrapCapBehavior::Enable),
486         _ => Err(io::Errno::RANGE),
487     }
488 }
489 
490 //
491 // PROC_NO_NEW_PRIVS_STATUS/PROC_NO_NEW_PRIVS_CTL
492 //
493 
494 const PROC_NO_NEW_PRIVS_CTL: c_int = 19;
495 
496 const PROC_NO_NEW_PRIVS_ENABLE: c_int = 1;
497 
498 /// Enable the `no_new_privs` mode that ignores SUID and SGID bits on `execve`
499 /// in the specified process and its future descendants.
500 ///
501 /// This is similar to `set_no_new_privs` on Linux, with the exception that on
502 /// FreeBSD there is no argument `no_new_privs` argument as it's only possible
503 /// to enable this mode and there's no going back.
504 ///
505 /// # References
506 ///  - [Linux: `prctl(PR_SET_NO_NEW_PRIVS,...)`]
507 ///  - [FreeBSD: `procctl(PROC_NO_NEW_PRIVS_CTL,...)`]
508 ///
509 /// [Linux: `prctl(PR_SET_NO_NEW_PRIVS,...)`]: https://man7.org/linux/man-pages/man2/prctl.2.html
510 /// [FreeBSD: `procctl(PROC_NO_NEW_PRIVS_CTL,...)`]: https://man.freebsd.org/cgi/man.cgi?query=procctl&sektion=2
511 #[inline]
set_no_new_privs(process: ProcSelector) -> io::Result<()>512 pub fn set_no_new_privs(process: ProcSelector) -> io::Result<()> {
513     unsafe { procctl_set::<c_int>(PROC_NO_NEW_PRIVS_CTL, process, &PROC_NO_NEW_PRIVS_ENABLE) }
514 }
515 
516 const PROC_NO_NEW_PRIVS_STATUS: c_int = 20;
517 
518 /// Check the `no_new_privs` mode of the specified process.
519 ///
520 /// # References
521 ///  - [Linux: `prctl(PR_GET_NO_NEW_PRIVS,...)`]
522 ///  - [FreeBSD: `procctl(PROC_NO_NEW_PRIVS_STATUS,...)`]
523 ///
524 /// [Linux: `prctl(PR_GET_NO_NEW_PRIVS,...)`]: https://man7.org/linux/man-pages/man2/prctl.2.html
525 /// [FreeBSD: `procctl(PROC_NO_NEW_PRIVS_STATUS,...)`]: https://man.freebsd.org/cgi/man.cgi?query=procctl&sektion=2
526 #[inline]
no_new_privs(process: ProcSelector) -> io::Result<bool>527 pub fn no_new_privs(process: ProcSelector) -> io::Result<bool> {
528     unsafe { procctl_get_optional::<c_int>(PROC_NO_NEW_PRIVS_STATUS, process) }
529         .map(|x| x == PROC_NO_NEW_PRIVS_ENABLE)
530 }
531