diff --git a/.github/workflows/build_and_test.yml b/.github/workflows/build_and_test.yml index b204f1af10..1a2cfcd7d5 100644 --- a/.github/workflows/build_and_test.yml +++ b/.github/workflows/build_and_test.yml @@ -63,10 +63,10 @@ jobs: # cargo-hack's --feature-powerset would be nice here but libafl has a too many knobs - name: Check each feature # Skipping python as it has to be built with the `maturin` tool - run: cargo hack check --feature-powerset --depth=2 --exclude-features=agpl,nautilus,python,sancov_pcguard_edges,sancov_pcguard_edges_ptr --no-dev-deps + run: cargo hack check --feature-powerset --depth=2 --exclude-features=agpl,nautilus,python,sancov_pcguard_edges --no-dev-deps # pcguard edges and pcguard hitcounts are not compatible and we need to build them seperately - name: Check pcguard edges - run: cargo check --features=sancov_pcguard_edges,sancov_pcguard_edges_ptr + run: cargo check --features=sancov_pcguard_edges - name: Build examples run: cargo build --examples --verbose - uses: actions/checkout@v2 diff --git a/fuzzers/libafl_atheris/Cargo.toml b/fuzzers/libafl_atheris/Cargo.toml index 864409c10a..cada5b5524 100644 --- a/fuzzers/libafl_atheris/Cargo.toml +++ b/fuzzers/libafl_atheris/Cargo.toml @@ -21,7 +21,7 @@ num_cpus = "1.0" [dependencies] libafl = { path = "../../libafl/" } -libafl_targets = { path = "../../libafl_targets/", features = ["sancov_pcguard_hitcounts_ptr", "sancov_cmplog", "libfuzzer"] } +libafl_targets = { path = "../../libafl_targets/", features = ["pointer_maps", "sancov_cmplog", "libfuzzer"] } clap = { version = "3.0.0-beta.4", features = ["default", "yaml"] } [lib] diff --git a/fuzzers/libfuzzer_libpng_ctx/.gitignore b/fuzzers/libfuzzer_libpng_ctx/.gitignore new file mode 100644 index 0000000000..a977a2ca5b --- /dev/null +++ b/fuzzers/libfuzzer_libpng_ctx/.gitignore @@ -0,0 +1 @@ +libpng-* \ No newline at end of file diff --git a/fuzzers/libfuzzer_libpng_ctx/Cargo.toml b/fuzzers/libfuzzer_libpng_ctx/Cargo.toml new file mode 100644 index 0000000000..315a34c555 --- /dev/null +++ b/fuzzers/libfuzzer_libpng_ctx/Cargo.toml @@ -0,0 +1,31 @@ +[package] +name = "libfuzzer_libpng_launcher" +version = "0.6.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", "anymap_debug", "derive", "llmp_compression", "introspection"] } +libafl_targets = { path = "../../libafl_targets/", features = ["libfuzzer"] } +# TODO Include it only when building cc +libafl_cc = { path = "../../libafl_cc/" } +clap = { version = "3.0.0-beta.2", features = ["yaml"] } + +[lib] +name = "libfuzzer_libpng" +crate-type = ["staticlib"] diff --git a/fuzzers/libfuzzer_libpng_ctx/Makefile b/fuzzers/libfuzzer_libpng_ctx/Makefile new file mode 100644 index 0000000000..ca0a514241 --- /dev/null +++ b/fuzzers/libfuzzer_libpng_ctx/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 & + +short_test: all + rm -rf libafl_unix_shmem_server || true + timeout 10s ./$(FUZZER_NAME) --cores 0 & + +test: all + timeout 60s ./$(FUZZER_NAME) --cores 0 & diff --git a/fuzzers/libfuzzer_libpng_ctx/README.md b/fuzzers/libfuzzer_libpng_ctx/README.md new file mode 100644 index 0000000000..b96b0e851c --- /dev/null +++ b/fuzzers/libfuzzer_libpng_ctx/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_ctx/corpus/not_kitty.png b/fuzzers/libfuzzer_libpng_ctx/corpus/not_kitty.png new file mode 100644 index 0000000000..eff7c1707b Binary files /dev/null and b/fuzzers/libfuzzer_libpng_ctx/corpus/not_kitty.png differ diff --git a/fuzzers/libfuzzer_libpng_ctx/corpus/not_kitty_alpha.png b/fuzzers/libfuzzer_libpng_ctx/corpus/not_kitty_alpha.png new file mode 100644 index 0000000000..2fb8da2c8f Binary files /dev/null and b/fuzzers/libfuzzer_libpng_ctx/corpus/not_kitty_alpha.png differ diff --git a/fuzzers/libfuzzer_libpng_ctx/corpus/not_kitty_gamma.png b/fuzzers/libfuzzer_libpng_ctx/corpus/not_kitty_gamma.png new file mode 100644 index 0000000000..939d9d29a9 Binary files /dev/null and b/fuzzers/libfuzzer_libpng_ctx/corpus/not_kitty_gamma.png differ diff --git a/fuzzers/libfuzzer_libpng_ctx/corpus/not_kitty_icc.png b/fuzzers/libfuzzer_libpng_ctx/corpus/not_kitty_icc.png new file mode 100644 index 0000000000..f0c7804d99 Binary files /dev/null and b/fuzzers/libfuzzer_libpng_ctx/corpus/not_kitty_icc.png differ diff --git a/fuzzers/libfuzzer_libpng_ctx/harness.cc b/fuzzers/libfuzzer_libpng_ctx/harness.cc new file mode 100644 index 0000000000..65faff685d --- /dev/null +++ b/fuzzers/libfuzzer_libpng_ctx/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_ctx/src/bin/libafl_cc.rs b/fuzzers/libfuzzer_libpng_ctx/src/bin/libafl_cc.rs new file mode 100644 index 0000000000..b3fa85f501 --- /dev/null +++ b/fuzzers/libfuzzer_libpng_ctx/src/bin/libafl_cc.rs @@ -0,0 +1,37 @@ +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") + .add_pass(LLVMPasses::AFLCoverage) + .add_arg("-mllvm") + .add_arg("-ctx") // Context sensitive coverage + .link_staticlib(&dir, "libfuzzer_libpng") + .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_ctx/src/bin/libafl_cxx.rs b/fuzzers/libfuzzer_libpng_ctx/src/bin/libafl_cxx.rs new file mode 100644 index 0000000000..ce786239b0 --- /dev/null +++ b/fuzzers/libfuzzer_libpng_ctx/src/bin/libafl_cxx.rs @@ -0,0 +1,5 @@ +pub mod libafl_cc; + +fn main() { + libafl_cc::main() +} diff --git a/fuzzers/libfuzzer_libpng_ctx/src/clap-config.yaml b/fuzzers/libfuzzer_libpng_ctx/src/clap-config.yaml new file mode 100644 index 0000000000..387fe0fb54 --- /dev/null +++ b/fuzzers/libfuzzer_libpng_ctx/src/clap-config.yaml @@ -0,0 +1,12 @@ +name: libfuzzer libpng +version: "0.1.0" +author: "Andrea Fioraldi , Dominik Maier " +about: A clone of libfuzzer using libafl for a libpng harness. +args: + - cores: + short: c + long: cores + about: "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." + value_name: CORES + required: true + takes_value: true diff --git a/fuzzers/libfuzzer_libpng_ctx/src/lib.rs b/fuzzers/libfuzzer_libpng_ctx/src/lib.rs new file mode 100644 index 0000000000..68245ca092 --- /dev/null +++ b/fuzzers/libfuzzer_libpng_ctx/src/lib.rs @@ -0,0 +1,183 @@ +//! 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 clap::{load_yaml, App}; +use core::time::Duration; +use std::{env, path::PathBuf}; + +use libafl::{ + bolts::{ + current_nanos, + launcher::Launcher, + os::parse_core_bind_arg, + rands::StdRand, + shmem::{ShMemProvider, StdShMemProvider}, + tuples::{tuple_list, Merge}, + }, + corpus::{ + Corpus, InMemoryCorpus, IndexesLenTimeMinimizerCorpusScheduler, 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::{edges_map_from_ptr, libfuzzer_initialize, libfuzzer_test_one_input}; + +/// 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 yaml = load_yaml!("clap-config.yaml"); + let matches = App::from(yaml).get_matches(); + + let broker_port = 1337; + + let cores = parse_core_bind_arg(matches.value_of("cores").unwrap()) + .expect("No valid core count given!"); + + 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| { + let corpus_dirs = &[PathBuf::from("./corpus")]; + let objective_dir = PathBuf::from("./crashes"); + + // Create an observation channel using the coverage map + let edges = edges_map_from_ptr(); + 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(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!("We're a client, let's fuzz :)"); + + // Create a PNG dictionary if not existing + if state.metadata().get::().is_none() { + state.add_metadata(Tokens::new(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 = 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 + }; + + // 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 + Duration::new(10, 0), + ); + + // 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, corpus_dirs) + .unwrap_or_else(|_| panic!("Failed to load initial corpus at {:?}", corpus_dirs)); + 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) + .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/libafl_cc/build.rs b/libafl_cc/build.rs index 3d3f1fdbe7..a9844125d2 100644 --- a/libafl_cc/build.rs +++ b/libafl_cc/build.rs @@ -62,9 +62,17 @@ fn main() { let out_dir = Path::new(&out_dir); let src_dir = Path::new("src"); + let mut custom_flags = vec![]; + let dest_path = Path::new(&out_dir).join("clang_constants.rs"); let mut clang_constants_file = File::create(&dest_path).expect("Could not create file"); + let edges_map_size: usize = option_env!("LIBAFL_EDGES_MAP_SIZE") + .map_or(Ok(65536), str::parse) + .expect("Could not parse LIBAFL_EDGES_MAP_SIZE"); + println!("cargo:rerun-if-env-changed=LIBAFL_EDGES_MAP_SIZE"); + custom_flags.push(format!("-DLIBAFL_EDGES_MAP_SIZE={}", edges_map_size)); + let llvm_config = find_llvm_config(); if let Ok(output) = Command::new(&llvm_config).args(&["--bindir"]).output() { @@ -80,14 +88,16 @@ fn main() { pub const CLANG_PATH: &str = {:?}; pub const CLANGXX_PATH: &str = {:?}; + + /// The size of the edges map + pub const EDGES_MAP_SIZE: usize = {}; ", llvm_bindir.join("clang"), - llvm_bindir.join("clang++") + llvm_bindir.join("clang++"), + edges_map_size ) .expect("Could not write file"); - println!("cargo:rerun-if-changed=src/cmplog-routines-pass.cc"); - let output = Command::new(&llvm_config) .args(&["--cxxflags"]) .output() @@ -110,14 +120,28 @@ fn main() { ldflags.push("dynamic_lookup"); }; + println!("cargo:rerun-if-changed=src/cmplog-routines-pass.cc"); + println!("cargo:rerun-if-changed=src/afl-coverage-pass.cc"); + let _ = Command::new(llvm_bindir.join("clang++")) .args(&cxxflags) + .args(&custom_flags) .arg(src_dir.join("cmplog-routines-pass.cc")) .args(&ldflags) .args(&["-fPIC", "-shared", "-o"]) .arg(out_dir.join(format!("cmplog-routines-pass.{}", dll_extension()))) .status() .expect("Failed to compile cmplog-routines-pass.cc"); + + let _ = Command::new(llvm_bindir.join("clang++")) + .args(&cxxflags) + .args(&custom_flags) + .arg(src_dir.join("afl-coverage-pass.cc")) + .args(&ldflags) + .args(&["-fPIC", "-shared", "-o"]) + .arg(out_dir.join(format!("afl-coverage-pass.{}", dll_extension()))) + .status() + .expect("Failed to compile afl-coverage-pass.cc"); } else { write!( &mut clang_constants_file, diff --git a/libafl_cc/src/afl-coverage-pass.cc b/libafl_cc/src/afl-coverage-pass.cc new file mode 100644 index 0000000000..c72873881b --- /dev/null +++ b/libafl_cc/src/afl-coverage-pass.cc @@ -0,0 +1,842 @@ +/* + 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 +#include +#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" +#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; + +/* Maximum ngram size */ +#define NGRAM_SIZE_MAX 16U + +/* Maximum K for top-K context sensitivity */ +#define CTX_MAX_K 32U + +#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); +static cl::opt InstRatio("inst_ratio", cl::desc("Instrumentation ratio in percentage"), cl::init(100), cl::NotHidden); +static cl::opt NotZero("not_zero", cl::desc("Never hit 0 again in the hitcount"), cl::init(true), cl::NotHidden); +static cl::opt Ngram("ngram", cl::desc("Size of the Ngram instrumentation (0 to disable)"), cl::init(0), cl::NotHidden); +static cl::opt CtxK("ctx_k", cl::desc("Size of the context for K-Ctx context sensitivity (0 to disable)"), cl::init(0), cl::NotHidden); +static cl::opt Ctx("ctx", cl::desc("Enable full context sensitive coverage"), cl::init(false), 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, "AFLCoverage", "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 == "AFLCoverage" ) { + MPM.addPass(AFLCoverage()); + return true; + } else { + return false; + } + } + ); +#endif + } + }; +} +#else + +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 +bool AFLCoverage::runOnModule(Module &M) { +#endif + + LLVMContext &C = M.getContext(); + + IntegerType *Int8Ty = IntegerType::getInt8Ty(C); + IntegerType *Int32Ty = IntegerType::getInt32Ty(C); +#ifdef HAVE_VECTOR_INTRINSICS + IntegerType *IntLocTy = + IntegerType::getIntNTy(C, sizeof(prev_loc_t) * CHAR_BIT); +#endif + 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); + + /* + char *ptr; + if ((ptr = getenv("AFL_MAP_SIZE")) || (ptr = getenv("AFL_MAPSIZE"))) { + + map_size = atoi(ptr); + if (map_size < 8 || map_size > (1 << 29)) + FATAL("illegal AFL_MAP_SIZE %u, must be between 2^3 and 2^30", + map_size); if (map_size % 8) map_size = (((map_size >> 3) + 1) << 3); + + } + + */ + + /* Decide instrumentation ratio */ + + if (!InstRatio || InstRatio > 100) + FATAL("Bad value of the instrumentation ratio (must be between 1 and 100)"); + + unsigned PrevLocSize = 0; + unsigned PrevCallerSize = 0; + + bool instrument_ctx = Ctx || CtxK > 0; + bool instrument_caller = false; + +#ifdef HAVE_VECTOR_INTRINSICS + /* Decide previous location vector size (must be a power of two) */ + VectorType *PrevLocTy = NULL; + + if (Ngram && (Ngram < 2 || Ngram > NGRAM_SIZE_MAX)) + FATAL( + "Bad value of the Ngram size (must be between 2 and NGRAM_SIZE_MAX " + "(%u))", + NGRAM_SIZE_MAX); + + if (Ngram) + PrevLocSize = Ngram - 1; + else + PrevLocSize = 1; + + /* Decide K-Ctx vector size (must be a power of two) */ + VectorType *PrevCallerTy = NULL; + + if (CtxK > CTX_MAX_K) + FATAL("Bad value of K for K-context sensitivity (must be between 1 and CTX_MAX_K (%u))", + CTX_MAX_K); + + if (CtxK == 1) { + + CtxK = 0; + instrument_ctx = true; + instrument_caller = true; // Enable CALLER instead + + } + + if (CtxK) { + + PrevCallerSize = CtxK; + instrument_ctx = true; + + } + +#else + if (Ngram) + #ifndef LLVM_VERSION_PATCH + FATAL( + "Sorry, NGRAM branch coverage is not supported with llvm version " + "%d.%d.%d!", + LLVM_VERSION_MAJOR, LLVM_VERSION_MINOR, 0); + #else + FATAL( + "Sorry, NGRAM branch coverage is not supported with llvm version " + "%d.%d.%d!", + LLVM_VERSION_MAJOR, LLVM_VERSION_MINOR, LLVM_VERSION_PATCH); + #endif + if (CtxK) + #ifndef LLVM_VERSION_PATCH + FATAL( + "Sorry, K-CTX branch coverage is not supported with llvm version " + "%d.%d.%d!", + LLVM_VERSION_MAJOR, LLVM_VERSION_MINOR, 0); + #else + FATAL( + "Sorry, K-CTX branch coverage is not supported with llvm version " + "%d.%d.%d!", + LLVM_VERSION_MAJOR, LLVM_VERSION_MINOR, LLVM_VERSION_PATCH); + #endif + PrevLocSize = 1; +#endif + +#ifdef HAVE_VECTOR_INTRINSICS + int PrevLocVecSize = PowerOf2Ceil(PrevLocSize); + if (Ngram) + PrevLocTy = VectorType::get(IntLocTy, PrevLocVecSize + #if LLVM_VERSION_MAJOR >= 12 + , + false + #endif + ); +#endif + +#ifdef HAVE_VECTOR_INTRINSICS + int PrevCallerVecSize = PowerOf2Ceil(PrevCallerSize); + if (CtxK) + PrevCallerTy = VectorType::get(IntLocTy, PrevCallerVecSize + #if LLVM_VERSION_MAJOR >= 12 + , + false + #endif + ); +#endif + + /* Get globals for the SHM region and the previous location. Note that + __afl_prev_loc is thread-local. */ + + GlobalVariable *AFLMapPtr = + new GlobalVariable(M, PointerType::get(Int8Ty, 0), false, + GlobalValue::ExternalLinkage, 0, "__afl_area_ptr"); + GlobalVariable *AFLPrevLoc; + GlobalVariable *AFLPrevCaller; + GlobalVariable *AFLContext = NULL; + + if (Ctx || instrument_caller) +#if defined(__ANDROID__) || defined(__HAIKU__) + AFLContext = new GlobalVariable( + M, Int32Ty, false, GlobalValue::ExternalLinkage, 0, "__afl_prev_ctx"); +#else + AFLContext = new GlobalVariable( + M, Int32Ty, false, GlobalValue::ExternalLinkage, 0, "__afl_prev_ctx", 0, + GlobalVariable::GeneralDynamicTLSModel, 0, false); +#endif + +#ifdef HAVE_VECTOR_INTRINSICS + if (Ngram) + #if defined(__ANDROID__) || defined(__HAIKU__) + AFLPrevLoc = new GlobalVariable( + M, PrevLocTy, /* isConstant */ false, GlobalValue::ExternalLinkage, + /* Initializer */ nullptr, "__afl_prev_loc"); + #else + AFLPrevLoc = new GlobalVariable( + M, PrevLocTy, /* isConstant */ false, GlobalValue::ExternalLinkage, + /* Initializer */ nullptr, "__afl_prev_loc", + /* InsertBefore */ nullptr, GlobalVariable::GeneralDynamicTLSModel, + /* AddressSpace */ 0, /* IsExternallyInitialized */ false); + #endif + else +#endif +#if defined(__ANDROID__) || defined(__HAIKU__) + AFLPrevLoc = new GlobalVariable( + M, Int32Ty, false, GlobalValue::ExternalLinkage, 0, "__afl_prev_loc"); +#else + AFLPrevLoc = new GlobalVariable( + M, Int32Ty, false, GlobalValue::ExternalLinkage, 0, "__afl_prev_loc", 0, + GlobalVariable::GeneralDynamicTLSModel, 0, false); +#endif + +#ifdef HAVE_VECTOR_INTRINSICS + if (CtxK) + #if defined(__ANDROID__) || defined(__HAIKU__) + AFLPrevCaller = new GlobalVariable( + M, PrevCallerTy, /* isConstant */ false, GlobalValue::ExternalLinkage, + /* Initializer */ nullptr, "__afl_prev_caller"); + #else + AFLPrevCaller = new GlobalVariable( + M, PrevCallerTy, /* isConstant */ false, GlobalValue::ExternalLinkage, + /* Initializer */ nullptr, "__afl_prev_caller", + /* InsertBefore */ nullptr, GlobalVariable::GeneralDynamicTLSModel, + /* AddressSpace */ 0, /* IsExternallyInitialized */ false); + #endif + else +#endif +#if defined(__ANDROID__) || defined(__HAIKU__) + AFLPrevCaller = + new GlobalVariable(M, Int32Ty, false, GlobalValue::ExternalLinkage, 0, + "__afl_prev_caller"); +#else + AFLPrevCaller = new GlobalVariable( + M, Int32Ty, false, GlobalValue::ExternalLinkage, 0, "__afl_prev_caller", + 0, GlobalVariable::GeneralDynamicTLSModel, 0, false); +#endif + +#ifdef HAVE_VECTOR_INTRINSICS + /* Create the vector shuffle mask for updating the previous block history. + Note that the first element of the vector will store cur_loc, so just set + it to undef to allow the optimizer to do its thing. */ + + SmallVector PrevLocShuffle = {UndefValue::get(Int32Ty)}; + + for (unsigned I = 0; I < PrevLocSize - 1; ++I) + PrevLocShuffle.push_back(ConstantInt::get(Int32Ty, I)); + + for (int I = PrevLocSize; I < PrevLocVecSize; ++I) + PrevLocShuffle.push_back(ConstantInt::get(Int32Ty, PrevLocSize)); + + Constant *PrevLocShuffleMask = ConstantVector::get(PrevLocShuffle); + + Constant * PrevCallerShuffleMask = NULL; + SmallVector PrevCallerShuffle = {UndefValue::get(Int32Ty)}; + + if (CtxK) { + + for (unsigned I = 0; I < PrevCallerSize - 1; ++I) + PrevCallerShuffle.push_back(ConstantInt::get(Int32Ty, I)); + + for (int I = PrevCallerSize; I < PrevCallerVecSize; ++I) + PrevCallerShuffle.push_back(ConstantInt::get(Int32Ty, PrevCallerSize)); + + PrevCallerShuffleMask = ConstantVector::get(PrevCallerShuffle); + + } + +#endif + + // other constants we need + ConstantInt *One = ConstantInt::get(Int8Ty, 1); + + Value * PrevCtx = NULL; // CTX sensitive coverage + LoadInst *PrevCaller = NULL; // K-CTX coverage + + /* 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)); + + // Context sensitive coverage + if (instrument_ctx && &BB == &F.getEntryBlock()) { + +#ifdef HAVE_VECTOR_INTRINSICS + if (CtxK) { + + PrevCaller = IRB.CreateLoad(AFLPrevCaller); + PrevCaller->setMetadata(M.getMDKindID("nosanitize"), + MDNode::get(C, None)); + PrevCtx = + IRB.CreateZExt(IRB.CreateXorReduce(PrevCaller), IRB.getInt32Ty()); + + } else + +#endif + { + + // load the context ID of the previous function and write to to a + // local variable on the stack + LoadInst *PrevCtxLoad = IRB.CreateLoad(AFLContext); + PrevCtxLoad->setMetadata(M.getMDKindID("nosanitize"), + MDNode::get(C, None)); + PrevCtx = PrevCtxLoad; + + } + + // does the function have calls? and is any of the calls larger than one + // basic block? + for (auto &BB_2 : F) { + + if (has_calls) break; + for (auto &IN : BB_2) { + + CallInst *callInst = nullptr; + if ((callInst = dyn_cast(&IN))) { + + Function *Callee = callInst->getCalledFunction(); + if (!Callee || Callee->size() < function_minimum_size) + continue; + else { + + has_calls = 1; + break; + + } + + } + + } + + } + + // if yes we store a context ID for this function in the global var + if (has_calls) { + + Value *NewCtx = ConstantInt::get(Int32Ty, RandBelow(map_size)); +#ifdef HAVE_VECTOR_INTRINSICS + if (CtxK) { + + Value *ShuffledPrevCaller = IRB.CreateShuffleVector( + PrevCaller, UndefValue::get(PrevCallerTy), + PrevCallerShuffleMask); + Value *UpdatedPrevCaller = IRB.CreateInsertElement( + ShuffledPrevCaller, NewCtx, (uint64_t)0); + + StoreInst *Store = + IRB.CreateStore(UpdatedPrevCaller, AFLPrevCaller); + Store->setMetadata(M.getMDKindID("nosanitize"), + MDNode::get(C, None)); + + } else + +#endif + { + + if (Ctx) NewCtx = IRB.CreateXor(PrevCtx, NewCtx); + StoreInst *StoreCtx = IRB.CreateStore(NewCtx, AFLContext); + StoreCtx->setMetadata(M.getMDKindID("nosanitize"), + MDNode::get(C, None)); + + } + + } + + } + + if (RandBelow(100) >= InstRatio) continue; + + /* Make up cur_loc */ + + // cur_loc++; + cur_loc = RandBelow(map_size); + +/* There is a problem with Ubuntu 18.04 and llvm 6.0 (see issue #63). + The inline function successors() is not inlined and also not found at runtime + :-( As I am unable to detect Ubuntu18.04 heree, the next best thing is to + disable this optional optimization for LLVM 6.0.0 and Linux */ +#if !(LLVM_VERSION_MAJOR == 6 && LLVM_VERSION_MINOR == 0) || !defined __linux__ + // only instrument if this basic block is the destination of a previous + // basic block that has multiple successors + // this gets rid of ~5-10% of instrumentations that are unnecessary + // result: a little more speed and less map pollution + int more_than_one = -1; + // fprintf(stderr, "BB %u: ", cur_loc); + for (pred_iterator PI = pred_begin(&BB), E = pred_end(&BB); PI != E; + ++PI) { + + BasicBlock *Pred = *PI; + + int count = 0; + if (more_than_one == -1) more_than_one = 0; + // fprintf(stderr, " %p=>", Pred); + + for (succ_iterator SI = succ_begin(Pred), E = succ_end(Pred); SI != E; + ++SI) { + + BasicBlock *Succ = *SI; + + // if (count > 0) + // fprintf(stderr, "|"); + if (Succ != NULL) count++; + // fprintf(stderr, "%p", Succ); + + } + + if (count > 1) more_than_one = 1; + + } + + // fprintf(stderr, " == %d\n", more_than_one); + if (F.size() > 1 && more_than_one != 1) { + + // in CTX mode we have to restore the original context for the caller - + // she might be calling other functions which need the correct CTX + if (instrument_ctx && has_calls) { + + Instruction *Inst = BB.getTerminator(); + if (isa(Inst) || isa(Inst)) { + + IRBuilder<> Post_IRB(Inst); + + StoreInst *RestoreCtx; + #ifdef HAVE_VECTOR_INTRINSICS + if (CtxK) + RestoreCtx = IRB.CreateStore(PrevCaller, AFLPrevCaller); + else + #endif + RestoreCtx = Post_IRB.CreateStore(PrevCtx, AFLContext); + RestoreCtx->setMetadata(M.getMDKindID("nosanitize"), + MDNode::get(C, None)); + + } + + } + + continue; + + } + +#endif + + ConstantInt *CurLoc; + +#ifdef HAVE_VECTOR_INTRINSICS + if (Ngram) + CurLoc = ConstantInt::get(IntLocTy, cur_loc); + else +#endif + CurLoc = ConstantInt::get(Int32Ty, cur_loc); + + /* Load prev_loc */ + + LoadInst *PrevLoc = IRB.CreateLoad(AFLPrevLoc); + PrevLoc->setMetadata(M.getMDKindID("nosanitize"), MDNode::get(C, None)); + Value *PrevLocTrans; + +#ifdef HAVE_VECTOR_INTRINSICS + /* "For efficiency, we propose to hash the tuple as a key into the + hit_count map as (prev_block_trans << 1) ^ curr_block_trans, where + prev_block_trans = (block_trans_1 ^ ... ^ block_trans_(n-1)" */ + + if (Ngram) + PrevLocTrans = + IRB.CreateZExt(IRB.CreateXorReduce(PrevLoc), IRB.getInt32Ty()); + else +#endif + PrevLocTrans = PrevLoc; + + if (instrument_ctx) + PrevLocTrans = + IRB.CreateZExt(IRB.CreateXor(PrevLocTrans, PrevCtx), Int32Ty); + else + PrevLocTrans = IRB.CreateZExt(PrevLocTrans, IRB.getInt32Ty()); + + /* Load SHM pointer */ + + LoadInst *MapPtr = IRB.CreateLoad(AFLMapPtr); + MapPtr->setMetadata(M.getMDKindID("nosanitize"), MDNode::get(C, None)); + + Value *MapPtrIdx; +#ifdef HAVE_VECTOR_INTRINSICS + if (Ngram) + MapPtrIdx = IRB.CreateGEP( + MapPtr, + IRB.CreateZExt( + IRB.CreateXor(PrevLocTrans, IRB.CreateZExt(CurLoc, Int32Ty)), + Int32Ty)); + else +#endif + MapPtrIdx = IRB.CreateGEP(MapPtr, IRB.CreateXor(PrevLocTrans, CurLoc)); + + /* Update bitmap */ + + if (ThreadSafe) { /* Atomic */ + /* + #if LLVM_VERSION_MAJOR < 9 + if (neverZero_counters_str != + NULL) { // with llvm 9 we make this the default as the bug + in llvm + // is then fixed + #else + if (NotZero) { + + #endif + // register MapPtrIdx in a todo list + todo.push_back(MapPtrIdx); + + } else { + + */ + IRB.CreateAtomicRMW(llvm::AtomicRMWInst::BinOp::Add, MapPtrIdx, One, +#if LLVM_VERSION_MAJOR >= 13 + llvm::MaybeAlign(1), +#endif + llvm::AtomicOrdering::Monotonic); + /* + + } + + */ + + } else { + + LoadInst *Counter = IRB.CreateLoad(MapPtrIdx); + Counter->setMetadata(M.getMDKindID("nosanitize"), MDNode::get(C, None)); + + Value *Incr = IRB.CreateAdd(Counter, One); + +#if LLVM_VERSION_MAJOR < 9 + if (neverZero_counters_str != + NULL) { // with llvm 9 we make this the default as the bug in llvm + // is then fixed +#else + if (NotZero) { + +#endif + /* hexcoder: Realize a counter that skips zero during overflow. + * Once this counter reaches its maximum value, it next increments to + * 1 + * + * Instead of + * Counter + 1 -> Counter + * we inject now this + * Counter + 1 -> {Counter, OverflowFlag} + * Counter + OverflowFlag -> Counter + */ + + ConstantInt *Zero = ConstantInt::get(Int8Ty, 0); + auto cf = IRB.CreateICmpEQ(Incr, Zero); + auto carry = IRB.CreateZExt(cf, Int8Ty); + Incr = IRB.CreateAdd(Incr, carry); + + } + + IRB.CreateStore(Incr, MapPtrIdx) + ->setMetadata(M.getMDKindID("nosanitize"), MDNode::get(C, None)); + + } /* non atomic case */ + + /* Update prev_loc history vector (by placing cur_loc at the head of the + vector and shuffle the other elements back by one) */ + + StoreInst *Store; + +#ifdef HAVE_VECTOR_INTRINSICS + if (Ngram) { + + Value *ShuffledPrevLoc = IRB.CreateShuffleVector( + PrevLoc, UndefValue::get(PrevLocTy), PrevLocShuffleMask); + Value *UpdatedPrevLoc = IRB.CreateInsertElement( + ShuffledPrevLoc, IRB.CreateLShr(CurLoc, (uint64_t)1), (uint64_t)0); + + Store = IRB.CreateStore(UpdatedPrevLoc, AFLPrevLoc); + Store->setMetadata(M.getMDKindID("nosanitize"), MDNode::get(C, None)); + + } else + +#endif + { + + Store = IRB.CreateStore(ConstantInt::get(Int32Ty, cur_loc >> 1), + AFLPrevLoc); + Store->setMetadata(M.getMDKindID("nosanitize"), MDNode::get(C, None)); + + } + + // in CTX mode we have to restore the original context for the caller - + // she might be calling other functions which need the correct CTX. + // Currently this is only needed for the Ubuntu clang-6.0 bug + if (instrument_ctx && has_calls) { + + Instruction *Inst = BB.getTerminator(); + if (isa(Inst) || isa(Inst)) { + + IRBuilder<> Post_IRB(Inst); + + StoreInst *RestoreCtx; +#ifdef HAVE_VECTOR_INTRINSICS + if (CtxK) + RestoreCtx = IRB.CreateStore(PrevCaller, AFLPrevCaller); + else +#endif + RestoreCtx = Post_IRB.CreateStore(PrevCtx, AFLContext); + RestoreCtx->setMetadata(M.getMDKindID("nosanitize"), + MDNode::get(C, None)); + + } + + } + + inst_blocks++; + + } + + } + + /* Say something nice. */ + + /*if (!be_quiet) { + + if (!inst_blocks) + WARNF("No instrumentation targets found."); + else { + + char modeline[100]; + snprintf(modeline, sizeof(modeline), "%s%s%s%s%s", + getenv("AFL_HARDEN") ? "hardened" : "non-hardened", + getenv("AFL_USE_ASAN") ? ", ASAN" : "", + getenv("AFL_USE_MSAN") ? ", MSAN" : "", + getenv("AFL_USE_CFISAN") ? ", CFISAN" : "", + getenv("AFL_USE_UBSAN") ? ", UBSAN" : ""); + OKF("Instrumented %d locations (%s mode, ratio %u%%).", inst_blocks, + modeline, InstRatio); + + } + + }*/ + + 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_cc/src/clang.rs b/libafl_cc/src/clang.rs index 3c0f576452..8cf15f1271 100644 --- a/libafl_cc/src/clang.rs +++ b/libafl_cc/src/clang.rs @@ -26,6 +26,7 @@ include!(concat!(env!("OUT_DIR"), "/clang_constants.rs")); pub enum LLVMPasses { //CmpLogIns, CmpLogRtn, + AFLCoverage, } impl LLVMPasses { @@ -34,6 +35,8 @@ impl LLVMPasses { match self { LLVMPasses::CmpLogRtn => PathBuf::from(env!("OUT_DIR")) .join(format!("cmplog-routines-pass.{}", dll_extension())), + LLVMPasses::AFLCoverage => PathBuf::from(env!("OUT_DIR")) + .join(format!("afl-coverage-pass.{}", dll_extension())), } } } @@ -210,6 +213,11 @@ impl CompilerWrapper for ClangWrapper { } args.extend_from_slice(self.link_args.as_slice()); + + if cfg!(unix) { + args.push("-pthread".into()); + args.push("-ldl".into()); + } } else { args.extend_from_slice(self.cc_args.as_slice()); } diff --git a/libafl_frida/Cargo.toml b/libafl_frida/Cargo.toml index cc096d1907..fc489015f2 100644 --- a/libafl_frida/Cargo.toml +++ b/libafl_frida/Cargo.toml @@ -20,7 +20,7 @@ cc = { version = "1.0", features = ["parallel"] } [dependencies] libafl = { path = "../libafl", version = "0.6.1", features = ["std", "libafl_derive"] } -libafl_targets = { path = "../libafl_targets", version = "0.6.1", features = ["sancov_cmplog"] } +libafl_targets = { path = "../libafl_targets", version = "0.6.1", features = ["std", "sancov_cmplog"] } nix = "0.23.0" libc = "0.2" hashbrown = "0.11" diff --git a/libafl_targets/Cargo.toml b/libafl_targets/Cargo.toml index 0e80b84974..40e837fa88 100644 --- a/libafl_targets/Cargo.toml +++ b/libafl_targets/Cargo.toml @@ -11,12 +11,12 @@ keywords = ["fuzzing", "testing"] edition = "2021" [features] -default = [] +default = ["std"] +std = [] libfuzzer = [] +pointer_maps = [] sancov_pcguard_edges = [] sancov_pcguard_hitcounts = [] -sancov_pcguard_edges_ptr = [] -sancov_pcguard_hitcounts_ptr = [] sancov_value_profile = [] sancov_8bit = [] sancov_cmplog = [] diff --git a/libafl_targets/build.rs b/libafl_targets/build.rs index 86d727d517..5c33d8341c 100644 --- a/libafl_targets/build.rs +++ b/libafl_targets/build.rs @@ -28,14 +28,14 @@ fn main() { &mut constants_file, "// These constants are autogenerated by build.rs -/// The size of the edges map -pub const EDGES_MAP_SIZE: usize = {}; -/// The size of the cmps map -pub const CMP_MAP_SIZE: usize = {}; -/// The width of the `CmpLog` map -pub const CMPLOG_MAP_W: usize = {}; -/// The height of the `CmpLog` map -pub const CMPLOG_MAP_H: usize = {}; + /// The size of the edges map + pub const EDGES_MAP_SIZE: usize = {}; + /// The size of the cmps map + pub const CMP_MAP_SIZE: usize = {}; + /// The width of the `CmpLog` map + pub const CMPLOG_MAP_W: usize = {}; + /// The height of the `CmpLog` map + pub const CMPLOG_MAP_H: usize = {}; ", edges_map_size, cmp_map_size, cmplog_map_w, cmplog_map_h ) @@ -90,6 +90,13 @@ pub const CMPLOG_MAP_H: usize = {}; .file(src_dir.join("common.c")) .compile("common"); + println!("cargo:rerun-if-changed=src/coverage.c"); + + cc::Build::new() + .file(src_dir.join("coverage.c")) + .define("EDGES_MAP_SIZE", Some(&*format!("{}", edges_map_size))) + .compile("coverage"); + println!("cargo:rerun-if-changed=src/cmplog.h"); println!("cargo:rerun-if-changed=src/cmplog.c"); diff --git a/libafl_targets/src/common.h b/libafl_targets/src/common.h index 815ca4906c..97489b4ce9 100644 --- a/libafl_targets/src/common.h +++ b/libafl_targets/src/common.h @@ -8,6 +8,37 @@ #define STATIC_ASSERT(pred) switch(0){case 0:case pred:;} +// From https://stackoverflow.com/a/18298965 +#if __STDC_VERSION__ >= 201112 && !defined __STDC_NO_THREADS__ + #define THREAD_LOCAL _Thread_local +#elif defined _WIN32 && ( \ + defined _MSC_VER || \ + defined __ICL || \ + defined __DMC__ || \ + defined __BORLANDC__ ) + #define THREAD_LOCAL __declspec(thread) +/* note that ICC (linux) and Clang are covered by __GNUC__ */ +#elif defined __GNUC__ || \ + defined __SUNPRO_C || \ + defined __xlC__ + #define THREAD_LOCAL __thread +#endif + +#if defined(__ANDROID__) || defined(__HAIKU__) + #undef THREAD_LOCAL +#elif defined(__APPLE__) + #include + #if TARGET_OS_IPHONE + #undef THREAD_LOCAL + #endif +#endif + +#ifdef THREAD_LOCAL + #define MAYBE_THREAD_LOCAL THREAD_LOCAL +#else + #define MAYBE_THREAD_LOCAL +#endif + #ifdef _WIN32 #define RETADDR (uintptr_t)_ReturnAddress() #define EXPORT_FN __declspec(dllexport) diff --git a/libafl_targets/src/coverage.c b/libafl_targets/src/coverage.c new file mode 100644 index 0000000000..cf28f51e51 --- /dev/null +++ b/libafl_targets/src/coverage.c @@ -0,0 +1,17 @@ +#include "common.h" + +typedef uint32_t prev_loc_t; + +/* Maximum ngram size */ +#define NGRAM_SIZE_MAX 16U + +/* Maximum K for top-K context sensitivity */ +#define CTX_MAX_K 32U + +extern uint8_t __afl_area_ptr_local[EDGES_MAP_SIZE]; +uint8_t* __afl_area_ptr = __afl_area_ptr_local; + +//#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; diff --git a/libafl_targets/src/coverage.rs b/libafl_targets/src/coverage.rs index 66ac857527..bfffdc9519 100644 --- a/libafl_targets/src/coverage.rs +++ b/libafl_targets/src/coverage.rs @@ -1,19 +1,47 @@ //! Coverage maps as static mut array use crate::EDGES_MAP_SIZE; -#[cfg(any( - feature = "sancov_pcguard_edges_ptr", - feature = "sancov_pcguard_hitcounts_ptr" -))] -use core::ptr; +use core::slice::from_raw_parts_mut; /// The map for edges. -pub static mut EDGES_MAP: [u8; EDGES_MAP_SIZE] = [0; EDGES_MAP_SIZE]; -#[cfg(any( - feature = "sancov_pcguard_edges_ptr", - feature = "sancov_pcguard_hitcounts_ptr" -))] -pub static mut EDGES_MAP_PTR: *mut u8 = ptr::null_mut(); +#[no_mangle] +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 max count of edges tracked. pub static mut MAX_EDGES_NUM: usize = 0; + +extern "C" { + pub static mut __afl_area_ptr: *mut u8; +} +pub use __afl_area_ptr as EDGES_MAP_PTR; + +#[no_mangle] +pub static mut __afl_map_size: usize = EDGES_MAP_SIZE; +pub use __afl_map_size as EDGES_MAP_PTR_SIZE; + +#[must_use] +pub fn edges_map_from_ptr<'a>() -> &'a mut [u8] { + unsafe { + assert!(!EDGES_MAP_PTR.is_null()); + from_raw_parts_mut(EDGES_MAP_PTR, EDGES_MAP_PTR_SIZE) + } +} + +#[must_use] +pub fn edges_max_num() -> usize { + unsafe { + if MAX_EDGES_NUM > 0 { + MAX_EDGES_NUM + } else { + #[cfg(feature = "pointer_maps")] + { + EDGES_MAP_PTR_SIZE + } + #[cfg(not(feature = "pointer_maps"))] + { + EDGES_MAP.len() + } + } + } +} diff --git a/libafl_targets/src/drcov.rs b/libafl_targets/src/drcov.rs index ddffb04eb9..4703e38dc3 100644 --- a/libafl_targets/src/drcov.rs +++ b/libafl_targets/src/drcov.rs @@ -45,7 +45,7 @@ impl<'a> DrCovWriter<'a> { ) -> Self { Self { writer: BufWriter::new( - File::create(path).expect("unable to create file for coverage data"), + File::create(path).expect("Unable to create file for coverage data"), ), module_mapping, basic_blocks, diff --git a/libafl_targets/src/lib.rs b/libafl_targets/src/lib.rs index d52757da92..1376ac9c8a 100644 --- a/libafl_targets/src/lib.rs +++ b/libafl_targets/src/lib.rs @@ -5,19 +5,9 @@ extern crate alloc; include!(concat!(env!("OUT_DIR"), "/constants.rs")); -#[cfg(any( - feature = "sancov_pcguard_edges", - feature = "sancov_pcguard_hitcounts", - feature = "sancov_pcguard_edges_ptr", - feature = "sancov_pcguard_hitcounts_ptr" -))] +#[cfg(any(feature = "sancov_pcguard_edges", feature = "sancov_pcguard_hitcounts",))] pub mod sancov_pcguard; -#[cfg(any( - feature = "sancov_pcguard_edges", - feature = "sancov_pcguard_hitcounts", - feature = "sancov_pcguard_edges_ptr", - feature = "sancov_pcguard_hitcounts_ptr" -))] +#[cfg(any(feature = "sancov_pcguard_edges", feature = "sancov_pcguard_hitcounts",))] pub use sancov_pcguard::*; #[cfg(any(feature = "sancov_cmplog", feature = "sancov_value_profile"))] @@ -44,4 +34,5 @@ pub use value_profile::*; pub mod cmplog; pub use cmplog::*; +#[cfg(feature = "std")] pub mod drcov; diff --git a/libafl_targets/src/sancov_pcguard.rs b/libafl_targets/src/sancov_pcguard.rs index 750d041e4d..05daa71c75 100644 --- a/libafl_targets/src/sancov_pcguard.rs +++ b/libafl_targets/src/sancov_pcguard.rs @@ -1,20 +1,8 @@ //! [`LLVM` `PcGuard`](https://clang.llvm.org/docs/SanitizerCoverage.html#tracing-pcs-with-guards) runtime for `LibAFL`. -#[cfg(any( - feature = "sancov_pcguard_edges_ptr", - feature = "sancov_pcguard_hitcounts_ptr" -))] -use crate::coverage::EDGES_MAP_PTR; use crate::coverage::{EDGES_MAP, MAX_EDGES_NUM}; - -#[cfg(all( - feature = "sancov_pcguard_edges_ptr", - feature = "sancov_pcguard_hitcounts_ptr" -))] -#[cfg(not(any(doc, feature = "clippy")))] -compile_error!( - "the libafl_targets `sancov_pcguard_edges_ptr` and `sancov_pcguard_hitcounts_ptr` features are mutually exclusive." -); +#[cfg(feature = "pointer_maps")] +use crate::coverage::{EDGES_MAP_PTR, EDGES_MAP_PTR_SIZE}; #[cfg(all(feature = "sancov_pcguard_edges", feature = "sancov_pcguard_hitcounts"))] #[cfg(not(any(doc, feature = "clippy")))] @@ -27,33 +15,33 @@ compile_error!( /// # Safety /// Dereferences `guard`, reads the position from there, then dereferences the [`EDGES_MAP`] at that position. /// Should usually not be called directly. -#[cfg(any( - feature = "sancov_pcguard_edges", - feature = "sancov_pcguard_hitcounts", - feature = "sancov_pcguard_edges_ptr", - feature = "sancov_pcguard_hitcounts_ptr" -))] #[no_mangle] pub unsafe extern "C" fn __sanitizer_cov_trace_pc_guard(guard: *mut u32) { let pos = *guard as usize; - #[cfg(feature = "sancov_pcguard_edges")] + #[cfg(feature = "pointer_maps")] { - *EDGES_MAP.get_unchecked_mut(pos) = 1; + #[cfg(feature = "sancov_pcguard_edges")] + { + (EDGES_MAP_PTR as *mut u8).add(pos).write(1); + } + #[cfg(feature = "sancov_pcguard_hitcounts")] + { + let addr = (EDGES_MAP_PTR as *mut u8).add(pos); + let val = addr.read().wrapping_add(1); + addr.write(val); + } } - #[cfg(feature = "sancov_pcguard_hitcounts")] + #[cfg(not(feature = "pointer_maps"))] { - let val = (*EDGES_MAP.get_unchecked(pos) as u8).wrapping_add(1); - *EDGES_MAP.get_unchecked_mut(pos) = val; - } - #[cfg(feature = "sancov_pcguard_edges_ptr")] - { - (EDGES_MAP_PTR as *mut u8).add(pos).write(1); - } - #[cfg(feature = "sancov_pcguard_hitcounts_ptr")] - { - let addr = (EDGES_MAP_PTR as *mut u8).add(pos); - let val = addr.read().wrapping_add(1); - addr.write(val); + #[cfg(feature = "sancov_pcguard_edges")] + { + *EDGES_MAP.get_unchecked_mut(pos) = 1; + } + #[cfg(feature = "sancov_pcguard_hitcounts")] + { + let val = (*EDGES_MAP.get_unchecked(pos) as u8).wrapping_add(1); + *EDGES_MAP.get_unchecked_mut(pos) = val; + } } } @@ -61,20 +49,12 @@ pub unsafe extern "C" fn __sanitizer_cov_trace_pc_guard(guard: *mut u32) { /// /// # Safety /// Dereferences at `start` and writes to it. -#[cfg(any( - feature = "sancov_pcguard_edges", - feature = "sancov_pcguard_hitcounts", - feature = "sancov_pcguard_edges_ptr", - feature = "sancov_pcguard_hitcounts_ptr" -))] #[no_mangle] pub unsafe extern "C" fn __sanitizer_cov_trace_pc_guard_init(mut start: *mut u32, stop: *mut u32) { - #[cfg(any( - feature = "sancov_pcguard_edges_ptr", - feature = "sancov_pcguard_hitcounts_ptr" - ))] + #[cfg(feature = "pointer_maps")] if EDGES_MAP_PTR.is_null() { EDGES_MAP_PTR = EDGES_MAP.as_mut_ptr(); + EDGES_MAP_PTR_SIZE = EDGES_MAP.len(); } if start == stop || *start != 0 { @@ -84,8 +64,15 @@ pub unsafe extern "C" fn __sanitizer_cov_trace_pc_guard_init(mut start: *mut u32 while start < stop { *start = MAX_EDGES_NUM as u32; start = start.offset(1); - MAX_EDGES_NUM = MAX_EDGES_NUM.wrapping_add(1); - assert!((MAX_EDGES_NUM <= EDGES_MAP.len()), "The number of edges reported by SanitizerCoverage exceed the size of the edges map ({}). Use the LIBAFL_EDGES_MAP_SIZE env to increase it at compile time.", EDGES_MAP.len()); + #[cfg(feature = "pointer_maps")] + { + MAX_EDGES_NUM = MAX_EDGES_NUM.wrapping_add(1) % EDGES_MAP_PTR_SIZE; + } + #[cfg(not(feature = "pointer_maps"))] + { + MAX_EDGES_NUM = MAX_EDGES_NUM.wrapping_add(1); + assert!((MAX_EDGES_NUM <= EDGES_MAP.len()), "The number of edges reported by SanitizerCoverage exceed the size of the edges map ({}). Use the LIBAFL_EDGES_MAP_SIZE env to increase it at compile time.", EDGES_MAP.len()); + } } }