From b048ddf4700bfb9e21f10c7e49611fccaee9b5b0 Mon Sep 17 00:00:00 2001 From: s1341 Date: Fri, 5 Mar 2021 16:36:44 +0200 Subject: [PATCH] Better Unix Signal Handling Abstractions (#22) * WIP: unix_signal_handling * WIP: unix_signal_handling, another try * only emit a single illegal instruction * unix_signal_handling: Now working * unix_signal_handling: squash warnings * unix_signal_handling: formatting * fix spelling * unix_signal_handling: add missing file * unix_signal_handling: port LlmpBroker * unix_signal_handling: fix missing import * moving towards no_std compatibility * unix_signal_handling: get rid of HashMap, Mutex and lazy-static * unix_signal_handling: formatting * readme * no_std fixes * fixed windows build Co-authored-by: Dominik Maier Co-authored-by: Andrea Fioraldi --- fuzzers/libfuzzer_libmozjpeg/src/fuzzer.rs | 2 +- fuzzers/libfuzzer_libpng/harness.cc | 1 - fuzzers/libfuzzer_libpng/src/fuzzer.rs | 2 +- fuzzers/qemufuzzer/src/lib.rs | 2 +- libafl/Cargo.toml | 1 + libafl/src/bolts/bindings.rs | 2 +- libafl/src/bolts/llmp.rs | 136 +++--- libafl/src/bolts/mod.rs | 1 + libafl/src/bolts/os/mod.rs | 2 + libafl/src/bolts/os/unix_signals.rs | 169 ++++++++ libafl/src/bolts/serdeany.rs | 3 +- libafl/src/bolts/shmem.rs | 4 +- libafl/src/executors/inprocess.rs | 463 ++++++++++----------- libafl/src/lib.rs | 3 +- libafl/src/utils.rs | 10 +- 15 files changed, 468 insertions(+), 333 deletions(-) create mode 100644 libafl/src/bolts/os/mod.rs create mode 100644 libafl/src/bolts/os/unix_signals.rs diff --git a/fuzzers/libfuzzer_libmozjpeg/src/fuzzer.rs b/fuzzers/libfuzzer_libmozjpeg/src/fuzzer.rs index 664f8ae46b..ffbbcdfa78 100644 --- a/fuzzers/libfuzzer_libmozjpeg/src/fuzzer.rs +++ b/fuzzers/libfuzzer_libmozjpeg/src/fuzzer.rs @@ -128,7 +128,7 @@ fn fuzz(corpus_dirs: Vec, objective_dir: PathBuf, broker_port: u16) -> tuple_list!(edges_observer), &mut state, &mut restarting_mgr, - ); + )?; // The actual target run starts here. // Call LLVMFUzzerInitialize() if present. diff --git a/fuzzers/libfuzzer_libpng/harness.cc b/fuzzers/libfuzzer_libpng/harness.cc index 15c204dc9f..65faff685d 100644 --- a/fuzzers/libfuzzer_libpng/harness.cc +++ b/fuzzers/libfuzzer_libpng/harness.cc @@ -158,7 +158,6 @@ extern "C" int LLVMFuzzerTestOneInput(const uint8_t* data, size_t size) { if (width && height > 100000000 / width) { PNG_CLEANUP #ifdef HAS_DUMMY_CRASH - asm("ud2"); #ifdef __aarch64__ asm volatile (".word 0xf7f0a000\n"); #else diff --git a/fuzzers/libfuzzer_libpng/src/fuzzer.rs b/fuzzers/libfuzzer_libpng/src/fuzzer.rs index 5c9e1e1489..64f05e2a8c 100644 --- a/fuzzers/libfuzzer_libpng/src/fuzzer.rs +++ b/fuzzers/libfuzzer_libpng/src/fuzzer.rs @@ -150,7 +150,7 @@ fn fuzz(corpus_dirs: Vec, objective_dir: PathBuf, broker_port: u16) -> tuple_list!(edges_observer), &mut state, &mut restarting_mgr, - ); + )?; // The actual target run starts here. // Call LLVMFUzzerInitialize() if present. diff --git a/fuzzers/qemufuzzer/src/lib.rs b/fuzzers/qemufuzzer/src/lib.rs index 8ed917e4b0..4413e0f67e 100644 --- a/fuzzers/qemufuzzer/src/lib.rs +++ b/fuzzers/qemufuzzer/src/lib.rs @@ -72,7 +72,7 @@ pub extern "C" fn fuzz_main_loop() { }); let edges_feedback = MaxMapFeedback::new_with_observer(&NAME_COV_MAP, &edges_observer); - let executor = InProcessExecutor::new("QEMUFuzzer", harness, tuple_list!(edges_observer)); + let executor = InProcessExecutor::new("QEMUFuzzer", harness, tuple_list!(edges_observer))?; let mut state = State::new(tuple_list!(edges_feedback)); let mut engine = Engine::new(executor); diff --git a/libafl/Cargo.toml b/libafl/Cargo.toml index e521f99128..01a78fc319 100644 --- a/libafl/Cargo.toml +++ b/libafl/Cargo.toml @@ -54,6 +54,7 @@ ctor = "*" libafl_derive = { version = "*", optional = true, path = "../libafl_derive" } serde_json = { version = "1.0", optional = true, default-features = false, features = ["alloc"] } # an easy way to debug print SerdeAnyMap #TODO: for llmp brotli = { version = "3.3.0", default-features = false } # brotli compression +num_enum = "0.5.1" [target.'cfg(unix)'.dependencies] libc = "0.2" # For (*nix) libc diff --git a/libafl/src/bolts/bindings.rs b/libafl/src/bolts/bindings.rs index 02f7aa456c..a3fa41413e 100644 --- a/libafl/src/bolts/bindings.rs +++ b/libafl/src/bolts/bindings.rs @@ -1,2 +1,2 @@ -#[cfg(windows)] +#[cfg(all(windows, feature = "std"))] ::windows::include_bindings!(); diff --git a/libafl/src/bolts/llmp.rs b/libafl/src/bolts/llmp.rs index df975c8088..435deb4bab 100644 --- a/libafl/src/bolts/llmp.rs +++ b/libafl/src/bolts/llmp.rs @@ -66,8 +66,7 @@ use serde::{Deserialize, Serialize}; use std::{ env, fs, io::{Read, Write}, - net::SocketAddr, - net::{TcpListener, TcpStream}, + net::{SocketAddr, TcpListener, TcpStream}, thread, }; @@ -79,10 +78,10 @@ use nix::{ uio::IoVec, }, }; + #[cfg(all(feature = "std", unix))] use std::{ ffi::CStr, - mem::zeroed, os::unix::{ self, net::{UnixListener, UnixStream}, @@ -91,11 +90,10 @@ use std::{ }; #[cfg(all(feature = "std", unix))] -use libc::{ - c_char, c_int, c_void, malloc, sigaction, sigaltstack, siginfo_t, SA_NODEFER, SA_ONSTACK, - SA_SIGINFO, SIGINT, SIGQUIT, SIGTERM, -}; +use libc::c_char; +#[cfg(unix)] +use crate::bolts::os::unix_signals::{c_void, setup_signal_handler, siginfo_t, Handler, Signal}; use crate::{ bolts::shmem::{ShMem, ShMemDescription}, Error, @@ -131,6 +129,12 @@ const EOP_MSG_SIZE: usize = /// The header length of a llmp page in a shared map (until messages start) const LLMP_PAGE_HEADER_LEN: usize = size_of::(); +/// The llmp broker registers a signal handler for cleanups on `SIGINT`. +#[cfg(unix)] +static mut GLOBAL_SIGHANDLER_STATE: LlmpBrokerSignalHandler = LlmpBrokerSignalHandler { + shutting_down: false, +}; + /// TAGs used thorughout llmp pub type Tag = u32; @@ -873,7 +877,7 @@ where let last_message_offset = if self.last_msg_sent.is_null() { None } else { - Some(map.msg_to_offset(self.last_msg_sent)?) + Some(unsafe { map.msg_to_offset(self.last_msg_sent) }?) }; Ok(LlmpDescription { shmem: map.shmem.description(), @@ -1084,7 +1088,7 @@ where let last_message_offset = if self.last_msg_recvd.is_null() { None } else { - Some(map.msg_to_offset(self.last_msg_recvd)?) + Some(unsafe { map.msg_to_offset(self.last_msg_recvd) }?) }; Ok(LlmpDescription { shmem: map.shmem.description(), @@ -1164,18 +1168,18 @@ where /// Gets the offset of a message on this here page. /// Will return IllegalArgument error if msg is not on page. - pub fn msg_to_offset(&self, msg: *const LlmpMsg) -> Result { - unsafe { - let page = self.page(); - if llmp_msg_in_page(page, msg) { - // Cast both sides to u8 arrays, get the offset, then cast the return isize to u64 - Ok((msg as *const u8).offset_from((*page).messages.as_ptr() as *const u8) as u64) - } else { - Err(Error::IllegalArgument(format!( - "Message (0x{:X}) not in page (0x{:X})", - page as u64, msg as u64 - ))) - } + /// # Safety + /// This dereferences msg, make sure to pass a proper pointer to it. + pub unsafe fn msg_to_offset(&self, msg: *const LlmpMsg) -> Result { + let page = self.page(); + if llmp_msg_in_page(page, msg) { + // Cast both sides to u8 arrays, get the offset, then cast the return isize to u64 + Ok((msg as *const u8).offset_from((*page).messages.as_ptr() as *const u8) as u64) + } else { + Err(Error::IllegalArgument(format!( + "Message (0x{:X}) not in page (0x{:X})", + page as u64, msg as u64 + ))) } } @@ -1198,7 +1202,7 @@ where } else { env::set_var( &format!("{}_OFFSET", map_env_name), - format!("{}", self.msg_to_offset(msg)?), + format!("{}", unsafe { self.msg_to_offset(msg) }?), ) }; Ok(()) @@ -1244,9 +1248,21 @@ where shutting_down: bool, } -/// used to access the current broker in signal handler. -#[cfg(all(feature = "std", unix))] -static mut CURRENT_BROKER_PTR: *const c_void = ptr::null(); +#[cfg(unix)] +pub struct LlmpBrokerSignalHandler { + shutting_down: bool, +} + +#[cfg(all(unix))] +impl Handler for LlmpBrokerSignalHandler { + fn handle(&mut self, _signal: Signal, _info: siginfo_t, _void: c_void) { + unsafe { ptr::write_volatile(&mut self.shutting_down, true) }; + } + + fn signals(&self) -> Vec { + vec![Signal::SigTerm, Signal::SigInterrupt, Signal::SigQuit] + } +} /// The broker forwards all messages to its own bus-like broadcast map. /// It may intercept messages passing through. @@ -1327,54 +1343,18 @@ where Ok(()) } - /// Called from an interrupt: Sets broker `shutting_down` flag to `true`. - /// Currently only supported on `std` unix systems. - #[cfg(all(feature = "std", unix))] - fn shutdown(&mut self) { - unsafe { ptr::write_volatile(&mut self.shutting_down, true) }; - compiler_fence(Ordering::SeqCst); + /// Internal function, returns true when shuttdown is requested by a `SIGINT` signal + #[inline] + #[cfg(unix)] + fn is_shutting_down(&self) -> bool { + unsafe { !ptr::read_volatile(&GLOBAL_SIGHANDLER_STATE.shutting_down) } } - #[cfg(all(feature = "std", unix))] - unsafe fn handle_signal(_sig: c_int, _: siginfo_t, _: c_void) { - if !CURRENT_BROKER_PTR.is_null() { - let broker = (CURRENT_BROKER_PTR as *mut LlmpBroker) - .as_mut() - .unwrap(); - broker.shutdown(); - }; - } - - /// For proper cleanup on sigint, we set up a sigint handler - #[cfg(all(feature = "std", unix))] - unsafe fn setup_sigint_handler(&mut self) { - CURRENT_BROKER_PTR = self as *const _ as *const c_void; - - // First, set up our own stack to be used during segfault handling. (and specify `SA_ONSTACK` in `sigaction`) - let signal_stack_size = 2 << 22; - // TODO: We leak the signal stack. Removing the signal handlers, then freeing this mem on teardown would be more correct. - let signal_stack_ptr = malloc(signal_stack_size); - if signal_stack_ptr.is_null() { - panic!( - "Failed to allocate signal stack with {} bytes!", - signal_stack_size - ); - } - sigaltstack(signal_stack_ptr as _, ptr::null_mut() as _); - - let mut sa: sigaction = zeroed(); - libc::sigemptyset(&mut sa.sa_mask as *mut libc::sigset_t); - sa.sa_flags = SA_NODEFER | SA_SIGINFO | SA_ONSTACK; - sa.sa_sigaction = LlmpBroker::::handle_signal as usize; - for (sig, msg) in &[ - (SIGTERM, "segterm"), - (SIGINT, "sigint"), - (SIGQUIT, "sigquit"), - ] { - if sigaction(*sig, &mut sa as *mut sigaction, ptr::null_mut()) < 0 { - panic!("Could not set up {} handler", &msg); - } - } + /// Always returns true on platforms, where no shutdown signal handlers are supported + #[inline] + #[cfg(not(unix))] + fn is_shutting_down(&self) -> bool { + true } /// Loops infinitely, forwarding and handling all incoming messages from clients. @@ -1384,12 +1364,14 @@ where where F: FnMut(u32, Tag, &[u8]) -> Result, { - #[cfg(all(feature = "std", unix))] - unsafe { - self.setup_sigint_handler() - }; + #[cfg(unix)] + if let Err(_e) = unsafe { setup_signal_handler(&mut GLOBAL_SIGHANDLER_STATE) } { + // We can live without a proper ctrl+c signal handler. Print and ignore. + #[cfg(feature = "std")] + println!("Failed to setup signal handlers: {}", _e); + } - while unsafe { !ptr::read_volatile(&self.shutting_down) } { + while !self.is_shutting_down() { compiler_fence(Ordering::SeqCst); self.once(on_new_msg) .expect("An error occurred when brokering. Exiting."); @@ -1873,7 +1855,7 @@ impl LlmpClient where SH: ShMem + HasFd, { - #[cfg(all(feature = "std", unix))] + #[cfg(all(unix, feature = "std"))] /// Create a LlmpClient, getting the ID from a given filename pub fn create_attach_to_unix(filename: &str) -> Result { let stream = UnixStream::connect(filename)?; diff --git a/libafl/src/bolts/mod.rs b/libafl/src/bolts/mod.rs index f73268ce7c..86c761710b 100644 --- a/libafl/src/bolts/mod.rs +++ b/libafl/src/bolts/mod.rs @@ -2,6 +2,7 @@ pub mod bindings; pub mod llmp; +pub mod os; pub mod ownedref; pub mod serdeany; pub mod shmem; diff --git a/libafl/src/bolts/os/mod.rs b/libafl/src/bolts/os/mod.rs new file mode 100644 index 0000000000..0a1b95601f --- /dev/null +++ b/libafl/src/bolts/os/mod.rs @@ -0,0 +1,2 @@ +#[cfg(unix)] +pub mod unix_signals; diff --git a/libafl/src/bolts/os/unix_signals.rs b/libafl/src/bolts/os/unix_signals.rs new file mode 100644 index 0000000000..1dd5d0579e --- /dev/null +++ b/libafl/src/bolts/os/unix_signals.rs @@ -0,0 +1,169 @@ +use alloc::vec::Vec; +use core::{ + cell::UnsafeCell, + convert::TryFrom, + fmt::{self, Display, Formatter}, + mem, ptr, +}; + +#[cfg(feature = "std")] +use std::ffi::CString; + +use libc::{ + c_int, malloc, sigaction, sigaltstack, sigemptyset, stack_t, SA_NODEFER, SA_ONSTACK, + SA_SIGINFO, SIGABRT, SIGALRM, SIGBUS, SIGFPE, SIGHUP, SIGILL, SIGINT, SIGKILL, SIGPIPE, + SIGQUIT, SIGSEGV, SIGTERM, SIGUSR2, +}; +use num_enum::{IntoPrimitive, TryFromPrimitive}; + +use crate::Error; + +pub use libc::{c_void, siginfo_t}; + +#[derive(IntoPrimitive, TryFromPrimitive, Hash, Clone, Copy)] +#[repr(i32)] +pub enum Signal { + SigAbort = SIGABRT, + SigBus = SIGBUS, + SigFloatingPointException = SIGFPE, + SigIllegalInstruction = SIGILL, + SigPipe = SIGPIPE, + SigSegmentationFault = SIGSEGV, + SigUser2 = SIGUSR2, + SigAlarm = SIGALRM, + SigHangUp = SIGHUP, + SigKill = SIGKILL, + SigQuit = SIGQUIT, + SigTerm = SIGTERM, + SigInterrupt = SIGINT, +} + +pub static CRASH_SIGNALS: &[Signal] = &[ + Signal::SigAbort, + Signal::SigBus, + Signal::SigFloatingPointException, + Signal::SigIllegalInstruction, + Signal::SigPipe, + Signal::SigSegmentationFault, +]; + +impl PartialEq for Signal { + fn eq(&self, other: &Self) -> bool { + *self as i32 == *other as i32 + } +} + +impl Eq for Signal {} + +unsafe impl Sync for Signal {} + +impl Display for Signal { + fn fmt(&self, f: &mut Formatter<'_>) -> Result<(), fmt::Error> { + match self { + Signal::SigAbort => write!(f, "SIGABRT")?, + Signal::SigBus => write!(f, "SIGBUS")?, + Signal::SigFloatingPointException => write!(f, "SIGFPE")?, + Signal::SigIllegalInstruction => write!(f, "SIGILL")?, + Signal::SigPipe => write!(f, "SIGPIPE")?, + Signal::SigSegmentationFault => write!(f, "SIGSEGV")?, + Signal::SigUser2 => write!(f, "SIGUSR2")?, + Signal::SigAlarm => write!(f, "SIGALRM")?, + Signal::SigHangUp => write!(f, "SIGHUP")?, + Signal::SigKill => write!(f, "SIGKILL")?, + Signal::SigQuit => write!(f, "SIGQUIT")?, + Signal::SigTerm => write!(f, "SIGTERM")?, + Signal::SigInterrupt => write!(f, "SIGINT")?, + }; + + Ok(()) + } +} + +pub trait Handler { + /// Handle a signal + fn handle(&mut self, signal: Signal, info: siginfo_t, _void: c_void); + /// Return a list of signals to handle + fn signals(&self) -> Vec; +} + +struct HandlerHolder { + handler: UnsafeCell<*mut dyn Handler>, +} + +unsafe impl Send for HandlerHolder {} + +/// Let's get 8 mb for now. +const SIGNAL_STACK_SIZE: usize = 2 << 22; +/// To be able to handle SIGSEGV when the stack is exhausted, we need our own little stack space. +static mut SIGNAL_STACK_PTR: *mut c_void = ptr::null_mut(); + +/// Keep track of which handler is registered for which signal +static mut SIGNAL_HANDLERS: [Option; 32] = [ + // We cannot use [None; 32] because it requires Copy. Ugly, but I don't think there's an + // alternative. + None, None, None, None, None, None, None, None, None, None, None, None, None, None, None, None, + None, None, None, None, None, None, None, None, None, None, None, None, None, None, None, None, +]; + +/// Internal function that is being called whenever a signal we are registered for arrives. +/// # Safety +/// This should be somewhat safe to call for signals previously registered, +/// unless the signal handlers registered using [setup_signal_handler] are broken. +unsafe fn handle_signal(sig: c_int, info: siginfo_t, void: c_void) { + let signal = &Signal::try_from(sig).unwrap(); + let handler = { + match &SIGNAL_HANDLERS[*signal as usize] { + Some(handler_holder) => &mut **handler_holder.handler.get(), + None => return, + } + }; + handler.handle(*signal, info, void); +} + +/// Setup signal handlers in a somewhat rusty way. +/// This will allocate a signal stack and set the signal handlers accordingly. +/// It is, for example, used in the [crate::executors::InProcessExecutor] to restart the fuzzer in case of a crash, +/// or to handle `SIGINT` in the broker process. +/// # Safety +/// The signal handlers will be called on any signal. They should (tm) be async safe. +/// A lot can go south in signal handling. Be sure you know what you are doing. +pub unsafe fn setup_signal_handler(handler: &mut T) -> Result<(), Error> { + // First, set up our own stack to be used during segfault handling. (and specify `SA_ONSTACK` in `sigaction`) + if SIGNAL_STACK_PTR.is_null() { + SIGNAL_STACK_PTR = malloc(SIGNAL_STACK_SIZE); + + if SIGNAL_STACK_PTR.is_null() { + // Rust always panics on OOM, so we will, too. + panic!( + "Failed to allocate signal stack with {} bytes!", + SIGNAL_STACK_SIZE + ); + } + } + let mut ss: stack_t = mem::zeroed(); + ss.ss_size = SIGNAL_STACK_SIZE; + ss.ss_sp = SIGNAL_STACK_PTR; + sigaltstack(&mut ss as *mut stack_t, ptr::null_mut() as _); + + let mut sa: sigaction = mem::zeroed(); + sigemptyset(&mut sa.sa_mask as *mut libc::sigset_t); + sa.sa_flags = SA_NODEFER | SA_SIGINFO | SA_ONSTACK; + sa.sa_sigaction = handle_signal as usize; + let signals = handler.signals(); + for sig in signals { + SIGNAL_HANDLERS[sig as usize] = Some(HandlerHolder { + handler: UnsafeCell::new(handler as *mut dyn Handler), + }); + + if sigaction(sig as i32, &mut sa as *mut sigaction, ptr::null_mut()) < 0 { + #[cfg(feature = "std")] + { + let err_str = CString::new(format!("Failed to setup {} handler", sig)).unwrap(); + libc::perror(err_str.as_ptr()); + } + return Err(Error::Unknown(format!("Could not set up {} handler", sig))); + } + } + + Ok(()) +} diff --git a/libafl/src/bolts/serdeany.rs b/libafl/src/bolts/serdeany.rs index 4f6b292ec1..01a2a2e85f 100644 --- a/libafl/src/bolts/serdeany.rs +++ b/libafl/src/bolts/serdeany.rs @@ -125,8 +125,7 @@ macro_rules! create_serde_registry_for_trait { panic!("Registry is already finalized!"); } - let deserializers = - self.deserializers.get_or_insert_with(|| HashMap::default()); + let deserializers = self.deserializers.get_or_insert_with(HashMap::default); deserializers.insert(unpack_type_id(TypeId::of::()), |de| { Ok(Box::new(erased_serde::deserialize::(de)?)) }); diff --git a/libafl/src/bolts/shmem.rs b/libafl/src/bolts/shmem.rs index a4a4d24436..4803654d43 100644 --- a/libafl/src/bolts/shmem.rs +++ b/libafl/src/bolts/shmem.rs @@ -235,7 +235,7 @@ pub mod unix_shmem { return 0 as *mut c_void; } - print!("shmat() = {:?}\n", ptr); + println!("shmat() = {:?}", ptr); ptr } @@ -429,7 +429,7 @@ pub mod unix_shmem { ); (*shm).shm_id = shm_str .to_str() - .expect(&format!("illegal shm_str {:?}", shm_str)) + .unwrap_or_else(|_| panic!("illegal shm_str {:?}", shm_str)) .parse::() .unwrap(); (*shm).map = shmat((*shm).shm_id, ptr::null(), 0 as c_int) as *mut c_uchar; diff --git a/libafl/src/executors/inprocess.rs b/libafl/src/executors/inprocess.rs index e4a17a4d0f..266588a3f9 100644 --- a/libafl/src/executors/inprocess.rs +++ b/libafl/src/executors/inprocess.rs @@ -2,10 +2,11 @@ //! It should usually be paired with extra error-handling, such as a restarting event manager, to be effective. use core::marker::PhantomData; -#[cfg(feature = "std")] #[cfg(unix)] -use os_signals::set_oncrash_ptrs; +use core::ptr; +#[cfg(unix)] +use crate::bolts::os::unix_signals::{c_void, setup_signal_handler}; use crate::{ bolts::tuples::Named, corpus::Corpus, @@ -18,17 +19,6 @@ use crate::{ Error, }; -#[cfg(feature = "std")] -#[cfg(unix)] -use unix_signals as os_signals; - -#[cfg(feature = "std")] -#[cfg(unix)] -use self::os_signals::reset_oncrash_ptrs; -#[cfg(feature = "std")] -#[cfg(unix)] -use self::os_signals::setup_crash_handlers; - /// The inmem executor harness type HarnessFunction = fn(&E, &[u8]) -> ExitKind; @@ -58,33 +48,23 @@ where _state: &mut S, _event_mgr: &mut EM, _input: &I, - ) -> Result<(), Error> - where - EM: EventManager, - { + ) -> Result<(), Error> { #[cfg(unix)] - #[cfg(feature = "std")] unsafe { - set_oncrash_ptrs(_state, _event_mgr, self.observers(), _input); + unix_signal_handler::GLOBAL_STATE.current_input_ptr = + _input as *const _ as *const c_void; } Ok(()) } - #[inline] - fn post_exec(&mut self, _state: &S, _event_mgr: &mut EM, _input: &I) -> Result<(), Error> - where - EM: EventManager, - { - #[cfg(unix)] - #[cfg(feature = "std")] - reset_oncrash_ptrs(); - Ok(()) - } - #[inline] fn run_target(&mut self, input: &I) -> Result { let bytes = input.target_bytes(); let ret = (self.harness_fn)(self, bytes.as_slice()); + #[cfg(unix)] + unsafe { + unix_signal_handler::GLOBAL_STATE.current_input_ptr = ptr::null(); + } Ok(ret) } } @@ -122,100 +102,267 @@ where { /// Create a new in mem executor. /// Caution: crash and restart in one of them will lead to odd behavior if multiple are used, - /// depnding on different corpus or state. + /// depending on different corpus or state. /// * `name` - the name of this executor (to address it along the way) /// * `harness_fn` - the harness, executiong the function /// * `observers` - the observers observing the target during execution + /// This may return an error on unix, if signal handler setup fails pub fn new( name: &'static str, harness_fn: HarnessFunction, observers: OT, _state: &mut S, _event_mgr: &mut EM, - ) -> Self + ) -> Result where EM: EventManager, OC: Corpus, OFT: FeedbacksTuple, S: HasObjectives + HasSolutions, { - #[cfg(feature = "std")] #[cfg(unix)] unsafe { - setup_crash_handlers::(); + let mut data = &mut unix_signal_handler::GLOBAL_STATE; + data.state_ptr = _state as *mut _ as *mut c_void; + data.event_mgr_ptr = _event_mgr as *mut _ as *mut c_void; + data.observers_ptr = &observers as *const _ as *const c_void; + data.crash_handler = unix_signal_handler::inproc_crash_handler::; + data.timeout_handler = + unix_signal_handler::inproc_timeout_handler::; + + setup_signal_handler(data)?; } - Self { + Ok(Self { harness_fn, observers, name, phantom: PhantomData, - } + }) } } -#[cfg(feature = "std")] #[cfg(unix)] -pub mod unix_signals { - - extern crate libc; - - // Unhandled signals: SIGALRM, SIGHUP, SIGINT, SIGKILL, SIGQUIT, SIGTERM - use libc::{ - c_int, c_void, malloc, sigaction, sigaltstack, siginfo_t, SA_NODEFER, SA_ONSTACK, - SA_SIGINFO, SIGABRT, SIGBUS, SIGFPE, SIGILL, SIGPIPE, SIGSEGV, SIGUSR2, - }; - +mod unix_signal_handler { + use alloc::vec::Vec; + use core::ptr; + use libc::{c_void, siginfo_t}; + #[cfg(feature = "std")] use std::{ fs, io::{stdout, Write}, - mem, ptr, }; use crate::{ + bolts::os::unix_signals::{Handler, Signal}, corpus::{Corpus, Testcase}, events::{Event, EventManager}, executors::ExitKind, feedbacks::FeedbacksTuple, - inputs::Input, + inputs::{HasTargetBytes, Input}, observers::ObserversTuple, state::{HasObjectives, HasSolutions}, }; - /// Let's get 8 mb for now. - const SIGNAL_STACK_SIZE: usize = 2 << 22; - /// To be able to handle SIGSEGV when the stack is exhausted, we need our own little stack space. - static mut SIGNAL_STACK_PTR: *const c_void = ptr::null_mut(); - /// Pointers to values only needed on crash. As the program will not continue after a crash, - /// we should (tm) be okay with raw pointers here, - static mut STATE_PTR: *mut c_void = ptr::null_mut(); - static mut EVENT_MGR_PTR: *mut c_void = ptr::null_mut(); - static mut OBSERVERS_PTR: *const c_void = ptr::null(); - /// The (unsafe) pointer to the current inmem input, for the current run. - /// This is needed for certain non-rust side effects, as well as unix signal handling. - static mut CURRENT_INPUT_PTR: *const c_void = ptr::null(); + /// Signal handling on unix systems needs some nasty unsafe. + pub static mut GLOBAL_STATE: InProcessExecutorHandlerData = InProcessExecutorHandlerData { + /// The state ptr for signal handling + state_ptr: ptr::null_mut(), + /// The event manager ptr for signal handling + event_mgr_ptr: ptr::null_mut(), + /// The observers ptr for signal handling + observers_ptr: ptr::null(), + /// The current input for signal handling + current_input_ptr: ptr::null(), + /// The crash handler fn + crash_handler: nop_handler, + /// The timeout handler fn + timeout_handler: nop_handler, + }; - unsafe fn inmem_handle_crash(_sig: c_int, info: siginfo_t, _void: c_void) - where + pub struct InProcessExecutorHandlerData { + pub state_ptr: *mut c_void, + pub event_mgr_ptr: *mut c_void, + pub observers_ptr: *const c_void, + pub current_input_ptr: *const c_void, + pub crash_handler: unsafe fn(Signal, siginfo_t, c_void, data: &mut Self), + pub timeout_handler: unsafe fn(Signal, siginfo_t, c_void, data: &mut Self), + } + + unsafe impl Send for InProcessExecutorHandlerData {} + unsafe impl Sync for InProcessExecutorHandlerData {} + + unsafe fn nop_handler( + _signal: Signal, + _info: siginfo_t, + _void: c_void, + _data: &mut InProcessExecutorHandlerData, + ) { + } + + #[cfg(unix)] + impl Handler for InProcessExecutorHandlerData { + fn handle(&mut self, signal: Signal, info: siginfo_t, void: c_void) { + unsafe { + let data = &mut GLOBAL_STATE; + match signal { + Signal::SigUser2 => (data.timeout_handler)(signal, info, void, data), + _ => (data.crash_handler)(signal, info, void, data), + } + } + } + + fn signals(&self) -> Vec { + vec![ + Signal::SigUser2, + Signal::SigAbort, + Signal::SigBus, + Signal::SigPipe, + Signal::SigFloatingPointException, + Signal::SigIllegalInstruction, + Signal::SigSegmentationFault, + ] + } + } + + #[cfg(unix)] + pub unsafe fn inproc_timeout_handler( + _signal: Signal, + _info: siginfo_t, + _void: c_void, + data: &mut InProcessExecutorHandlerData, + ) where EM: EventManager, OT: ObserversTuple, OC: Corpus, OFT: FeedbacksTuple, S: HasObjectives + HasSolutions, - I: Input, + I: Input + HasTargetBytes, { - if CURRENT_INPUT_PTR.is_null() { - #[cfg(target_os = "android")] - let si_addr = { ((info._pad[0] as usize) | ((info._pad[1] as usize) << 32)) as usize }; - #[cfg(not(target_os = "android"))] - let si_addr = { info.si_addr() as usize }; + let state = (data.state_ptr as *mut S).as_mut().unwrap(); + let event_mgr = (data.event_mgr_ptr as *mut EM).as_mut().unwrap(); + let observers = (data.observers_ptr as *const OT).as_ref().unwrap(); - println!( + if data.current_input_ptr.is_null() { + #[cfg(feature = "std")] + dbg!("TIMEOUT or SIGUSR2 happened, but currently not fuzzing. Exiting"); + } else { + #[cfg(feature = "std")] + println!("Timeout in fuzz run."); + #[cfg(feature = "std")] + let _ = stdout().flush(); + + let input = (data.current_input_ptr as *const I).as_ref().unwrap(); + data.current_input_ptr = ptr::null(); + + let obj_fitness = state + .objectives_mut() + .is_interesting_all(&input, observers, ExitKind::Crash) + .expect("In timeout handler objectives failure."); + if obj_fitness > 0 { + state + .solutions_mut() + .add(Testcase::new(input.clone())) + .expect("In timeout handler solutions failure."); + event_mgr + .fire( + state, + Event::Objective { + objective_size: state.solutions().count(), + }, + ) + .expect("Could not send timeouting input"); + } + + event_mgr.on_restart(state).unwrap(); + + #[cfg(feature = "std")] + println!("Waiting for broker..."); + event_mgr.await_restart_safe(); + #[cfg(feature = "std")] + println!("Bye!"); + + event_mgr.await_restart_safe(); + + libc::_exit(1); + } + } + + pub unsafe fn inproc_crash_handler( + _signal: Signal, + _info: siginfo_t, + _void: c_void, + data: &mut InProcessExecutorHandlerData, + ) where + EM: EventManager, + OT: ObserversTuple, + OC: Corpus, + OFT: FeedbacksTuple, + S: HasObjectives + HasSolutions, + I: Input + HasTargetBytes, + { + #[cfg(feature = "std")] + println!("Crashed with {}", _signal); + if !data.current_input_ptr.is_null() { + let state = (data.state_ptr as *mut S).as_mut().unwrap(); + let event_mgr = (data.event_mgr_ptr as *mut EM).as_mut().unwrap(); + let observers = (data.observers_ptr as *const OT).as_ref().unwrap(); + + #[cfg(feature = "std")] + println!("Child crashed!"); + #[cfg(feature = "std")] + let _ = stdout().flush(); + + let input = (data.current_input_ptr as *const I).as_ref().unwrap(); + // Make sure we don't crash in the crash handler forever. + data.current_input_ptr = ptr::null(); + + let obj_fitness = state + .objectives_mut() + .is_interesting_all(&input, observers, ExitKind::Crash) + .expect("In crash handler objectives failure."); + if obj_fitness > 0 { + let new_input = input.clone(); + state + .solutions_mut() + .add(Testcase::new(new_input)) + .expect("In crash handler solutions failure."); + event_mgr + .fire( + state, + Event::Objective { + objective_size: state.solutions().count(), + }, + ) + .expect("Could not send crashing input"); + } + + event_mgr.on_restart(state).unwrap(); + + #[cfg(feature = "std")] + println!("Waiting for broker..."); + event_mgr.await_restart_safe(); + #[cfg(feature = "std")] + println!("Bye!"); + + libc::_exit(1); + } else { + #[cfg(feature = "std")] + { + println!("Double crash\n"); + #[cfg(target_os = "android")] + let si_addr = + { ((_info._pad[0] as usize) | ((_info._pad[1] as usize) << 32)) as usize }; + #[cfg(not(target_os = "android"))] + let si_addr = { _info.si_addr() as usize }; + + println!( "We crashed at addr 0x{:x}, but are not in the target... Bug in the fuzzer? Exiting.", si_addr - ); + ); + } // let's yolo-cat the maps for debugging, if possible. - #[cfg(any(target_os = "linux", target_os = "android"))] + #[cfg(all(target_os = "linux", feature = "std"))] match fs::read_to_string("/proc/self/maps") { Ok(maps) => println!("maps:\n{}", maps), Err(e) => println!("Couldn't load mappings: {:?}", e), @@ -230,177 +377,7 @@ pub mod unix_signals { } // TODO tell the parent to not restart - std::process::exit(1); - } - - #[cfg(feature = "std")] - println!("Child crashed!"); - #[cfg(feature = "std")] - let _ = stdout().flush(); - - let input = (CURRENT_INPUT_PTR as *const I).as_ref().unwrap(); - // Make sure we don't crash in the crash handler forever. - CURRENT_INPUT_PTR = ptr::null(); - let state = (STATE_PTR as *mut S).as_mut().unwrap(); - let mgr = (EVENT_MGR_PTR as *mut EM).as_mut().unwrap(); - - let observers = (OBSERVERS_PTR as *const OT).as_ref().unwrap(); - let obj_fitness = state - .objectives_mut() - .is_interesting_all(&input, observers, ExitKind::Crash) - .expect("In crash handler objectives failure."); - if obj_fitness > 0 { - state - .solutions_mut() - .add(Testcase::new(input.clone())) - .expect("In crash handler solutions failure."); - mgr.fire( - state, - Event::Objective { - objective_size: state.solutions().count(), - }, - ) - .expect("Could not send crashing input"); - } - - mgr.on_restart(state).unwrap(); - - println!("Waiting for broker..."); - mgr.await_restart_safe(); - println!("Bye!"); - - std::process::exit(1); - } - - unsafe fn inmem_handle_timeout( - _sig: c_int, - _info: siginfo_t, - _void: c_void, - ) where - EM: EventManager, - OT: ObserversTuple, - OC: Corpus, - OFT: FeedbacksTuple, - S: HasObjectives + HasSolutions, - I: Input, - { - dbg!("TIMEOUT/SIGUSR2 received"); - if CURRENT_INPUT_PTR.is_null() { - dbg!("TIMEOUT or SIGUSR2 happened, but currently not fuzzing. Exiting"); - return; - } - - println!("Timeout in fuzz run."); - let _ = stdout().flush(); - - let input = (CURRENT_INPUT_PTR as *const I).as_ref().unwrap(); - // Make sure we don't crash in the crash handler forever. - CURRENT_INPUT_PTR = ptr::null(); - let state = (STATE_PTR as *mut S).as_mut().unwrap(); - let mgr = (EVENT_MGR_PTR as *mut EM).as_mut().unwrap(); - - let observers = (OBSERVERS_PTR as *const OT).as_ref().unwrap(); - let obj_fitness = state - .objectives_mut() - .is_interesting_all(&input, observers, ExitKind::Crash) - .expect("In timeout handler objectives failure."); - if obj_fitness > 0 { - state - .solutions_mut() - .add(Testcase::new(input.clone())) - .expect("In timeout handler solutions failure."); - mgr.fire( - state, - Event::Objective { - objective_size: state.solutions().count(), - }, - ) - .expect("Could not send timeouting input"); - } - - mgr.on_restart(state).unwrap(); - - println!("Waiting for broker..."); - mgr.await_restart_safe(); - println!("Bye!"); - - mgr.await_restart_safe(); - - std::process::exit(1); - } - - /// Sets the oncrash pointers before executing a single testcase - /// # Safety - /// As long as no signals are called, this is fine. - /// Once a signal occurs, the pointer needs to be up to date. - #[inline] - pub unsafe fn set_oncrash_ptrs( - state: &mut S, - event_mgr: &mut EM, - observers: &OT, - input: &I, - ) { - CURRENT_INPUT_PTR = input as *const _ as *const c_void; - STATE_PTR = state as *mut _ as *mut c_void; - EVENT_MGR_PTR = event_mgr as *mut _ as *mut c_void; - OBSERVERS_PTR = observers as *const _ as *const c_void; - } - - /// Resets the oncrash pointers to null - #[inline] - pub fn reset_oncrash_ptrs() { - unsafe { - CURRENT_INPUT_PTR = ptr::null(); - STATE_PTR = ptr::null_mut(); - EVENT_MGR_PTR = ptr::null_mut(); - OBSERVERS_PTR = ptr::null(); - } - } - - /// Sets up the crash handler - /// # Safety - /// Everything using signals is unsafe. Don't do it unless you really need to. - pub unsafe fn setup_crash_handlers() - where - EM: EventManager, - OT: ObserversTuple, - OC: Corpus, - OFT: FeedbacksTuple, - S: HasObjectives + HasSolutions, - I: Input, - { - // First, set up our own stack to be used during segfault handling. (and specify `SA_ONSTACK` in `sigaction`) - if SIGNAL_STACK_PTR.is_null() { - SIGNAL_STACK_PTR = malloc(SIGNAL_STACK_SIZE); - if SIGNAL_STACK_PTR.is_null() { - panic!( - "Failed to allocate signal stack with {} bytes!", - SIGNAL_STACK_SIZE - ); - } - } - sigaltstack(SIGNAL_STACK_PTR as _, ptr::null_mut() as _); - - let mut sa: sigaction = mem::zeroed(); - libc::sigemptyset(&mut sa.sa_mask as *mut libc::sigset_t); - sa.sa_flags = SA_NODEFER | SA_SIGINFO | SA_ONSTACK; - sa.sa_sigaction = inmem_handle_crash:: as usize; - for (sig, msg) in &[ - (SIGSEGV, "segfault"), - (SIGBUS, "sigbus"), - (SIGABRT, "sigabrt"), - (SIGILL, "illegal instruction"), - (SIGFPE, "fp exception"), - (SIGPIPE, "pipe"), - ] { - if sigaction(*sig, &mut sa as *mut sigaction, ptr::null_mut()) < 0 { - panic!("Could not set up {} handler", &msg); - } - } - - sa.sa_sigaction = inmem_handle_timeout:: as usize; - if sigaction(SIGUSR2, &mut sa as *mut sigaction, ptr::null_mut()) < 0 { - panic!("Could not set up sigusr2 handler for timeouts"); + libc::_exit(1); } } } diff --git a/libafl/src/lib.rs b/libafl/src/lib.rs index 092a73dfab..55170850ad 100644 --- a/libafl/src/lib.rs +++ b/libafl/src/lib.rs @@ -182,7 +182,8 @@ mod tests { //Box::new(|_, _, _, _, _| ()), &mut state, &mut event_manager, - ); + ) + .unwrap(); let mut mutator = StdScheduledMutator::new(); mutator.add_mutation(mutation_bitflip); diff --git a/libafl/src/utils.rs b/libafl/src/utils.rs index 9bdeef2b3b..27fcb93115 100644 --- a/libafl/src/utils.rs +++ b/libafl/src/utils.rs @@ -15,7 +15,7 @@ use std::{ time::{SystemTime, UNIX_EPOCH}, }; -#[cfg(feature = "std")] +#[cfg(any(unix, feature = "std"))] use crate::Error; pub trait AsSlice { @@ -417,14 +417,18 @@ pub enum ForkResult { /// Unix has forks. /// # Safety /// A Normal fork. Runs on in two processes. Should be memory safe in general. -#[cfg(all(unix, feature = "std"))] +#[cfg(unix)] pub unsafe fn fork() -> Result { match libc::fork() { pid if pid > 0 => Ok(ForkResult::Parent(ChildHandle { pid })), pid if pid < 0 => { // Getting errno from rust is hard, we'll just let the libc print to stderr for now. // In any case, this should usually not happen. - libc::perror(CString::new("Fork failed").unwrap().as_ptr()); + #[cfg(feature = "std")] + { + let err_str = CString::new("Fork failed").unwrap(); + libc::perror(err_str.as_ptr()); + } Err(Error::Unknown(format!("Fork failed ({})", pid))) } _ => Ok(ForkResult::Child),