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 <domenukk@gmail.com>
Co-authored-by: Andrea Fioraldi <andreafioraldi@gmail.com>
This commit is contained in:
s1341 2021-03-05 16:36:44 +02:00 committed by GitHub
parent 55def9b966
commit b048ddf470
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
15 changed files with 468 additions and 333 deletions

View File

@ -128,7 +128,7 @@ fn fuzz(corpus_dirs: Vec<PathBuf>, 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.

View File

@ -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

View File

@ -150,7 +150,7 @@ fn fuzz(corpus_dirs: Vec<PathBuf>, 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.

View File

@ -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);

View File

@ -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

View File

@ -1,2 +1,2 @@
#[cfg(windows)]
#[cfg(all(windows, feature = "std"))]
::windows::include_bindings!();

View File

@ -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::<LlmpPage>();
/// 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<u64, Error> {
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<u64, Error> {
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<Signal> {
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<SH>)
.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::<SH>::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<LlmpMsgHookResult, Error>,
{
#[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<SH> LlmpClient<SH>
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<Self, Error> {
let stream = UnixStream::connect(filename)?;

View File

@ -2,6 +2,7 @@
pub mod bindings;
pub mod llmp;
pub mod os;
pub mod ownedref;
pub mod serdeany;
pub mod shmem;

View File

@ -0,0 +1,2 @@
#[cfg(unix)]
pub mod unix_signals;

View File

@ -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<Signal>;
}
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<HandlerHolder>; 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<T: 'static + 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(())
}

View File

@ -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::<T>()), |de| {
Ok(Box::new(erased_serde::deserialize::<T>(de)?))
});

View File

@ -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::<i32>()
.unwrap();
(*shm).map = shmat((*shm).shm_id, ptr::null(), 0 as c_int) as *mut c_uchar;

View File

@ -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<E> = fn(&E, &[u8]) -> ExitKind;
@ -58,33 +48,23 @@ where
_state: &mut S,
_event_mgr: &mut EM,
_input: &I,
) -> Result<(), Error>
where
EM: EventManager<I, S>,
{
) -> 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<EM, S>(&mut self, _state: &S, _event_mgr: &mut EM, _input: &I) -> Result<(), Error>
where
EM: EventManager<I, S>,
{
#[cfg(unix)]
#[cfg(feature = "std")]
reset_oncrash_ptrs();
Ok(())
}
#[inline]
fn run_target(&mut self, input: &I) -> Result<ExitKind, Error> {
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<EM, OC, OFT, S>(
name: &'static str,
harness_fn: HarnessFunction<Self>,
observers: OT,
_state: &mut S,
_event_mgr: &mut EM,
) -> Self
) -> Result<Self, Error>
where
EM: EventManager<I, S>,
OC: Corpus<I>,
OFT: FeedbacksTuple<I>,
S: HasObjectives<OFT, I> + HasSolutions<OC, I>,
{
#[cfg(feature = "std")]
#[cfg(unix)]
unsafe {
setup_crash_handlers::<EM, I, OC, OFT, OT, S>();
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::<EM, I, OC, OFT, OT, S>;
data.timeout_handler =
unix_signal_handler::inproc_timeout_handler::<EM, I, OC, OFT, OT, S>;
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<EM, I, OC, OFT, OT, S>(_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<Signal> {
vec![
Signal::SigUser2,
Signal::SigAbort,
Signal::SigBus,
Signal::SigPipe,
Signal::SigFloatingPointException,
Signal::SigIllegalInstruction,
Signal::SigSegmentationFault,
]
}
}
#[cfg(unix)]
pub unsafe fn inproc_timeout_handler<EM, I, OC, OFT, OT, S>(
_signal: Signal,
_info: siginfo_t,
_void: c_void,
data: &mut InProcessExecutorHandlerData,
) where
EM: EventManager<I, S>,
OT: ObserversTuple,
OC: Corpus<I>,
OFT: FeedbacksTuple<I>,
S: HasObjectives<OFT, I> + HasSolutions<OC, I>,
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<EM, I, OC, OFT, OT, S>(
_signal: Signal,
_info: siginfo_t,
_void: c_void,
data: &mut InProcessExecutorHandlerData,
) where
EM: EventManager<I, S>,
OT: ObserversTuple,
OC: Corpus<I>,
OFT: FeedbacksTuple<I>,
S: HasObjectives<OFT, I> + HasSolutions<OC, I>,
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<EM, I, OC, OFT, OT, S>(
_sig: c_int,
_info: siginfo_t,
_void: c_void,
) where
EM: EventManager<I, S>,
OT: ObserversTuple,
OC: Corpus<I>,
OFT: FeedbacksTuple<I>,
S: HasObjectives<OFT, I> + HasSolutions<OC, I>,
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<EM, I, OT, S>(
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<EM, I, OC, OFT, OT, S>()
where
EM: EventManager<I, S>,
OT: ObserversTuple,
OC: Corpus<I>,
OFT: FeedbacksTuple<I>,
S: HasObjectives<OFT, I> + HasSolutions<OC, I>,
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::<EM, I, OC, OFT, OT, S> 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::<EM, I, OC, OFT, OT, S> 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);
}
}
}

View File

@ -182,7 +182,8 @@ mod tests {
//Box::new(|_, _, _, _, _| ()),
&mut state,
&mut event_manager,
);
)
.unwrap();
let mut mutator = StdScheduledMutator::new();
mutator.add_mutation(mutation_bitflip);

View File

@ -15,7 +15,7 @@ use std::{
time::{SystemTime, UNIX_EPOCH},
};
#[cfg(feature = "std")]
#[cfg(any(unix, feature = "std"))]
use crate::Error;
pub trait AsSlice<T> {
@ -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<ForkResult, Error> {
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),