| //! Extracting more information from the C [`siginfo_t`] structure. |
| //! |
| //! See [`Origin`]. |
| |
| use std::fmt::{Debug, Formatter, Result as FmtResult}; |
| |
| use libc::{c_int, pid_t, siginfo_t, uid_t}; |
| |
| use crate::low_level; |
| |
| // Careful: make sure the signature and the constants match the C source |
| extern "C" { |
| fn sighook_signal_cause(info: &siginfo_t) -> ICause; |
| fn sighook_signal_pid(info: &siginfo_t) -> pid_t; |
| fn sighook_signal_uid(info: &siginfo_t) -> uid_t; |
| } |
| |
| // Warning: must be in sync with the C code |
| #[derive(Copy, Clone, Debug, Eq, PartialEq)] |
| #[non_exhaustive] |
| #[repr(u8)] |
| // For some reason, the fact it comes from the C makes rustc emit warning that *some* of these are |
| // not constructed. No idea why only some of them. |
| #[allow(dead_code)] |
| enum ICause { |
| Unknown = 0, |
| Kernel = 1, |
| User = 2, |
| TKill = 3, |
| Queue = 4, |
| MesgQ = 5, |
| Exited = 6, |
| Killed = 7, |
| Dumped = 8, |
| Trapped = 9, |
| Stopped = 10, |
| Continued = 11, |
| } |
| |
| impl ICause { |
| // The MacOs doesn't use the SI_* constants and leaves si_code at 0. But it doesn't use an |
| // union, it has a good-behaved struct with fields and therefore we *can* read the values, |
| // even though they'd contain nonsense (zeroes). We wipe that out later. |
| #[cfg(target_os = "macos")] |
| fn has_process(self) -> bool { |
| true |
| } |
| |
| #[cfg(not(target_os = "macos"))] |
| fn has_process(self) -> bool { |
| use ICause::*; |
| match self { |
| Unknown | Kernel => false, |
| User | TKill | Queue | MesgQ | Exited | Killed | Dumped | Trapped | Stopped |
| | Continued => true, |
| } |
| } |
| } |
| |
| /// Information about process, as presented in the signal metadata. |
| #[derive(Copy, Clone, Debug, Eq, PartialEq)] |
| #[non_exhaustive] |
| pub struct Process { |
| /// The process ID. |
| pub pid: pid_t, |
| |
| /// The user owning the process. |
| pub uid: uid_t, |
| } |
| |
| impl Process { |
| /** |
| * Extract the process information. |
| * |
| * # Safety |
| * |
| * The `info` must have a `si_code` corresponding to some situation that has the `si_pid` |
| * and `si_uid` filled in. |
| */ |
| unsafe fn extract(info: &siginfo_t) -> Self { |
| Self { |
| pid: sighook_signal_pid(info), |
| uid: sighook_signal_uid(info), |
| } |
| } |
| } |
| |
| /// The means by which a signal was sent by other process. |
| #[derive(Copy, Clone, Debug, Eq, PartialEq)] |
| #[non_exhaustive] |
| pub enum Sent { |
| /// The `kill` call. |
| User, |
| |
| /// The `tkill` call. |
| /// |
| /// This is likely linux specific. |
| TKill, |
| |
| /// `sigqueue`. |
| Queue, |
| |
| /// `mq_notify`. |
| MesgQ, |
| } |
| |
| /// A child changed its state. |
| #[derive(Copy, Clone, Debug, Eq, PartialEq)] |
| #[non_exhaustive] |
| pub enum Chld { |
| /// The child exited normally. |
| Exited, |
| |
| /// It got killed by a signal. |
| Killed, |
| |
| /// It got killed by a signal and dumped core. |
| Dumped, |
| |
| /// The child was trapped by a `SIGTRAP` signal. |
| Trapped, |
| |
| /// The child got stopped. |
| Stopped, |
| |
| /// The child continued (after being stopped). |
| Continued, |
| } |
| |
| /// What caused a signal. |
| /// |
| /// This is a best-effort (and possibly incomplete) representation of the C `siginfo_t::si_code`. |
| /// It may differ between OSes and may be extended in future versions. |
| /// |
| /// Note that this doesn't contain all the „fault“ signals (`SIGILL`, `SIGSEGV` and similar). |
| /// There's no reasonable way to use the exfiltrators with them, since the handler either needs to |
| /// terminate the process or somehow recover from the situation. Things based on exfiltrators do |
| /// neither, which would cause an UB and therefore these values just don't make sense. |
| #[derive(Copy, Clone, Debug, Eq, PartialEq)] |
| #[non_exhaustive] |
| pub enum Cause { |
| /// The cause is unknown. |
| /// |
| /// Some systems don't fill this in. Some systems have values we don't understand. Some signals |
| /// don't have specific reasons to come to being. |
| Unknown, |
| |
| /// Sent by the kernel. |
| /// |
| /// This probably exists only on Linux. |
| Kernel, |
| |
| /// The signal was sent by other process. |
| Sent(Sent), |
| |
| /// A `SIGCHLD`, caused by a child process changing state. |
| Chld(Chld), |
| } |
| |
| impl From<ICause> for Cause { |
| fn from(c: ICause) -> Cause { |
| match c { |
| ICause::Kernel => Cause::Kernel, |
| ICause::User => Cause::Sent(Sent::User), |
| ICause::TKill => Cause::Sent(Sent::TKill), |
| ICause::Queue => Cause::Sent(Sent::Queue), |
| ICause::MesgQ => Cause::Sent(Sent::MesgQ), |
| ICause::Exited => Cause::Chld(Chld::Exited), |
| ICause::Killed => Cause::Chld(Chld::Killed), |
| ICause::Dumped => Cause::Chld(Chld::Dumped), |
| ICause::Trapped => Cause::Chld(Chld::Trapped), |
| ICause::Stopped => Cause::Chld(Chld::Stopped), |
| ICause::Continued => Cause::Chld(Chld::Continued), |
| // Unknown and possibly others if the underlying lib is updated |
| _ => Cause::Unknown, |
| } |
| } |
| } |
| |
| /// Information about a signal and its origin. |
| /// |
| /// This is produced by the [`WithOrigin`] exfiltrator (or can be [extracted][Origin::extract] from |
| /// `siginfo_t` by hand). |
| #[derive(Clone, Eq, PartialEq)] |
| #[non_exhaustive] |
| pub struct Origin { |
| /// The signal that happened. |
| pub signal: c_int, |
| |
| /// Information about the process that caused the signal. |
| /// |
| /// Note that not all signals are caused by a specific process or have the information |
| /// available („fault“ signals like `SIGBUS` don't have, any signal may be sent by the kernel |
| /// instead of a specific process). |
| /// |
| /// This is filled in whenever available. For most signals, this is the process that sent the |
| /// signal (by `kill` or similar), for `SIGCHLD` it is the child that caused the signal. |
| pub process: Option<Process>, |
| |
| /// How the signal happened. |
| /// |
| /// This is a best-effort value. In particular, some systems may have causes not known to this |
| /// library. Some other systems (MacOS) does not fill the value in so there's no way to know. |
| /// In all these cases, this will contain [`Cause::Unknown`]. |
| /// |
| /// Some values are platform specific and not available on other systems. |
| /// |
| /// Future versions may enrich the enum by further values. |
| pub cause: Cause, |
| } |
| |
| impl Debug for Origin { |
| fn fmt(&self, fmt: &mut Formatter) -> FmtResult { |
| fn named_signal(sig: c_int) -> String { |
| low_level::signal_name(sig) |
| .map(|n| format!("{} ({})", n, sig)) |
| .unwrap_or_else(|| sig.to_string()) |
| } |
| fmt.debug_struct("Origin") |
| .field("signal", &named_signal(self.signal)) |
| .field("process", &self.process) |
| .field("cause", &self.cause) |
| .finish() |
| } |
| } |
| |
| impl Origin { |
| /// Extracts the Origin from a raw `siginfo_t` structure. |
| /// |
| /// This function is async-signal-safe, can be called inside a signal handler. |
| /// |
| /// # Safety |
| /// |
| /// On systems where the structure is backed by an union on the C side, this requires the |
| /// `si_code` and `si_signo` fields must be set properly according to what fields are |
| /// available. |
| /// |
| /// The value passed by kernel satisfies this, care must be taken only when constructed |
| /// manually. |
| pub unsafe fn extract(info: &siginfo_t) -> Self { |
| let cause = sighook_signal_cause(info); |
| let process = if cause.has_process() { |
| let process = Process::extract(info); |
| // On macos we don't have the si_code to go by, but we can go by the values being |
| // empty there. |
| if cfg!(target_os = "macos") && process.pid == 0 && process.uid == 0 { |
| None |
| } else { |
| Some(process) |
| } |
| } else { |
| None |
| }; |
| let signal = info.si_signo; |
| Origin { |
| cause: cause.into(), |
| signal, |
| process, |
| } |
| } |
| } |