Skip to content

Commit 92b940d

Browse files
committed
Add the signal-hook-sys crate
For C code that extracts stuff. Because the libc crate is not really very helpful there: * The SI_* constants are missing. * The si_pid/si_uid methods are available only on few selected targets, not everywhere.
1 parent 8fc6076 commit 92b940d

File tree

12 files changed

+348
-6
lines changed

12 files changed

+348
-6
lines changed

.github/workflows/test.yaml

+6-3
Original file line numberDiff line numberDiff line change
@@ -21,7 +21,10 @@ jobs:
2121
- stable
2222
- beta
2323
- nightly
24+
# Introduction of self: Arc<..>, needed for the iterator module
2425
- 1.36.0
26+
# Introduction of non_exhaustive, used at certain exfiltrators
27+
- 1.40.0
2528

2629
runs-on: ${{ matrix.os }}
2730

@@ -170,7 +173,7 @@ jobs:
170173
uses: Swatinem/rust-cache@v1
171174

172175
- name: Run clippy linter
173-
run: cargo clippy --all --tests -- -D clippy::all -D warnings
176+
run: cargo clippy --all --all-features --tests -- -D clippy::all -D warnings
174177

175178
# There's bunch of platforms that have some weird quirks (or we don't know
176179
# that they don't). While fully compiling and testing on them is a bit of a
@@ -210,7 +213,7 @@ jobs:
210213
uses: Swatinem/rust-cache@v1
211214

212215
- name: Run the check
213-
run: cargo check --all --tests --target=${{ matrix.target }}
216+
run: cargo check --all --all-features --tests --target=${{ matrix.target }}
214217

215218
# Check some either weirder platforms, but these support only the base crate,
216219
# not all the fancy async ones.
@@ -242,4 +245,4 @@ jobs:
242245
uses: Swatinem/rust-cache@v1
243246

244247
- name: Run the check
245-
run: cargo check --tests --target=${{ matrix.target }}
248+
run: cargo check --tests --all-features --target=${{ matrix.target }}

.gitignore

