diff --git a/src/corpus/mod.rs b/src/corpus/mod.rs index 09462087f3..b1df6b1912 100644 --- a/src/corpus/mod.rs +++ b/src/corpus/mod.rs @@ -1,20 +1,11 @@ -use crate::inputs::Input; +pub mod testcase; +pub use testcase::{Testcase, SimpleTestcase}; + use crate::utils::Rand; use crate::AflError; -use hashbrown::HashMap; -use std::fmt::Debug; - -pub trait TestcaseMetadata: Debug {} - -pub trait Testcase: Debug { - fn load_input(&mut self) -> Result<&Box, AflError>; - fn is_on_disk(&self) -> bool; - fn get_filename(&self) -> &str; - fn get_metadatas(&mut self) -> &mut HashMap>; -} /// Corpus with all current testcases -pub trait Corpus: Debug { +pub trait Corpus { /// Returns the number of elements fn count(&self) -> usize; @@ -30,14 +21,13 @@ pub trait Corpus: Debug { fn get(&mut self) -> Result<&Box, AflError>; } -#[derive(Debug)] -pub struct RandomCorpus<'a, RandT: Rand> { +pub struct BaseCorpus<'a, RandT: Rand> { rand: &'a mut RandT, entries: Vec>, dir_path: String, } -impl Corpus for RandomCorpus<'_, RandT> { +impl Corpus for BaseCorpus<'_, RandT> { /// Returns the number of elements fn count(&self) -> usize { self.entries.len() @@ -76,9 +66,9 @@ impl Corpus for RandomCorpus<'_, RandT> { } } -impl RandomCorpus<'_, RandT> { - pub fn new<'a>(rand: &'a mut RandT, dir_path: &str) -> RandomCorpus<'a, RandT> { - RandomCorpus { +impl BaseCorpus<'_, RandT> { + pub fn new<'a>(rand: &'a mut RandT, dir_path: &str) -> BaseCorpus<'a, RandT> { + BaseCorpus { dir_path: dir_path.to_owned(), entries: vec![], rand: rand, @@ -87,9 +77,8 @@ impl RandomCorpus<'_, RandT> { } /// A queue-like corpus -#[derive(Debug)] pub struct QueueCorpus<'a, RandT: Rand> { - random_corpus: RandomCorpus<'a, RandT>, + base: BaseCorpus<'a, RandT>, pos: usize, cycles: u64, } @@ -97,21 +86,21 @@ pub struct QueueCorpus<'a, RandT: Rand> { impl Corpus for QueueCorpus<'_, RandT> { /// Returns the number of elements fn count(&self) -> usize { - self.random_corpus.count() + self.base.count() } fn add(&mut self, entry: Box) { - self.random_corpus.add(entry); + self.base.add(entry); } /// Removes an entry from the corpus, returning it if it was present. fn remove(&mut self, entry: &dyn Testcase) -> Option> { - self.random_corpus.remove(entry) + self.base.remove(entry) } /// Gets a random entry fn random_entry(&mut self) -> Result<&Box, AflError> { - self.random_corpus.random_entry() + self.base.random_entry() } /// Gets the next entry @@ -124,14 +113,14 @@ impl Corpus for QueueCorpus<'_, RandT> { self.cycles = self.cycles + 1; self.pos = 0; } - Ok(self.random_corpus.entries.get_mut(self.pos).unwrap()) + Ok(self.base.entries.get_mut(self.pos).unwrap()) } } impl QueueCorpus<'_, RandT> { pub fn new<'a>(rand: &'a mut RandT, dir_path: &str) -> QueueCorpus<'a, RandT> { QueueCorpus { - random_corpus: RandomCorpus::new(rand, dir_path), + base: BaseCorpus::new(rand, dir_path), cycles: 0, pos: 0, } @@ -146,56 +135,25 @@ impl QueueCorpus<'_, RandT> { } } -#[derive(Debug, Default)] -pub struct SimpleTestcase { - is_on_disk: bool, - filename: String, - metadatas: HashMap>, -} - -impl Testcase for SimpleTestcase { - fn load_input(&mut self) -> Result<&Box, AflError> { - // TODO: Implement - Err(AflError::NotImplemented("load_input".to_string())) - } - - fn is_on_disk(&self) -> bool { - self.is_on_disk - } - - fn get_filename(&self) -> &str { - &self.filename - } - - fn get_metadatas(&mut self) -> &mut HashMap> { - &mut self.metadatas - } -} - -impl SimpleTestcase { - pub fn new(filename: &str) -> Self { - SimpleTestcase { - filename: filename.to_owned(), - is_on_disk: false, - metadatas: HashMap::default(), - } - } -} - #[cfg(test)] mod tests { use crate::corpus::Corpus; use crate::corpus::QueueCorpus; use crate::corpus::SimpleTestcase; + use crate::corpus::Testcase; + use crate::inputs::bytes::BytesInput; use crate::utils::Xoshiro256StarRand; #[test] fn test_queuecorpus() { let mut rand = Xoshiro256StarRand::new(); let mut q = QueueCorpus::new(&mut rand, "fancy/path"); - q.add(Box::new(SimpleTestcase::new("fancyfile"))); - let filename = q.get().unwrap().get_filename().to_owned(); - assert_eq!(filename, q.get().unwrap().get_filename()); - assert_eq!(filename, "fancyfile"); + let i = Box::new(BytesInput::new(vec![0; 4])); + let mut t = Box::new(SimpleTestcase::new(i)); + t.set_filename("fancyfile".to_string()); + q.add(t); + let filename = q.get().unwrap().get_filename().unwrap().to_owned(); + assert_eq!(filename, q.get().unwrap().get_filename().unwrap().to_owned()); + assert_eq!(filename, "fancyfile".to_string()); } } diff --git a/src/corpus/testcase.rs b/src/corpus/testcase.rs new file mode 100644 index 0000000000..42c3144485 --- /dev/null +++ b/src/corpus/testcase.rs @@ -0,0 +1,67 @@ +use crate::inputs::Input; +use crate::AflError; + +use hashbrown::HashMap; + +pub trait TestcaseMetadata {} + +pub trait Testcase { + + fn load_input(&mut self) -> Result<&Box, AflError>; + + fn get_input(&self) -> Option<& Box>; + + fn is_on_disk(&self) -> bool; + + fn get_filename(&self) -> Option<& String>; + + fn set_filename(&mut self, filename: String); + + fn get_metadatas(&mut self) -> &mut HashMap>; + +} + +#[derive(Default)] +pub struct SimpleTestcase { + input: Option>, + // is_on_disk: bool, // not needed, look at the Option + filename: Option, + metadatas: HashMap>, +} + +impl Testcase for SimpleTestcase { + fn load_input(&mut self) -> Result<&Box, AflError> { + // TODO: Implement cache to disk + self.input.as_ref().ok_or(AflError::NotImplemented("load_input".to_string())) + } + + fn get_input(&self) -> Option<& Box> { + self.input.as_ref() + } + + fn is_on_disk(&self) -> bool { + !self.input.is_some() && self.filename.is_some() + } + + fn get_filename(&self) -> Option<& String> { + self.filename.as_ref() + } + + fn set_filename(&mut self, filename: String) { + self.filename = Some(filename) + } + + fn get_metadatas(&mut self) -> &mut HashMap> { + &mut self.metadatas + } +} + +impl SimpleTestcase { + pub fn new(input: Box) -> Self { + SimpleTestcase { + input: Some(input), + filename: None, + metadatas: HashMap::default(), + } + } +} diff --git a/src/executors/inmemory.rs b/src/executors/inmemory.rs new file mode 100644 index 0000000000..84a8b0fce9 --- /dev/null +++ b/src/executors/inmemory.rs @@ -0,0 +1,220 @@ +use crate::inputs::Input; +use crate::observers::Observer; +use crate::AflError; + +use crate::executors::{Executor, ExitKind, ExecutorBase}; + +use std::ptr; + +type HarnessFunction = fn(&dyn Executor, &[u8]) -> ExitKind; + +pub struct InMemoryExecutor { + base: ExecutorBase, + harness: HarnessFunction, +} + +static mut CURRENT_INMEMORY_EXECUTOR_PTR: *const InMemoryExecutor = ptr::null(); + +/// TODO move in platform/ +#[cfg(unix)] +pub mod unix_signals { + + extern crate libc; + use self::libc::{c_int, c_void, sigaction, siginfo_t}; + // Unhandled signals: SIGALRM, SIGHUP, SIGINT, SIGKILL, SIGQUIT, SIGTERM + use self::libc::{ + SA_NODEFER, SA_SIGINFO, SIGABRT, SIGBUS, SIGFPE, SIGILL, SIGPIPE, SIGSEGV, SIGUSR2, + }; + use std::io::{stdout, Write}; // Write brings flush() into scope + use std::{mem, process, ptr}; + + use crate::executors::inmemory::CURRENT_INMEMORY_EXECUTOR_PTR; + + pub extern "C" fn libaflrs_executor_inmem_handle_crash( + _sig: c_int, + info: siginfo_t, + _void: c_void, + ) { + unsafe { + if CURRENT_INMEMORY_EXECUTOR_PTR == ptr::null() { + println!( + "We died accessing addr {}, but are not in client...", + info.si_addr() as usize + ); + } + } + // TODO: LLMP + println!("Child crashed!"); + let _ = stdout().flush(); + } + + pub extern "C" fn libaflrs_executor_inmem_handle_timeout( + _sig: c_int, + _info: siginfo_t, + _void: c_void, + ) { + dbg!("TIMEOUT/SIGUSR2 received"); + unsafe { + if CURRENT_INMEMORY_EXECUTOR_PTR == ptr::null() { + dbg!("TIMEOUT or SIGUSR2 happened, but currently not fuzzing."); + return; + } + } + // TODO: send LLMP. + println!("Timeout in fuzz run."); + let _ = stdout().flush(); + process::abort(); + } + + pub unsafe fn setup_crash_handlers() { + 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.sa_sigaction = libaflrs_executor_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 = libaflrs_executor_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"); + } + } +} + +#[cfg(unix)] +use unix_signals as os_signals; +#[cfg(not(unix))] +compile_error!("InMemoryExecutor not yet supported on this OS"); + +impl Executor for InMemoryExecutor { + fn run_target(&mut self) -> Result { + let bytes = match self.base.cur_input.as_ref() { + Some(i) => i.serialize(), + None => return Err(AflError::Empty("cur_input".to_string())), + }; + unsafe { + CURRENT_INMEMORY_EXECUTOR_PTR = self as *const InMemoryExecutor; + } + let ret = match bytes { + Ok(b) => Ok((self.harness)(self, b)), + Err(e) => Err(e), + }; + unsafe { + CURRENT_INMEMORY_EXECUTOR_PTR = ptr::null(); + } + ret + } + + fn place_input(&mut self, input: Box) -> Result<(), AflError> { + self.base.cur_input = Some(input); + Ok(()) + } + + fn get_input(&self) -> Option<& Box> { + self.base.cur_input.as_ref() + } + + fn reset_observers(&mut self) -> Result<(), AflError> { + for observer in &mut self.base.observers { + observer.reset()?; + } + Ok(()) + } + + fn post_exec_observers(&mut self) -> Result<(), AflError> { + self.base + .observers + .iter_mut() + .map(|x| x.post_exec()) + .fold(Ok(()), |acc, x| if x.is_err() { x } else { acc }) + } + + fn add_observer(&mut self, observer: Box) { + self.base.observers.push(observer); + } + + fn get_observers(&self) -> &Vec> { + &self.base.observers + } +} + +impl InMemoryExecutor { + pub fn new(harness_fn: HarnessFunction) -> InMemoryExecutor { + unsafe { + os_signals::setup_crash_handlers(); + } + InMemoryExecutor { + base: ExecutorBase { + observers: vec![], + cur_input: Option::None, + }, + harness: harness_fn, + } + } +} + +#[cfg(test)] +mod tests { + use std::any::Any; + use crate::executors::{Executor, ExitKind}; + use crate::executors::inmemory::InMemoryExecutor; + use crate::inputs::Input; + use crate::observers::Observer; + use crate::AflError; + + struct NopInput {} + impl Input for NopInput { + fn serialize(&self) -> Result<&[u8], AflError> { + Ok("NOP".as_bytes()) + } + fn deserialize(&mut self, _buf: &[u8]) -> Result<(), AflError> { + Ok(()) + } + } + + struct Nopserver {} + + impl Observer for Nopserver { + fn reset(&mut self) -> Result<(), AflError> { + Err(AflError::Unknown("Nop reset, testing only".to_string())) + } + fn post_exec(&mut self) -> Result<(), AflError> { + Err(AflError::Unknown("Nop exec, testing only".to_string())) + } + + fn as_any(&self) -> &dyn Any { + self + } + } + + fn test_harness_fn_nop(_executor: &dyn Executor, buf: &[u8]) -> ExitKind { + println! {"Fake exec with buf of len {}", buf.len()}; + ExitKind::Ok + } + + #[test] + fn test_inmem_post_exec() { + let mut in_mem_executor = InMemoryExecutor::new(test_harness_fn_nop); + let nopserver = Nopserver {}; + in_mem_executor.add_observer(Box::new(nopserver)); + assert_eq!(in_mem_executor.post_exec_observers().is_err(), true); + } + + #[test] + fn test_inmem_exec() { + let mut in_mem_executor = InMemoryExecutor::new(test_harness_fn_nop); + let input = NopInput {}; + assert!(in_mem_executor.place_input(Box::new(input)).is_ok()); + assert!(in_mem_executor.run_target().is_ok()); + } +} diff --git a/src/executors/mod.rs b/src/executors/mod.rs index cce70a6868..d0f484054b 100644 --- a/src/executors/mod.rs +++ b/src/executors/mod.rs @@ -1,9 +1,10 @@ +pub mod inmemory; +pub use inmemory::{InMemoryExecutor}; + use crate::inputs::Input; use crate::observers::Observer; use crate::AflError; -use std::ptr; - pub enum ExitKind { Ok, Crash, @@ -16,6 +17,8 @@ pub trait Executor { fn place_input(&mut self, input: Box) -> Result<(), AflError>; + fn get_input(&self) -> Option<& Box>; + fn reset_observers(&mut self) -> Result<(), AflError>; fn post_exec_observers(&mut self) -> Result<(), AflError>; @@ -30,208 +33,3 @@ pub struct ExecutorBase { observers: Vec>, cur_input: Option>, } - -type HarnessFunction = fn(&dyn Executor, &[u8]) -> ExitKind; - -pub struct InMemoryExecutor { - base: ExecutorBase, - harness: HarnessFunction, -} - -static mut CURRENT_INMEMORY_EXECUTOR_PTR: *const InMemoryExecutor = ptr::null(); - -#[cfg(unix)] -pub mod unix_signals { - - extern crate libc; - use self::libc::{c_int, c_void, sigaction, siginfo_t}; - // Unhandled signals: SIGALRM, SIGHUP, SIGINT, SIGKILL, SIGQUIT, SIGTERM - use self::libc::{ - SA_NODEFER, SA_SIGINFO, SIGABRT, SIGBUS, SIGFPE, SIGILL, SIGPIPE, SIGSEGV, SIGUSR2, - }; - use std::io::{stdout, Write}; // Write brings flush() into scope - use std::{mem, process, ptr}; - - use crate::executors::CURRENT_INMEMORY_EXECUTOR_PTR; - - pub extern "C" fn libaflrs_executor_inmem_handle_crash( - _sig: c_int, - info: siginfo_t, - _void: c_void, - ) { - unsafe { - if CURRENT_INMEMORY_EXECUTOR_PTR == ptr::null() { - println!( - "We died accessing addr {}, but are not in client...", - info.si_addr() as usize - ); - } - } - // TODO: LLMP - println!("Child crashed!"); - let _ = stdout().flush(); - } - - pub extern "C" fn libaflrs_executor_inmem_handle_timeout( - _sig: c_int, - _info: siginfo_t, - _void: c_void, - ) { - dbg!("TIMEOUT/SIGUSR2 received"); - unsafe { - if CURRENT_INMEMORY_EXECUTOR_PTR == ptr::null() { - dbg!("TIMEOUT or SIGUSR2 happened, but currently not fuzzing."); - return; - } - } - // TODO: send LLMP. - println!("Timeout in fuzz run."); - let _ = stdout().flush(); - process::abort(); - } - - pub unsafe fn setup_crash_handlers() { - 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.sa_sigaction = libaflrs_executor_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 = libaflrs_executor_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"); - } - } -} - -#[cfg(unix)] -use unix_signals as os_signals; -#[cfg(not(unix))] -compile_error!("InMemoryExecutor not yet supported on this OS"); - -impl Executor for InMemoryExecutor { - fn run_target(&mut self) -> Result { - let bytes = match self.base.cur_input.as_ref() { - Some(i) => i.serialize(), - None => return Err(AflError::Empty("cur_input".to_string())), - }; - unsafe { - CURRENT_INMEMORY_EXECUTOR_PTR = self as *const InMemoryExecutor; - os_signals::setup_crash_handlers(); - } - let ret = match bytes { - Ok(b) => Ok((self.harness)(self, b)), - Err(e) => Err(e), - }; - unsafe { - CURRENT_INMEMORY_EXECUTOR_PTR = ptr::null(); - } - ret - } - - fn place_input(&mut self, input: Box) -> Result<(), AflError> { - self.base.cur_input = Some(input); - Ok(()) - } - - fn reset_observers(&mut self) -> Result<(), AflError> { - for observer in &mut self.base.observers { - observer.reset()?; - } - Ok(()) - } - - fn post_exec_observers(&mut self) -> Result<(), AflError> { - self.base - .observers - .iter_mut() - .map(|x| x.post_exec()) - .fold(Ok(()), |acc, x| if x.is_err() { x } else { acc }) - } - - fn add_observer(&mut self, observer: Box) { - self.base.observers.push(observer); - } - - fn get_observers(&self) -> &Vec> { - &self.base.observers - } -} - -impl InMemoryExecutor { - pub fn new(harness_fn: HarnessFunction) -> InMemoryExecutor { - InMemoryExecutor { - base: ExecutorBase { - observers: vec![], - cur_input: Option::None, - }, - harness: harness_fn, - } - } -} - -#[cfg(test)] -mod tests { - use std::any::Any; - use crate::executors::{Executor, ExitKind, InMemoryExecutor}; - use crate::inputs::Input; - use crate::observers::Observer; - use crate::AflError; - - struct NopInput {} - impl Input for NopInput { - fn serialize(&self) -> Result<&[u8], AflError> { - Ok("NOP".as_bytes()) - } - fn deserialize(&mut self, _buf: &[u8]) -> Result<(), AflError> { - Ok(()) - } - } - - struct Nopserver {} - - impl Observer for Nopserver { - fn reset(&mut self) -> Result<(), AflError> { - Err(AflError::Unknown("Nop reset, testing only".to_string())) - } - fn post_exec(&mut self) -> Result<(), AflError> { - Err(AflError::Unknown("Nop exec, testing only".to_string())) - } - - fn as_any(&self) -> &dyn Any { - self - } - } - - fn test_harness_fn_nop(_executor: &dyn Executor, buf: &[u8]) -> ExitKind { - println! {"Fake exec with buf of len {}", buf.len()}; - ExitKind::Ok - } - - #[test] - fn test_inmem_post_exec() { - let mut in_mem_executor = InMemoryExecutor::new(test_harness_fn_nop); - let nopserver = Nopserver {}; - in_mem_executor.add_observer(Box::new(nopserver)); - assert_eq!(in_mem_executor.post_exec_observers().is_err(), true); - } - - #[test] - fn test_inmem_exec() { - let mut in_mem_executor = InMemoryExecutor::new(test_harness_fn_nop); - let input = NopInput {}; - assert!(in_mem_executor.place_input(Box::new(input)).is_ok()); - assert!(in_mem_executor.run_target().is_ok()); - } -} diff --git a/src/inputs/bytes.rs b/src/inputs/bytes.rs index 5b226c1061..9119ff513a 100644 --- a/src/inputs/bytes.rs +++ b/src/inputs/bytes.rs @@ -17,6 +17,14 @@ impl Input for BytesInput { } } +impl BytesInput { + pub fn new(bytes: Vec) -> Self { + BytesInput { + bytes: bytes + } + } +} + #[cfg(test)] mod tests { use crate::utils::{next_pow2, Rand, Xoshiro256StarRand};