Skip to content

Commit e7af44b

Browse files
author
Alexandra Iordache
committed
vmm: add signal handler for SIGBUS, SIGSEGV
* rework signal handling code to handle more than SIGSYS. * add a minimal representation of the siginfo_t structure that mirrors the union-model defined in libc. This works around the missing functionality in the libc crate, where siginfo_t is defined using a generic padding field. The union solution allows us to extract fields from the siginfo struct without pesky offset calculations and transmutations. Signed-off-by: Alexandra Iordache <[email protected]>
1 parent 5452cbc commit e7af44b

File tree

4 files changed

+257
-46
lines changed

4 files changed

+257
-46
lines changed

src/main.rs

+1-1
Original file line numberDiff line numberDiff line change
@@ -40,7 +40,7 @@ fn main() {
4040
.preinit(Some(DEFAULT_INSTANCE_ID.to_string()))
4141
.expect("Failed to register logger");
4242

43-
if let Err(e) = vmm::setup_sigsys_handler() {
43+
if let Err(e) = vmm::setup_signal_handlers() {
4444
error!("Failed to register signal handler: {}", e);
4545
process::exit(i32::from(vmm::FC_EXIT_CODE_GENERIC_ERROR));
4646
}

vmm/src/lib.rs

+17-1
Original file line numberDiff line numberDiff line change
@@ -39,6 +39,8 @@ extern crate sys_util;
3939
/// Syscalls allowed through the seccomp filter.
4040
pub mod default_syscalls;
4141
mod device_manager;
42+
/// Generic signal handling utilities.
43+
mod signal;
4244
/// Signal handling utilities for seccomp violations.
4345
mod sigsys_handler;
4446
/// Wrappers over structures used to configure the VMM.
@@ -77,7 +79,6 @@ use memory_model::{GuestAddress, GuestMemory};
7779
use net_util::TapError;
7880
#[cfg(target_arch = "aarch64")]
7981
use serde_json::Value;
80-
pub use sigsys_handler::setup_sigsys_handler;
8182
use sys_util::{EventFd, Terminal};
8283
use vmm_config::boot_source::{BootSourceConfig, BootSourceConfigError};
8384
use vmm_config::drive::{BlockDeviceConfig, BlockDeviceConfigs, DriveError};
@@ -114,6 +115,10 @@ pub const FC_EXIT_CODE_GENERIC_ERROR: u8 = 1;
114115
pub const FC_EXIT_CODE_UNEXPECTED_ERROR: u8 = 2;
115116
/// Firecracker was shut down after intercepting a restricted system call.
116117
pub const FC_EXIT_CODE_BAD_SYSCALL: u8 = 148;
118+
/// Firecracker was shut down after intercepting `SIGBUS`.
119+
pub const FC_EXIT_CODE_SIGBUS: u8 = 149;
120+
/// Firecracker was shut down after intercepting `SIGSEGV`.
121+
pub const FC_EXIT_CODE_SIGSEGV: u8 = 150;
117122

118123
/// Errors associated with the VMM internal logic. These errors cannot be generated by direct user
119124
/// input, but can result from bad configuration of the host (for example if Firecracker doesn't
@@ -2025,6 +2030,17 @@ pub fn start_vmm_thread(
20252030
.expect("VMM thread spawn failed.")
20262031
}
20272032

2033+
/// Sets up appropriate signal handlers for all signals relevant to Firecracker.
2034+
///
2035+
pub fn setup_signal_handlers() -> result::Result<(), io::Error> {
2036+
signal::setup_signal_handler(&vec![libc::SIGSYS], sigsys_handler::sigsys_handler)?;
2037+
signal::setup_signal_handler(
2038+
&vec![libc::SIGBUS, libc::SIGSEGV],
2039+
signal::sigbus_sigsegv_handler,
2040+
)?;
2041+
Ok(())
2042+
}
2043+
20282044
#[cfg(test)]
20292045
mod tests {
20302046
extern crate tempfile;

vmm/src/signal.rs

+230
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,230 @@
1+
// Copyright 2018 Amazon.com, Inc. or its affiliates. All Rights Reserved.
2+
// SPDX-License-Identifier: Apache-2.0
3+
4+
extern crate logger;
5+
extern crate sys_util;
6+
7+
use std::io;
8+
use std::mem;
9+
use std::ptr::null_mut;
10+
use std::result::Result;
11+
12+
use libc::{
13+
_exit, c_int, c_uint, c_void, sigaction, sigfillset, siginfo_t, sigset_t, SA_SIGINFO, SIGBUS,
14+
SIGSEGV,
15+
};
16+
17+
use logger::LOGGER;
18+
19+
type SiginfoHandler = extern "C" fn(num: c_int, info: *mut siginfo_t, _unused: *mut c_void) -> ();
20+
21+
// `SIGBUS` codes.
22+
// Hardware memory error consumed on a machine check: action required.
23+
const BUS_MCEERR_AR: c_int = 4;
24+
// Hardware memory error detected in process but not consumed: action optional.
25+
const BUS_MCEERR_AO: c_int = 5;
26+
27+
// `SIGSEGV` codes.
28+
// Failed address bound checks.
29+
const SEGV_BNDERR: c_int = 3;
30+
// Failed protection key checks.
31+
const SEGV_PKUERR: c_int = 4;
32+
33+
/// Address bounds at time of fault.
34+
/// See https://elixir.bootlin.com/linux/v4.14/source/include/uapi/asm-generic/siginfo.h
35+
#[repr(C)]
36+
#[derive(Copy, Clone)]
37+
pub(crate) struct _si_addr_bnd_t {
38+
/// Lower bound.
39+
pub(crate) _lower: *const c_void,
40+
/// Upper bound.
41+
pub(crate) _upper: *const c_void,
42+
}
43+
44+
/// Representation of the fault stats for different `SIGSEGV` codes.
45+
/// See https://elixir.bootlin.com/linux/v4.14/source/include/uapi/asm-generic/siginfo.h
46+
#[repr(C)]
47+
#[derive(Copy, Clone)]
48+
pub(crate) union _si_fault_t {
49+
/// Used when `si_code` == `SEGV_BNDERR`.
50+
pub(crate) _addr_bnd: _si_addr_bnd_t,
51+
/// Used when `si_code` == `SEGV_PKUERR`.
52+
pub(crate) _pkey: c_uint,
53+
}
54+
55+
/// Stats filled in for `SIGILL`, `SIGFPE`, `SIGSEGV`, `SIGBUS`.
56+
/// See https://elixir.bootlin.com/linux/v4.14/source/include/uapi/asm-generic/siginfo.h
57+
#[repr(C)]
58+
#[derive(Copy, Clone)]
59+
pub(crate) struct _sigfault_t {
60+
/// Faulting instruction / memory reference address.
61+
pub(crate) _si_addr: *const c_void,
62+
/// Valid LSB of the reported address.
63+
pub(crate) _si_addr_lsb: i16,
64+
/// Fault stats.
65+
pub(crate) _si_stats: _si_fault_t,
66+
}
67+
68+
/// Stats filled in for `SIGSYS`.
69+
/// See https://elixir.bootlin.com/linux/v4.14/source/include/uapi/asm-generic/siginfo.h
70+
#[repr(C)]
71+
#[derive(Copy, Clone)]
72+
pub(crate) struct _sigsys_t {
73+
/// Call address.
74+
pub(crate) _call_addr: *const c_void,
75+
/// Offending syscall number.
76+
pub(crate) _syscall: c_int,
77+
/// Architecture identifier.
78+
pub(crate) _arch: c_uint,
79+
}
80+
81+
/// Union of possible additional stats returned in the `siginfo` struct.
82+
/// See https://elixir.bootlin.com/linux/v4.14/source/include/uapi/asm-generic/siginfo.h
83+
#[repr(C)]
84+
#[derive(Copy, Clone)]
85+
pub(crate) union _si_fields_t {
86+
/// Fault info. Filled in for `SIGSEGV`, `SIGBUS`, `SIGILL`, `SIGFPE`.
87+
pub(crate) _si_sigfault: _sigfault_t,
88+
/// `SIGSYS` info. Filled in for seccomp faults.
89+
pub(crate) _si_sigsys: _sigsys_t,
90+
/// Padding.
91+
_pad: [c_int; 29],
92+
}
93+
94+
/// Representation of the `siginfo` struct with its relevant fields.
95+
/// See https://elixir.bootlin.com/linux/v4.14/source/include/uapi/asm-generic/siginfo.h
96+
#[repr(C)]
97+
#[derive(Copy, Clone)]
98+
pub(crate) struct _si_siginfo_t {
99+
/// Signal number.
100+
pub(crate) si_signo: c_int,
101+
/// If non-zero, errno value associated with the signal.
102+
pub(crate) si_errno: c_int,
103+
/// Signal code.
104+
pub(crate) si_code: c_int,
105+
/// Additional fields.
106+
pub(crate) si_fields: _si_fields_t,
107+
}
108+
109+
/// Sets up the specified handler for the signals.
110+
///
111+
/// # Arguments
112+
///
113+
/// * `signals` - vector of signals to be handled.
114+
/// * `handler` - signal handler function.
115+
///
116+
pub(crate) fn setup_signal_handler(
117+
signals: &Vec<c_int>,
118+
handler: SiginfoHandler,
119+
) -> Result<(), io::Error> {
120+
// Safe, because this is a POD struct.
121+
let mut sigact: sigaction = unsafe { mem::zeroed() };
122+
sigact.sa_flags = SA_SIGINFO;
123+
sigact.sa_sigaction = handler as usize;
124+
125+
// We set all the bits of sa_mask, so all signals are blocked on the current thread while the
126+
// signal handler is executing. Safe because the parameter is valid and we check the return
127+
// value.
128+
if unsafe { sigfillset(&mut sigact.sa_mask as *mut sigset_t) } < 0 {
129+
return Err(io::Error::last_os_error());
130+
}
131+
132+
for signal in signals.iter() {
133+
// Safe because the parameters are valid and we check the return value.
134+
if unsafe { sigaction(*signal, &sigact, null_mut()) } < 0 {
135+
return Err(io::Error::last_os_error());
136+
}
137+
}
138+
139+
Ok(())
140+
}
141+
142+
/// Handles `SIGBUS` and `SIGSEGV`.
143+
///
144+
/// Logs all the available information on the fault that occurred and exits the process.
145+
///
146+
/// # Arguments
147+
///
148+
/// * `num` - signal number.
149+
/// * `info` - signal information filled in by the kernel.
150+
/// * `c_void` - signal context. Unused.
151+
///
152+
pub(crate) extern "C" fn sigbus_sigsegv_handler(
153+
num: c_int,
154+
info: *mut siginfo_t,
155+
_unused: *mut c_void,
156+
) {
157+
let siginfo = info as *mut _si_siginfo_t;
158+
// Safe because we dereference a valid value.
159+
let si_signo = unsafe { (*siginfo).si_signo };
160+
let si_code = unsafe { (*siginfo).si_code };
161+
162+
// Sanity check. The condition should never be true.
163+
if num != si_signo || (num != SIGBUS && num != SIGSEGV) {
164+
// Safe because we're terminating the process anyway.
165+
unsafe { _exit(i32::from(super::FC_EXIT_CODE_UNEXPECTED_ERROR)) };
166+
}
167+
168+
// `SIGSEGV` and `SIGBUS` fill in `si_addr` with the address of the fault.
169+
// http://man7.org/linux/man-pages/man2/sigaction.2.html
170+
// Safe because we dereference a valid value.
171+
let si_addr = unsafe { (*siginfo).si_fields._si_sigfault._si_addr as usize };
172+
error!(
173+
"Caught signal {}. Code: {}. Fault address: {:x?}",
174+
si_signo, si_code, si_addr
175+
);
176+
177+
match si_signo {
178+
SIGBUS => {
179+
match si_code {
180+
// `BUS_MCEERR_AO` and `BUS_MCEERR_AR` also fill in `si_addr_lsb`.
181+
// This field indicates the LSB of the reported address and therefore the extent of
182+
// the corruption. For example, if a full page was corrupted, `si_addr_lsb` contains
183+
// `log2(sysconf(_SC_PAGESIZE))`.
184+
BUS_MCEERR_AO | BUS_MCEERR_AR => {
185+
// Safe because we dereference a valid value.
186+
let si_addr_lsb = unsafe { (*siginfo).si_fields._si_sigfault._si_addr_lsb };
187+
error!("LSB of the reported address: {:x?}", si_addr_lsb);
188+
}
189+
_ => (),
190+
}
191+
}
192+
SIGSEGV => {
193+
match si_code {
194+
SEGV_BNDERR => {
195+
// The `SEGV_BNDERR` suberror of `SIGSEGV` populates `si_lower` and `si_upper`.
196+
// Safe because we dereference a valid value.
197+
let addr_bnd = unsafe { (*siginfo).si_fields._si_sigfault._si_stats._addr_bnd };
198+
error!(
199+
"Failed address bound checks. Bounds: lower {:x?} upper {:x?}",
200+
addr_bnd._lower, addr_bnd._upper
201+
);
202+
}
203+
SEGV_PKUERR => {
204+
// The `SEGV_PKUERR` suberror of `SIGSEGV` populates `si_pkey`.
205+
// Safe because we dereference a valid value.
206+
let pkey = unsafe { (*siginfo).si_fields._si_sigfault._si_stats._pkey };
207+
error!("Failed protection key checks: {}", pkey);
208+
}
209+
_ => (),
210+
}
211+
}
212+
_ => (),
213+
}
214+
215+
// Log the metrics before exiting.
216+
if let Err(e) = LOGGER.log_metrics() {
217+
error!("Failed to log metrics while stopping: {}", e);
218+
}
219+
220+
// Safe because we're terminating the process anyway. We don't actually do anything when
221+
// running unit tests.
222+
#[cfg(not(test))]
223+
unsafe {
224+
_exit(i32::from(match si_signo {
225+
SIGBUS => super::FC_EXIT_CODE_SIGBUS,
226+
SIGSEGV => super::FC_EXIT_CODE_SIGSEGV,
227+
_ => super::FC_EXIT_CODE_UNEXPECTED_ERROR,
228+
}))
229+
};
230+
}

vmm/src/sigsys_handler.rs

+9-44
Original file line numberDiff line numberDiff line change
@@ -4,57 +4,20 @@
44
extern crate logger;
55
extern crate sys_util;
66

7-
use std::io;
8-
use std::mem;
9-
use std::ptr::null_mut;
10-
use std::result::Result;
11-
12-
use libc::{sigaction, sigfillset, sigset_t};
13-
147
use logger::{Metric, LOGGER, METRICS};
15-
16-
// The offset of `si_syscall` (offending syscall identifier) within the siginfo structure
17-
// expressed as an `(u)int*`.
18-
// Offset `6` for an `i32` field means that the needed information is located at `6 * sizeof(i32)`.
19-
// See /usr/include/linux/signal.h for the C struct definition.
20-
// See https://github.com/rust-lang/libc/issues/716 for why the offset is different in Rust.
21-
const SI_OFF_SYSCALL: isize = 6;
8+
use signal::_si_siginfo_t;
229

2310
const SYS_SECCOMP_CODE: i32 = 1;
2411

25-
// This no longer relies on sys_util::register_signal_handler(), which is a lot weirder than it
26-
// should be (at least for this use case). Also, we want to change the sa_mask field of the
27-
// sigaction struct.
28-
/// Sets up the specified signal handler for `SIGSYS`.
29-
pub fn setup_sigsys_handler() -> Result<(), io::Error> {
30-
// Safe, because this is a POD struct.
31-
let mut sigact: sigaction = unsafe { mem::zeroed() };
32-
sigact.sa_flags = libc::SA_SIGINFO;
33-
sigact.sa_sigaction = sigsys_handler as usize;
34-
35-
// We set all the bits of sa_mask, so all signals are blocked on the current thread while the
36-
// SIGSYS handler is executing. Safe because the parameter is valid and we check the return
37-
// value.
38-
if unsafe { sigfillset(&mut sigact.sa_mask as *mut sigset_t) } < 0 {
39-
return Err(io::Error::last_os_error());
40-
}
41-
42-
// Safe because the parameters are valid and we check the return value.
43-
if unsafe { sigaction(libc::SIGSYS, &sigact, null_mut()) } < 0 {
44-
return Err(io::Error::last_os_error());
45-
}
46-
47-
Ok(())
48-
}
49-
50-
extern "C" fn sigsys_handler(
12+
pub(crate) extern "C" fn sigsys_handler(
5113
num: libc::c_int,
5214
info: *mut libc::siginfo_t,
5315
_unused: *mut libc::c_void,
5416
) {
17+
let siginfo = info as *mut _si_siginfo_t;
5518
// Safe because we're just reading some fields from a supposedly valid argument.
56-
let si_signo = unsafe { (*info).si_signo };
57-
let si_code = unsafe { (*info).si_code };
19+
let si_signo = unsafe { (*siginfo).si_signo };
20+
let si_code = unsafe { (*siginfo).si_code };
5821

5922
// Sanity check. The condition should never be true.
6023
if num != si_signo || num != libc::SIGSYS || si_code != SYS_SECCOMP_CODE as i32 {
@@ -64,7 +27,8 @@ extern "C" fn sigsys_handler(
6427

6528
// Other signals which might do async unsafe things incompatible with the rest of this
6629
// function are blocked due to the sa_mask used when registering the signal handler.
67-
let syscall = unsafe { *(info as *const i32).offset(SI_OFF_SYSCALL) as usize };
30+
// Safe because we dereference a valid value.
31+
let syscall = unsafe { (*siginfo).si_fields._si_sigsys._syscall };
6832
METRICS.seccomp.num_faults.inc();
6933
error!(
7034
"Shutting down VM after intercepting a bad syscall ({}).",
@@ -85,6 +49,7 @@ extern "C" fn sigsys_handler(
8549

8650
#[cfg(test)]
8751
mod tests {
52+
use super::super::signal::setup_signal_handler;
8853
use super::*;
8954

9055
use std::mem;
@@ -120,7 +85,7 @@ mod tests {
12085

12186
#[test]
12287
fn test_signal_handler() {
123-
assert!(setup_sigsys_handler().is_ok());
88+
assert!(setup_signal_handler(&vec![libc::SIGSYS], sigsys_handler).is_ok());
12489

12590
let filter = SeccompFilter::new(
12691
vec![

0 commit comments

Comments
 (0)