+1
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,4 @@
11
/target
22
**/*.rs.bk
33
tags
4+
.ccls-cache

Cargo.lock

+9
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

Cargo.toml

+3
Original file line numberDiff line numberDiff line change
@@ -19,6 +19,7 @@ maintenance = { status = "actively-developed" }
1919
[features]
2020
default = ["iterator"]
2121
iterator = []
22+
extended-siginfo = ["iterator", "signal-hook-sys"]
2223

2324
[workspace]
2425
members = [
@@ -27,11 +28,13 @@ members = [
2728
"signal-hook-tokio",
2829
"signal-hook-mio",
2930
"signal-hook-async-std",
31+
"signal-hook-sys",
3032
]
3133

3234
[dependencies]
3335
libc = "~0.2"
3436
signal-hook-registry = { version = "~1.2", path = "signal-hook-registry" }
37+
signal-hook-sys = { version = "~0.1", path = "signal-hook-sys", optional = true }
3538

3639
[dev-dependencies]
3740
serial_test = "~0.5"

ci-check.sh

+4-2
Original file line numberDiff line numberDiff line change
@@ -8,16 +8,18 @@
88
set -ex
99

1010
rm -f Cargo.lock
11-
cargo build --all --exclude signal-hook-async-std
11+
cargo build --all --exclude signal-hook-async-std --exclude signal-hook-sys
1212

1313
if [ "$RUST_VERSION" = 1.36.0 ] ; then
1414
exit
1515
fi
1616

17-
if [ "$OS" = "windows-latest" ] ; then
17+
if [ "$OS" = "windows-latest" ] || [ "$RUST_VERSION" = 1.40.0 ]; then
1818
# The async support crates rely on the iterator module
1919
# which isn't available for windows. So exclude them
2020
# from the build.
21+
22+
# Also, some dependencies of the runtimes don't like 1.40.
2123
EXCLUDE_FROM_BUILD="--exclude signal-hook-mio --exclude signal-hook-tokio --exclude signal-hook-async-std"
2224
else
2325
EXCLUDE_FROM_BUILD=""

signal-hook-sys/Cargo.toml

+13
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,13 @@
1+
[package]
2+
name = "signal-hook-sys"
3+
version = "0.1.0"
4+
authors = ["Michal 'vorner' Vaner <vorner@vorner.cz>"]
5+
edition = "2018"
6+
7+
# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html
8+
9+
[dependencies]
10+
libc = "~0.2"
11+
12+
[build-dependencies]
13+
cc = "~1"

signal-hook-sys/build.rs

+5
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
use cc::Build;
2+
3+
fn main() {
4+
Build::new().file("src/extract.c").compile("extract");
5+
}

signal-hook-sys/src/extract.c

+51
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,51 @@
1+
#include <stdbool.h>
2+
#include <signal.h>
3+
#include <stdint.h>
4+
5+
struct Const {
6+
int native;
7+
// The signal this applies to, or -1 if it applies to anything.
8+
int signal;
9+
uint8_t translated;
10+
};
11+
12+
// Warning: must be in sync with the rust source code
13+
struct Const consts[] = {
14+
#ifdef SI_KERNEL
15+
{ SI_KERNEL, -1, 1 },
16+
#endif
17+
{ SI_USER, -1, 2 },
18+
#ifdef SI_TKILL
19+
{ SI_TKILL, -1, 3 },
20+
#endif
21+
{ SI_QUEUE, -1, 4 },
22+
{ SI_MESGQ, -1, 5 },
23+
{ CLD_EXITED, SIGCHLD, 6 },
24+
{ CLD_KILLED, SIGCHLD, 7 },
25+
{ CLD_DUMPED, SIGCHLD, 8 },
26+
{ CLD_TRAPPED, SIGCHLD, 9 },
27+
{ CLD_STOPPED, SIGCHLD, 10 },
28+
{ CLD_CONTINUED, SIGCHLD, 11 },
29+
};
30+
31+
uint8_t sighook_signal_cause(const siginfo_t *info) {
32+
const size_t const_len = sizeof consts / sizeof *consts;
33+
size_t i;
34+
for (i = 0; i < const_len; i ++) {
35+
if (
36+
consts[i].native == info->si_code &&
37+
(consts[i].signal == -1 || consts[i].signal == info->si_signo)
38+
) {
39+
return consts[i].translated;
40+
}
41+
}
42+
return 0; // The "Unknown" variant
43+
}
44+
45+
pid_t sighook_signal_pid(const siginfo_t *info) {
46+
return info->si_pid;
47+
}
48+
49+
uid_t sighook_signal_uid(const siginfo_t *info) {
50+
return info->si_uid;
51+
}

signal-hook-sys/src/lib.rs

+123
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,123 @@
1+
//! Low-level internals of [`signal-hook`](https://docs.rs/signal-hook).
2+
//!
3+
//! This crate contains some internal APIs, split off to a separate crate for technical reasons. Do
4+
//! not use directly. There are no stability guarantees, no documentation and you should use
5+
//! `signal-hook` directly.
6+
7+
#[doc(hidden)]
8+
pub mod internal {
9+
use libc::{pid_t, siginfo_t, uid_t};
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) -> Cause;
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+
pub enum Cause {
23+
Unknown = 0,
24+
Kernel = 1,
25+
User = 2,
26+
TKill = 3,
27+
Queue = 4,
28+
MesgQ = 5,
29+
Exited = 6,
30+
Killed = 7,
31+
Dumped = 8,
32+
Trapped = 9,
33+
Stopped = 10,
34+
Continued = 11,
35+
}
36+
37+
impl Cause {
38+
// The MacOs doesn't use the SI_* constants and leaves si_code at 0. But it doesn't use an
39+
// union, it has a good-behaved struct with fields and therefore we *can* read the values,
40+
// even though they'd contain nonsense (zeroes). We wipe that out later.
41+
#[cfg(target_os = "macos")]
42+
fn has_process(self) -> bool {
43+
true
44+
}
45+
46+
#[cfg(not(target_os = "macos"))]
47+
fn has_process(self) -> bool {
48+
use Cause::*;
49+
match self {
50+
Unknown | Kernel => false,
51+
User | TKill | Queue | MesgQ | Exited | Killed | Dumped | Trapped | Stopped
52+
| Continued => true,
53+
}
54+
}
55+
}
56+
57+
#[derive(Copy, Clone, Debug, Eq, PartialEq)]
58+
#[non_exhaustive]
59+
pub struct Process {
60+
pub pid: pid_t,
61+
pub uid: uid_t,
62+
}
63+
64+
impl Process {
65+
/**
66+
* Extract the process information.
67+
*
68+
* # Safety
69+
*
70+
* The `info` must have a `si_code` corresponding to some situation that has the `si_pid`
71+
* and `si_uid` filled in.
72+
*/
73+
unsafe fn extract(info: &siginfo_t) -> Self {
74+
Self {
75+
pid: sighook_signal_pid(info),
76+
uid: sighook_signal_uid(info),
77+
}
78+
}
79+
80+
pub const fn to_u64(self) -> u64 {
81+
let pid = self.pid as u32; // With overflow for negative ones
82+
let uid = self.uid as u32;
83+
((pid as u64) << 32) | (uid as u64)
84+
}
85+
86+
pub const fn from_u64(encoded: u64) -> Self {
87+
let pid = ((encoded >> 32) as u32) as _;
88+
let uid = (encoded as u32) as _;
89+
Self { pid, uid }
90+
}
91+
92+
pub const EMPTY: Self = Self { pid: -1, uid: 0 };
93+
94+
pub const NO_PROCESS: Self = Self { pid: -1, uid: 1 };
95+
}
96+
97+
#[derive(Clone, Debug, Eq, PartialEq)]
98+
#[non_exhaustive]
99+
pub struct SigInfo {
100+
pub cause: Cause,
101+
pub process: Option<Process>,
102+
}
103+
104+
impl SigInfo {
105+
// Note: shall be async-signal-safe
106+
pub fn extract(info: &siginfo_t) -> Self {
107+
let cause = unsafe { sighook_signal_cause(info) };
108+
let process = if cause.has_process() {
109+
let process = unsafe { Process::extract(info) };
110+
// On macos we don't have the si_code to go by, but we can go by the values being
111+
// empty there.
112+
if cfg!(target_os = "macos") && process.pid == 0 && process.uid == 0 {
113+
None
114+
} else {
115+
Some(process)
116+
}
117+
} else {
118+
None
119+
};
120+
Self { cause, process }
121+
}
122+
}
123+
}

src/iterator/exfiltrator.rs src/iterator/exfiltrator/mod.rs

+3
Original file line numberDiff line numberDiff line change
@@ -11,6 +11,9 @@
1111
//! Currently, the trait is sealed and all methods hidden. This is likely temporary, until some
1212
//! experience with them is gained.
1313
14+
#[cfg(feature = "extended-siginfo")]
15+
pub mod origin;
16+
1417
use std::sync::atomic::{AtomicBool, Ordering};
1518

1619
use libc::{c_int, siginfo_t};

0 commit comments

Comments
 (0)