//! Bindings for the FreeBSD `procctl` system call. //! //! There are similarities (but also differences) with Linux's `prctl` system //! call, whose interface is located in the `prctl.rs` file. #![allow(unsafe_code)] use core::mem::MaybeUninit; use core::ptr; use bitflags::bitflags; use crate::backend::c::{c_int, c_uint, c_void}; use crate::backend::process::syscalls; use crate::backend::process::types::{RawId, Signal}; use crate::io; use crate::process::{Pid, RawPid}; // // Helper functions. // /// Subset of `idtype_t` C enum, with only the values allowed by `procctl`. #[repr(i32)] pub enum IdType { /// Process id. Pid = 0, /// Process group id. Pgid = 2, } /// A process selector for use with the `procctl` interface. /// /// `None` represents the current process. `Some((IdType::Pid, pid))` /// represents the process with pid `pid`. `Some((IdType::Pgid, pgid))` /// represents the control processes belonging to the process group with id /// `pgid`. pub type ProcSelector = Option<(IdType, Pid)>; fn proc_selector_to_raw(selector: ProcSelector) -> (IdType, RawPid) { match selector { Some((idtype, id)) => (idtype, id.as_raw_nonzero().get()), None => (IdType::Pid, 0), } } #[inline] pub(crate) unsafe fn procctl( option: c_int, process: ProcSelector, data: *mut c_void, ) -> io::Result<()> { let (idtype, id) = proc_selector_to_raw(process); syscalls::procctl(idtype as c_uint, id as RawId, option, data) } #[inline] pub(crate) unsafe fn procctl_set

( option: c_int, process: ProcSelector, data: &P, ) -> io::Result<()> { procctl(option, process, (data as *const P as *mut P).cast()) } #[inline] pub(crate) unsafe fn procctl_get_optional

( option: c_int, process: ProcSelector, ) -> io::Result

