signal_hook/low_level/siginfo.rs
1//! Extracting more information from the C [`siginfo_t`] structure.
2//!
3//! See [`Origin`].
4
5use std::fmt::{Debug, Formatter, Result as FmtResult};
6
7use libc::{c_int, pid_t, siginfo_t, uid_t};
8
9use crate::low_level;
10
11// Careful: make sure the signature and the constants match the C source
12extern "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)]
25enum 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
40impl 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]
63pub 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
71impl 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]
91pub 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]
110pub 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]
141pub 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
160impl 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]
186pub 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
212impl 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
227impl 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}