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" {
sighook_signal_cause(info: &siginfo_t) -> ICause13     fn sighook_signal_cause(info: &siginfo_t) -> ICause;
sighook_signal_pid(info: &siginfo_t) -> pid_t14     fn sighook_signal_pid(info: &siginfo_t) -> pid_t;
sighook_signal_uid(info: &siginfo_t) -> uid_t15     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")]
has_process(self) -> bool45     fn has_process(self) -> bool {
46         true
47     }
48 
49     #[cfg(not(target_os = "macos"))]
has_process(self) -> bool50     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      */
extract(info: &siginfo_t) -> Self80     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 {
from(c: ICause) -> Cause161     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 {
fmt(&self, fmt: &mut Formatter) -> FmtResult213     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.
extract(info: &siginfo_t) -> Self240     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