{ let mut value: MaybeUninit

= MaybeUninit::uninit(); procctl(option, process, value.as_mut_ptr().cast())?; Ok(value.assume_init()) } // // PROC_PDEATHSIG_STATUS/PROC_PDEATHSIG_CTL // const PROC_PDEATHSIG_STATUS: c_int = 12; /// Get the current value of the parent process death signal. /// /// # References /// - [Linux: `prctl(PR_GET_PDEATHSIG,...)`] /// - [FreeBSD: `procctl(PROC_PDEATHSIG_STATUS,...)`] /// /// [Linux: `prctl(PR_GET_PDEATHSIG,...)`]: https://man7.org/linux/man-pages/man2/prctl.2.html /// [FreeBSD: `procctl(PROC_PDEATHSIG_STATUS,...)`]: https://www.freebsd.org/cgi/man.cgi?query=procctl&sektion=2 #[inline] pub fn parent_process_death_signal() -> io::Result> { unsafe { procctl_get_optional::(PROC_PDEATHSIG_STATUS, None) }.map(Signal::from_raw) } const PROC_PDEATHSIG_CTL: c_int = 11; /// Set the parent-death signal of the calling process. /// /// # References /// - [Linux: `prctl(PR_SET_PDEATHSIG,...)`] /// - [FreeBSD: `procctl(PROC_PDEATHSIG_CTL,...)`] /// /// [Linux: `prctl(PR_SET_PDEATHSIG,...)`]: https://man7.org/linux/man-pages/man2/prctl.2.html /// [FreeBSD: `procctl(PROC_PDEATHSIG_CTL,...)`]: https://www.freebsd.org/cgi/man.cgi?query=procctl&sektion=2 #[inline] pub fn set_parent_process_death_signal(signal: Option) -> io::Result<()> { let signal = signal.map_or(0, |signal| signal as c_int); unsafe { procctl_set::(PROC_PDEATHSIG_CTL, None, &signal) } } // // PROC_TRACE_CTL // const PROC_TRACE_CTL: c_int = 7; const PROC_TRACE_CTL_ENABLE: i32 = 1; const PROC_TRACE_CTL_DISABLE: i32 = 2; const PROC_TRACE_CTL_DISABLE_EXEC: i32 = 3; /// `PROC_TRACE_CTL_*`. #[derive(Copy, Clone, Debug, Eq, PartialEq)] #[repr(i32)] pub enum DumpableBehavior { /// Not dumpable. NotDumpable = PROC_TRACE_CTL_DISABLE, /// Dumpable. Dumpable = PROC_TRACE_CTL_ENABLE, /// Not dumpable, and this behaviour is preserved across `execve` calls. NotDumpableExecPreserved = PROC_TRACE_CTL_DISABLE_EXEC, } /// Set the state of the `dumpable` attribute for the process indicated by /// `idtype` and `id`. This determines whether the process can be traced and /// whether core dumps are produced for the process upon delivery of a signal /// whose default behavior is to produce a core dump. /// /// This is similar to `set_dumpable_behavior` on Linux, with the exception /// that on FreeBSD there is an extra argument `process`. When `process` is set /// to `None`, the operation is performed for the current process, like on /// Linux. /// /// # References /// - [`procctl(PROC_TRACE_CTL,...)`] /// /// [`procctl(PROC_TRACE_CTL,...)`]: https://www.freebsd.org/cgi/man.cgi?query=procctl&sektion=2 #[inline] pub fn set_dumpable_behavior(process: ProcSelector, config: DumpableBehavior) -> io::Result<()> { unsafe { procctl(PROC_TRACE_CTL, process, config as usize as *mut _) } } // // PROC_TRACE_STATUS // const PROC_TRACE_STATUS: c_int = 8; /// Tracing status as returned by [`trace_status`]. #[derive(Copy, Clone, Debug, Eq, PartialEq)] pub enum TracingStatus { /// Tracing is disabled for the process. NotTraceble, /// Tracing is not disabled for the process, but not debugger/tracer is /// attached. Tracable, /// The process is being traced by the process whose pid is stored in the /// first component of this variant. BeingTraced(Pid), } /// Get the tracing status of the process indicated by `idtype` and `id`. /// /// # References /// - [`procctl(PROC_TRACE_STATUS,...)`] /// /// [`procctl(PROC_TRACE_STATUS,...)`]: https://www.freebsd.org/cgi/man.cgi?query=procctl&sektion=2 #[inline] pub fn trace_status(process: ProcSelector) -> io::Result { let val = unsafe { procctl_get_optional::(PROC_TRACE_STATUS, process) }?; match val { -1 => Ok(TracingStatus::NotTraceble), 0 => Ok(TracingStatus::Tracable), pid => { let pid = unsafe { Pid::from_raw(pid as RawPid) }.ok_or(io::Errno::RANGE)?; Ok(TracingStatus::BeingTraced(pid)) } } } // // PROC_REAP_* // const PROC_REAP_ACQUIRE: c_int = 2; const PROC_REAP_RELEASE: c_int = 3; /// Acquire or release the reaper status of the calling process. /// /// # References /// - [FreeBSD: `procctl(PROC_REAP_ACQUIRE/RELEASE,...)`] /// /// [FreeBSD: `procctl(PROC_REAP_ACQUIRE/RELEASE,...)`]: https://www.freebsd.org/cgi/man.cgi?query=procctl&sektion=2 #[inline] pub fn set_reaper_status(reaper: bool) -> io::Result<()> { unsafe { procctl( if reaper { PROC_REAP_ACQUIRE } else { PROC_REAP_RELEASE }, None, ptr::null_mut(), ) } } const PROC_REAP_STATUS: c_int = 4; bitflags! { /// `REAPER_STATUS_*`. pub struct ReaperStatusFlags: c_uint { /// The process has acquired reaper status. const OWNED = 1; /// The process is the root of the reaper tree (pid 1). const REALINIT = 2; } } #[repr(C)] struct procctl_reaper_status { rs_flags: c_uint, rs_children: c_uint, rs_descendants: c_uint, rs_reaper: RawPid, rs_pid: RawPid, rs_pad0: [c_uint; 15], } /// Reaper status as returned by [`get_reaper_status`]. #[derive(Copy, Clone, Debug, Eq, PartialEq)] pub struct ReaperStatus { /// The flags. pub flags: ReaperStatusFlags, /// The number of children of the reaper among the descendants. pub children: usize, /// The total number of descendants of the reaper(s), not counting /// descendants of the reaper in the subtree. pub descendants: usize, /// The pid of the reaper for the specified process id. pub reaper: Pid, /// The pid of one reaper child if there are any descendants. pub pid: Pid, } /// Get information about the reaper of the specified process (or the process /// itself if it is a reaper). /// /// # References /// - [FreeBSD: `procctl(PROC_REAP_STATUS,...)`] /// /// [FreeBSD: `procctl(PROC_REAP_STATUS,...)`]: https://www.freebsd.org/cgi/man.cgi?query=procctl&sektion=2 #[inline] pub fn get_reaper_status(process: ProcSelector) -> io::Result { let raw = unsafe { procctl_get_optional::(PROC_REAP_STATUS, process) }?; Ok(ReaperStatus { flags: ReaperStatusFlags::from_bits_truncate(raw.rs_flags), children: raw.rs_children as _, descendants: raw.rs_descendants as _, reaper: unsafe { Pid::from_raw(raw.rs_reaper) }.ok_or(io::Errno::RANGE)?, pid: unsafe { Pid::from_raw(raw.rs_pid) }.ok_or(io::Errno::RANGE)?, }) } const PROC_REAP_GETPIDS: c_int = 5; bitflags! { /// `REAPER_PIDINFO_*`. pub struct PidInfoFlags: c_uint { /// This structure was filled by the kernel. const VALID = 1; /// The pid field identifies a direct child of the reaper. const CHILD = 2; /// The reported process is itself a reaper. Descendants of a subordinate reaper are not reported. const REAPER = 4; } } #[repr(C)] #[derive(Default, Clone)] struct procctl_reaper_pidinfo { pi_pid: RawPid, pi_subtree: RawPid, pi_flags: c_uint, pi_pad0: [c_uint; 15], } #[repr(C)] struct procctl_reaper_pids { rp_count: c_uint, rp_pad0: [c_uint; 15], rp_pids: *mut procctl_reaper_pidinfo, } /// A child process of a reaper. #[derive(Copy, Clone, Debug, Eq, PartialEq)] pub struct PidInfo { /// The flags of the process. pub flags: PidInfoFlags, /// The pid of the process. pub pid: Pid, /// The pid of the child of the reaper which is the (grand-..)parent of the /// process. pub subtree: Pid, } /// Get the list of descendants of the specified reaper process. /// /// # References /// - [FreeBSD: `procctl(PROC_REAP_GETPIDS,...)`] /// /// [FreeBSD: `procctl(PROC_REAP_GETPIDS,...)`]: https://www.freebsd.org/cgi/man.cgi?query=procctl&sektion=2 pub fn get_reaper_pids(process: ProcSelector) -> io::Result> { // Sadly no better way to guarantee that we get all the results than to // allocate ~8MB of memory.. const PID_MAX: usize = 99999; let mut pids: Vec = vec![Default::default(); PID_MAX]; let mut pinfo = procctl_reaper_pids { rp_count: PID_MAX as _, rp_pad0: [0; 15], rp_pids: pids.as_mut_slice().as_mut_ptr(), }; unsafe { procctl( PROC_REAP_GETPIDS, process, (&mut pinfo as *mut procctl_reaper_pids).cast(), )? }; let mut result = Vec::new(); for raw in pids.into_iter() { let flags = PidInfoFlags::from_bits_truncate(raw.pi_flags); if !flags.contains(PidInfoFlags::VALID) { break; } result.push(PidInfo { flags, subtree: unsafe { Pid::from_raw(raw.pi_subtree) }.ok_or(io::Errno::RANGE)?, pid: unsafe { Pid::from_raw(raw.pi_pid) }.ok_or(io::Errno::RANGE)?, }); } Ok(result) } const PROC_REAP_KILL: c_int = 6; bitflags! { /// `REAPER_KILL_*`. struct KillFlags: c_uint { const CHILDREN = 1; const SUBTREE = 2; } } #[repr(C)] struct procctl_reaper_kill { rk_sig: c_int, rk_flags: c_uint, rk_subtree: RawPid, rk_killed: c_uint, rk_fpid: RawPid, rk_pad0: [c_uint; 15], } /// Reaper status as returned by [`get_reaper_status`]. #[derive(Copy, Clone, Debug, Eq, PartialEq)] pub struct KillResult { /// The number of processes that were signalled. pub killed: usize, /// The pid of the first process that wasn't successfully signalled. pub first_failed: Option, } /// Deliver a signal to some subset of /// /// # References /// - [FreeBSD: `procctl(PROC_REAP_KILL,...)`] /// /// [FreeBSD: `procctl(PROC_REAP_KILL,...)`]: https://www.freebsd.org/cgi/man.cgi?query=procctl&sektion=2 pub fn reaper_kill( process: ProcSelector, signal: Signal, direct_children: bool, subtree: Option, ) -> io::Result { let mut flags = KillFlags::empty(); flags.set(KillFlags::CHILDREN, direct_children); flags.set(KillFlags::SUBTREE, subtree.is_some()); let mut req = procctl_reaper_kill { rk_sig: signal as c_int, rk_flags: flags.bits(), rk_subtree: subtree.map(|p| p.as_raw_nonzero().into()).unwrap_or(0), rk_killed: 0, rk_fpid: 0, rk_pad0: [0; 15], }; unsafe { procctl( PROC_REAP_KILL, process, (&mut req as *mut procctl_reaper_kill).cast(), )? }; Ok(KillResult { killed: req.rk_killed as _, first_failed: if req.rk_fpid == -1 { None } else { unsafe { Pid::from_raw(req.rk_fpid) } }, }) } // // PROC_TRAPCAP_STATUS/PROC_TRAPCAP_CTL // const PROC_TRAPCAP_CTL: c_int = 9; const PROC_TRAPCAP_CTL_ENABLE: i32 = 1; const PROC_TRAPCAP_CTL_DISABLE: i32 = 2; /// `PROC_TRAPCAP_CTL_*`. #[derive(Copy, Clone, Debug, Eq, PartialEq)] #[repr(i32)] pub enum TrapCapBehavior { /// Disable the SIGTRAP signal delivery on capability mode access /// violations. Disable = PROC_TRAPCAP_CTL_DISABLE, /// Enable the SIGTRAP signal delivery on capability mode access /// violations. Enable = PROC_TRAPCAP_CTL_ENABLE, } /// Set the current value of the capability mode violation trapping behavior. /// If this behavior is enabled, the kernel would deliver a SIGTRAP signal on /// any return from a system call that would result in a `ENOTCAPABLE` or /// `ECAPMODE` error. /// /// This behavior is inherited by the children of the process and is kept /// across `execve` calls. /// /// # References /// - [FreeBSD: `procctl(PROC_TRAPCAP_CTL,...)`] /// /// [FreeBSD: `procctl(PROC_TRAPCAP_CTL,...)`]: https://www.freebsd.org/cgi/man.cgi?query=procctl&sektion=2 #[inline] pub fn set_trap_cap_behavior(process: ProcSelector, config: TrapCapBehavior) -> io::Result<()> { let config = config as c_int; unsafe { procctl_set::(PROC_TRAPCAP_CTL, process, &config) } } const PROC_TRAPCAP_STATUS: c_int = 10; /// Get the current value of the capability mode violation trapping behavior. /// /// # References /// - [FreeBSD: `procctl(PROC_TRAPCAP_STATUS,...)`] /// /// [FreeBSD: `procctl(PROC_TRAPCAP_STATUS,...)`]: https://www.freebsd.org/cgi/man.cgi?query=procctl&sektion=2 #[inline] pub fn trap_cap_behavior(process: ProcSelector) -> io::Result { let val = unsafe { procctl_get_optional::(PROC_TRAPCAP_STATUS, process) }?; match val { PROC_TRAPCAP_CTL_DISABLE => Ok(TrapCapBehavior::Disable), PROC_TRAPCAP_CTL_ENABLE => Ok(TrapCapBehavior::Enable), _ => Err(io::Errno::RANGE), } } // // PROC_NO_NEW_PRIVS_STATUS/PROC_NO_NEW_PRIVS_CTL // const PROC_NO_NEW_PRIVS_CTL: c_int = 19; const PROC_NO_NEW_PRIVS_ENABLE: c_int = 1; /// Enable the `no_new_privs` mode that ignores SUID and SGID bits /// on `execve` in the specified process and its future descendants. /// /// This is similar to `set_no_new_privs` on Linux, with the exception /// that on FreeBSD there is no argument `no_new_privs` argument as it's /// only possible to enable this mode and there's no going back. /// /// # References /// - [Linux: `prctl(PR_SET_NO_NEW_PRIVS,...)`] /// - [FreeBSD: `procctl(PROC_NO_NEW_PRIVS_CTL,...)`] /// /// [Linux: `prctl(PR_SET_NO_NEW_PRIVS,...)`]: https://man7.org/linux/man-pages/man2/prctl.2.html /// [FreeBSD: `procctl(PROC_NO_NEW_PRIVS_CTL,...)`]: https://www.freebsd.org/cgi/man.cgi?query=procctl&sektion=2 #[inline] pub fn set_no_new_privs(process: ProcSelector) -> io::Result<()> { unsafe { procctl_set::(PROC_NO_NEW_PRIVS_CTL, process, &PROC_NO_NEW_PRIVS_ENABLE) } } const PROC_NO_NEW_PRIVS_STATUS: c_int = 20; /// Check the `no_new_privs` mode of the specified process. /// /// # References /// - [Linux: `prctl(PR_GET_NO_NEW_PRIVS,...)`] /// - [FreeBSD: `procctl(PROC_NO_NEW_PRIVS_STATUS,...)`] /// /// [Linux: `prctl(PR_GET_NO_NEW_PRIVS,...)`]: https://man7.org/linux/man-pages/man2/prctl.2.html /// [FreeBSD: `procctl(PROC_NO_NEW_PRIVS_STATUS,...)`]: https://www.freebsd.org/cgi/man.cgi?query=procctl&sektion=2 #[inline] pub fn no_new_privs(process: ProcSelector) -> io::Result { unsafe { procctl_get_optional::(PROC_NO_NEW_PRIVS_STATUS, process) } .map(|x| x == PROC_NO_NEW_PRIVS_ENABLE) }