diff --git a/.dockerignore b/.dockerignore index 991eee5de6..533cef3cac 100644 --- a/.dockerignore +++ b/.dockerignore @@ -25,3 +25,7 @@ test.dict # Ignore all built fuzzers fuzzer_* AFLplusplus + +# Ignore common dummy and logfiles +*.log +a diff --git a/.gitignore b/.gitignore index 991eee5de6..533cef3cac 100644 --- a/.gitignore +++ b/.gitignore @@ -25,3 +25,7 @@ test.dict # Ignore all built fuzzers fuzzer_* AFLplusplus + +# Ignore common dummy and logfiles +*.log +a diff --git a/Dockerfile b/Dockerfile index 0293e6b11e..83cfd9aad0 100644 --- a/Dockerfile +++ b/Dockerfile @@ -1,79 +1,58 @@ -# -# This Dockerfile for LibAFL uses rust:bullseye as base. -# - +# syntax=docker/dockerfile:1.2 FROM rust:bullseye AS libafl LABEL "maintainer"="afl++ team " -LABEL "about"="AFLplusplus docker image" +LABEL "about"="LibAFL Docker image" -ARG DEBIAN_FRONTEND=noninteractive +# install sccache to cache subsequent builds of dependencies +RUN cargo install sccache -RUN sh -c 'echo set encoding=utf-8 > /root/.vimrc' \ - echo "export PS1='"'[LibAFL \h] \w$(__git_ps1) \$ '"'" >> ~/.bashrc +ENV HOME=/root +ENV SCCACHE_CACHE_SIZE="1G" +ENV SCCACHE_DIR=$HOME/.cache/sccache +ENV RUSTC_WRAPPER="/usr/local/cargo/bin/sccache" ENV IS_DOCKER="1" +RUN sh -c 'echo set encoding=utf-8 > /root/.vimrc' \ + echo "export PS1='"'[LibAFL \h] \w$(__git_ps1) \$ '"'" >> ~/.bashrc && \ + mkdir ~/.cargo && \ + echo "[build]\nrustc-wrapper = \"${RUSTC_WRAPPER}\"" >> ~/.cargo/config + +RUN rustup component add rustfmt clippy + +# Install clang 11, common build tools +RUN apt update && apt install -y build-essential gdb git wget clang clang-tools libc++-11-dev libc++abi-11-dev # Copy a dummy.rs and Cargo.toml first, so that dependencies are cached WORKDIR /libafl -COPY Cargo.toml Cargo.toml -COPY README.md README.md +COPY Cargo.toml README.md ./ COPY libafl_derive/Cargo.toml libafl_derive/Cargo.toml COPY scripts/dummy.rs libafl_derive/src/lib.rs -COPY libafl/Cargo.toml libafl/Cargo.toml -COPY libafl/build.rs libafl/build.rs +COPY libafl/Cargo.toml libafl/build.rs libafl/ COPY libafl/benches libafl/benches COPY libafl/examples libafl/examples COPY scripts/dummy.rs libafl/src/lib.rs -COPY libafl_frida/Cargo.toml libafl_frida/Cargo.toml -COPY libafl_frida/build.rs libafl_frida/build.rs +COPY libafl_frida/Cargo.toml libafl_frida/build.rs libafl_frida/ COPY scripts/dummy.rs libafl_frida/src/lib.rs +COPY libafl_frida/src/gettls.c libafl_frida/src/gettls.c COPY libafl_cc/Cargo.toml libafl_cc/Cargo.toml COPY scripts/dummy.rs libafl_cc/src/lib.rs -COPY libafl_targets/Cargo.toml libafl_targets/Cargo.toml -COPY libafl_targets/build.rs libafl_targets/build.rs +COPY libafl_targets/Cargo.toml libafl_targets/build.rs libafl_targets/ +COPY libafl_targets/src libafl_targets/src COPY scripts/dummy.rs libafl_targets/src/lib.rs -COPY libafl_tests/Cargo.toml libafl_tests/Cargo.toml -COPY libafl_tests/build.rs libafl_tests/build.rs +COPY libafl_tests/Cargo.toml libafl_tests/build.rs libafl_tests/ COPY scripts/dummy.rs libafl_tests/src/lib.rs +RUN cargo build && cargo build --release + +COPY scripts scripts COPY docs docs -RUN cargo build && cargo build --release - # Pre-build dependencies for a few common fuzzers -COPY fuzzers/baby_fuzzer/Cargo.toml fuzzers/baby_fuzzer/Cargo.toml -COPY fuzzers/baby_fuzzer/README.md fuzzers/baby_fuzzer/README.md -COPY scripts/dummy.rs fuzzers/baby_fuzzer/src/main.rs -WORKDIR /libafl/fuzzers/baby_fuzzer -RUN cargo build && cargo build --release -WORKDIR /libafl - -COPY fuzzers/forkserver_simple/Cargo.toml fuzzers/forkserver_simple/Cargo.toml -COPY fuzzers/forkserver_simple/README.md fuzzers/forkserver_simple/README.md -COPY scripts/dummy.rs fuzzers/forkserver_simple/src/main.rs -WORKDIR /libafl/fuzzers/forkserver_simple -RUN cargo build && cargo build --release -WORKDIR /libafl - -COPY fuzzers/frida_libpng/Cargo.toml fuzzers/frida_libpng/Cargo.toml -COPY fuzzers/frida_libpng/README.md fuzzers/frida_libpng/README.md -COPY fuzzers/frida_libpng/build.rs fuzzers/frida_libpng/build.rs -COPY scripts/dummy.rs fuzzers/frida_libpng/src/main.rs -WORKDIR /libafl/fuzzers/frida_libpng -RUN cargo build && cargo build --release -WORKDIR /libafl - -COPY fuzzers/generic_inmemory/Cargo.toml fuzzers/generic_inmemory/Cargo.toml -COPY fuzzers/generic_inmemory/README.md fuzzers/generic_inmemory/README.md -COPY scripts/dummy.rs fuzzers/generic_inmemory/src/main.rs -WORKDIR /libafl/fuzzers/generic_inmemory -RUN cargo build && cargo build --release -WORKDIR /libafl # Dep chain: # libafl_cc (independent) @@ -84,42 +63,22 @@ WORKDIR /libafl # Build once without source COPY libafl_cc/src libafl_cc/src -RUN touch libafl_cc/src/lib.rs && cargo build && cargo build --release - +RUN touch libafl_cc/src/lib.rs COPY libafl_derive/src libafl_derive/src -RUN touch libafl_derive/src/lib.rs && cargo build && cargo build --release - +RUN touch libafl_derive/src/lib.rs COPY libafl_tests/src libafl_tests/src -RUN touch libafl_tests/src/lib.rs && cargo build && cargo build --release - +RUN touch libafl_tests/src/lib.rs COPY libafl/src libafl/src -RUN touch libafl/src/lib.rs && cargo build && cargo build --release - +RUN touch libafl/src/lib.rs COPY libafl_targets/src libafl_targets/src -RUN touch libafl_targets/src/lib.rs && cargo build && cargo build --release - +RUN touch libafl_targets/src/lib.rs COPY libafl_frida/src libafl_frida/src -RUN touch libafl_frida/src/lib.rs && cargo build && cargo build --release +RUN touch libafl_frida/src/lib.rs +RUN cargo build && cargo build --release # Copy fuzzers over -COPY fuzzers/baby_fuzzer/src fuzzers/baby_fuzzer/src/src -RUN touch fuzzers/baby_fuzzer/src/main.rs -COPY fuzzers/forkserver_simple/corpus fuzzers/forkserver_simple/corpus -COPY fuzzers/forkserver_simple/src fuzzers/forkserver_simple/src -RUN touch fuzzers/forkserver_simple/src/main.rs -COPY fuzzers/frida_libpng/src fuzzers/frida_libpng/src -COPY fuzzers/frida_libpng/harness.cc fuzzers/frida_libpng/harness.cc -RUN touch fuzzers/frida_libpng/src/main.rs -COPY fuzzers/generic_inmemory/src fuzzers/generic_inmemory/src -COPY fuzzers/generic_inmemory/fuzz.c fuzzers/generic_inmemory/fuzz.c -RUN touch fuzzers/frida_libpng/src/main.rs -COPY fuzzers/libfuzzer_libmozjpeg fuzzers/libfuzzer_libmozjpg -COPY fuzzers/libfuzzer_libpng fuzzers/libfuzzer_libpng -COPY fuzzers/libfuzzer_libpng_launcher fuzzers/libfuzzer_libpng_launcher -COPY fuzzers/libfuzzer_reachability fuzzers/libfuzzer_reachability -COPY fuzzers/libfuzzer_stb_image fuzzers/libfuzzer_stb_image - -#RUN ./scripts/build_all_fuzzers.sh +COPY fuzzers fuzzers +# RUN ./scripts/build_all_fuzzers.sh --no-fmt ENTRYPOINT [ "/bin/bash" ] diff --git a/fuzzers/fuzzbench/.gitignore b/fuzzers/fuzzbench/.gitignore new file mode 100644 index 0000000000..a977a2ca5b --- /dev/null +++ b/fuzzers/fuzzbench/.gitignore @@ -0,0 +1 @@ +libpng-* \ No newline at end of file diff --git a/fuzzers/fuzzbench/Cargo.toml b/fuzzers/fuzzbench/Cargo.toml new file mode 100644 index 0000000000..f44e86f079 --- /dev/null +++ b/fuzzers/fuzzbench/Cargo.toml @@ -0,0 +1,44 @@ +[package] +name = "fuzzbench" +version = "0.3.2" +authors = ["Andrea Fioraldi ", "Dominik Maier "] +edition = "2018" +build = "build.rs" + +[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"] + +[[bin]] +# For c binaries +name = "libafl_cc" +path = "src/bin/libafl_cc.rs" + +[[bin]] +# For cpp binaries +name = "libafl_cxx" +path = "src/bin/libafl_cc.rs" + diff --git a/fuzzers/fuzzbench/README.md b/fuzzers/fuzzbench/README.md new file mode 100644 index 0000000000..df34f5e090 --- /dev/null +++ b/fuzzers/fuzzbench/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/build.rs b/fuzzers/fuzzbench/build.rs new file mode 100644 index 0000000000..2bcf6d0d48 --- /dev/null +++ b/fuzzers/fuzzbench/build.rs @@ -0,0 +1,9 @@ +// build.rs + +fn main() { + cc::Build::new() + .file("src/libafl_wrapper.c") + .compile("libafl_sys.a"); + println!("cargo:rerun-if-changed=build.rs"); + println!("cargo:rerun-if-changed=src/libafl_wrapper.c"); +} diff --git a/fuzzers/fuzzbench/fuzz.c b/fuzzers/fuzzbench/fuzz.c new file mode 100644 index 0000000000..7eea9f3b30 --- /dev/null +++ b/fuzzers/fuzzbench/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/src/bin/libafl_cc.rs b/fuzzers/fuzzbench/src/bin/libafl_cc.rs new file mode 100644 index 0000000000..c2b477eb14 --- /dev/null +++ b/fuzzers/fuzzbench/src/bin/libafl_cc.rs @@ -0,0 +1,34 @@ +use libafl_cc::{ClangWrapper, CompilerWrapper}; +use std::env; + +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("clang", if is_cpp { "clang++" } else { "clang" }); + + cc.is_cpp(is_cpp) + .from_args(&args) + .unwrap() + .link_staticlib(&dir, "fuzzbench".into()) + .unwrap() + .add_arg("-fsanitize-coverage=trace-pc-guard,trace-cmp".into()) + .unwrap() + // silence the compiler wrapper output, needed for some configure scripts. + .silence() + .run() + .unwrap(); + } else { + panic!("LibAFL CC: No Arguments given"); + } +} diff --git a/fuzzers/fuzzbench/src/lib.rs b/fuzzers/fuzzbench/src/lib.rs new file mode 100644 index 0000000000..e12e2c0017 --- /dev/null +++ b/fuzzers/fuzzbench/src/lib.rs @@ -0,0 +1,314 @@ +//! 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::{File, OpenOptions}, + io, + io::Write, + path::PathBuf, +}; + +use libafl::{ + bolts::{ + current_nanos, current_time, + os::dup2, + rands::StdRand, + shmem::{ShMemProvider, StdShMemProvider}, + tuples::{tuple_list, Merge}, + }, + corpus::{Corpus, IndexesLenTimeMinimizerCorpusScheduler, OnDiskCorpus, QueueCorpusScheduler}, + events::SimpleRestartingEventManager, + executors::{inprocess::InProcessExecutor, ExitKind, TimeoutExecutor}, + feedback_or, + feedbacks::{CrashFeedback, MapFeedbackState, MaxMapFeedback, TimeFeedback, TimeoutFeedback}, + fuzzer::{Fuzzer, StdFuzzer}, + inputs::{BytesInput, HasTargetBytes}, + mutators::{ + scheduled::{havoc_mutations, StdScheduledMutator}, + token_mutations::I2SRandReplace, + tokens_mutations, Tokens, + }, + observers::{StdMapObserver, TimeObserver}, + stages::{StdMutationalStage, 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 extern "C" fn fuzzer_main() { + // Registry the metadata types used in this fuzzer + // Needed only on no_std + //RegistryBuilder::register::(); + + let res = 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"), + ) + .get_matches(); + + 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 corpus = PathBuf::from(res.value_of("corpus").unwrap().to_string()); + let mut crashes = corpus.clone(); + crashes.push("crashes"); + corpus.push("queue"); + + let seeds = PathBuf::from(res.value_of("seeds").unwrap().to_string()); + + 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(corpus, crashes, seeds, 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. + #[cfg(target_os = "android")] + AshmemService::start().expect("Failed to start Ashmem service"); + 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!(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(); + let args = []; + if libfuzzer_initialize(&args) == -1 { + println!("Warning: LLVMFuzzerInitialize failed with -1") + } + + // A minimization+queue policy to get testcasess from the corpus + let scheduler = IndexesLenTimeMinimizerCorpusScheduler::new(QueueCorpusScheduler::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, + )); + + // Setup a randomic Input2State stage + let i2s = StdMutationalStage::new(StdScheduledMutator::new(tuple_list!(I2SRandReplace::new()))); + + // Setup a basic mutator + let mutator = StdScheduledMutator::new(havoc_mutations().merge(tokens_mutations())); + let mutational = StdMutationalStage::new(mutator); + + // The order of the stages matter! + let mut stages = tuple_list!(tracing, i2s, mutational); + + // 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(|_| panic!("Failed to load initial corpus at {:?}", &seed_dir)); + 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(()) +} diff --git a/fuzzers/fuzzbench/src/libafl_wrapper.c b/fuzzers/fuzzbench/src/libafl_wrapper.c new file mode 100644 index 0000000000..c56e742ffd --- /dev/null +++ b/fuzzers/fuzzbench/src/libafl_wrapper.c @@ -0,0 +1,24 @@ +// We only want to link our fuzzer main, if the target doesn't specify its own main - hence we define `main` as `weak` in this file. +#include +#include +#include +#include + +// jump to rust +void fuzzer_main(); + +// Link in a dummy llvm test to non-fuzzing builds, for configure et al. +int __attribute__((weak)) LLVMFuzzerTestOneInput(const uint8_t *buf, size_t len) { + (void) buf; + (void) len; + fprintf(stderr, "LibAFL - No LLVMFuzzerTestOneInput function found! Linker error?\n"); + fflush(stderr); + abort(); +} + +int __attribute__((weak)) main(int argc, char *argv[]) { + (void) argc; + (void) argv; + fuzzer_main(); + return 0; +} \ No newline at end of file diff --git a/fuzzers/generic_inmemory/src/clap-config.yaml b/fuzzers/generic_inmemory/src/clap-config.yaml index 15fe5a941a..d167a93439 100644 --- a/fuzzers/generic_inmemory/src/clap-config.yaml +++ b/fuzzers/generic_inmemory/src/clap-config.yaml @@ -33,9 +33,10 @@ args: about: "Set the execution timeout in milliseconds, default 10000" value_name: TIMEOUT takes_value: true - - dict: - long: dict - about: "Feed the fuzzer with an user-specified dictionary of tokens" + - tokens: + long: tokens + short: x + about: "Feed the fuzzer with an user-specified list of tokens (often called \"dictionary\")" value_name: DICT multiple: true takes_value: true diff --git a/fuzzers/generic_inmemory/src/lib.rs b/fuzzers/generic_inmemory/src/lib.rs index 829f727147..dc21430a8a 100644 --- a/fuzzers/generic_inmemory/src/lib.rs +++ b/fuzzers/generic_inmemory/src/lib.rs @@ -66,8 +66,8 @@ pub fn main() { .value_of("output") .map(|s| PathBuf::from(s)) .unwrap_or(workdir.clone()); - let dicts: Vec<&str> = matches - .values_of("dict") + let token_files: Vec<&str> = matches + .values_of("tokens") .map(|v| v.collect()) .unwrap_or(vec![]); let timeout_ms = matches @@ -129,8 +129,8 @@ pub fn main() { // Create a PNG dictionary if not existing if state.metadata().get::().is_none() { - for dict in &dicts { - state.add_metadata(Tokens::from_tokens_file(dict)?); + for tokens_file in &token_files { + state.add_metadata(Tokens::from_tokens_file(tokens_file)?); } } diff --git a/libafl/src/bolts/rands.rs b/libafl/src/bolts/rands.rs index 45177bb383..a2287070ca 100644 --- a/libafl/src/bolts/rands.rs +++ b/libafl/src/bolts/rands.rs @@ -13,7 +13,7 @@ const HASH_CONST: u64 = 0xa5b35705; /// The standard rand implementation for `LibAFL`. /// It is usually the right choice, with very good speed and a reasonable randomness. /// Not cryptographically secure (which is not what you want during fuzzing ;) ) -pub type StdRand = RomuTrioRand; +pub type StdRand = RomuDuoJrRand; /// Ways to get random around here /// Please note that these are not cryptographically secure diff --git a/libafl/src/events/simple.rs b/libafl/src/events/simple.rs index 2e53c41f64..82975e034b 100644 --- a/libafl/src/events/simple.rs +++ b/libafl/src/events/simple.rs @@ -8,8 +8,6 @@ use core::{ }; #[cfg(feature = "std")] use serde::{de::DeserializeOwned, Serialize}; -#[cfg(feature = "std")] -use typed_builder::TypedBuilder; #[cfg(all(feature = "std", windows))] use crate::bolts::os::startable_self; @@ -203,7 +201,6 @@ where /// `restarter` will start a new process each time the child crashes or times out. #[cfg(feature = "std")] #[allow(clippy::default_trait_access)] -#[derive(TypedBuilder, Debug)] pub struct SimpleRestartingEventManager where I: Input, @@ -213,16 +210,9 @@ where { /// The actual simple event mgr simple_event_mgr: SimpleEventManager, - /// The shared memory provider to use for the broker or client spawned by the restarting - /// manager. - shmem_provider: SP, - /// The stats to use - #[builder(setter(strip_option))] - stats: Option, /// [`LlmpSender`] for restarts sender: LlmpSender, /// Phantom data - #[builder(setter(skip), default = PhantomData {})] _phantom: PhantomData<(I, S)>, } @@ -281,26 +271,6 @@ where { } -#[cfg(feature = "std")] -impl SimpleRestartingEventManager -where - I: Input, - S: Serialize, - SP: ShMemProvider, - ST: Stats, //TODO CE: CustomEvent, -{ - /// Creates a new [`SimpleEventManager`]. - pub fn new(stats: ST, sender: LlmpSender, shmem_provider: SP) -> Self { - Self { - stats: None, - sender, - simple_event_mgr: SimpleEventManager::new(stats), - shmem_provider, - _phantom: PhantomData {}, - } - } -} - #[cfg(feature = "std")] #[allow(clippy::type_complexity, clippy::too_many_lines)] impl SimpleRestartingEventManager @@ -308,24 +278,32 @@ where I: Input, S: DeserializeOwned + Serialize, SP: ShMemProvider, - ST: Stats + Clone, + ST: Stats, //TODO CE: CustomEvent, { - /// Launch the restarting manager + /// Creates a new [`SimpleEventManager`]. + fn new_launched(stats: ST, sender: LlmpSender) -> Self { + Self { + sender, + simple_event_mgr: SimpleEventManager::new(stats), + _phantom: PhantomData {}, + } + } + + /// Launch the simple restarting manager. + /// This [`EventManager`] is simple and single threaded, + /// but can still used shared maps to recover from crashes and timeouts. + #[allow(clippy::similar_names)] pub fn launch( - &mut self, + stats: ST, + shmem_provider: &mut SP, ) -> Result<(Option, SimpleRestartingEventManager), Error> { // We start ourself as child process to actually fuzz - let (mut sender, mut receiver, new_shmem_provider) = if std::env::var(_ENV_FUZZER_SENDER) - .is_err() - { + let (mut sender, mut receiver) = if std::env::var(_ENV_FUZZER_SENDER).is_err() { // First, create a channel from the fuzzer (sender) to us (receiver) to report its state for restarts. - let sender = { LlmpSender::new(self.shmem_provider.clone(), 0, false)? }; + let sender = { LlmpSender::new(shmem_provider.clone(), 0, false)? }; - let map = { - self.shmem_provider - .clone_ref(&sender.out_maps.last().unwrap().shmem)? - }; - let receiver = LlmpReceiver::on_existing_map(self.shmem_provider.clone(), map, None)?; + let map = { shmem_provider.clone_ref(&sender.out_maps.last().unwrap().shmem)? }; + let receiver = LlmpReceiver::on_existing_map(shmem_provider.clone(), map, None)?; // Store the information to a map. sender.to_env(_ENV_FUZZER_SENDER)?; receiver.to_env(_ENV_FUZZER_RECEIVER)?; @@ -338,15 +316,15 @@ where // On Unix, we fork #[cfg(unix)] let child_status = { - self.shmem_provider.pre_fork()?; + shmem_provider.pre_fork()?; match unsafe { fork() }? { ForkResult::Parent(handle) => { - self.shmem_provider.post_fork(false)?; + shmem_provider.post_fork(false)?; handle.status() } ForkResult::Child => { - self.shmem_provider.post_fork(true)?; - break (sender, receiver, self.shmem_provider.clone()); + shmem_provider.post_fork(true)?; + break (sender, receiver); } } }; @@ -376,12 +354,8 @@ where // We get here *only on Windows*, if we were started by a restarting fuzzer. // A sender and a receiver for single communication ( - LlmpSender::on_existing_from_env(self.shmem_provider.clone(), _ENV_FUZZER_SENDER)?, - LlmpReceiver::on_existing_from_env( - self.shmem_provider.clone(), - _ENV_FUZZER_RECEIVER, - )?, - self.shmem_provider.clone(), + LlmpSender::on_existing_from_env(shmem_provider.clone(), _ENV_FUZZER_SENDER)?, + LlmpReceiver::on_existing_from_env(shmem_provider.clone(), _ENV_FUZZER_RECEIVER)?, ) }; @@ -394,11 +368,7 @@ where // Mgr to send and receive msgs from/to all other fuzzer instances ( None, - SimpleRestartingEventManager::new( - self.stats.take().unwrap(), - sender, - new_shmem_provider, - ), + SimpleRestartingEventManager::new_launched(stats, sender), ) } // Restoring from a previous run, deserialize state and corpus. @@ -412,11 +382,7 @@ where ( Some(state), - SimpleRestartingEventManager::new( - self.stats.take().unwrap(), - sender, - new_shmem_provider, - ), + SimpleRestartingEventManager::new_launched(stats, sender), ) } }; diff --git a/libafl/src/executors/forkserver.rs b/libafl/src/executors/forkserver.rs index 126f1ac4dd..d92df92c5b 100644 --- a/libafl/src/executors/forkserver.rs +++ b/libafl/src/executors/forkserver.rs @@ -206,7 +206,7 @@ impl Forkserver { Ok(_) => {} Err(err) => { return Err(Error::Forkserver(format!( - "Could not spawn forkserver: {:#?}", + "Could not spawn the forkserver: {:#?}", err ))); } diff --git a/libafl/src/fuzzer.rs b/libafl/src/fuzzer.rs index 94833510eb..e9191548ab 100644 --- a/libafl/src/fuzzer.rs +++ b/libafl/src/fuzzer.rs @@ -460,7 +460,8 @@ where stats_timeout: Duration, ) -> Result { let cur = current_time(); - if cur - last > stats_timeout { + // default to 0 here to avoid crashes on clock skew + if cur.checked_sub(last).unwrap_or_default() > stats_timeout { // Default no introspection implmentation #[cfg(not(feature = "introspection"))] manager.fire( diff --git a/libafl/src/stats/mod.rs b/libafl/src/stats/mod.rs index 42a38ae475..709ecccda5 100644 --- a/libafl/src/stats/mod.rs +++ b/libafl/src/stats/mod.rs @@ -19,6 +19,12 @@ use crate::bolts::current_time; const CLIENT_STATS_TIME_WINDOW_SECS: u64 = 5; // 5 seconds +/// Number of stages in the fuzzer +pub(crate) const NUM_STAGES: usize = 8; + +/// Number of feedback mechanisms to measure for performance +pub(crate) const NUM_FEEDBACKS: usize = 16; + /// User-defined stats types #[derive(Serialize, Deserialize, Debug, Clone)] pub enum UserStats { @@ -224,6 +230,7 @@ impl Stats for NopStats { impl NopStats { /// Create new [`NopStats`] + #[must_use] pub fn new() -> Self { Self { start_time: current_time(), @@ -350,12 +357,6 @@ macro_rules! mark_feedback_time { }}; } -/// Number of stages in the fuzzer -pub(crate) const NUM_STAGES: usize = 8; - -/// Number of feedback mechanisms to measure for performance -pub(crate) const NUM_FEEDBACKS: usize = 16; - /// Client performance statistics #[derive(Serialize, Deserialize, Debug, Copy, Clone)] pub struct ClientPerfStats { @@ -770,6 +771,7 @@ impl core::fmt::Display for ClientPerfStats { #[cfg(feature = "introspection")] impl Default for ClientPerfStats { + #[must_use] fn default() -> Self { Self::new() } diff --git a/libafl_cc/src/lib.rs b/libafl_cc/src/lib.rs index 0e1f9b292d..6c1c259962 100644 --- a/libafl_cc/src/lib.rs +++ b/libafl_cc/src/lib.rs @@ -51,10 +51,19 @@ pub trait CompilerWrapper { /// Get if in linking mode fn is_linking(&self) -> bool; + /// Silences `libafl_cc` output + fn silence(&mut self) -> &'_ mut Self; + + /// Returns `true` if `silence` was called + fn is_silent(&self) -> bool; + /// Run the compiler fn run(&mut self) -> Result<(), Error> { let args = self.command()?; - dbg!(&args); + + if !self.is_silent() { + dbg!(&args); + } if args.is_empty() { return Err(Error::InvalidArguments( "The number of arguments cannot be 0".into(), @@ -64,7 +73,9 @@ pub trait CompilerWrapper { Ok(s) => s, Err(e) => return Err(Error::Io(e)), }; - dbg!(status); + if !self.is_silent() { + dbg!(status); + } Ok(()) } } @@ -72,6 +83,7 @@ pub trait CompilerWrapper { /// Wrap Clang #[allow(clippy::struct_excessive_bools)] pub struct ClangWrapper { + is_silent: bool, optimize: bool, wrapped_cc: String, wrapped_cxx: String, @@ -167,13 +179,21 @@ impl CompilerWrapper for ClangWrapper { } fn link_staticlib(&mut self, dir: &Path, name: String) -> Result<&'_ mut Self, Error> { - self.add_link_arg("-Wl,--whole-archive".into())? - .add_link_arg( - dir.join(format!("{}{}.{}", LIB_PREFIX, name, LIB_EXT)) - .display() - .to_string(), - )? - .add_link_arg("-Wl,-no-whole-archive".into()) + if cfg!(any(target_os = "macos", target_os = "ios")) { + //self.add_link_arg("-force_load".into())?; + } else { + self.add_link_arg("-Wl,--whole-archive".into())?; + } + self.add_link_arg( + dir.join(format!("{}{}.{}", LIB_PREFIX, name, LIB_EXT)) + .display() + .to_string(), + )?; + if cfg!(any(target_os = "macos", target_os = "ios")) { + Ok(self) + } else { + self.add_link_arg("-Wl,-no-whole-archive".into()) + } } fn command(&mut self) -> Result, Error> { @@ -201,6 +221,15 @@ impl CompilerWrapper for ClangWrapper { fn is_linking(&self) -> bool { self.linking } + + fn silence(&mut self) -> &'_ mut Self { + self.is_silent = true; + self + } + + fn is_silent(&self) -> bool { + self.is_silent + } } impl ClangWrapper { @@ -219,6 +248,7 @@ impl ClangWrapper { base_args: vec![], cc_args: vec![], link_args: vec![], + is_silent: false, } } diff --git a/scripts/build_all_fuzzers.sh b/scripts/build_all_fuzzers.sh index f96de961e7..4d3058197b 100755 --- a/scripts/build_all_fuzzers.sh +++ b/scripts/build_all_fuzzers.sh @@ -1,4 +1,5 @@ #!/bin/bash + SCRIPT_DIR="$( cd "$( dirname "${BASH_SOURCE[0]}" )" &> /dev/null && pwd )" cd "$SCRIPT_DIR/.." @@ -8,11 +9,20 @@ cd fuzzers for fuzzer in *; do - echo "[+] Checking fmt and building $fuzzer" - cd $fuzzer \ - && cargo fmt --all -- --check \ - && cargo clippy \ - && cargo build \ - && cd .. \ - || exit 1 + cd $fuzzer + # Clippy checks + if [ "$1" != "--no-fmt" ]; then + + echo "[*] Checking fmt for $fuzzer" + cargo fmt --all -- --check || exit 1 + echo "[*] Running clippy for $fuzzer" + cargo clippy || exit 1 + else + echo "[+] Skipping fmt and clippy for $fuzzer (--no-fmt specified)" + fi + echo "[*] Building $fuzzer" + cargo build || exit 1 + cd .. + echo "[+] Done building $fuzzer" + echo "" done