From fd39938ac76b2952dbef2d1b3034577f2617a3f6 Mon Sep 17 00:00:00 2001 From: Andrea Fioraldi Date: Fri, 24 Sep 2021 11:23:26 +0200 Subject: [PATCH] Add fuzzbench gsoc to fuzzers/ --- fuzzers/fuzzbench_gsoc/.gitignore | 2 + fuzzers/fuzzbench_gsoc/Cargo.toml | 32 ++ fuzzers/fuzzbench_gsoc/Makefile | 48 +++ fuzzers/fuzzbench_gsoc/README.md | 17 + fuzzers/fuzzbench_gsoc/fuzz.c | 15 + fuzzers/fuzzbench_gsoc/src/bin/libafl_cc.rs | 36 ++ fuzzers/fuzzbench_gsoc/src/bin/libafl_cxx.rs | 5 + fuzzers/fuzzbench_gsoc/src/lib.rs | 349 +++++++++++++++++++ 8 files changed, 504 insertions(+) create mode 100644 fuzzers/fuzzbench_gsoc/.gitignore create mode 100644 fuzzers/fuzzbench_gsoc/Cargo.toml create mode 100644 fuzzers/fuzzbench_gsoc/Makefile create mode 100644 fuzzers/fuzzbench_gsoc/README.md create mode 100644 fuzzers/fuzzbench_gsoc/fuzz.c create mode 100644 fuzzers/fuzzbench_gsoc/src/bin/libafl_cc.rs create mode 100644 fuzzers/fuzzbench_gsoc/src/bin/libafl_cxx.rs create mode 100644 fuzzers/fuzzbench_gsoc/src/lib.rs diff --git a/fuzzers/fuzzbench_gsoc/.gitignore b/fuzzers/fuzzbench_gsoc/.gitignore new file mode 100644 index 0000000000..d3561edaf7 --- /dev/null +++ b/fuzzers/fuzzbench_gsoc/.gitignore @@ -0,0 +1,2 @@ +libpng-* +fuzzer diff --git a/fuzzers/fuzzbench_gsoc/Cargo.toml b/fuzzers/fuzzbench_gsoc/Cargo.toml new file mode 100644 index 0000000000..078a7d67e2 --- /dev/null +++ b/fuzzers/fuzzbench_gsoc/Cargo.toml @@ -0,0 +1,32 @@ +[package] +name = "fuzzbench" +version = "0.6.1" +authors = ["Andrea Fioraldi ", "Dominik Maier "] +edition = "2018" + +[features] +default = ["std"] +std = [] + +[profile.release] +lto = true +codegen-units = 1 +opt-level = 3 +debug = true + +[build-dependencies] +cc = { version = "1.0", features = ["parallel"] } +which = { version = "4.0.2" } +num_cpus = "1.0" + +[dependencies] +libafl = { path = "../../libafl/" } +libafl_targets = { path = "../../libafl_targets/", features = ["sancov_pcguard_hitcounts", "sancov_cmplog", "libfuzzer"] } +# TODO Include it only when building cc +libafl_cc = { path = "../../libafl_cc/" } +clap = { version = "3.0.0-beta.2", features = ["default"] } +nix = "0.20.0" + +[lib] +name = "fuzzbench" +crate-type = ["staticlib"] diff --git a/fuzzers/fuzzbench_gsoc/Makefile b/fuzzers/fuzzbench_gsoc/Makefile new file mode 100644 index 0000000000..50eb875f89 --- /dev/null +++ b/fuzzers/fuzzbench_gsoc/Makefile @@ -0,0 +1,48 @@ +FUZZER_NAME="fuzzer" +PROJECT_DIR=$(shell dirname $(realpath $(firstword $(MAKEFILE_LIST)))) + +PHONY: all + +all: fuzzer + +target/release/libafl_cxx: src/* src/bin/* + # Build the libpng libfuzzer library + cargo build --release + +target/release/libafl_cc: target/release/libafl_cxx + +fuzz.o: fuzz.c target/release/libafl_cc + target/release/libafl_cc -O3 -c $^ -o $@ + +fuzzer: target/release/libafl_cxx fuzz.o + # Build the fuzzer compiler + cargo build --release + + # Build the harness + target/release/libafl_cxx \ + fuzz.o \ + -o $(FUZZER_NAME) \ + -lm -lz + +clean: + rm ./$(FUZZER_NAME) || true + rm fuzz.o || true + +run: all + ./$(FUZZER_NAME) + +short_test: all + rm -rf libafl_unix_shmem_server || true + mkdir in || true + echo a > in/a + # Allow sigterm as exit code + (timeout 11s ./$(FUZZER_NAME) out in || [ $$? -eq 124 ]) + rm -rf out + rm -rf in + +test: all + mkdir in || true + echo a > in/a + (timeout 60s ./$(FUZZER_NAME) out in || [ $$? -eq 124 ]) + rm -rf out + rm -rf in \ No newline at end of file diff --git a/fuzzers/fuzzbench_gsoc/README.md b/fuzzers/fuzzbench_gsoc/README.md new file mode 100644 index 0000000000..df34f5e090 --- /dev/null +++ b/fuzzers/fuzzbench_gsoc/README.md @@ -0,0 +1,17 @@ +# Fuzzbench Harness + +This folder contains an example fuzzer tailored for fuzzbench. +It uses the best possible setting, with the exception of a SimpleRestartingEventManager instead of an LlmpEventManager - since fuzzbench is single threaded. +Real fuzz campaigns should consider using multithreaded LlmpEventManager, see the other examples. + +## Build + +To build this example, run `cargo build --release`. +This will build the fuzzer compilers (`libafl_cc` and `libafl_cpp`) with `src/lib.rs` as fuzzer. +The fuzzer uses the libfuzzer compatibility layer and the SanitizerCoverage runtime functions for coverage feedback. + +These can then be used to build libfuzzer harnesses in the software project of your choice. +Finally, just run the resulting binary with `out_dir`, `in_dir`. + +In any real-world scenario, you should use `taskset` to pin each client to an empty CPU core, the lib does not pick an empty core automatically (yet). + diff --git a/fuzzers/fuzzbench_gsoc/fuzz.c b/fuzzers/fuzzbench_gsoc/fuzz.c new file mode 100644 index 0000000000..7eea9f3b30 --- /dev/null +++ b/fuzzers/fuzzbench_gsoc/fuzz.c @@ -0,0 +1,15 @@ +#include +#include + +int LLVMFuzzerTestOneInput(const uint8_t *Data, size_t Size) { + if (Size >= 8 && *(uint32_t*)Data == 0xaabbccdd) + abort(); +} + +/* +int main() { + + char buf [10] = {0}; + LLVMFuzzerTestOneInput(buf, 10); + +}*/ diff --git a/fuzzers/fuzzbench_gsoc/src/bin/libafl_cc.rs b/fuzzers/fuzzbench_gsoc/src/bin/libafl_cc.rs new file mode 100644 index 0000000000..c84bfceddd --- /dev/null +++ b/fuzzers/fuzzbench_gsoc/src/bin/libafl_cc.rs @@ -0,0 +1,36 @@ +use libafl_cc::{ClangWrapper, CompilerWrapper, LLVMPasses}; +use std::env; + +pub fn main() { + let args: Vec = env::args().collect(); + if args.len() > 1 { + let mut dir = env::current_exe().unwrap(); + let wrapper_name = dir.file_name().unwrap().to_str().unwrap(); + + let is_cpp = match wrapper_name[wrapper_name.len()-2..].to_lowercase().as_str() { + "cc" => false, + "++" | "pp" | "xx" => true, + _ => panic!("Could not figure out if c or c++ warpper was called. Expected {:?} to end with c or cxx", dir), + }; + + dir.pop(); + + let mut cc = ClangWrapper::new(); + if let Some(code) = cc + .cpp(is_cpp) + // silence the compiler wrapper output, needed for some configure scripts. + .silence(true) + .from_args(&args) + .expect("Failed to parse the command line") + .link_staticlib(&dir, "fuzzbench") + .add_arg("-fsanitize-coverage=trace-pc-guard,trace-cmp") + .add_pass(LLVMPasses::CmpLogRtn) + .run() + .expect("Failed to run the wrapped compiler") + { + std::process::exit(code); + } + } else { + panic!("LibAFL CC: No Arguments given"); + } +} diff --git a/fuzzers/fuzzbench_gsoc/src/bin/libafl_cxx.rs b/fuzzers/fuzzbench_gsoc/src/bin/libafl_cxx.rs new file mode 100644 index 0000000000..ce786239b0 --- /dev/null +++ b/fuzzers/fuzzbench_gsoc/src/bin/libafl_cxx.rs @@ -0,0 +1,5 @@ +pub mod libafl_cc; + +fn main() { + libafl_cc::main() +} diff --git a/fuzzers/fuzzbench_gsoc/src/lib.rs b/fuzzers/fuzzbench_gsoc/src/lib.rs new file mode 100644 index 0000000000..30a2326daf --- /dev/null +++ b/fuzzers/fuzzbench_gsoc/src/lib.rs @@ -0,0 +1,349 @@ +//! A singlethreade libfuzzer-like fuzzer that can auto-restart. + +use clap::{App, Arg}; +use core::{cell::RefCell, time::Duration}; +#[cfg(unix)] +use nix::{self, unistd::dup}; +#[cfg(unix)] +use std::os::unix::io::{AsRawFd, FromRawFd}; +use std::{ + env, + fs::{self, File, OpenOptions}, + io::{self, Write}, + path::PathBuf, + process, +}; + +use libafl::{ + bolts::{ + current_nanos, current_time, + os::dup2, + rands::StdRand, + shmem::{ShMemProvider, StdShMemProvider}, + tuples::{tuple_list, Merge}, + }, + corpus::{ + Corpus, IndexesLenTimeMinimizerCorpusScheduler, OnDiskCorpus, PowerQueueCorpusScheduler, + }, + events::SimpleRestartingEventManager, + executors::{inprocess::InProcessExecutor, ExitKind, TimeoutExecutor}, + feedback_or, feedback_or_fast, + feedbacks::{CrashFeedback, MapFeedbackState, MaxMapFeedback, TimeFeedback, TimeoutFeedback}, + fuzzer::{Fuzzer, StdFuzzer}, + inputs::{BytesInput, HasTargetBytes}, + mutators::{ + scheduled::havoc_mutations, token_mutations::I2SRandReplace, tokens_mutations, + StdMOptMutator, Tokens, + }, + observers::{StdMapObserver, TimeObserver}, + stages::{ + calibrate::CalibrationStage, + power::{PowerMutationalStage, PowerSchedule}, + TracingStage, + }, + state::{HasCorpus, HasMetadata, StdState}, + stats::SimpleStats, + Error, +}; +use libafl_targets::{ + libfuzzer_initialize, libfuzzer_test_one_input, CmpLogObserver, CMPLOG_MAP, EDGES_MAP, + MAX_EDGES_NUM, +}; + +/// The fuzzer main (as `no_mangle` C function) +#[no_mangle] +pub fn libafl_main() { + // Registry the metadata types used in this fuzzer + // Needed only on no_std + //RegistryBuilder::register::(); + + let res = match App::new("libafl_fuzzbench") + .version("0.4.0") + .author("AFLplusplus team") + .about("LibAFL-based fuzzer for Fuzzbench") + .arg( + Arg::new("out") + .about("The directory to place finds in ('corpus')") + .required(true) + .index(1) + .takes_value(true), + ) + .arg( + Arg::new("in") + .about("The directory to read initial inputs from ('seeds')") + .required(true) + .index(2) + .takes_value(true), + ) + .arg( + Arg::new("tokens") + .short('x') + .long("tokens") + .about("A file to read tokens from, to be used during fuzzing") + .takes_value(true), + ) + .arg( + Arg::new("logfile") + .short('l') + .long("logfile") + .about("Duplicates all output to this file") + .default_value("libafl.log"), + ) + .arg( + Arg::new("timeout") + .short('t') + .long("timeout") + .about("Timeout for each individual execution, in milliseconds") + .default_value("1000"), + ) + .try_get_matches() + { + Ok(res) => res, + Err(err) => { + println!( + "Syntax: {}, [-x dictionary] corpus_dir seed_dir\n{:?}", + env::current_exe() + .unwrap_or_else(|_| "fuzzer".into()) + .to_string_lossy(), + err.info, + ); + return; + } + }; + + println!( + "Workdir: {:?}", + env::current_dir().unwrap().to_string_lossy().to_string() + ); + + // For fuzzbench, crashes and finds are inside the same `corpus` directory, in the "queue" and "crashes" subdir. + let mut out_dir = PathBuf::from(res.value_of("out").unwrap().to_string()); + if fs::create_dir(&out_dir).is_err() { + println!("Out dir at {:?} already exists.", &out_dir); + if !out_dir.is_dir() { + println!("Out dir at {:?} is not a valid directory!", &out_dir); + return; + } + } + let mut crashes = out_dir.clone(); + crashes.push("crashes"); + out_dir.push("queue"); + + let in_dir = PathBuf::from(res.value_of("in").unwrap().to_string()); + if !in_dir.is_dir() { + println!("In dir at {:?} is not a valid directory!", &in_dir); + return; + } + + let tokens = res.value_of("tokens").map(PathBuf::from); + + let logfile = PathBuf::from(res.value_of("logfile").unwrap().to_string()); + + let timeout = Duration::from_millis( + res.value_of("timeout") + .unwrap() + .to_string() + .parse() + .expect("Could not parse timeout in milliseconds"), + ); + + fuzz(out_dir, crashes, in_dir, tokens, logfile, timeout) + .expect("An error occurred while fuzzing"); +} + +/// The actual fuzzer +fn fuzz( + corpus_dir: PathBuf, + objective_dir: PathBuf, + seed_dir: PathBuf, + tokenfile: Option, + logfile: PathBuf, + timeout: Duration, +) -> Result<(), Error> { + let log = RefCell::new( + OpenOptions::new() + .append(true) + .create(true) + .open(&logfile)?, + ); + + #[cfg(unix)] + let mut stdout_cpy = unsafe { + let new_fd = dup(io::stdout().as_raw_fd())?; + File::from_raw_fd(new_fd) + }; + #[cfg(unix)] + let file_null = File::open("/dev/null")?; + + // 'While the stats are state, they are usually used in the broker - which is likely never restarted + let stats = SimpleStats::new(|s| { + #[cfg(unix)] + writeln!(&mut stdout_cpy, "{}", s).unwrap(); + #[cfg(windows)] + println!("{}", s); + writeln!(log.borrow_mut(), "{:?} {}", current_time(), s).unwrap(); + }); + + // We need a shared map to store our state before a crash. + // This way, we are able to continue fuzzing afterwards. + let mut shmem_provider = StdShMemProvider::new()?; + + let (state, mut mgr) = match SimpleRestartingEventManager::launch(stats, &mut shmem_provider) { + // The restarting state will spawn the same process again as child, then restarted it each time it crashes. + Ok(res) => res, + Err(err) => match err { + Error::ShuttingDown => { + return Ok(()); + } + _ => { + panic!("Failed to setup the restarter: {}", err); + } + }, + }; + + // Create an observation channel using the coverage map + // We don't use the hitcounts (see the Cargo.toml, we use pcguard_edges) + let edges = unsafe { &mut EDGES_MAP[0..MAX_EDGES_NUM] }; + let edges_observer = StdMapObserver::new("edges", edges); + + // Create an observation channel to keep track of the execution time + let time_observer = TimeObserver::new("time"); + + let cmplog = unsafe { &mut CMPLOG_MAP }; + let cmplog_observer = CmpLogObserver::new("cmplog", cmplog, true); + + // The state of the edges feedback. + let feedback_state = MapFeedbackState::with_observer(&edges_observer); + + // Feedback to rate the interestingness of an input + // This one is composed by two Feedbacks in OR + let feedback = feedback_or!( + // New maximization map feedback linked to the edges observer and the feedback state + MaxMapFeedback::new_tracking(&feedback_state, &edges_observer, true, false), + // Time feedback, this one does not need a feedback state + TimeFeedback::new_with_observer(&time_observer) + ); + + // A feedback to choose if an input is a solution or not + let objective = feedback_or_fast!(CrashFeedback::new(), TimeoutFeedback::new()); + + // If not restarting, create a State from scratch + let mut state = state.unwrap_or_else(|| { + StdState::new( + // RNG + StdRand::with_seed(current_nanos()), + // Corpus that will be evolved, we keep it in memory for performance + OnDiskCorpus::new(corpus_dir).unwrap(), + // Corpus in which we store solutions (crashes in this example), + // on disk so the user can get them after stopping the fuzzer + OnDiskCorpus::new(objective_dir).unwrap(), + // States of the feedbacks. + // They are the data related to the feedbacks that you want to persist in the State. + tuple_list!(feedback_state), + ) + }); + + println!("Let's fuzz :)"); + + // The actual target run starts here. + // Call LLVMFUzzerInitialize() if present. + let args: Vec = env::args().collect(); + if libfuzzer_initialize(&args) == -1 { + println!("Warning: LLVMFuzzerInitialize failed with -1") + } + + let calibration = CalibrationStage::new(&mut state, &edges_observer); + + // Setup a MOPT mutator + let mutator = StdMOptMutator::new( + &mut state, + havoc_mutations() + .merge(tokens_mutations()) + .merge(tuple_list!(I2SRandReplace::new())), + 16, + )?; + + let power = PowerMutationalStage::new(mutator, PowerSchedule::FAST, &edges_observer); + + // A minimization+queue policy to get testcasess from the corpus + let scheduler = IndexesLenTimeMinimizerCorpusScheduler::new(PowerQueueCorpusScheduler::new()); + + // A fuzzer with feedbacks and a corpus scheduler + let mut fuzzer = StdFuzzer::new(scheduler, feedback, objective); + + // The wrapped harness function, calling out to the LLVM-style harness + let mut harness = |input: &BytesInput| { + let target = input.target_bytes(); + let buf = target.as_slice(); + libfuzzer_test_one_input(buf); + ExitKind::Ok + }; + + let mut tracing_harness = harness; + + // Create the executor for an in-process function with one observer for edge coverage and one for the execution time + let mut executor = TimeoutExecutor::new( + InProcessExecutor::new( + &mut harness, + tuple_list!(edges_observer, time_observer), + &mut fuzzer, + &mut state, + &mut mgr, + )?, + timeout, + ); + + // Setup a tracing stage in which we log comparisons + let tracing = TracingStage::new(TimeoutExecutor::new( + InProcessExecutor::new( + &mut tracing_harness, + tuple_list!(cmplog_observer), + &mut fuzzer, + &mut state, + &mut mgr, + )?, + // Give it more time! + timeout * 10, + )); + + // The order of the stages matter! + let mut stages = tuple_list!(calibration, tracing, power); + + // Read tokens + if let Some(tokenfile) = tokenfile { + if state.metadata().get::().is_none() { + state.add_metadata(Tokens::from_tokens_file(tokenfile)?); + } + } + + // In case the corpus is empty (on first run), reset + if state.corpus().count() < 1 { + state + .load_initial_inputs(&mut fuzzer, &mut executor, &mut mgr, &[seed_dir.clone()]) + .unwrap_or_else(|_| { + println!("Failed to load initial corpus at {:?}", &seed_dir); + process::exit(0); + }); + println!("We imported {} inputs from disk.", state.corpus().count()); + } + + // Remove target ouput (logs still survive) + #[cfg(unix)] + { + let null_fd = file_null.as_raw_fd(); + dup2(null_fd, io::stdout().as_raw_fd())?; + dup2(null_fd, io::stderr().as_raw_fd())?; + } + // reopen file to make sure we're at the end + log.replace( + OpenOptions::new() + .append(true) + .create(true) + .open(&logfile)?, + ); + + fuzzer.fuzz_loop(&mut stages, &mut executor, &mut state, &mut mgr)?; + + // Never reached + Ok(()) +}