Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Signal handler for SIGBUS, SIGSEGV #1070

Merged
merged 9 commits into from
May 13, 2019

Conversation

alxiord
Copy link

@alxiord alxiord commented Apr 23, 2019

Added a custom signal handler that terminates the Firecracker process (with libc::_exit) when a SIGSEGV or SIGBUS is intercepted. See #1064 for details on the current handling of these signals.

Issue #, if available: #1064

By submitting this pull request, I confirm that my contribution is made under the terms of the Apache 2.0 license.

@alxiord alxiord self-assigned this Apr 23, 2019
@serban300 serban300 self-requested a review April 25, 2019 08:45
@serban300
Copy link
Contributor

Could we get away with just printing a hex dump of the siginfo struct ? Something like this maybe.

@serban300
Copy link
Contributor

Hmmm actually, if we write the handler in C and just call it from Rust shouldn't it work on different versions of linux, provided that the names of the members of the struct remain the same. Because the definition of the struct will be imported directly from siginfo.h .

@@ -0,0 +1,230 @@
// Copyright 2018 Amazon.com, Inc. or its affiliates. All Rights Reserved.
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I'm not really keen on having linux signals related code split around different crates like so. For example there is also sys_util/src/signal.rs.

The logic looks solid 👍

@nbsdx
Copy link

nbsdx commented May 1, 2019

Just dropping by with my 2 cents (I spent a fair amount of time messing with siginfo_t in rust to get some waitid stuff working properly) and trying to share issues I ran into. My full comments on the issue can be found in rust-lang/libc#716.

@serban300 If the user built the binaries themselves, yes. But distributed versions would need to have runtime kernel version detection to pick the correct structure layout to use, or be built to target specific kernel versions.

@aghecenco Your _siginfo_t struct is going to be larger than the expected 128 bytes. The 29 c_int padding assumes a 4 byte alignment of the union, which isn't the case. The SIGSYS and SIGFAULT union members require 8 byte alignment on 64-bit systems due to the void * being the first member of those structures. Since the sifields member of _siginfo_t is at an offset of 12 bytes, it will add 4 bytes of padding to fix alignment issues, resulting in a 16 byte header, and then your 29*4 bytes of padding, resulting in a total size of 132 bytes. So the padding should probably be conditionally sized based on the alignment of the system.

@alxiord
Copy link
Author

alxiord commented May 6, 2019

@nbsdx Thanks for that, good call! I followed your issue - I first stumbled across it when I added the signal handler for seccomp, and again now for the more general purpose one. I got a bit lost in the details and would need to backtrack and fix the alignment issues but first I need to know if there's value for Firecracker in doing this in Rust. The problem is with kernel compatibility, as Firecracker isn't strongly tied to a specific kernel version, and I find it would be overkill to add kernel-specific functionalities to work around known bugs in older kernels that we support (plus runtime version checks).

I don't understand why the solution @serban300 proposes wouldn't work. The C API also exposes #define'd getters for certain values, and their implementation differs from kernel to kernel - see for instance si_pkey:

4.14:

#define si_pkey		_sifields._sigfault._pkey

4.16:

#define si_pkey		_sifields._sigfault._addr_pkey._pkey

Using the getters instead of accessing the struct fields themselves should protect against compatibility issues.

That said, I'm wary about adding raw C in Firecracker. @firecracker-microvm/compute-capsule thoughts?

@nbsdx
Copy link

nbsdx commented May 6, 2019

Sorry, I wasn't particularly clear on that. The issue would be with binary releases. User-built binaries would generally be fine as they would be built against the correct headers. But if the firecracker build server builds against 4.14, and a user tries to run the prebuilt binaries against 4.16, the field access will be incorrect, since the kernel/userspace interface (ie, structure layout) changed.

For what it's worth, Linux 4.14 LTS support ends in Jan 2020. But since Firecracker claims to support 4.14 onwards, there should be proper handling of kernel interface changes (probably at minimum across Linux LTS versions).

Again - just my 2 cents - I obviously don't have any control over decisions :)

@alxiord
Copy link
Author

alxiord commented May 7, 2019

@nbsdx You're right again, I'd missed that, it won't work. For now we resolved to restring handling to _exit and revisit the idea if the need for more siginfo stuff arises later. I will rework this PR in this direction.

@acatangiu yup there's already signal handling functionality split between the sys_util and vnm crates. I propose this:

  • keep generic stuff in sys_util (unless you know a better place). This would be code such as install a generic signal handler; for instance, the setup_sigsys_handler in vmm would move here and change to a generic form;
  • place Firecracker/VMM specifics in vmm. This would consist in the actual signal handlers, where we want exact actions to be taken for specific signals (well, not much other than logging and exiting, but still).

What do you think?

