testcase take an input, not filename

This commit is contained in:
Andrea Fioraldi 2020-10-30 12:23:38 +01:00
parent 1fc5f1d6e5
commit 3653217f16
5 changed files with 325 additions and 274 deletions

View File

@ -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<dyn Input>, AflError>;
fn is_on_disk(&self) -> bool;
fn get_filename(&self) -> &str;
fn get_metadatas(&mut self) -> &mut HashMap<String, Box<dyn TestcaseMetadata>>;
}
/// 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<dyn Testcase>, AflError>;
}
#[derive(Debug)]
pub struct RandomCorpus<'a, RandT: Rand> {
pub struct BaseCorpus<'a, RandT: Rand> {
rand: &'a mut RandT,
entries: Vec<Box<dyn Testcase>>,
dir_path: String,
}
impl<RandT: Rand> Corpus for RandomCorpus<'_, RandT> {
impl<RandT: Rand> Corpus for BaseCorpus<'_, RandT> {
/// Returns the number of elements
fn count(&self) -> usize {
self.entries.len()
@ -76,9 +66,9 @@ impl<RandT: Rand> Corpus for RandomCorpus<'_, RandT> {
}
}
impl<RandT: Rand> RandomCorpus<'_, RandT> {
pub fn new<'a>(rand: &'a mut RandT, dir_path: &str) -> RandomCorpus<'a, RandT> {
RandomCorpus {
impl<RandT: Rand> 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<RandT: Rand> 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<RandT: Rand> 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<dyn Testcase>) {
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<Box<dyn Testcase>> {
self.random_corpus.remove(entry)
self.base.remove(entry)
}
/// Gets a random entry
fn random_entry(&mut self) -> Result<&Box<dyn Testcase>, AflError> {
self.random_corpus.random_entry()
self.base.random_entry()
}
/// Gets the next entry
@ -124,14 +113,14 @@ impl<RandT: Rand> 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<RandT: Rand> 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<RandT: Rand> QueueCorpus<'_, RandT> {
}
}
#[derive(Debug, Default)]
pub struct SimpleTestcase {
is_on_disk: bool,
filename: String,
metadatas: HashMap<String, Box<dyn TestcaseMetadata>>,
}
impl Testcase for SimpleTestcase {
fn load_input(&mut self) -> Result<&Box<dyn Input>, 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<String, Box<dyn TestcaseMetadata>> {
&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());
}
}

67
src/corpus/testcase.rs Normal file
View File

@ -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<dyn Input>, AflError>;
fn get_input(&self) -> Option<& Box<dyn Input>>;
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<String, Box<dyn TestcaseMetadata>>;
}
#[derive(Default)]
pub struct SimpleTestcase {
input: Option<Box<dyn Input>>,
// is_on_disk: bool, // not needed, look at the Option
filename: Option<String>,
metadatas: HashMap<String, Box<dyn TestcaseMetadata>>,
}
impl Testcase for SimpleTestcase {
fn load_input(&mut self) -> Result<&Box<dyn Input>, AflError> {
// TODO: Implement cache to disk
self.input.as_ref().ok_or(AflError::NotImplemented("load_input".to_string()))
}
fn get_input(&self) -> Option<& Box<dyn Input>> {
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<String, Box<dyn TestcaseMetadata>> {
&mut self.metadatas
}
}
impl SimpleTestcase {
pub fn new(input: Box<dyn Input>) -> Self {
SimpleTestcase {
input: Some(input),
filename: None,
metadatas: HashMap::default(),
}
}
}

220
src/executors/inmemory.rs Normal file
View File

@ -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<ExitKind, AflError> {
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<dyn Input>) -> Result<(), AflError> {
self.base.cur_input = Some(input);
Ok(())
}
fn get_input(&self) -> Option<& Box<dyn Input>> {
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<dyn Observer>) {
self.base.observers.push(observer);
}
fn get_observers(&self) -> &Vec<Box<dyn Observer>> {
&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());
}
}

View File

@ -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<dyn Input>) -> Result<(), AflError>;
fn get_input(&self) -> Option<& Box<dyn Input>>;
fn reset_observers(&mut self) -> Result<(), AflError>;
fn post_exec_observers(&mut self) -> Result<(), AflError>;
@ -30,208 +33,3 @@ pub struct ExecutorBase {
observers: Vec<Box<dyn Observer>>,
cur_input: Option<Box<dyn Input>>,
}
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<ExitKind, AflError> {
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<dyn Input>) -> 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<dyn Observer>) {
self.base.observers.push(observer);
}
fn get_observers(&self) -> &Vec<Box<dyn Observer>> {
&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());
}
}

View File

@ -17,6 +17,14 @@ impl Input for BytesInput {
}
}
impl BytesInput {
pub fn new(bytes: Vec<u8>) -> Self {
BytesInput {
bytes: bytes
}
}
}
#[cfg(test)]
mod tests {
use crate::utils::{next_pow2, Rand, Xoshiro256StarRand};