1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 198 199 200 201 202 203 204 205 206 207 208 209 210 211 212 213 214 215 216 217 218 219 220 221 222 223 224 225 226 227 228 229 230 231 232 233 234 235 236 237 238 239 240 241 242 243 244 245 246 247 248 249 250 251 252 253 254 255 256 257 258 259 260 261
//! 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,
}
}
}