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