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}