1 //! Extracting more information from the C [`siginfo_t`] structure. 2 //! 3 //! See [`Origin`]. 4 5 use std::fmt::{Debug, Formatter, Result as FmtResult}; 6 7 use libc::{c_int, pid_t, siginfo_t, uid_t}; 8 9 use crate::low_level; 10 11 // Careful: make sure the signature and the constants match the C source 12 extern "C" { 13 fn sighook_signal_cause(info: &siginfo_t) -> ICause; 14 fn sighook_signal_pid(info: &siginfo_t) -> pid_t; 15 fn sighook_signal_uid(info: &siginfo_t) -> uid_t; 16 } 17 18 // Warning: must be in sync with the C code 19 #[derive(Copy, Clone, Debug, Eq, PartialEq)] 20 #[non_exhaustive] 21 #[repr(u8)] 22 // For some reason, the fact it comes from the C makes rustc emit warning that *some* of these are 23 // not constructed. No idea why only some of them. 24 #[allow(dead_code)] 25 enum ICause { 26 Unknown = 0, 27 Kernel = 1, 28 User = 2, 29 TKill = 3, 30 Queue = 4, 31 MesgQ = 5, 32 Exited = 6, 33 Killed = 7, 34 Dumped = 8, 35 Trapped = 9, 36 Stopped = 10, 37 Continued = 11, 38 } 39 40 impl ICause { 41 // The MacOs doesn't use the SI_* constants and leaves si_code at 0. But it doesn't use an 42 // union, it has a good-behaved struct with fields and therefore we *can* read the values, 43 // even though they'd contain nonsense (zeroes). We wipe that out later. 44 #[cfg(target_os = "macos")] 45 fn has_process(self) -> bool { 46 true 47 } 48 49 #[cfg(not(target_os = "macos"))] 50 fn has_process(self) -> bool { 51 use ICause::*; 52 match self { 53 Unknown | Kernel => false, 54 User | TKill | Queue | MesgQ | Exited | Killed | Dumped | Trapped | Stopped 55 | Continued => true, 56 } 57 } 58 } 59 60 /// Information about process, as presented in the signal metadata. 61 #[derive(Copy, Clone, Debug, Eq, PartialEq)] 62 #[non_exhaustive] 63 pub struct Process { 64 /// The process ID. 65 pub pid: pid_t, 66 67 /// The user owning the process. 68 pub uid: uid_t, 69 } 70 71 impl Process { 72 /** 73 * Extract the process information. 74 * 75 * # Safety 76 * 77 * The `info` must have a `si_code` corresponding to some situation that has the `si_pid` 78 * and `si_uid` filled in. 79 */ 80 unsafe fn extract(info: &siginfo_t) -> Self { 81 Self { 82 pid: sighook_signal_pid(info), 83 uid: sighook_signal_uid(info), 84 } 85 } 86 } 87 88 /// The means by which a signal was sent by other process. 89 #[derive(Copy, Clone, Debug, Eq, PartialEq)] 90 #[non_exhaustive] 91 pub enum Sent { 92 /// The `kill` call. 93 User, 94 95 /// The `tkill` call. 96 /// 97 /// This is likely linux specific. 98 TKill, 99 100 /// `sigqueue`. 101 Queue, 102 103 /// `mq_notify`. 104 MesgQ, 105 } 106 107 /// A child changed its state. 108 #[derive(Copy, Clone, Debug, Eq, PartialEq)] 109 #[non_exhaustive] 110 pub enum Chld { 111 /// The child exited normally. 112 Exited, 113 114 /// It got killed by a signal. 115 Killed, 116 117 /// It got killed by a signal and dumped core. 118 Dumped, 119 120 /// The child was trapped by a `SIGTRAP` signal. 121 Trapped, 122 123 /// The child got stopped. 124 Stopped, 125 126 /// The child continued (after being stopped). 127 Continued, 128 } 129 130 /// What caused a signal. 131 /// 132 /// This is a best-effort (and possibly incomplete) representation of the C `siginfo_t::si_code`. 133 /// It may differ between OSes and may be extended in future versions. 134 /// 135 /// Note that this doesn't contain all the „fault“ signals (`SIGILL`, `SIGSEGV` and similar). 136 /// There's no reasonable way to use the exfiltrators with them, since the handler either needs to 137 /// terminate the process or somehow recover from the situation. Things based on exfiltrators do 138 /// neither, which would cause an UB and therefore these values just don't make sense. 139 #[derive(Copy, Clone, Debug, Eq, PartialEq)] 140 #[non_exhaustive] 141 pub enum Cause { 142 /// The cause is unknown. 143 /// 144 /// Some systems don't fill this in. Some systems have values we don't understand. Some signals 145 /// don't have specific reasons to come to being. 146 Unknown, 147 148 /// Sent by the kernel. 149 /// 150 /// This probably exists only on Linux. 151 Kernel, 152 153 /// The signal was sent by other process. 154 Sent(Sent), 155 156 /// A `SIGCHLD`, caused by a child process changing state. 157 Chld(Chld), 158 } 159 160 impl From<ICause> for Cause { 161 fn from(c: ICause) -> Cause { 162 match c { 163 ICause::Kernel => Cause::Kernel, 164 ICause::User => Cause::Sent(Sent::User), 165 ICause::TKill => Cause::Sent(Sent::TKill), 166 ICause::Queue => Cause::Sent(Sent::Queue), 167 ICause::MesgQ => Cause::Sent(Sent::MesgQ), 168 ICause::Exited => Cause::Chld(Chld::Exited), 169 ICause::Killed => Cause::Chld(Chld::Killed), 170 ICause::Dumped => Cause::Chld(Chld::Dumped), 171 ICause::Trapped => Cause::Chld(Chld::Trapped), 172 ICause::Stopped => Cause::Chld(Chld::Stopped), 173 ICause::Continued => Cause::Chld(Chld::Continued), 174 // Unknown and possibly others if the underlying lib is updated 175 _ => Cause::Unknown, 176 } 177 } 178 } 179 180 /// Information about a signal and its origin. 181 /// 182 /// This is produced by the [`WithOrigin`] exfiltrator (or can be [extracted][Origin::extract] from 183 /// `siginfo_t` by hand). 184 #[derive(Clone, Eq, PartialEq)] 185 #[non_exhaustive] 186 pub struct Origin { 187 /// The signal that happened. 188 pub signal: c_int, 189 190 /// Information about the process that caused the signal. 191 /// 192 /// Note that not all signals are caused by a specific process or have the information 193 /// available („fault“ signals like `SIGBUS` don't have, any signal may be sent by the kernel 194 /// instead of a specific process). 195 /// 196 /// This is filled in whenever available. For most signals, this is the process that sent the 197 /// signal (by `kill` or similar), for `SIGCHLD` it is the child that caused the signal. 198 pub process: Option<Process>, 199 200 /// How the signal happened. 201 /// 202 /// This is a best-effort value. In particular, some systems may have causes not known to this 203 /// library. Some other systems (MacOS) does not fill the value in so there's no way to know. 204 /// In all these cases, this will contain [`Cause::Unknown`]. 205 /// 206 /// Some values are platform specific and not available on other systems. 207 /// 208 /// Future versions may enrich the enum by further values. 209 pub cause: Cause, 210 } 211 212 impl Debug for Origin { 213 fn fmt(&self, fmt: &mut Formatter) -> FmtResult { 214 fn named_signal(sig: c_int) -> String { 215 low_level::signal_name(sig) 216 .map(|n| format!("{} ({})", n, sig)) 217 .unwrap_or_else(|| sig.to_string()) 218 } 219 fmt.debug_struct("Origin") 220 .field("signal", &named_signal(self.signal)) 221 .field("process", &self.process) 222 .field("cause", &self.cause) 223 .finish() 224 } 225 } 226 227 impl Origin { 228 /// Extracts the Origin from a raw `siginfo_t` structure. 229 /// 230 /// This function is async-signal-safe, can be called inside a signal handler. 231 /// 232 /// # Safety 233 /// 234 /// On systems where the structure is backed by an union on the C side, this requires the 235 /// `si_code` and `si_signo` fields must be set properly according to what fields are 236 /// available. 237 /// 238 /// The value passed by kernel satisfies this, care must be taken only when constructed 239 /// manually. 240 pub unsafe fn extract(info: &siginfo_t) -> Self { 241 let cause = sighook_signal_cause(info); 242 let process = if cause.has_process() { 243 let process = Process::extract(info); 244 // On macos we don't have the si_code to go by, but we can go by the values being 245 // empty there. 246 if cfg!(target_os = "macos") && process.pid == 0 && process.uid == 0 { 247 None 248 } else { 249 Some(process) 250 } 251 } else { 252 None 253 }; 254 let signal = info.si_signo; 255 Origin { 256 cause: cause.into(), 257 signal, 258 process, 259 } 260 } 261 } 262