diff --git a/Dockerfile b/Dockerfile index 812ea76c19..27764ddd20 100644 --- a/Dockerfile +++ b/Dockerfile @@ -43,9 +43,9 @@ COPY libafl_sugar/Cargo.toml libafl_sugar/ COPY scripts/dummy.rs libafl_sugar/src/lib.rs COPY libafl_cc/Cargo.toml libafl_cc/Cargo.toml -COPY scripts/dummy.rs libafl_cc/src/lib.rs COPY libafl_cc/build.rs libafl_cc/build.rs -COPY libafl_cc/src/cmplog-routines-pass.cc libafl_cc/src/cmplog-routines-pass.cc +COPY libafl_cc/src libafl_cc/src +COPY scripts/dummy.rs libafl_cc/src/lib.rs COPY libafl_targets/Cargo.toml libafl_targets/build.rs libafl_targets/ COPY libafl_targets/src libafl_targets/src diff --git a/fuzzers/libfuzzer_libpng_accounting/.gitignore b/fuzzers/libfuzzer_libpng_accounting/.gitignore new file mode 100644 index 0000000000..a977a2ca5b --- /dev/null +++ b/fuzzers/libfuzzer_libpng_accounting/.gitignore @@ -0,0 +1 @@ +libpng-* \ No newline at end of file diff --git a/fuzzers/libfuzzer_libpng_accounting/Cargo.toml b/fuzzers/libfuzzer_libpng_accounting/Cargo.toml new file mode 100644 index 0000000000..9c20c456a2 --- /dev/null +++ b/fuzzers/libfuzzer_libpng_accounting/Cargo.toml @@ -0,0 +1,32 @@ +[package] +name = "libfuzzer_libpng_launcher" +version = "0.7.1" +authors = ["Andrea Fioraldi ", "Dominik Maier "] +edition = "2021" + +[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/", features = ["std", "derive", "llmp_compression", "introspection"] } +libafl_targets = { path = "../../libafl_targets/", features = ["sancov_pcguard_hitcounts", "libfuzzer"] } +# TODO Include it only when building cc +libafl_cc = { path = "../../libafl_cc/" } +clap = { version = "3.0", features = ["derive"] } +mimalloc = { version = "*", default-features = false } + +[lib] +name = "libfuzzer_libpng" +crate-type = ["staticlib"] diff --git a/fuzzers/libfuzzer_libpng_accounting/Makefile b/fuzzers/libfuzzer_libpng_accounting/Makefile new file mode 100644 index 0000000000..e7ed9d10c5 --- /dev/null +++ b/fuzzers/libfuzzer_libpng_accounting/Makefile @@ -0,0 +1,50 @@ +FUZZER_NAME="fuzzer_libpng" +PROJECT_DIR=$(shell dirname $(realpath $(firstword $(MAKEFILE_LIST)))) +UNAME := $(shell uname) + +PHONY: all + +all: fuzzer + +libpng-1.6.37: + wget https://deac-fra.dl.sourceforge.net/project/libpng/libpng16/1.6.37/libpng-1.6.37.tar.xz + tar -xvf libpng-1.6.37.tar.xz + +target/release/libafl_cxx: src/* src/bin/* + # Build the libpng libfuzzer library + cargo build --release + +libafl_cxx: target/release/libafl_cxx + +libafl_cc: target/release/libafl_cxx + +libpng-1.6.37/.libs/libpng16.a: libpng-1.6.37 libafl_cc + cd libpng-1.6.37 && ./configure --enable-shared=no --with-pic=yes --enable-hardware-optimizations=yes + $(MAKE) -C libpng-1.6.37 CC="$(PROJECT_DIR)/target/release/libafl_cc" CXX="$(PROJECT_DIR)/target/release/libafl_cxx" + + +fuzzer: libpng-1.6.37/.libs/libpng16.a libafl_cxx + # Build the libpng libfuzzer library + cargo build --release + + # Build the libpng harness + target/release/libafl_cxx \ + $(PROJECT_DIR)/harness.cc \ + $(PROJECT_DIR)/libpng-1.6.37/.libs/libpng16.a \ + -I$(PROJECT_DIR)/libpng-1.6.37/ \ + -o $(FUZZER_NAME) \ + -lm -lz + +clean: + rm ./$(FUZZER_NAME) + $(MAKE) -C libpng-1.6.37 clean + +run: all + ./$(FUZZER_NAME) --cores 0 --input ./corpus & + +short_test: all + rm -rf libafl_unix_shmem_server || true + timeout 10s ./$(FUZZER_NAME) --cores 0 --input ./corpus & + +test: all + timeout 60s ./$(FUZZER_NAME) --cores 0 --input ./corpus & diff --git a/fuzzers/libfuzzer_libpng_accounting/README.md b/fuzzers/libfuzzer_libpng_accounting/README.md new file mode 100644 index 0000000000..b96b0e851c --- /dev/null +++ b/fuzzers/libfuzzer_libpng_accounting/README.md @@ -0,0 +1,47 @@ +# Libfuzzer for libpng, with launcher + +This folder contains an example fuzzer for libpng, using LLMP for fast multi-process fuzzing and crash detection. +To show off crash detection, we added a `ud2` instruction to the harness, edit harness.cc if you want a non-crashing example. +It has been tested on Linux. + +In contrast to the normal libfuzzer libpng example, this uses the `launcher` feature, that automatically spawns `n` child processes, and binds them to a free core. + +## Build + +To build this example, run + +```bash +cargo build --release +``` + +This will build the library with the fuzzer (src/lib.rs) with the libfuzzer compatibility layer and the SanitizerCoverage runtime functions for coverage feedback. +In addition, it will also build two C and C++ compiler wrappers (bin/libafl_c(libafl_c/xx).rs) that you must use to compile the target. + +Then download libpng, and unpack the archive: +```bash +wget https://deac-fra.dl.sourceforge.net/project/libpng/libpng16/1.6.37/libpng-1.6.37.tar.xz +tar -xvf libpng-1.6.37.tar.xz +``` + +Now compile libpng, using the libafl_cc compiler wrapper: + +```bash +cd libpng-1.6.37 +./configure +make CC=../target/release/libafl_cc CXX=../target/release/libafl_cxx -j `nproc` +``` + +You can find the static lib at `libpng-1.6.37/.libs/libpng16.a`. + +Now, we have to build the libfuzzer harness and link all together to create our fuzzer binary. + +``` +cd .. +./target/release/libafl_cxx ./harness.cc libpng-1.6.37/.libs/libpng16.a -I libpng-1.6.37/ -o fuzzer_libpng -lz -lm +``` + +Afterwards, the fuzzer will be ready to run. + +## Run + +Just run once, the launcher feature should do the rest. \ No newline at end of file diff --git a/fuzzers/libfuzzer_libpng_accounting/corpus/not_kitty.png b/fuzzers/libfuzzer_libpng_accounting/corpus/not_kitty.png new file mode 100644 index 0000000000..eff7c1707b Binary files /dev/null and b/fuzzers/libfuzzer_libpng_accounting/corpus/not_kitty.png differ diff --git a/fuzzers/libfuzzer_libpng_accounting/corpus/not_kitty_alpha.png b/fuzzers/libfuzzer_libpng_accounting/corpus/not_kitty_alpha.png new file mode 100644 index 0000000000..2fb8da2c8f Binary files /dev/null and b/fuzzers/libfuzzer_libpng_accounting/corpus/not_kitty_alpha.png differ diff --git a/fuzzers/libfuzzer_libpng_accounting/corpus/not_kitty_gamma.png b/fuzzers/libfuzzer_libpng_accounting/corpus/not_kitty_gamma.png new file mode 100644 index 0000000000..939d9d29a9 Binary files /dev/null and b/fuzzers/libfuzzer_libpng_accounting/corpus/not_kitty_gamma.png differ diff --git a/fuzzers/libfuzzer_libpng_accounting/corpus/not_kitty_icc.png b/fuzzers/libfuzzer_libpng_accounting/corpus/not_kitty_icc.png new file mode 100644 index 0000000000..f0c7804d99 Binary files /dev/null and b/fuzzers/libfuzzer_libpng_accounting/corpus/not_kitty_icc.png differ diff --git a/fuzzers/libfuzzer_libpng_accounting/harness.cc b/fuzzers/libfuzzer_libpng_accounting/harness.cc new file mode 100644 index 0000000000..65faff685d --- /dev/null +++ b/fuzzers/libfuzzer_libpng_accounting/harness.cc @@ -0,0 +1,197 @@ +// libpng_read_fuzzer.cc +// Copyright 2017-2018 Glenn Randers-Pehrson +// Copyright 2015 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that may +// be found in the LICENSE file https://cs.chromium.org/chromium/src/LICENSE + +// Last changed in libpng 1.6.35 [July 15, 2018] + +// The modifications in 2017 by Glenn Randers-Pehrson include +// 1. addition of a PNG_CLEANUP macro, +// 2. setting the option to ignore ADLER32 checksums, +// 3. adding "#include " which is needed on some platforms +// to provide memcpy(). +// 4. adding read_end_info() and creating an end_info structure. +// 5. adding calls to png_set_*() transforms commonly used by browsers. + +#include +#include +#include + +#include + +#define PNG_INTERNAL +#include "png.h" + +#define PNG_CLEANUP \ + if(png_handler.png_ptr) \ + { \ + if (png_handler.row_ptr) \ + png_free(png_handler.png_ptr, png_handler.row_ptr); \ + if (png_handler.end_info_ptr) \ + png_destroy_read_struct(&png_handler.png_ptr, &png_handler.info_ptr,\ + &png_handler.end_info_ptr); \ + else if (png_handler.info_ptr) \ + png_destroy_read_struct(&png_handler.png_ptr, &png_handler.info_ptr,\ + nullptr); \ + else \ + png_destroy_read_struct(&png_handler.png_ptr, nullptr, nullptr); \ + png_handler.png_ptr = nullptr; \ + png_handler.row_ptr = nullptr; \ + png_handler.info_ptr = nullptr; \ + png_handler.end_info_ptr = nullptr; \ + } + +struct BufState { + const uint8_t* data; + size_t bytes_left; +}; + +struct PngObjectHandler { + png_infop info_ptr = nullptr; + png_structp png_ptr = nullptr; + png_infop end_info_ptr = nullptr; + png_voidp row_ptr = nullptr; + BufState* buf_state = nullptr; + + ~PngObjectHandler() { + if (row_ptr) + png_free(png_ptr, row_ptr); + if (end_info_ptr) + png_destroy_read_struct(&png_ptr, &info_ptr, &end_info_ptr); + else if (info_ptr) + png_destroy_read_struct(&png_ptr, &info_ptr, nullptr); + else + png_destroy_read_struct(&png_ptr, nullptr, nullptr); + delete buf_state; + } +}; + +void user_read_data(png_structp png_ptr, png_bytep data, size_t length) { + BufState* buf_state = static_cast(png_get_io_ptr(png_ptr)); + if (length > buf_state->bytes_left) { + png_error(png_ptr, "read error"); + } + memcpy(data, buf_state->data, length); + buf_state->bytes_left -= length; + buf_state->data += length; +} + +static const int kPngHeaderSize = 8; + +// Entry point for LibFuzzer. +// Roughly follows the libpng book example: +// http://www.libpng.org/pub/png/book/chapter13.html +extern "C" int LLVMFuzzerTestOneInput(const uint8_t* data, size_t size) { + if (size < kPngHeaderSize) { + return 0; + } + + std::vector v(data, data + size); + if (png_sig_cmp(v.data(), 0, kPngHeaderSize)) { + // not a PNG. + return 0; + } + + PngObjectHandler png_handler; + png_handler.png_ptr = nullptr; + png_handler.row_ptr = nullptr; + png_handler.info_ptr = nullptr; + png_handler.end_info_ptr = nullptr; + + png_handler.png_ptr = png_create_read_struct + (PNG_LIBPNG_VER_STRING, nullptr, nullptr, nullptr); + if (!png_handler.png_ptr) { + return 0; + } + + png_handler.info_ptr = png_create_info_struct(png_handler.png_ptr); + if (!png_handler.info_ptr) { + PNG_CLEANUP + return 0; + } + + png_handler.end_info_ptr = png_create_info_struct(png_handler.png_ptr); + if (!png_handler.end_info_ptr) { + PNG_CLEANUP + return 0; + } + + png_set_crc_action(png_handler.png_ptr, PNG_CRC_QUIET_USE, PNG_CRC_QUIET_USE); +#ifdef PNG_IGNORE_ADLER32 + png_set_option(png_handler.png_ptr, PNG_IGNORE_ADLER32, PNG_OPTION_ON); +#endif + + // Setting up reading from buffer. + png_handler.buf_state = new BufState(); + png_handler.buf_state->data = data + kPngHeaderSize; + png_handler.buf_state->bytes_left = size - kPngHeaderSize; + png_set_read_fn(png_handler.png_ptr, png_handler.buf_state, user_read_data); + png_set_sig_bytes(png_handler.png_ptr, kPngHeaderSize); + + if (setjmp(png_jmpbuf(png_handler.png_ptr))) { + PNG_CLEANUP + return 0; + } + + // Reading. + png_read_info(png_handler.png_ptr, png_handler.info_ptr); + + // reset error handler to put png_deleter into scope. + if (setjmp(png_jmpbuf(png_handler.png_ptr))) { + PNG_CLEANUP + return 0; + } + + png_uint_32 width, height; + int bit_depth, color_type, interlace_type, compression_type; + int filter_type; + + if (!png_get_IHDR(png_handler.png_ptr, png_handler.info_ptr, &width, + &height, &bit_depth, &color_type, &interlace_type, + &compression_type, &filter_type)) { + PNG_CLEANUP + return 0; + } + + // This is going to be too slow. + if (width && height > 100000000 / width) { + PNG_CLEANUP +#ifdef HAS_DUMMY_CRASH + #ifdef __aarch64__ + asm volatile (".word 0xf7f0a000\n"); + #else + asm("ud2"); + #endif +#endif + return 0; + } + + // Set several transforms that browsers typically use: + png_set_gray_to_rgb(png_handler.png_ptr); + png_set_expand(png_handler.png_ptr); + png_set_packing(png_handler.png_ptr); + png_set_scale_16(png_handler.png_ptr); + png_set_tRNS_to_alpha(png_handler.png_ptr); + + int passes = png_set_interlace_handling(png_handler.png_ptr); + + png_read_update_info(png_handler.png_ptr, png_handler.info_ptr); + + png_handler.row_ptr = png_malloc( + png_handler.png_ptr, png_get_rowbytes(png_handler.png_ptr, + png_handler.info_ptr)); + + for (int pass = 0; pass < passes; ++pass) { + for (png_uint_32 y = 0; y < height; ++y) { + png_read_row(png_handler.png_ptr, + static_cast(png_handler.row_ptr), nullptr); + } + } + + png_read_end(png_handler.png_ptr, png_handler.end_info_ptr); + + PNG_CLEANUP + return 0; +} + diff --git a/fuzzers/libfuzzer_libpng_accounting/src/bin/libafl_cc.rs b/fuzzers/libfuzzer_libpng_accounting/src/bin/libafl_cc.rs new file mode 100644 index 0000000000..cb589657eb --- /dev/null +++ b/fuzzers/libfuzzer_libpng_accounting/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) + .parse_args(&args) + .expect("Failed to parse the command line") + .link_staticlib(&dir, "libfuzzer_libpng") + .add_arg("-fsanitize-coverage=trace-pc-guard") + .add_pass(LLVMPasses::CoverageAccounting) + .run() + .expect("Failed to run the wrapped compiler") + { + std::process::exit(code); + } + } else { + panic!("LibAFL CC: No Arguments given"); + } +} diff --git a/fuzzers/libfuzzer_libpng_accounting/src/bin/libafl_cxx.rs b/fuzzers/libfuzzer_libpng_accounting/src/bin/libafl_cxx.rs new file mode 100644 index 0000000000..ce786239b0 --- /dev/null +++ b/fuzzers/libfuzzer_libpng_accounting/src/bin/libafl_cxx.rs @@ -0,0 +1,5 @@ +pub mod libafl_cc; + +fn main() { + libafl_cc::main() +} diff --git a/fuzzers/libfuzzer_libpng_accounting/src/lib.rs b/fuzzers/libfuzzer_libpng_accounting/src/lib.rs new file mode 100644 index 0000000000..36c0ff3b4a --- /dev/null +++ b/fuzzers/libfuzzer_libpng_accounting/src/lib.rs @@ -0,0 +1,272 @@ +//! A libfuzzer-like fuzzer with llmp-multithreading support and restarts +//! The example harness is built for libpng. +//! In this example, you will see the use of the `launcher` feature. +//! The `launcher` will spawn new processes for each cpu core. +use mimalloc::MiMalloc; +#[global_allocator] +static GLOBAL: MiMalloc = MiMalloc; + +use clap::{self, StructOpt}; +use core::time::Duration; +use std::{env, net::SocketAddr, path::PathBuf}; + +use libafl::{ + bolts::{ + current_nanos, + launcher::Launcher, + os::Cores, + rands::StdRand, + shmem::{ShMemProvider, StdShMemProvider}, + tuples::{tuple_list, Merge}, + AsSlice, + }, + corpus::{ + Corpus, CoverageAccountingCorpusScheduler, InMemoryCorpus, OnDiskCorpus, + QueueCorpusScheduler, + }, + events::EventConfig, + executors::{inprocess::InProcessExecutor, ExitKind, TimeoutExecutor}, + feedback_or, feedback_or_fast, + feedbacks::{CrashFeedback, MapFeedbackState, MaxMapFeedback, TimeFeedback, TimeoutFeedback}, + fuzzer::{Fuzzer, StdFuzzer}, + inputs::{BytesInput, HasTargetBytes}, + monitors::MultiMonitor, + mutators::scheduled::{havoc_mutations, tokens_mutations, StdScheduledMutator}, + mutators::token_mutations::Tokens, + observers::{HitcountsMapObserver, StdMapObserver, TimeObserver}, + stages::mutational::StdMutationalStage, + state::{HasCorpus, HasMetadata, StdState}, + Error, +}; + +use libafl_targets::{ + libfuzzer_initialize, libfuzzer_test_one_input, ACCOUNTING_MEMOP_MAP, EDGES_MAP, MAX_EDGES_NUM, +}; + +/// Parse a millis string to a [`Duration`]. Used for arg parsing. +fn timeout_from_millis_str(time: &str) -> Result { + Ok(Duration::from_millis(time.parse()?)) +} + +/// The commandline args this fuzzer accepts +#[derive(Debug, StructOpt)] +#[clap( + name = "libfuzzer_libpng_launcher", + about = "A libfuzzer-like fuzzer for libpng with llmp-multithreading support and a launcher", + author = "Andrea Fioraldi , Dominik Maier " +)] +struct Opt { + #[clap( + short, + long, + parse(try_from_str = Cores::from_cmdline), + help = "Spawn a client in each of the provided cores. Broker runs in the 0th core. 'all' to select all available cores. 'none' to run a client without binding to any core. eg: '1,2-4,6' selects the cores 1,2,3,4,6.", + name = "CORES" + )] + cores: Cores, + + #[clap( + short = 'p', + long, + help = "Choose the broker TCP port, default is 1337", + name = "PORT", + default_value = "1337" + )] + broker_port: u16, + + #[clap( + parse(try_from_str), + short = 'a', + long, + help = "Specify a remote broker", + name = "REMOTE" + )] + remote_broker_addr: Option, + + #[clap( + parse(try_from_str), + short, + long, + help = "Set an initial corpus directory", + name = "INPUT" + )] + input: Vec, + + #[clap( + short, + long, + parse(try_from_str), + help = "Set the output directory, default is ./out", + name = "OUTPUT", + default_value = "./out" + )] + output: PathBuf, + + #[clap( + parse(try_from_str = timeout_from_millis_str), + short, + long, + help = "Set the exeucution timeout in milliseconds, default is 10000", + name = "TIMEOUT", + default_value = "10000" + )] + timeout: Duration, + /* + /// This fuzzer has hard-coded tokens + #[clap( + parse(from_os_str), + short = "x", + long, + help = "Feed the fuzzer with an user-specified list of tokens (often called \"dictionary\"", + name = "TOKENS", + multiple = true + )] + tokens: Vec, + */ +} + +/// The main fn, `no_mangle` as it is a C symbol +#[no_mangle] +pub fn libafl_main() { + // Registry the metadata types used in this fuzzer + // Needed only on no_std + //RegistryBuilder::register::(); + let opt = Opt::parse(); + + let broker_port = opt.broker_port; + let cores = opt.cores; + + println!( + "Workdir: {:?}", + env::current_dir().unwrap().to_string_lossy().to_string() + ); + + let shmem_provider = StdShMemProvider::new().expect("Failed to init shared memory"); + + let monitor = MultiMonitor::new(|s| println!("{}", s)); + + let mut run_client = |state: Option>, mut restarting_mgr, _core_id| { + // Create an observation channel using the coverage map + let edges = unsafe { &mut EDGES_MAP[0..MAX_EDGES_NUM] }; + let edges_observer = HitcountsMapObserver::new(StdMapObserver::new("edges", edges)); + + // Create an observation channel to keep track of the execution time + let time_observer = TimeObserver::new("time"); + + // 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 + InMemoryCorpus::new(), + // Corpus in which we store solutions (crashes in this example), + // on disk so the user can get them after stopping the fuzzer + OnDiskCorpus::new(&opt.output).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!("We're a client, let's fuzz :)"); + + // Create a PNG dictionary if not existing + if state.metadata().get::().is_none() { + state.add_metadata(Tokens::from(vec![ + vec![137, 80, 78, 71, 13, 10, 26, 10], // PNG header + "IHDR".as_bytes().to_vec(), + "IDAT".as_bytes().to_vec(), + "PLTE".as_bytes().to_vec(), + "IEND".as_bytes().to_vec(), + ])); + } + + // Setup a basic mutator with a mutational stage + let mutator = StdScheduledMutator::new(havoc_mutations().merge(tokens_mutations())); + let mut stages = tuple_list!(StdMutationalStage::new(mutator)); + + // A minimization+queue policy to get testcasess from the corpus + let scheduler = CoverageAccountingCorpusScheduler::new( + &mut state, + QueueCorpusScheduler::new(), + unsafe { &ACCOUNTING_MEMOP_MAP }, + ); + + // 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 + }; + + // 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 restarting_mgr, + )?, + // 10 seconds timeout + opt.timeout, + ); + + // 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") + } + + // In case the corpus is empty (on first run), reset + if state.corpus().count() < 1 { + state + .load_initial_inputs(&mut fuzzer, &mut executor, &mut restarting_mgr, &opt.input) + .unwrap_or_else(|e| { + panic!("Failed to load initial corpus at {:?} {:?}", &opt.input, e) + }); + println!("We imported {} inputs from disk.", state.corpus().count()); + } + + fuzzer.fuzz_loop(&mut stages, &mut executor, &mut state, &mut restarting_mgr)?; + Ok(()) + }; + + match Launcher::builder() + .shmem_provider(shmem_provider) + .configuration(EventConfig::from_name("default")) + .monitor(monitor) + .run_client(&mut run_client) + .cores(&cores) + .broker_port(broker_port) + .remote_broker_addr(opt.remote_broker_addr) + //.stdout_file(Some("/dev/null")) + .build() + .launch() + { + Ok(()) => (), + Err(Error::ShuttingDown) => println!("Fuzzing stopped by user. Good bye."), + Err(err) => panic!("Failed to run launcher: {:?}", err), + } +} diff --git a/fuzzers/libfuzzer_libpng_launcher/src/lib.rs b/fuzzers/libfuzzer_libpng_launcher/src/lib.rs index e5ba30da77..3033958660 100644 --- a/fuzzers/libfuzzer_libpng_launcher/src/lib.rs +++ b/fuzzers/libfuzzer_libpng_launcher/src/lib.rs @@ -31,7 +31,6 @@ use libafl::{ fuzzer::{Fuzzer, StdFuzzer}, inputs::{BytesInput, HasTargetBytes}, monitors::tui::TuiMonitor, - monitors::MultiMonitor, mutators::scheduled::{havoc_mutations, tokens_mutations, StdScheduledMutator}, mutators::token_mutations::Tokens, observers::{HitcountsMapObserver, StdMapObserver, TimeObserver}, @@ -142,7 +141,6 @@ pub fn libafl_main() { let shmem_provider = StdShMemProvider::new().expect("Failed to init shared memory"); - //let monitor = MultiMonitor::new(|s| println!("{}", s)); let monitor = TuiMonitor::new("Test fuzzer on libpng".into(), true); let mut run_client = |state: Option>, mut restarting_mgr, _core_id| { diff --git a/fuzzers/qemu_launcher/libpng_harness b/fuzzers/qemu_launcher/libpng_harness deleted file mode 100755 index 9863da3776..0000000000 Binary files a/fuzzers/qemu_launcher/libpng_harness and /dev/null differ diff --git a/libafl/src/corpus/accounting.rs b/libafl/src/corpus/accounting.rs new file mode 100644 index 0000000000..4ab473054d --- /dev/null +++ b/libafl/src/corpus/accounting.rs @@ -0,0 +1,291 @@ +//! Coverage accounting corpus scheduler, more details at + +use crate::{ + bolts::{rands::Rand, AsMutSlice, AsSlice, HasLen, HasRefCnt}, + corpus::{ + minimizer::{ + IsFavoredMetadata, LenTimeMulFavFactor, MinimizerCorpusScheduler, + DEFAULT_SKIP_NON_FAVORED_PROB, + }, + Corpus, CorpusScheduler, Testcase, + }, + feedbacks::MapIndexesMetadata, + inputs::Input, + state::{HasCorpus, HasMetadata, HasRand}, + Error, +}; + +use alloc::vec::Vec; +use hashbrown::HashMap; +use serde::{Deserialize, Serialize}; + +/// A testcase metadata holding a list of indexes of a map +#[derive(Debug, Serialize, Deserialize)] +pub struct AccountingIndexesMetadata { + /// The list of indexes. + pub list: Vec, + /// A refcount used to know when remove this meta + pub tcref: isize, +} + +crate::impl_serdeany!(AccountingIndexesMetadata); + +impl AsSlice for AccountingIndexesMetadata { + /// Convert to a slice + fn as_slice(&self) -> &[usize] { + self.list.as_slice() + } +} +impl AsMutSlice for AccountingIndexesMetadata { + /// Convert to a slice + fn as_mut_slice(&mut self) -> &mut [usize] { + self.list.as_mut_slice() + } +} + +impl HasRefCnt for AccountingIndexesMetadata { + fn refcnt(&self) -> isize { + self.tcref + } + + fn refcnt_mut(&mut self) -> &mut isize { + &mut self.tcref + } +} + +impl AccountingIndexesMetadata { + /// Creates a new [`struct@AccountingIndexesMetadata`]. + #[must_use] + pub fn new(list: Vec) -> Self { + Self { list, tcref: 0 } + } + + /// Creates a new [`struct@AccountingIndexesMetadata`] specifying the refcount. + #[must_use] + pub fn with_tcref(list: Vec, tcref: isize) -> Self { + Self { list, tcref } + } +} + +/// A state metadata holding a map of favoreds testcases for each map entry +#[derive(Debug, Serialize, Deserialize)] +pub struct TopAccountingMetadata { + /// map index -> corpus index + pub map: HashMap, + /// If changed sicne the previous add to the corpus + pub changed: bool, + /// The max accounting seen so far + pub max_accounting: Vec, +} + +crate::impl_serdeany!(TopAccountingMetadata); + +impl TopAccountingMetadata { + /// Creates a new [`struct@TopAccountingMetadata`] + #[must_use] + pub fn new(acc_len: usize) -> Self { + Self { + map: HashMap::default(), + changed: false, + max_accounting: vec![0; acc_len], + } + } +} + +/// A minimizer scheduler using coverage accounting +#[derive(Debug)] +pub struct CoverageAccountingCorpusScheduler<'a, CS, I, S> +where + CS: CorpusScheduler, + I: Input + HasLen, + S: HasCorpus + HasMetadata + HasRand, +{ + accounting_map: &'a [u32], + skip_non_favored_prob: u64, + inner: MinimizerCorpusScheduler, I, MapIndexesMetadata, S>, +} + +impl<'a, CS, I, S> CorpusScheduler for CoverageAccountingCorpusScheduler<'a, CS, I, S> +where + CS: CorpusScheduler, + I: Input + HasLen, + S: HasCorpus + HasMetadata + HasRand, +{ + fn on_add(&self, state: &mut S, idx: usize) -> Result<(), Error> { + self.update_accounting_score(state, idx)?; + self.inner.on_add(state, idx) + } + + fn on_replace(&self, state: &mut S, idx: usize, testcase: &Testcase) -> Result<(), Error> { + self.inner.on_replace(state, idx, testcase) + } + + fn on_remove( + &self, + state: &mut S, + idx: usize, + testcase: &Option>, + ) -> Result<(), Error> { + self.inner.on_remove(state, idx, testcase) + } + + fn next(&self, state: &mut S) -> Result { + if state + .metadata() + .get::() + .map_or(false, |x| x.changed) + { + self.accounting_cull(state)?; + } else { + self.inner.cull(state)?; + } + let mut idx = self.inner.base().next(state)?; + while { + let has = !state + .corpus() + .get(idx)? + .borrow() + .has_metadata::(); + has + } && state.rand_mut().below(100) < self.skip_non_favored_prob + { + idx = self.inner.base().next(state)?; + } + Ok(idx) + } +} + +impl<'a, CS, I, S> CoverageAccountingCorpusScheduler<'a, CS, I, S> +where + CS: CorpusScheduler, + I: Input + HasLen, + S: HasCorpus + HasMetadata + HasRand, +{ + /// Update the `Corpus` score + #[allow(clippy::unused_self)] + #[allow(clippy::cast_possible_wrap)] + pub fn update_accounting_score(&self, state: &mut S, idx: usize) -> Result<(), Error> { + let mut indexes = vec![]; + let mut new_favoreds = vec![]; + { + for idx in 0..self.accounting_map.len() { + if self.accounting_map[idx] == 0 { + continue; + } + indexes.push(idx); + + let mut equal_score = false; + { + let top_acc = state.metadata().get::().unwrap(); + + if let Some(old_idx) = top_acc.map.get(&idx) { + if top_acc.max_accounting[idx] > self.accounting_map[idx] { + continue; + } + + if top_acc.max_accounting[idx] >= self.accounting_map[idx] { + equal_score = true; + } + + let mut old = state.corpus().get(*old_idx)?.borrow_mut(); + let must_remove = { + let old_meta = old.metadata_mut().get_mut::().ok_or_else(|| { + Error::KeyNotFound(format!( + "AccountingIndexesMetadata, needed by CoverageAccountingCorpusScheduler, not found in testcase #{}", + old_idx + )) + })?; + *old_meta.refcnt_mut() -= 1; + old_meta.refcnt() <= 0 + }; + + if must_remove { + drop(old.metadata_mut().remove::()); + } + } + } + + let top_acc = state + .metadata_mut() + .get_mut::() + .unwrap(); + + // if its accounting is equal to others', it's not favored + if equal_score { + top_acc.map.remove(&idx); + } else if top_acc.max_accounting[idx] < self.accounting_map[idx] { + new_favoreds.push(idx); + + top_acc.max_accounting[idx] = self.accounting_map[idx]; + } + } + } + + if new_favoreds.is_empty() { + return Ok(()); + } + + state.corpus().get(idx)?.borrow_mut().metadata_mut().insert( + AccountingIndexesMetadata::with_tcref(indexes, new_favoreds.len() as isize), + ); + + let top_acc = state + .metadata_mut() + .get_mut::() + .unwrap(); + top_acc.changed = true; + + for elem in new_favoreds { + top_acc.map.insert(elem, idx); + } + + Ok(()) + } + + /// Cull the `Corpus` + #[allow(clippy::unused_self)] + pub fn accounting_cull(&self, state: &mut S) -> Result<(), Error> { + let top_rated = match state.metadata().get::() { + None => return Ok(()), + Some(val) => val, + }; + + for (_key, idx) in &top_rated.map { + let mut entry = state.corpus().get(*idx)?.borrow_mut(); + if entry.fuzzed() { + continue; + } + + entry.add_metadata(IsFavoredMetadata {}); + } + + Ok(()) + } + + /// Creates a new [`CoverageAccountingCorpusScheduler`] that wraps a `base` [`CorpusScheduler`] + /// and has a default probability to skip non-faved [`Testcase`]s of [`DEFAULT_SKIP_NON_FAVORED_PROB`]. + pub fn new(state: &mut S, base: CS, accounting_map: &'a [u32]) -> Self { + state.add_metadata(TopAccountingMetadata::new(accounting_map.len())); + Self { + accounting_map, + inner: MinimizerCorpusScheduler::new(base), + skip_non_favored_prob: DEFAULT_SKIP_NON_FAVORED_PROB, + } + } + + /// Creates a new [`CoverageAccountingCorpusScheduler`] that wraps a `base` [`CorpusScheduler`] + /// and has a non-default probability to skip non-faved [`Testcase`]s using (`skip_non_favored_prob`). + pub fn with_skip_prob( + state: &mut S, + base: CS, + skip_non_favored_prob: u64, + accounting_map: &'a [u32], + ) -> Self { + state.add_metadata(TopAccountingMetadata::new(accounting_map.len())); + Self { + accounting_map, + inner: MinimizerCorpusScheduler::with_skip_prob(base, skip_non_favored_prob), + skip_non_favored_prob, + } + } +} diff --git a/libafl/src/corpus/minimizer.rs b/libafl/src/corpus/minimizer.rs index 5db16136c1..d78ea9597e 100644 --- a/libafl/src/corpus/minimizer.rs +++ b/libafl/src/corpus/minimizer.rs @@ -257,6 +257,11 @@ where Ok(()) } + /// Get a reference to the base scheduler + pub fn base(&self) -> &CS { + &self.base + } + /// Creates a new [`MinimizerCorpusScheduler`] that wraps a `base` [`CorpusScheduler`] /// and has a default probability to skip non-faved [`Testcase`]s of [`DEFAULT_SKIP_NON_FAVORED_PROB`]. pub fn new(base: CS) -> Self { diff --git a/libafl/src/corpus/mod.rs b/libafl/src/corpus/mod.rs index a474c16480..7bb382ad50 100644 --- a/libafl/src/corpus/mod.rs +++ b/libafl/src/corpus/mod.rs @@ -19,6 +19,9 @@ pub use cached::CachedOnDiskCorpus; pub mod queue; pub use queue::QueueCorpusScheduler; +pub mod accounting; +pub use accounting::*; + pub mod minimizer; pub use minimizer::{ FavFactor, IndexesLenTimeMinimizerCorpusScheduler, IsFavoredMetadata, diff --git a/libafl/src/corpus/testcase.rs b/libafl/src/corpus/testcase.rs index de7c15b493..69aca9a2a6 100644 --- a/libafl/src/corpus/testcase.rs +++ b/libafl/src/corpus/testcase.rs @@ -13,7 +13,7 @@ use crate::{ }; /// An entry in the Testcase Corpus -#[derive(Default, Serialize, Deserialize, Clone, Debug)] +#[derive(Serialize, Deserialize, Clone, Debug)] #[serde(bound = "I: serde::de::DeserializeOwned")] pub struct Testcase where @@ -31,6 +31,8 @@ where cached_len: Option, /// Number of executions done at discovery time executions: usize, + /// If it has been fuzzed + fuzzed: bool, } impl HasMetadata for Testcase @@ -152,6 +154,18 @@ where &mut self.executions } + /// Get if it was fuzzed + #[inline] + pub fn fuzzed(&self) -> bool { + self.fuzzed + } + + /// Set if it was fuzzed + #[inline] + pub fn set_fuzzed(&mut self, fuzzed: bool) { + self.fuzzed = fuzzed; + } + /// Create a new Testcase instace given an input #[inline] pub fn new(input: T) -> Self @@ -160,11 +174,7 @@ where { let mut slf = Testcase { input: Some(input.into()), - filename: None, - metadata: SerdeAnyMap::new(), - exec_time: None, - cached_len: None, - executions: 0, + ..Testcase::default() }; slf.input.as_mut().unwrap().wrapped_as_testcase(); slf @@ -177,10 +187,7 @@ where Testcase { input: Some(input), filename: Some(filename), - metadata: SerdeAnyMap::new(), - exec_time: None, - cached_len: None, - executions: 0, + ..Testcase::default() } } @@ -190,18 +197,19 @@ where input.wrapped_as_testcase(); Testcase { input: Some(input), - filename: None, - metadata: SerdeAnyMap::new(), - exec_time: None, - cached_len: None, executions, + ..Testcase::default() } } +} - /// Create a new, empty, [`Testcase`]. - #[must_use] +impl Default for Testcase +where + I: Input, +{ + /// Create a new default Testcase #[inline] - pub fn default() -> Self { + fn default() -> Self { Testcase { input: None, filename: None, @@ -209,6 +217,7 @@ where exec_time: None, cached_len: None, executions: 0, + fuzzed: false, } } } diff --git a/libafl_cc/build.rs b/libafl_cc/build.rs index 55949a8213..0de90593c6 100644 --- a/libafl_cc/build.rs +++ b/libafl_cc/build.rs @@ -73,6 +73,7 @@ fn find_llvm_config() -> String { }) } +#[allow(clippy::too_many_lines)] fn main() { let out_dir = env::var_os("OUT_DIR").unwrap(); let out_dir = Path::new(&out_dir); @@ -80,6 +81,7 @@ fn main() { println!("cargo:rerun-if-env-changed=LLVM_CONFIG"); println!("cargo:rerun-if-env-changed=LIBAFL_EDGES_MAP_SIZE"); + println!("cargo:rerun-if-env-changed=LIBAFL_ACCOUNTING_MAP_SIZE"); let mut custom_flags = vec![]; @@ -91,6 +93,11 @@ fn main() { .expect("Could not parse LIBAFL_EDGES_MAP_SIZE"); custom_flags.push(format!("-DLIBAFL_EDGES_MAP_SIZE={}", edges_map_size)); + let acc_map_size: usize = option_env!("LIBAFL_ACCOUNTING_MAP_SIZE") + .map_or(Ok(65536), str::parse) + .expect("Could not parse LIBAFL_ACCOUNTING_MAP_SIZE"); + custom_flags.push(format!("-DLIBAFL_ACCOUNTING_MAP_SIZE={}", acc_map_size)); + let llvm_config = find_llvm_config(); if let Ok(output) = Command::new(&llvm_config).args(&["--bindir"]).output() { @@ -99,7 +106,6 @@ fn main() { .expect("Invalid llvm-config output") .trim(), ); - write!( clang_constants_file, "// These constants are autogenerated by build.rs @@ -111,10 +117,14 @@ fn main() { /// The size of the edges map pub const EDGES_MAP_SIZE: usize = {}; + + /// The size of the accounting maps + pub const ACCOUNTING_MAP_SIZE: usize = {}; ", llvm_bindir.join("clang"), llvm_bindir.join("clang++"), - edges_map_size + edges_map_size, + acc_map_size ) .expect("Could not write file"); @@ -126,6 +136,7 @@ fn main() { let output = Command::new(&llvm_config) .args(&["--ldflags"]) + .args(&["--libs"]) .output() .expect("Failed to execute llvm-config"); let ldflags = str::from_utf8(&output.stdout).expect("Invalid llvm-config output"); @@ -140,11 +151,13 @@ fn main() { ldflags.push("dynamic_lookup"); }; + println!("cargo:rerun-if-changed=src/common-llvm.h"); println!("cargo:rerun-if-changed=src/cmplog-routines-pass.cc"); println!("cargo:rerun-if-changed=src/afl-coverage-pass.cc"); println!("cargo:rerun-if-changed=src/autotokens-pass.cc"); + println!("cargo:rerun-if-changed=src/coverage-accounting-pass.cc"); - let _ = Command::new(llvm_bindir.join("clang++")) + assert!(Command::new(llvm_bindir.join("clang++")) .args(&cxxflags) .args(&custom_flags) .arg(src_dir.join("cmplog-routines-pass.cc")) @@ -152,9 +165,10 @@ fn main() { .args(&["-fPIC", "-shared", "-o"]) .arg(out_dir.join(format!("cmplog-routines-pass.{}", dll_extension()))) .status() - .expect("Failed to compile cmplog-routines-pass.cc"); + .expect("Failed to compile cmplog-routines-pass.cc") + .success()); - let _ = Command::new(llvm_bindir.join("clang++")) + assert!(Command::new(llvm_bindir.join("clang++")) .args(&cxxflags) .args(&custom_flags) .arg(src_dir.join("afl-coverage-pass.cc")) @@ -162,9 +176,10 @@ fn main() { .args(&["-fPIC", "-shared", "-o"]) .arg(out_dir.join(format!("afl-coverage-pass.{}", dll_extension()))) .status() - .expect("Failed to compile afl-coverage-pass.cc"); + .expect("Failed to compile afl-coverage-pass.cc") + .success()); - let _ = Command::new(llvm_bindir.join("clang++")) + assert!(Command::new(llvm_bindir.join("clang++")) .args(&cxxflags) .args(&custom_flags) .arg(src_dir.join("autotokens-pass.cc")) @@ -172,7 +187,19 @@ fn main() { .args(&["-fPIC", "-shared", "-o"]) .arg(out_dir.join(format!("autotokens-pass.{}", dll_extension()))) .status() - .expect("Failed to compile autotokens-pass.cc"); + .expect("Failed to compile autotokens-pass.cc") + .success()); + + assert!(Command::new(llvm_bindir.join("clang++")) + .args(&cxxflags) + .args(&custom_flags) + .arg(src_dir.join("coverage-accounting-pass.cc")) + .args(&ldflags) + .args(&["-fPIC", "-shared", "-o"]) + .arg(out_dir.join(format!("coverage-accounting-pass.{}", dll_extension()))) + .status() + .expect("Failed to compile coverage-accounting-pass.cc") + .success()); } else { write!( clang_constants_file, diff --git a/libafl_cc/src/afl-coverage-pass.cc b/libafl_cc/src/afl-coverage-pass.cc index c72873881b..9cd82c6fd4 100644 --- a/libafl_cc/src/afl-coverage-pass.cc +++ b/libafl_cc/src/afl-coverage-pass.cc @@ -26,33 +26,16 @@ */ -#include -#include +#include "common-llvm.h" + #include #include #include #include -#include "llvm/Config/llvm-config.h" -#if LLVM_VERSION_MAJOR == 3 && LLVM_VERSION_MINOR < 5 -typedef long double max_align_t; -#endif - -#if LLVM_VERSION_MAJOR >= 7 /* use new pass manager */ -//#define USE_NEW_PM 1 -#endif - #include "llvm/Support/CommandLine.h" #include "llvm/IR/IRBuilder.h" -#ifdef USE_NEW_PM -#include "llvm/Passes/PassPlugin.h" -#include "llvm/Passes/PassBuilder.h" -#include "llvm/IR/PassManager.h" -#else -#include "llvm/IR/LegacyPassManager.h" -#include "llvm/Transforms/IPO/PassManagerBuilder.h" -#endif #include "llvm/IR/BasicBlock.h" #include "llvm/IR/Module.h" #include "llvm/Support/Debug.h" @@ -77,8 +60,6 @@ typedef uint32_t prev_loc_t; #define MAP_SIZE LIBAFL_EDGES_MAP_SIZE -#define FATAL(...) do { fprintf(stderr, "FATAL: " __VA_ARGS__); exit(1); } while (0) - using namespace llvm; static cl::opt Debug("debug", cl::desc("Debug prints"), cl::init(false), cl::NotHidden); @@ -156,36 +137,6 @@ llvmGetPassPluginInfo() { char AFLCoverage::ID = 0; #endif -static uint32_t RandBelow(uint32_t max) { - return (uint32_t)rand() % (max +1); -} - -/* needed up to 3.9.0 */ -#if LLVM_VERSION_MAJOR == 3 && \ - (LLVM_VERSION_MINOR < 9 || \ - (LLVM_VERSION_MINOR == 9 && LLVM_VERSION_PATCH < 1)) -static uint64_t PowerOf2Ceil(unsigned in) { - - uint64_t in64 = in - 1; - in64 |= (in64 >> 1); - in64 |= (in64 >> 2); - in64 |= (in64 >> 4); - in64 |= (in64 >> 8); - in64 |= (in64 >> 16); - in64 |= (in64 >> 32); - return in64 + 1; - -} - -#endif - -/* #if LLVM_VERSION_STRING >= "4.0.1" */ -#if LLVM_VERSION_MAJOR > 4 || \ - (LLVM_VERSION_MAJOR == 4 && LLVM_VERSION_PATCH >= 1) - #define HAVE_VECTOR_INTRINSICS 1 -#endif - - #ifdef USE_NEW_PM PreservedAnalyses AFLCoverage::run(Module &M, ModuleAnalysisManager &MAM) { #else diff --git a/libafl_cc/src/clang.rs b/libafl_cc/src/clang.rs index fd30fda047..d8d499c62a 100644 --- a/libafl_cc/src/clang.rs +++ b/libafl_cc/src/clang.rs @@ -33,6 +33,8 @@ pub enum LLVMPasses { AFLCoverage, /// The Autotoken pass AutoTokens, + /// The Coverage Accouting (BB metric) pass + CoverageAccounting, } impl LLVMPasses { @@ -47,6 +49,8 @@ impl LLVMPasses { LLVMPasses::AutoTokens => { PathBuf::from(env!("OUT_DIR")).join(format!("autotokens-pass.{}", dll_extension())) } + LLVMPasses::CoverageAccounting => PathBuf::from(env!("OUT_DIR")) + .join(format!("coverage-accounting-pass.{}", dll_extension())), } } } diff --git a/libafl_cc/src/common-llvm.h b/libafl_cc/src/common-llvm.h new file mode 100644 index 0000000000..b2bc5fd206 --- /dev/null +++ b/libafl_cc/src/common-llvm.h @@ -0,0 +1,55 @@ +#ifndef LIBAFL_COMMON_LLVM_H +#define LIBAFL_COMMON_LLVM_H + +#include +#include + +#include "llvm/Config/llvm-config.h" +#if LLVM_VERSION_MAJOR == 3 && LLVM_VERSION_MINOR < 5 +typedef long double max_align_t; +#endif + +#if LLVM_VERSION_MAJOR >= 7 /* use new pass manager */ +//#define USE_NEW_PM 1 +#endif + +/* #if LLVM_VERSION_STRING >= "4.0.1" */ +#if LLVM_VERSION_MAJOR > 4 || \ + (LLVM_VERSION_MAJOR == 4 && LLVM_VERSION_PATCH >= 1) + #define HAVE_VECTOR_INTRINSICS 1 +#endif + +#ifdef USE_NEW_PM +#include "llvm/Passes/PassPlugin.h" +#include "llvm/Passes/PassBuilder.h" +#include "llvm/IR/PassManager.h" +#else +#include "llvm/IR/LegacyPassManager.h" +#include "llvm/Transforms/IPO/PassManagerBuilder.h" +#endif + +#define FATAL(...) do { fprintf(stderr, "FATAL: " __VA_ARGS__); exit(1); } while (0) + +static uint32_t RandBelow(uint32_t max) { + return (uint32_t)rand() % (max +1); +} + +/* needed up to 3.9.0 */ +#if LLVM_VERSION_MAJOR == 3 && \ + (LLVM_VERSION_MINOR < 9 || \ + (LLVM_VERSION_MINOR == 9 && LLVM_VERSION_PATCH < 1)) +static uint64_t PowerOf2Ceil(unsigned in) { + + uint64_t in64 = in - 1; + in64 |= (in64 >> 1); + in64 |= (in64 >> 2); + in64 |= (in64 >> 4); + in64 |= (in64 >> 8); + in64 |= (in64 >> 16); + in64 |= (in64 >> 32); + return in64 + 1; + +} +#endif + +#endif // LIBAFL_COMMON_LLVM_H diff --git a/libafl_cc/src/coverage-accounting-pass.cc b/libafl_cc/src/coverage-accounting-pass.cc new file mode 100644 index 0000000000..2d4ead9571 --- /dev/null +++ b/libafl_cc/src/coverage-accounting-pass.cc @@ -0,0 +1,272 @@ +/* + american fuzzy lop++ - LLVM-mode instrumentation pass + --------------------------------------------------- + + Written by Laszlo Szekeres , + Adrian Herrera , + Michal Zalewski + + LLVM integration design comes from Laszlo Szekeres. C bits copied-and-pasted + from afl-as.c are Michal's fault. + + NGRAM previous location coverage comes from Adrian Herrera. + + Copyright 2015, 2016 Google Inc. All rights reserved. + Copyright 2019-2020 AFLplusplus Project. All rights reserved. + + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at: + + http://www.apache.org/licenses/LICENSE-2.0 + + This library is plugged into LLVM when invoking clang through afl-clang-fast. + It tells the compiler to add code roughly equivalent to the bits discussed + in ../afl-as.h. + + */ + +#include "common-llvm.h" + +#include + +#include +#include +#include + +#include "llvm/Support/CommandLine.h" +#include "llvm/IR/IRBuilder.h" +#include "llvm/IR/BasicBlock.h" +#include "llvm/IR/Module.h" +#include "llvm/Support/Debug.h" +#include "llvm/Support/MathExtras.h" + +#if LLVM_VERSION_MAJOR > 3 || \ + (LLVM_VERSION_MAJOR == 3 && LLVM_VERSION_MINOR > 4) + #include "llvm/IR/DebugInfo.h" + #include "llvm/IR/CFG.h" +#else + #include "llvm/DebugInfo.h" + #include "llvm/Support/CFG.h" +#endif + +typedef uint32_t prev_loc_t; + +#define MAP_SIZE LIBAFL_ACCOUNTING_MAP_SIZE + +using namespace llvm; + +static cl::opt Debug("debug", cl::desc("Debug prints"), cl::init(false), cl::NotHidden); +static cl::opt InstRatio("inst_ratio", cl::desc("Instrumentation ratio in percentage"), cl::init(100), cl::NotHidden); +static cl::opt ThreadSafe("thread_safe", cl::desc("Use the thread safe instrumentation"), cl::init(false), cl::NotHidden); + +namespace { + +#ifdef USE_NEW_PM +class AFLCoverage : public PassInfoMixin { + public: + AFLCoverage() { +#else +class AFLCoverage : public ModulePass { + public: + static char ID; + AFLCoverage() : ModulePass(ID) { +#endif + + // initInstrumentList(); + + } + +#ifdef USE_NEW_PM + PreservedAnalyses run(Module &M, ModuleAnalysisManager &MAM); +#else + bool runOnModule(Module &M) override; +#endif + + protected: + uint32_t map_size = MAP_SIZE; + uint32_t function_minimum_size = 1; + +}; + +} // namespace + +#ifdef USE_NEW_PM +extern "C" ::llvm::PassPluginLibraryInfo LLVM_ATTRIBUTE_WEAK +llvmGetPassPluginInfo() { + return { + LLVM_PLUGIN_API_VERSION, "AFLCoverageAccounting", "v0.1", + /* lambda to insert our pass into the pass pipeline. */ + [](PassBuilder &PB) { +#if 1 + using OptimizationLevel = typename PassBuilder::OptimizationLevel; + PB.registerOptimizerLastEPCallback( + [](ModulePassManager &MPM, OptimizationLevel OL) { + MPM.addPass(AFLCoverage()); + } + ); +/* TODO LTO registration */ +#else + using PipelineElement = typename PassBuilder::PipelineElement; + PB.registerPipelineParsingCallback( + [](StringRef Name, ModulePassManager &MPM, ArrayRef) { + if ( Name == "AFLCoverageAccounting" ) { + MPM.addPass(AFLCoverage()); + return true; + } else { + return false; + } + } + ); +#endif + } + }; +} +#else + +char AFLCoverage::ID = 0; +#endif + +#ifdef USE_NEW_PM +PreservedAnalyses AFLCoverage::run(Module &M, ModuleAnalysisManager &MAM) { +#else +bool AFLCoverage::runOnModule(Module &M) { +#endif + + LLVMContext &C = M.getContext(); + + IntegerType *Int32Ty = IntegerType::getInt32Ty(C); + uint32_t rand_seed; + unsigned int cur_loc = 0; + +#ifdef USE_NEW_PM + auto PA = PreservedAnalyses::all(); +#endif + + /* Setup random() so we get Actually Random(TM) */ + rand_seed = time(NULL); + srand(rand_seed); + + /* Decide instrumentation ratio */ + + if (!InstRatio || InstRatio > 100) + FATAL("Bad value of the instrumentation ratio (must be between 1 and 100)"); + + /* Get globals for the SHM region and the previous location. Note that + __afl_acc_prev_loc is thread-local. */ + + GlobalVariable *AFLMemOpPtr = + new GlobalVariable(M, PointerType::get(Int32Ty, 0), false, + GlobalValue::ExternalLinkage, 0, "__afl_acc_memop_ptr"); + + GlobalVariable *AFLPrevLoc; + +#if defined(__ANDROID__) || defined(__HAIKU__) + AFLPrevLoc = new GlobalVariable( + M, Int32Ty, false, GlobalValue::ExternalLinkage, 0, "__afl_acc_prev_loc"); +#else + AFLPrevLoc = new GlobalVariable( + M, Int32Ty, false, GlobalValue::ExternalLinkage, 0, "__afl_acc_prev_loc", 0, + GlobalVariable::GeneralDynamicTLSModel, 0, false); +#endif + + /* Instrument all the things! */ + + int inst_blocks = 0; + // scanForDangerousFunctions(&M); + + for (auto &F : M) { + + int has_calls = 0; + if (Debug) + fprintf(stderr, "FUNCTION: %s (%zu)\n", F.getName().str().c_str(), + F.size()); + + // if (!isInInstrumentList(&F)) { continue; } + + if (F.size() < function_minimum_size) { continue; } + + std::list todo; + for (auto &BB : F) { + + BasicBlock::iterator IP = BB.getFirstInsertionPt(); + IRBuilder<> IRB(&(*IP)); + + if (RandBelow(100) >= InstRatio) continue; + + // Start with 1 to implicitly track edge coverage too + uint32_t MemCnt = 1; + + for (auto &I : BB) { + if (I.mayReadFromMemory() || I.mayWriteToMemory()) + ++MemCnt; + } + + /* Make up cur_loc */ + + cur_loc = RandBelow(map_size); + + ConstantInt *CurLoc = ConstantInt::get(Int32Ty, cur_loc); + + /* Load prev_loc */ + + LoadInst *PrevLoc = IRB.CreateLoad(AFLPrevLoc); + PrevLoc->setMetadata(M.getMDKindID("nosanitize"), MDNode::get(C, None)); + + /* Load SHM pointer */ + + LoadInst *MemReadPtr = IRB.CreateLoad(AFLMemOpPtr); + MemReadPtr->setMetadata(M.getMDKindID("nosanitize"), MDNode::get(C, None)); + Value *MemReadPtrIdx = IRB.CreateGEP(MemReadPtr, IRB.CreateXor(PrevLoc, CurLoc)); + + /* Update bitmap */ + + LoadInst *MemReadCount = IRB.CreateLoad(MemReadPtrIdx); + MemReadCount->setMetadata(M.getMDKindID("nosanitize"), MDNode::get(C, None)); + Value *MemReadIncr = IRB.CreateAdd(MemReadCount, ConstantInt::get(Int32Ty, MemCnt)); + IRB.CreateStore(MemReadIncr, MemReadPtrIdx) + ->setMetadata(M.getMDKindID("nosanitize"), MDNode::get(C, None)); + + /* Update prev_loc */ + + StoreInst * Store = IRB.CreateStore(ConstantInt::get(Int32Ty, cur_loc >> 1), + AFLPrevLoc); + Store->setMetadata(M.getMDKindID("nosanitize"), MDNode::get(C, None)); + + inst_blocks++; + + } + + } + + if (Debug) { + + if (!inst_blocks) + fprintf(stderr, "No instrumentation targets found.\n"); + else + fprintf(stderr, "Instrumented %d locations (ratio %u%%).\n", inst_blocks, (unsigned)InstRatio); + + } + +#ifdef USE_NEW_PM + return PA; +#else + return true; +#endif + +} + +#ifndef USE_NEW_PM +static void registerAFLPass(const PassManagerBuilder &, + legacy::PassManagerBase &PM) { + + PM.add(new AFLCoverage()); + +} + +static RegisterStandardPasses RegisterAFLPass( + PassManagerBuilder::EP_OptimizerLast, registerAFLPass); + +static RegisterStandardPasses RegisterAFLPass0( + PassManagerBuilder::EP_EnabledOnOptLevel0, registerAFLPass); +#endif diff --git a/libafl_targets/build.rs b/libafl_targets/build.rs index ac2a192cdd..9cf05a6df0 100644 --- a/libafl_targets/build.rs +++ b/libafl_targets/build.rs @@ -23,6 +23,9 @@ fn main() { let cmplog_map_h: usize = option_env!("LIBAFL_CMPLOG_MAP_H") .map_or(Ok(32), str::parse) .expect("Could not parse LIBAFL_CMPLOG_MAP_H"); + let acc_map_size: usize = option_env!("LIBAFL_ACCOUNTING_MAP_SIZE") + .map_or(Ok(65536), str::parse) + .expect("Could not parse LIBAFL_ACCOUNTING_MAP_SIZE"); write!( constants_file, @@ -36,8 +39,10 @@ fn main() { pub const CMPLOG_MAP_W: usize = {}; /// The height of the `CmpLog` map pub const CMPLOG_MAP_H: usize = {}; + /// The size of the accounting maps + pub const ACCOUNTING_MAP_SIZE: usize = {}; ", - edges_map_size, cmp_map_size, cmplog_map_w, cmplog_map_h + edges_map_size, cmp_map_size, cmplog_map_w, cmplog_map_h, acc_map_size ) .expect("Could not write file"); @@ -45,6 +50,7 @@ fn main() { println!("cargo:rerun-if-env-changed=LIBAFL_CMP_MAP_SIZE"); println!("cargo:rerun-if-env-changed=LIBAFL_CMPLOG_MAP_W"); println!("cargo:rerun-if-env-changed=LIBAFL_CMPLOG_MAP_H"); + println!("cargo:rerun-if-env-changed=LIBAFL_ACCOUNTING_MAP_SIZE"); //std::env::set_var("CC", "clang"); //std::env::set_var("CXX", "clang++"); @@ -95,6 +101,7 @@ fn main() { cc::Build::new() .file(src_dir.join("coverage.c")) .define("EDGES_MAP_SIZE", Some(&*format!("{}", edges_map_size))) + .define("ACCOUNTING_MAP_SIZE", Some(&*format!("{}", acc_map_size))) .compile("coverage"); println!("cargo:rerun-if-changed=src/cmplog.h"); diff --git a/libafl_targets/src/coverage.c b/libafl_targets/src/coverage.c index cf9dbd6616..9d3149b939 100644 --- a/libafl_targets/src/coverage.c +++ b/libafl_targets/src/coverage.c @@ -9,9 +9,10 @@ typedef uint32_t prev_loc_t; #define CTX_MAX_K 32U extern uint8_t __afl_area_ptr_local[EDGES_MAP_SIZE]; - uint8_t* __afl_area_ptr = __afl_area_ptr_local; +extern uint32_t __afl_acc_memop_ptr_local[ACCOUNTING_MAP_SIZE]; +uint32_t* __afl_acc_memop_ptr = __afl_acc_memop_ptr_local; // Weak symbols, LLVM Passes overwrites them if we really use it #ifdef __linux__ @@ -23,9 +24,8 @@ uint8_t* __token_start = &__start_libafl_token; uint8_t* __token_stop = &__stop_libafl_token; #endif - - //#if defined(__ANDROID__) || defined(__HAIKU__) MAYBE_THREAD_LOCAL prev_loc_t __afl_prev_loc[NGRAM_SIZE_MAX]; MAYBE_THREAD_LOCAL prev_loc_t __afl_prev_caller[CTX_MAX_K]; MAYBE_THREAD_LOCAL uint32_t __afl_prev_ctx; +MAYBE_THREAD_LOCAL prev_loc_t __afl_acc_prev_loc; diff --git a/libafl_targets/src/coverage.rs b/libafl_targets/src/coverage.rs index 76e3fd28d6..947eaf8e1a 100644 --- a/libafl_targets/src/coverage.rs +++ b/libafl_targets/src/coverage.rs @@ -1,6 +1,6 @@ //! Coverage maps as static mut array -use crate::EDGES_MAP_SIZE; +use crate::{ACCOUNTING_MAP_SIZE, EDGES_MAP_SIZE}; #[cfg(target_os = "linux")] use libafl::{mutators::Tokens, Error}; @@ -9,6 +9,11 @@ use libafl::{mutators::Tokens, Error}; pub static mut __afl_area_ptr_local: [u8; EDGES_MAP_SIZE] = [0; EDGES_MAP_SIZE]; pub use __afl_area_ptr_local as EDGES_MAP; +/// The map for accounting mem writes. +#[no_mangle] +pub static mut __afl_acc_memop_ptr_local: [u32; ACCOUNTING_MAP_SIZE] = [0; ACCOUNTING_MAP_SIZE]; +pub use __afl_acc_memop_ptr_local as ACCOUNTING_MEMOP_MAP; + /// The max count of edges tracked. pub static mut MAX_EDGES_NUM: usize = 0; @@ -16,6 +21,9 @@ extern "C" { /// The area pointer points to the edges map. pub static mut __afl_area_ptr: *mut u8; + /// The area pointer points to the accounting mem operations map. + pub static mut __afl_acc_memop_ptr: *mut u32; + /// Start of libafl token section #[cfg(target_os = "linux")] pub static __token_start: *const u8; @@ -24,6 +32,7 @@ extern "C" { #[cfg(target_os = "linux")] pub static __token_stop: *const u8; } +pub use __afl_acc_memop_ptr as ACCOUNTING_MEMOP_MAP_PTR; pub use __afl_area_ptr as EDGES_MAP_PTR; /// Return Tokens from the compile-time token section