use libc::{
c_int, c_void, siginfo_t, SIGBUS, SIGHUP, SIGILL, SIGPIPE, SIGSEGV, SIGSYS, SIGXCPU, SIGXFSZ,
};
use crate::{ExitCode, FC_EXIT_CODE_UNEXPECTED_ERROR};
use logger::{error, IncMetric, METRICS};
use utils::signal::register_signal_handler;
const SI_OFF_SYSCALL: isize = 6;
const SYS_SECCOMP_CODE: i32 = 1;
#[inline]
fn exit_with_code(exit_code: ExitCode) {
if let Err(e) = METRICS.write() {
error!("Failed to write metrics while stopping: {}", e);
}
unsafe { libc::_exit(exit_code) };
}
macro_rules! generate_handler {
($fn_name:ident ,$signal_name:ident, $exit_code:ident, $signal_metric:expr, $body:ident) => {
#[inline(always)]
extern "C" fn $fn_name(num: c_int, info: *mut siginfo_t, _unused: *mut c_void) {
let si_signo = unsafe { (*info).si_signo };
let si_code = unsafe { (*info).si_code };
if num != si_signo || num != $signal_name {
exit_with_code(FC_EXIT_CODE_UNEXPECTED_ERROR);
}
$signal_metric.inc();
error!(
"Shutting down VM after intercepting signal {}, code {}.",
si_signo, si_code
);
$body(si_code, info);
#[cfg(not(test))]
match si_signo {
$signal_name => exit_with_code(crate::$exit_code),
_ => exit_with_code(FC_EXIT_CODE_UNEXPECTED_ERROR),
};
}
};
}
fn log_sigsys_err(si_code: c_int, info: *mut siginfo_t) {
if si_code != SYS_SECCOMP_CODE as i32 {
exit_with_code(FC_EXIT_CODE_UNEXPECTED_ERROR);
}
let syscall = unsafe { *(info as *const i32).offset(SI_OFF_SYSCALL) as usize };
error!(
"Shutting down VM after intercepting a bad syscall ({}).",
syscall
);
}
fn empty_fn(_si_code: c_int, _info: *mut siginfo_t) {}
generate_handler!(
sigxfsz_handler,
SIGXFSZ,
FC_EXIT_CODE_SIGXFSZ,
METRICS.signals.sigxfsz,
empty_fn
);
generate_handler!(
sigxcpu_handler,
SIGXCPU,
FC_EXIT_CODE_SIGXCPU,
METRICS.signals.sigxcpu,
empty_fn
);
generate_handler!(
sigbus_handler,
SIGBUS,
FC_EXIT_CODE_SIGBUS,
METRICS.signals.sigbus,
empty_fn
);
generate_handler!(
sigsegv_handler,
SIGSEGV,
FC_EXIT_CODE_SIGSEGV,
METRICS.signals.sigsegv,
empty_fn
);
generate_handler!(
sigsys_handler,
SIGSYS,
FC_EXIT_CODE_BAD_SYSCALL,
METRICS.seccomp.num_faults,
log_sigsys_err
);
generate_handler!(
sighup_handler,
SIGHUP,
FC_EXIT_CODE_SIGHUP,
METRICS.signals.sighup,
empty_fn
);
generate_handler!(
sigill_handler,
SIGILL,
FC_EXIT_CODE_SIGILL,
METRICS.signals.sigill,
empty_fn
);
#[inline(always)]
extern "C" fn sigpipe_handler(num: c_int, info: *mut siginfo_t, _unused: *mut c_void) {
let si_signo = unsafe { (*info).si_signo };
let si_code = unsafe { (*info).si_code };
if num != si_signo || num != SIGPIPE {
error!("Received invalid signal {}, code {}.", si_signo, si_code);
return;
}
METRICS.signals.sigpipe.inc();
error!("Received signal {}, code {}.", si_signo, si_code);
}
pub fn register_signal_handlers() -> utils::errno::Result<()> {
register_signal_handler(SIGSYS, sigsys_handler)?;
register_signal_handler(SIGBUS, sigbus_handler)?;
register_signal_handler(SIGSEGV, sigsegv_handler)?;
register_signal_handler(SIGXFSZ, sigxfsz_handler)?;
register_signal_handler(SIGXCPU, sigxcpu_handler)?;
register_signal_handler(SIGPIPE, sigpipe_handler)?;
register_signal_handler(SIGHUP, sighup_handler)?;
register_signal_handler(SIGILL, sigill_handler)?;
Ok(())
}
#[cfg(test)]
mod tests {
use super::*;
use libc::{cpu_set_t, syscall};
use std::{mem, process, thread};
use seccompiler::sock_filter;
fn cpu_count() -> usize {
let mut cpuset: cpu_set_t = unsafe { mem::zeroed() };
unsafe {
libc::CPU_ZERO(&mut cpuset);
}
let ret = unsafe {
libc::sched_getaffinity(
0,
mem::size_of::<cpu_set_t>(),
&mut cpuset as *mut cpu_set_t,
)
};
assert_eq!(ret, 0);
let mut num = 0;
for i in 0..libc::CPU_SETSIZE as usize {
if unsafe { libc::CPU_ISSET(i, &cpuset) } {
num += 1;
}
}
num
}
#[test]
fn test_signal_handler() {
let child = thread::spawn(move || {
assert!(register_signal_handlers().is_ok());
let filter = make_test_seccomp_bpf_filter();
assert!(seccompiler::apply_filter(&filter).is_ok());
assert_eq!(METRICS.seccomp.num_faults.count(), 0);
unsafe { libc::syscall(libc::SYS_mkdirat, "/foo/bar\0") };
assert_eq!(METRICS.signals.sigbus.count(), 0);
unsafe {
syscall(libc::SYS_kill, process::id(), SIGBUS);
}
assert_eq!(METRICS.signals.sigsegv.count(), 0);
unsafe {
syscall(libc::SYS_kill, process::id(), SIGSEGV);
}
assert_eq!(METRICS.signals.sigxfsz.count(), 0);
unsafe {
syscall(libc::SYS_kill, process::id(), SIGXFSZ);
}
assert_eq!(METRICS.signals.sigxcpu.count(), 0);
unsafe {
syscall(libc::SYS_kill, process::id(), SIGXCPU);
}
assert_eq!(METRICS.signals.sigpipe.count(), 0);
unsafe {
syscall(libc::SYS_kill, process::id(), SIGPIPE);
}
assert_eq!(METRICS.signals.sighup.count(), 0);
unsafe {
syscall(libc::SYS_kill, process::id(), SIGHUP);
}
assert_eq!(METRICS.signals.sigill.count(), 0);
unsafe {
syscall(libc::SYS_kill, process::id(), SIGILL);
}
});
assert!(child.join().is_ok());
assert!(cpu_count() > 0);
if cpu_count() > 1 {
assert!(METRICS.seccomp.num_faults.count() >= 1);
}
assert!(METRICS.signals.sigbus.count() >= 1);
assert!(METRICS.signals.sigsegv.count() >= 1);
assert!(METRICS.signals.sigxfsz.count() >= 1);
assert!(METRICS.signals.sigxcpu.count() >= 1);
assert!(METRICS.signals.sigpipe.count() >= 1);
assert!(METRICS.signals.sighup.count() >= 1);
#[cfg(not(target_arch = "aarch64"))]
assert!(METRICS.signals.sigill.count() >= 1);
}
fn make_test_seccomp_bpf_filter() -> Vec<sock_filter> {
#[cfg(target_arch = "aarch64")]
#[allow(clippy::unreadable_literal)]
let bpf_filter = vec![
sock_filter {
code: 32,
jt: 0,
jf: 0,
k: 4,
},
sock_filter {
code: 21,
jt: 1,
jf: 0,
k: 3221225655,
},
sock_filter {
code: 6,
jt: 0,
jf: 0,
k: 0,
},
sock_filter {
code: 32,
jt: 0,
jf: 0,
k: 0,
},
sock_filter {
code: 21,
jt: 0,
jf: 1,
k: 34,
},
sock_filter {
code: 5,
jt: 0,
jf: 0,
k: 1,
},
sock_filter {
code: 5,
jt: 0,
jf: 0,
k: 2,
},
sock_filter {
code: 6,
jt: 0,
jf: 0,
k: 196608,
},
sock_filter {
code: 6,
jt: 0,
jf: 0,
k: 2147418112,
},
sock_filter {
code: 6,
jt: 0,
jf: 0,
k: 2147418112,
},
];
#[cfg(target_arch = "x86_64")]
#[allow(clippy::unreadable_literal)]
let bpf_filter = vec![
sock_filter {
code: 32,
jt: 0,
jf: 0,
k: 4,
},
sock_filter {
code: 21,
jt: 1,
jf: 0,
k: 3221225534,
},
sock_filter {
code: 6,
jt: 0,
jf: 0,
k: 0,
},
sock_filter {
code: 32,
jt: 0,
jf: 0,
k: 0,
},
sock_filter {
code: 21,
jt: 0,
jf: 1,
k: 258,
},
sock_filter {
code: 5,
jt: 0,
jf: 0,
k: 1,
},
sock_filter {
code: 5,
jt: 0,
jf: 0,
k: 2,
},
sock_filter {
code: 6,
jt: 0,
jf: 0,
k: 196608,
},
sock_filter {
code: 6,
jt: 0,
jf: 0,
k: 2147418112,
},
sock_filter {
code: 6,
jt: 0,
jf: 0,
k: 2147418112,
},
];
bpf_filter
}
}