Book a Demo!
CoCalc Logo Icon
StoreFeaturesDocsShareSupportNewsAboutPoliciesSign UpSign In
aos
GitHub Repository: aos/firecracker
Path: blob/main/src/vmm/src/signal_handler.rs
1958 views
1
// Copyright 2018 Amazon.com, Inc. or its affiliates. All Rights Reserved.
2
// SPDX-License-Identifier: Apache-2.0
3
4
use libc::{
5
c_int, c_void, siginfo_t, SIGBUS, SIGHUP, SIGILL, SIGPIPE, SIGSEGV, SIGSYS, SIGXCPU, SIGXFSZ,
6
};
7
8
use crate::{ExitCode, FC_EXIT_CODE_UNEXPECTED_ERROR};
9
use logger::{error, IncMetric, METRICS};
10
use utils::signal::register_signal_handler;
11
12
// The offset of `si_syscall` (offending syscall identifier) within the siginfo structure
13
// expressed as an `(u)int*`.
14
// Offset `6` for an `i32` field means that the needed information is located at `6 * sizeof(i32)`.
15
// See /usr/include/linux/signal.h for the C struct definition.
16
// See https://github.com/rust-lang/libc/issues/716 for why the offset is different in Rust.
17
const SI_OFF_SYSCALL: isize = 6;
18
19
const SYS_SECCOMP_CODE: i32 = 1;
20
21
#[inline]
22
fn exit_with_code(exit_code: ExitCode) {
23
// Write the metrics before exiting.
24
if let Err(e) = METRICS.write() {
25
error!("Failed to write metrics while stopping: {}", e);
26
}
27
// Safe because we're terminating the process anyway.
28
unsafe { libc::_exit(exit_code) };
29
}
30
31
macro_rules! generate_handler {
32
($fn_name:ident ,$signal_name:ident, $exit_code:ident, $signal_metric:expr, $body:ident) => {
33
#[inline(always)]
34
extern "C" fn $fn_name(num: c_int, info: *mut siginfo_t, _unused: *mut c_void) {
35
// Safe because we're just reading some fields from a supposedly valid argument.
36
let si_signo = unsafe { (*info).si_signo };
37
let si_code = unsafe { (*info).si_code };
38
39
if num != si_signo || num != $signal_name {
40
exit_with_code(FC_EXIT_CODE_UNEXPECTED_ERROR);
41
}
42
$signal_metric.inc();
43
44
error!(
45
"Shutting down VM after intercepting signal {}, code {}.",
46
si_signo, si_code
47
);
48
49
$body(si_code, info);
50
51
#[cfg(not(test))]
52
match si_signo {
53
$signal_name => exit_with_code(crate::$exit_code),
54
_ => exit_with_code(FC_EXIT_CODE_UNEXPECTED_ERROR),
55
};
56
}
57
};
58
}
59
60
fn log_sigsys_err(si_code: c_int, info: *mut siginfo_t) {
61
if si_code != SYS_SECCOMP_CODE as i32 {
62
// We received a SIGSYS for a reason other than `bad syscall`.
63
exit_with_code(FC_EXIT_CODE_UNEXPECTED_ERROR);
64
}
65
66
// Other signals which might do async unsafe things incompatible with the rest of this
67
// function are blocked due to the sa_mask used when registering the signal handler.
68
let syscall = unsafe { *(info as *const i32).offset(SI_OFF_SYSCALL) as usize };
69
error!(
70
"Shutting down VM after intercepting a bad syscall ({}).",
71
syscall
72
);
73
}
74
75
fn empty_fn(_si_code: c_int, _info: *mut siginfo_t) {}
76
77
generate_handler!(
78
sigxfsz_handler,
79
SIGXFSZ,
80
FC_EXIT_CODE_SIGXFSZ,
81
METRICS.signals.sigxfsz,
82
empty_fn
83
);
84
85
generate_handler!(
86
sigxcpu_handler,
87
SIGXCPU,
88
FC_EXIT_CODE_SIGXCPU,
89
METRICS.signals.sigxcpu,
90
empty_fn
91
);
92
93
generate_handler!(
94
sigbus_handler,
95
SIGBUS,
96
FC_EXIT_CODE_SIGBUS,
97
METRICS.signals.sigbus,
98
empty_fn
99
);
100
101
generate_handler!(
102
sigsegv_handler,
103
SIGSEGV,
104
FC_EXIT_CODE_SIGSEGV,
105
METRICS.signals.sigsegv,
106
empty_fn
107
);
108
109
generate_handler!(
110
sigsys_handler,
111
SIGSYS,
112
FC_EXIT_CODE_BAD_SYSCALL,
113
METRICS.seccomp.num_faults,
114
log_sigsys_err
115
);
116
117
generate_handler!(
118
sighup_handler,
119
SIGHUP,
120
FC_EXIT_CODE_SIGHUP,
121
METRICS.signals.sighup,
122
empty_fn
123
);
124
generate_handler!(
125
sigill_handler,
126
SIGILL,
127
FC_EXIT_CODE_SIGILL,
128
METRICS.signals.sigill,
129
empty_fn
130
);
131
132
#[inline(always)]
133
extern "C" fn sigpipe_handler(num: c_int, info: *mut siginfo_t, _unused: *mut c_void) {
134
// Just record the metric and allow the process to continue, the EPIPE error needs
135
// to be handled at caller level.
136
137
// Safe because we're just reading some fields from a supposedly valid argument.
138
let si_signo = unsafe { (*info).si_signo };
139
let si_code = unsafe { (*info).si_code };
140
141
if num != si_signo || num != SIGPIPE {
142
error!("Received invalid signal {}, code {}.", si_signo, si_code);
143
return;
144
}
145
146
METRICS.signals.sigpipe.inc();
147
148
error!("Received signal {}, code {}.", si_signo, si_code);
149
}
150
151
/// Registers all the required signal handlers.
152
///
153
/// Custom handlers are installed for: `SIGBUS`, `SIGSEGV`, `SIGSYS`
154
/// `SIGXFSZ` `SIGXCPU` `SIGPIPE` `SIGHUP` and `SIGILL`.
155
pub fn register_signal_handlers() -> utils::errno::Result<()> {
156
// Call to unsafe register_signal_handler which is considered unsafe because it will
157
// register a signal handler which will be called in the current thread and will interrupt
158
// whatever work is done on the current thread, so we have to keep in mind that the registered
159
// signal handler must only do async-signal-safe operations.
160
register_signal_handler(SIGSYS, sigsys_handler)?;
161
register_signal_handler(SIGBUS, sigbus_handler)?;
162
register_signal_handler(SIGSEGV, sigsegv_handler)?;
163
register_signal_handler(SIGXFSZ, sigxfsz_handler)?;
164
register_signal_handler(SIGXCPU, sigxcpu_handler)?;
165
register_signal_handler(SIGPIPE, sigpipe_handler)?;
166
register_signal_handler(SIGHUP, sighup_handler)?;
167
register_signal_handler(SIGILL, sigill_handler)?;
168
Ok(())
169
}
170
171
#[cfg(test)]
172
mod tests {
173
use super::*;
174
175
use libc::{cpu_set_t, syscall};
176
use std::{mem, process, thread};
177
178
use seccompiler::sock_filter;
179
180
// This function is used when running unit tests, so all the unsafes are safe.
181
fn cpu_count() -> usize {
182
let mut cpuset: cpu_set_t = unsafe { mem::zeroed() };
183
unsafe {
184
libc::CPU_ZERO(&mut cpuset);
185
}
186
let ret = unsafe {
187
libc::sched_getaffinity(
188
0,
189
mem::size_of::<cpu_set_t>(),
190
&mut cpuset as *mut cpu_set_t,
191
)
192
};
193
assert_eq!(ret, 0);
194
195
let mut num = 0;
196
for i in 0..libc::CPU_SETSIZE as usize {
197
if unsafe { libc::CPU_ISSET(i, &cpuset) } {
198
num += 1;
199
}
200
}
201
num
202
}
203
204
#[test]
205
fn test_signal_handler() {
206
let child = thread::spawn(move || {
207
assert!(register_signal_handlers().is_ok());
208
209
let filter = make_test_seccomp_bpf_filter();
210
211
assert!(seccompiler::apply_filter(&filter).is_ok());
212
assert_eq!(METRICS.seccomp.num_faults.count(), 0);
213
214
// Call the forbidden `SYS_mkdirat`.
215
unsafe { libc::syscall(libc::SYS_mkdirat, "/foo/bar\0") };
216
217
// Call SIGBUS signal handler.
218
assert_eq!(METRICS.signals.sigbus.count(), 0);
219
unsafe {
220
syscall(libc::SYS_kill, process::id(), SIGBUS);
221
}
222
223
// Call SIGSEGV signal handler.
224
assert_eq!(METRICS.signals.sigsegv.count(), 0);
225
unsafe {
226
syscall(libc::SYS_kill, process::id(), SIGSEGV);
227
}
228
229
// Call SIGXFSZ signal handler.
230
assert_eq!(METRICS.signals.sigxfsz.count(), 0);
231
unsafe {
232
syscall(libc::SYS_kill, process::id(), SIGXFSZ);
233
}
234
235
// Call SIGXCPU signal handler.
236
assert_eq!(METRICS.signals.sigxcpu.count(), 0);
237
unsafe {
238
syscall(libc::SYS_kill, process::id(), SIGXCPU);
239
}
240
241
// Call SIGPIPE signal handler.
242
assert_eq!(METRICS.signals.sigpipe.count(), 0);
243
unsafe {
244
syscall(libc::SYS_kill, process::id(), SIGPIPE);
245
}
246
247
// Call SIGHUP signal handler.
248
assert_eq!(METRICS.signals.sighup.count(), 0);
249
unsafe {
250
syscall(libc::SYS_kill, process::id(), SIGHUP);
251
}
252
253
// Call SIGILL signal handler.
254
assert_eq!(METRICS.signals.sigill.count(), 0);
255
unsafe {
256
syscall(libc::SYS_kill, process::id(), SIGILL);
257
}
258
});
259
assert!(child.join().is_ok());
260
261
// Sanity check.
262
assert!(cpu_count() > 0);
263
// Kcov somehow messes with our handler getting the SIGSYS signal when a bad syscall
264
// is caught, so the following assertion no longer holds. Ideally, we'd have a surefire
265
// way of either preventing this behaviour, or detecting for certain whether this test is
266
// run by kcov or not. The best we could do so far is to look at the perceived number of
267
// available CPUs. Kcov seems to make a single CPU available to the process running the
268
// tests, so we use this as an heuristic to decide if we check the assertion.
269
if cpu_count() > 1 {
270
// The signal handler should let the program continue during unit tests.
271
assert!(METRICS.seccomp.num_faults.count() >= 1);
272
}
273
assert!(METRICS.signals.sigbus.count() >= 1);
274
assert!(METRICS.signals.sigsegv.count() >= 1);
275
assert!(METRICS.signals.sigxfsz.count() >= 1);
276
assert!(METRICS.signals.sigxcpu.count() >= 1);
277
assert!(METRICS.signals.sigpipe.count() >= 1);
278
assert!(METRICS.signals.sighup.count() >= 1);
279
// Workaround to GitHub issue 2216.
280
#[cfg(not(target_arch = "aarch64"))]
281
assert!(METRICS.signals.sigill.count() >= 1);
282
}
283
284
fn make_test_seccomp_bpf_filter() -> Vec<sock_filter> {
285
// Create seccomp filter that allows all syscalls, except for `SYS_mkdirat`.
286
// For some reason, directly calling `SYS_kill` with SIGSYS, like we do with the
287
// other signals, results in an error. Probably because of the way `cargo test` is
288
// handling signals.
289
#[cfg(target_arch = "aarch64")]
290
#[allow(clippy::unreadable_literal)]
291
let bpf_filter = vec![
292
sock_filter {
293
code: 32,
294
jt: 0,
295
jf: 0,
296
k: 4,
297
},
298
sock_filter {
299
code: 21,
300
jt: 1,
301
jf: 0,
302
k: 3221225655,
303
},
304
sock_filter {
305
code: 6,
306
jt: 0,
307
jf: 0,
308
k: 0,
309
},
310
sock_filter {
311
code: 32,
312
jt: 0,
313
jf: 0,
314
k: 0,
315
},
316
sock_filter {
317
code: 21,
318
jt: 0,
319
jf: 1,
320
k: 34,
321
},
322
sock_filter {
323
code: 5,
324
jt: 0,
325
jf: 0,
326
k: 1,
327
},
328
sock_filter {
329
code: 5,
330
jt: 0,
331
jf: 0,
332
k: 2,
333
},
334
sock_filter {
335
code: 6,
336
jt: 0,
337
jf: 0,
338
k: 196608,
339
},
340
sock_filter {
341
code: 6,
342
jt: 0,
343
jf: 0,
344
k: 2147418112,
345
},
346
sock_filter {
347
code: 6,
348
jt: 0,
349
jf: 0,
350
k: 2147418112,
351
},
352
];
353
#[cfg(target_arch = "x86_64")]
354
#[allow(clippy::unreadable_literal)]
355
let bpf_filter = vec![
356
sock_filter {
357
code: 32,
358
jt: 0,
359
jf: 0,
360
k: 4,
361
},
362
sock_filter {
363
code: 21,
364
jt: 1,
365
jf: 0,
366
k: 3221225534,
367
},
368
sock_filter {
369
code: 6,
370
jt: 0,
371
jf: 0,
372
k: 0,
373
},
374
sock_filter {
375
code: 32,
376
jt: 0,
377
jf: 0,
378
k: 0,
379
},
380
sock_filter {
381
code: 21,
382
jt: 0,
383
jf: 1,
384
k: 258,
385
},
386
sock_filter {
387
code: 5,
388
jt: 0,
389
jf: 0,
390
k: 1,
391
},
392
sock_filter {
393
code: 5,
394
jt: 0,
395
jf: 0,
396
k: 2,
397
},
398
sock_filter {
399
code: 6,
400
jt: 0,
401
jf: 0,
402
k: 196608,
403
},
404
sock_filter {
405
code: 6,
406
jt: 0,
407
jf: 0,
408
k: 2147418112,
409
},
410
sock_filter {
411
code: 6,
412
jt: 0,
413
jf: 0,
414
k: 2147418112,
415
},
416
];
417
418
bpf_filter
419
}
420
}
421
422