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
//! Extracting more information from the C [`siginfo_t`] structure.
//!
//! See [`Origin`].

use libc::{c_int, pid_t, siginfo_t, uid_t};
// 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, Debug, 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 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,
        }
    }
}