@alxiord alxiord changed the title [RFC] Signal handler for SIGBUS, SIGSEGV Signal handler for SIGBUS, SIGSEGV May 7, 2019
@alxiord alxiord force-pushed the segv_bus branch 6 times, most recently from 747fd20 to eaf30fd Compare May 9, 2019 15:36
@@ -144,8 +170,8 @@ mod tests {
assert!(filter.apply().is_ok());
assert_eq!(METRICS.seccomp.num_faults.count(), 0);

// Calls the blacklisted SYS_getpid.
let _pid = process::id();
// Call the blacklisted `SYS_mkdir`.
Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

TLDR I allowed getpid and kill and changed the blackisted syscall from getpid to mkdir to make the GNU test pass consistently.

Long version:

  • The glibc SYS_getpid caches its result, so it doesn't always do the syscall. Therefore, process::id didn't always break the seccomp filter on GNU - depending on whether SYS_getpid had been called before.
  • I switched to:
    syscall(libc::SYS_getpid); // -> seccomp violation, handled
    ...
    raise(SIGBUS); // -> signal handler called
    That didn't work because glibc's raise calls both getpid and gettid (in some implementations - for instance, on the test machine it does, on mine it doesn't) so getpid had to be whitelisted. That's when I switched to whitelisting getpid and gettid for GNU - but:
    • the code became overly complicated with #[cfg]s for this simple test;
    • the test still failed on the test machine - despite gettid being whitelisted, it still caused a SIGSYS which, even more weirdly, did not get handled by the signal handler and terminated the test.
  • At this point I decided to eliminate #[cfg]s and workarounds and:
    • cause a SIGSYS with mkdir (instead of getpid);
    • emit a SIGBUS with kill (instead of raise);
    • whitelist kill and getpid (for the kill to go through).
      This solution works on both GNU and musl.

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Leaving this clarification here:

whitelist kill and getpid (for the kill to go through).

is done just in a particular unit-test, not whitelisted in general.

@alxiord alxiord force-pushed the segv_bus branch 3 times, most recently from 634a0c5 to 140fc56 Compare May 10, 2019 06:37
Copy link
Contributor

@acatangiu acatangiu left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Love the overall look and documentation 👍

sys_util/src/signal.rs Show resolved Hide resolved
tests/integration_tests/functional/test_signals.py Outdated Show resolved Hide resolved

msg = 'Shutting down VM after intercepting signal {}'.format(signum)
lines = log_fifo.sequential_reader(3)
assert msg in lines[1]
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Are you sure it will always be on line 1?

Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Umm... no 😄 I modified the test to leave the logger some room to wriggle.

/// }
/// ```
///
pub unsafe fn register_vcpu_signal_handler(
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

👍

return Err(io::Error::last_os_error());
// Other signals which might do async unsafe things incompatible with the rest of this
// function are blocked due to the sa_mask used when registering the signal handler.
let syscall = unsafe { *(info as *const i32).offset(SI_OFF_SYSCALL) as usize };
Copy link
Contributor

@acatangiu acatangiu May 10, 2019

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Hmm... magic? Is this the standard way of getting the signum?

Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The signum is fortunately always at the beginning of the sigaction struct - the syscall number is trickier to get to because it's embedded in one of those unions in the struct. I found it easier to just navigate to its offset than to unpack the whole thing, like I (incompletely) tried previously.

// function are blocked due to the sa_mask used when registering the signal handler.
let syscall = unsafe { *(info as *const i32).offset(SI_OFF_SYSCALL) as usize };
METRICS.seccomp.num_faults.inc();
error!(
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Can we log stuff in signal handler?

Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@@ -144,8 +170,8 @@ mod tests {
assert!(filter.apply().is_ok());
assert_eq!(METRICS.seccomp.num_faults.count(), 0);

// Calls the blacklisted SYS_getpid.
let _pid = process::id();
// Call the blacklisted `SYS_mkdir`.
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Leaving this clarification here:

whitelist kill and getpid (for the kill to go through).

is done just in a particular unit-test, not whitelisted in general.

Alexandra Iordache added 8 commits May 10, 2019 16:16
This test fails intermittently when linked against glibc.
Glibc's getpid() caches the result, so it doesn't always do
the syscall. To avoid GNU-related complications, the test
now forces a different syscall (mkdir) - as getpid will be
needed in a later commit.

Signed-off-by: Alexandra Iordache <aghecen@amazon.com>
This step is in preparation to a refactoring that moves generic signal
handling utilities to the sys_util crate, leaving only Firecracker
specifics (read: custom signal handlers themselves) in vmm.

Signed-off-by: Alexandra Iordache <aghecen@amazon.com>
* added a single function in vmm that installs all relevant
  signal handlers;
* left signal handler functions in vmm (currently only for SIGSYS);
* moved signal handling logic to sys_util;
* split sys_util's register_signal_handler into 2 separate functions,
  one to be called exclusively for vCPUs and a second general purpose
  one. The vCPU-specific one doen't change the signal mask and
  alters the signal number, forcing it between SIGRTMIN and SIGRTMAX.
  The general purpose one is setup_sigsys_handler renamed, and will
  morph into a generic one in a following commit.

Signed-off-by: Alexandra Iordache <aghecen@amazon.com>
Renamed register_sigsys_handler to register_signal_handler, enabling
the installation of custom signal handlers for any signal.

Signed-off-by: Alexandra Iordache <aghecen@amazon.com>
Log a message and exit with a specific exit code upon intercepting
SIGBUS/SIGSEGV.

Signed-off-by: Alexandra Iordache <aghecen@amazon.com>
Signed-off-by: Alexandra Iordache <aghecen@amazon.com>
The license checker now accepts files licensed in 2018 and 2019.

Signed-off-by: Alexandra Iordache <aghecen@amazon.com>
The test checks that Firecracker logs the appropriate mesage
when intercepting the signal.
Exit code is not checked because, as the jailer clones into a
new pid namespace, it's no longer in the test process' tree
and its pid cannot be waited on.

Signed-off-by: Alexandra Iordache <aghecen@amazon.com>
Signed-off-by: Alexandra Iordache <aghecen@amazon.com>
@alxiord alxiord merged commit 76594aa into firecracker-microvm:master May 13, 2019
@alxiord alxiord deleted the segv_bus branch May 13, 2019 12:09
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.

4 participants