-
Notifications
You must be signed in to change notification settings - Fork 10
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
[crashtracker] Enable client to configure which signals to trap #856
Changes from 1 commit
e9867ad
90442a0
bedcdc9
41baa22
be55d23
5037260
2cafba3
7881989
2693929
b0cde61
20c3d9e
ab7383e
d878f4a
335ab4c
50df7ed
ec64af0
8d3dd65
70346fb
0f4b6ab
8c26e26
b0bb2b3
a6154dd
4801326
6086d56
1aa8fb3
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -9,6 +9,7 @@ use super::saguard::SaGuard; | |
use crate::crash_info::Metadata; | ||
use crate::shared::configuration::{CrashtrackerConfiguration, CrashtrackerReceiverConfig}; | ||
use crate::shared::constants::*; | ||
use crate::signal_from_signum; | ||
use anyhow::Context; | ||
use libc::{ | ||
c_void, execve, mmap, nfds_t, sigaltstack, siginfo_t, ucontext_t, MAP_ANON, MAP_FAILED, | ||
|
@@ -19,6 +20,7 @@ use nix::sys::signal::{self, SaFlags, SigAction, SigHandler, SigSet}; | |
use nix::sys::socket; | ||
use nix::sys::wait::{waitpid, WaitPidFlag, WaitStatus}; | ||
use nix::unistd::{close, Pid}; | ||
use std::collections::HashMap; | ||
use std::ffi::CString; | ||
use std::fs::{File, OpenOptions}; | ||
use std::io::Write; | ||
|
@@ -54,12 +56,6 @@ use libc::fork as vfork; | |
#[cfg(target_os = "linux")] | ||
use libc::vfork; | ||
|
||
#[derive(Debug)] | ||
struct OldHandlers { | ||
pub sigbus: SigAction, | ||
pub sigsegv: SigAction, | ||
} | ||
|
||
struct Receiver { | ||
receiver_uds: RawFd, | ||
receiver_pid: i32, | ||
|
@@ -255,7 +251,8 @@ fn wait_for_pollhup(target_fd: RawFd, timeout_ms: i32) -> anyhow::Result<bool> { | |
// This means that we can always clean up the memory inside one of these using | ||
// `Box::from_raw` to recreate the box, then dropping it. | ||
static ALTSTACK_INIT: AtomicBool = AtomicBool::new(false); | ||
static OLD_HANDLERS: AtomicPtr<OldHandlers> = AtomicPtr::new(ptr::null_mut()); | ||
static OLD_HANDLERS: AtomicPtr<HashMap<i32, (signal::Signal, SigAction)>> = | ||
AtomicPtr::new(ptr::null_mut()); | ||
static METADATA: AtomicPtr<(Metadata, String)> = AtomicPtr::new(ptr::null_mut()); | ||
static CONFIG: AtomicPtr<(CrashtrackerConfiguration, String)> = AtomicPtr::new(ptr::null_mut()); | ||
static RECEIVER_CONFIG: AtomicPtr<CrashtrackerReceiverConfig> = AtomicPtr::new(ptr::null_mut()); | ||
|
@@ -397,46 +394,36 @@ extern "C" fn handle_posix_sigaction(signum: i32, sig_info: *mut siginfo_t, ucon | |
// instant of time between when the handlers are registered, and the | ||
// `OLD_HANDLERS` are set. This should be very short, but is hard to fully | ||
// eliminate given the existing POSIX APIs. | ||
let old_handlers = unsafe { &*OLD_HANDLERS.load(SeqCst) }; | ||
let old_sigaction = if signum == libc::SIGSEGV { | ||
old_handlers.sigsegv | ||
} else if signum == libc::SIGBUS { | ||
old_handlers.sigbus | ||
let old_handlers = unsafe { &*OLD_HANDLERS.swap(std::ptr::null_mut(), SeqCst) }; | ||
|
||
if let Some((signal, sigaction)) = old_handlers.get(&signum) { | ||
// How we chain depends on what kind of handler we're chaining to. | ||
// https://www.gnu.org/software/libc/manual/html_node/Signal-Handling.html | ||
// https://man7.org/linux/man-pages/man2/sigaction.2.html | ||
// Follow the approach here: | ||
// https://stackoverflow.com/questions/6015498/executing-default-signal-handler | ||
match sigaction.handler() { | ||
SigHandler::SigDfl => { | ||
// In the case of a default handler, we want to invoke it so that | ||
// the core-dump can be generated. Restoring the handler then | ||
// re-raising the signal accomplishes that. | ||
unsafe { signal::sigaction(*signal, sigaction) } | ||
.unwrap_or_else(|_| std::process::abort()); | ||
// Signals are only delivered once. | ||
// In the case where we were invoked because of a crash, returning | ||
// is technically UB but in practice re-invokes the crashing instr | ||
// and re-raises the signal. In the case where we were invoked by | ||
// `raise(SIGSEGV)` we need to re-raise the signal, or the default | ||
// handler will never receive it. | ||
unsafe { libc::raise(signum) }; | ||
} | ||
SigHandler::SigIgn => (), // Return and ignore the signal. | ||
SigHandler::Handler(f) => f(signum), | ||
SigHandler::SigAction(f) => f(signum, sig_info, ucontext), | ||
}; | ||
} else { | ||
unreachable!("The only signals we're registered for are SEGV and BUS") | ||
}; | ||
|
||
// How we chain depends on what kind of handler we're chaining to. | ||
// https://www.gnu.org/software/libc/manual/html_node/Signal-Handling.html | ||
// https://man7.org/linux/man-pages/man2/sigaction.2.html | ||
// Follow the approach here: | ||
// https://stackoverflow.com/questions/6015498/executing-default-signal-handler | ||
match old_sigaction.handler() { | ||
SigHandler::SigDfl => { | ||
// In the case of a default handler, we want to invoke it so that | ||
// the core-dump can be generated. Restoring the handler then | ||
// re-raising the signal accomplishes that. | ||
let signal = if signum == libc::SIGSEGV { | ||
signal::SIGSEGV | ||
} else if signum == libc::SIGBUS { | ||
signal::SIGBUS | ||
} else { | ||
unreachable!("The only signals we're registered for are SEGV and BUS") | ||
}; | ||
unsafe { signal::sigaction(signal, &old_sigaction) } | ||
.unwrap_or_else(|_| std::process::abort()); | ||
// Signals are only delivered once. | ||
// In the case where we were invoked because of a crash, returning | ||
// is technically UB but in practice re-invokes the crashing instr | ||
// and re-raises the signal. In the case where we were invoked by | ||
// `raise(SIGSEGV)` we need to re-raise the signal, or the default | ||
// handler will never receive it. | ||
unsafe { libc::raise(signum) }; | ||
} | ||
SigHandler::SigIgn => (), // Return and ignore the signal. | ||
SigHandler::Handler(f) => f(signum), | ||
SigHandler::SigAction(f) => f(signum, sig_info, ucontext), | ||
}; | ||
eprintln!("Unexpected signal number {signum}, can't chain the handlers"); | ||
} | ||
} | ||
|
||
fn receiver_from_socket(unix_socket_path: &str) -> anyhow::Result<Receiver> { | ||
|
@@ -611,29 +598,33 @@ fn handle_posix_signal_impl( | |
/// will not yet be stored. This would lead to unexpected behaviour for the | ||
/// user. This should only matter if something crashes concurrently with | ||
/// this function executing. | ||
pub fn register_crash_handlers() -> anyhow::Result<()> { | ||
pub fn register_crash_handlers(config: &CrashtrackerConfiguration) -> anyhow::Result<()> { | ||
if !OLD_HANDLERS.load(SeqCst).is_null() { | ||
return Ok(()); | ||
} | ||
|
||
let config_ptr = CONFIG.load(SeqCst); | ||
anyhow::ensure!(!config_ptr.is_null(), "No crashtracking config"); | ||
let (config, _config_str) = unsafe { config_ptr.as_ref().context("config ptr")? }; | ||
|
||
unsafe { | ||
if config.create_alt_stack { | ||
create_alt_stack()?; | ||
} | ||
let sigbus = register_signal_handler(signal::SIGBUS, config)?; | ||
let sigsegv = register_signal_handler(signal::SIGSEGV, config)?; | ||
let boxed_ptr = Box::into_raw(Box::new(OldHandlers { sigbus, sigsegv })); | ||
|
||
let res = OLD_HANDLERS.compare_exchange(ptr::null_mut(), boxed_ptr, SeqCst, SeqCst); | ||
if config.create_alt_stack { | ||
unsafe { create_alt_stack()? }; | ||
} | ||
let mut old_handlers = HashMap::new(); | ||
// TODO: if this fails, we end in a situation where handlers have been registered with no chain | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. What to do here? |
||
for signum in &config.signals { | ||
let signal = signal_from_signum(*signum)?; | ||
anyhow::ensure!( | ||
res.is_ok(), | ||
"TOCTTOU error in crashtracker::register_crash_handlers" | ||
!old_handlers.contains_key(signum), | ||
"Handler already registered for {signal}" | ||
); | ||
let handler = unsafe { register_signal_handler(signal, config)? }; | ||
old_handlers.insert(*signum, (signal, handler)); | ||
} | ||
let boxed_ptr = Box::into_raw(Box::new(old_handlers)); | ||
|
||
let res = OLD_HANDLERS.compare_exchange(ptr::null_mut(), boxed_ptr, SeqCst, SeqCst); | ||
anyhow::ensure!( | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. This leaks the old_handlers, if we care? There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I don't think so--small one-time leak, right? |
||
res.is_ok(), | ||
"TOCTTOU error in crashtracker::register_crash_handlers" | ||
); | ||
|
||
Ok(()) | ||
} | ||
|
||
|
@@ -674,9 +665,11 @@ pub fn restore_old_handlers(inside_signal_handler: bool) -> anyhow::Result<()> { | |
anyhow::ensure!(!prev.is_null(), "No crashtracking previous signal handlers"); | ||
// Safety: The only nonnull pointer stored here comes from Box::into_raw() | ||
let prev = unsafe { Box::from_raw(prev) }; | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. We could flip this around, and only rehydrate into a box when we are ready to drop it |
||
// Safety: The value restored here was returned from a previous sigaction call | ||
unsafe { signal::sigaction(signal::SIGBUS, &prev.sigbus)? }; | ||
unsafe { signal::sigaction(signal::SIGSEGV, &prev.sigsegv)? }; | ||
for (_signum, (signal, sigaction)) in prev.iter() { | ||
// Safety: The value restored here was returned from a previous sigaction call | ||
unsafe { signal::sigaction(*signal, sigaction)? }; | ||
} | ||
|
||
// We want to avoid freeing memory inside the handler, so just leak it | ||
// This is fine since we're crashing anyway at this point | ||
if inside_signal_handler { | ||
|
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
What should we do here? abort?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I guess this could happen if the user code changes the sigaction while we're in the middle of handling a signal? I'm not sure how it might arise.