From 6178fabb9908fa648c031922fa04af5d16e77574 Mon Sep 17 00:00:00 2001 From: golem9247 Date: Fri, 6 Dec 2024 17:04:35 +0100 Subject: [PATCH] libfuzzer_template_lib : inprocess template for librairies --- .../libfuzzer_template_lib/Cargo.toml | 36 ++++ .../libfuzzer_template_lib/Makefile.toml | 57 ++++++ .../libfuzzer_template_lib/README.md | 60 ++++++ .../libfuzzer_template_lib/corpus/a1 | 1 + .../libfuzzer_template_lib/corpus/a2 | 1 + .../libfuzzer_template_lib/harness.c | 123 +++++++++++ .../libsrc/CMakeLists.txt | 16 ++ .../libfuzzer_template_lib/libsrc/template.c | 16 ++ .../libfuzzer_template_lib/libsrc/template.h | 14 ++ .../src/bin/libafl_cc.rs | 36 ++++ .../src/bin/libafl_cxx.rs | 5 + .../libfuzzer_template_lib/src/lib.rs | 191 ++++++++++++++++++ 12 files changed, 556 insertions(+) create mode 100644 fuzzers/inprocess/libfuzzer_template_lib/Cargo.toml create mode 100644 fuzzers/inprocess/libfuzzer_template_lib/Makefile.toml create mode 100644 fuzzers/inprocess/libfuzzer_template_lib/README.md create mode 100644 fuzzers/inprocess/libfuzzer_template_lib/corpus/a1 create mode 100644 fuzzers/inprocess/libfuzzer_template_lib/corpus/a2 create mode 100644 fuzzers/inprocess/libfuzzer_template_lib/harness.c create mode 100644 fuzzers/inprocess/libfuzzer_template_lib/libsrc/CMakeLists.txt create mode 100644 fuzzers/inprocess/libfuzzer_template_lib/libsrc/template.c create mode 100644 fuzzers/inprocess/libfuzzer_template_lib/libsrc/template.h create mode 100644 fuzzers/inprocess/libfuzzer_template_lib/src/bin/libafl_cc.rs create mode 100644 fuzzers/inprocess/libfuzzer_template_lib/src/bin/libafl_cxx.rs create mode 100644 fuzzers/inprocess/libfuzzer_template_lib/src/lib.rs diff --git a/fuzzers/inprocess/libfuzzer_template_lib/Cargo.toml b/fuzzers/inprocess/libfuzzer_template_lib/Cargo.toml new file mode 100644 index 0000000000..cd88eece56 --- /dev/null +++ b/fuzzers/inprocess/libfuzzer_template_lib/Cargo.toml @@ -0,0 +1,36 @@ +[package] +name = "fuzzer_template" +version = "0.1.0" +edition = "2021" + +[features] +default = ["std"] +std = [] +crash = [] + +[profile.release] +lto = true +codegen-units = 1 +opt-level = 3 +debug = true + +[build-dependencies] +cc = { version = "1.1.21", features = ["parallel"] } + +[dependencies] +libafl = { path = "../../../libafl", features = ["default"] } +libafl_bolts = { path = "../../../libafl_bolts" } +libafl_targets = { path = "../../../libafl_targets", features = [ + "sancov_pcguard_hitcounts", + "libfuzzer", + "sancov_cmplog", +] } +# TODO Include it only when building cc +libafl_cc = { path = "../../../libafl_cc" } + +log = { version = "0.4.22", features = ["release_max_level_info"] } +mimalloc = { version = "0.1.43", default-features = false } + +[lib] +name = "fuzzer_template" +crate-type = ["staticlib"] diff --git a/fuzzers/inprocess/libfuzzer_template_lib/Makefile.toml b/fuzzers/inprocess/libfuzzer_template_lib/Makefile.toml new file mode 100644 index 0000000000..ec8dfdabc5 --- /dev/null +++ b/fuzzers/inprocess/libfuzzer_template_lib/Makefile.toml @@ -0,0 +1,57 @@ +[env] +FUZZER_NAME = 'fuzzer_template' +PROJECT_DIR = { script = ["pwd"] } +CARGO_TARGET_DIR = { value = "${PROJECT_DIR}/target", condition = { env_not_set = ["CARGO_TARGET_DIR",] } } +PROFILE = { value = "release", condition = { env_not_set = ["PROFILE"] } } +PROFILE_DIR = { source = "${PROFILE}", default_value = "release", mapping = { "release" = "release", "dev" = "debug" }, condition = { env_not_set = [ + "PROFILE_DIR", +] } } +LIBAFL_CC = '${CARGO_TARGET_DIR}/${PROFILE_DIR}/libafl_cc' +LIBAFL_CXX = '${CARGO_TARGET_DIR}/${PROFILE}/libafl_cxx' +FUZZER = '${CARGO_TARGET_DIR}/${PROFILE_DIR}/${FUZZER_NAME}' + +[tasks.clean] +dependencies = ["cargo-clean", "clean-lib"] + +[tasks.clean-lib] +cwd = "libsrc" +script = """ +rm -rf build +rm -rf ../fuzzer +""" + +[tasks.cargo-clean] +command = "cargo" +args = ["clean"] + +[tasks.rebuild] +dependencies = ["clean-lib", "build"] + +[tasks.build] +dependencies = ["build-compilers", "build-lib", "build-fuzzer"] + +[tasks.build-compilers] +script = """ +cargo build --release +mkdir -p ${PROJECT_DIR}/libs +cp -f ./target/${PROFILE_DIR}/libfuzzer_template.a ${PROJECT_DIR}/libs +""" + +[tasks.build-lib] +cwd = "libsrc" +script = """ +CC=${LIBAFL_CC} CXX=${LIBAFL_CXX} cmake -S . -B build +CC=${LIBAFL_CC} CXX=${LIBAFL_CXX} make -C build +mv build/libtemplate.a ${PROJECT_DIR}/libs +""" + +[tasks.build-fuzzer] +script = """ +${LIBAFL_CC} -I ./libsrc -L${PROJECT_DIR}/libs -o fuzzer harness.c libs/libtemplate.a -lm -lc -lgcc +""" + +[tasks.build-test] +script = """ +clang-15 -I ./libsrc -L${PROJECT_DIR}/libs -DTEST_CORPUS=1 -o fuzzer_test harness.c libs/libtemplate.a -lm -lc -lgcc +clang-15 -I ./libsrc -L${PROJECT_DIR}/libs -DTEST_ALL_CORPUS=1 -o fuzzer_testall harness.c libs/libtemplate.a -lm -lc -lgcc +""" \ No newline at end of file diff --git a/fuzzers/inprocess/libfuzzer_template_lib/README.md b/fuzzers/inprocess/libfuzzer_template_lib/README.md new file mode 100644 index 0000000000..e405928780 --- /dev/null +++ b/fuzzers/inprocess/libfuzzer_template_lib/README.md @@ -0,0 +1,60 @@ +# Libfuzzer template for statical libs + +This folder contains an example fuzzer for a template statical libs, using LLMP for fast multi-process fuzzing and crash detection. +In contrast to other fuzzer examples, this setup uses `fuzz_loop_for`, to occasionally respawn the fuzzer executor. +While this costs performance, it can be useful for targets with memory leaks or other instabilities. +If your target is really instable, however, consider exchanging the `InProcessExecutor` for a `ForkserverExecutor` instead. + +It also uses the `introspection` feature, printing fuzzer stats during execution. + +It has been tested on Linux. + +## Build + +To build this example, run + +```bash +cargo make build +``` + +(All build workflow is in Makefile.toml) + +This will build the library with the fuzzer (src/lib.rs) with the libfuzzer compatibility layer. +In addition, it will also build two C and C++ compiler wrappers (bin/libafl_c(libafl_c/xx).rs). +Those compilers will be used to compile libsrc/ containing a template statical library. + +Then, a fuzzer is created with libtemplate.a and harness.c, wrapper for libafl and calling the template lib function. + + +The fuzzer is ready to run. +Note that, unless you use the `launcher`, you will have to run the binary multiple times to actually start the fuzz process, see `Run` in the following. +This allows you to run multiple different builds of the same fuzzer alongside. + +## Run + +The first time you run the binary, the broker will open a tcp port (currently on port `1337`), waiting for fuzzer clients to connect. This port is local and only used for the initial handshake. All further communication happens via shared map, to be independent of the kernel. Currently, you must run the clients from the libfuzzer_template_lib directory for them to be able to access the test corpus. + +``` +./fuzzer_template + +[libafl/src/bolts/llmp.rs:407] "We're the broker" = "We\'re the broker" +Doing broker things. Run this tool again to start fuzzing in a client. +``` + +And after running the above again in a separate terminal: + +``` +[libafl/src/bolts/llmp.rs:1464] "New connection" = "New connection" +[libafl/src/bolts/llmp.rs:1464] addr = 127.0.0.1:33500 +[libafl/src/bolts/llmp.rs:1464] stream.peer_addr().unwrap() = 127.0.0.1:33500 +[LOG Debug]: Loaded 2 initial testcases. +[New Testcase #2] clients: 3, corpus: 2, objectives: 0, executions: 5, exec/sec: 0 +< fuzzing stats > +``` + +As this example uses in-process fuzzing, we added a Restarting Event Manager (`setup_restarting_mgr`). +This means each client will start itself again to listen for crashes and timeouts. +By restarting the actual fuzzer, it can recover from these exit conditions. + +In any real-world scenario, you should use `taskset` to pin each client to an empty CPU core, the lib does not pick an empty core automatically (yet). + diff --git a/fuzzers/inprocess/libfuzzer_template_lib/corpus/a1 b/fuzzers/inprocess/libfuzzer_template_lib/corpus/a1 new file mode 100644 index 0000000000..6178079822 --- /dev/null +++ b/fuzzers/inprocess/libfuzzer_template_lib/corpus/a1 @@ -0,0 +1 @@ +b diff --git a/fuzzers/inprocess/libfuzzer_template_lib/corpus/a2 b/fuzzers/inprocess/libfuzzer_template_lib/corpus/a2 new file mode 100644 index 0000000000..81bf396956 --- /dev/null +++ b/fuzzers/inprocess/libfuzzer_template_lib/corpus/a2 @@ -0,0 +1 @@ +ab diff --git a/fuzzers/inprocess/libfuzzer_template_lib/harness.c b/fuzzers/inprocess/libfuzzer_template_lib/harness.c new file mode 100644 index 0000000000..9d166f0866 --- /dev/null +++ b/fuzzers/inprocess/libfuzzer_template_lib/harness.c @@ -0,0 +1,123 @@ + +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include "template.h" + +#define MAX_CORPUS_SIZE 10 * 1024 * 1024 //Max 10Mo + +int LLVMFuzzerTestOneInput(const uint8_t *data, size_t size) { + int ret = template_run_some_data((char*)data, size); + return ret; + +} + +static int filter(const struct dirent *name){return 1;} +static int test_one_corpus(char * corpus) { + + int ret; + struct stat stat_; + + int fd = open(corpus, O_RDONLY); + if (fd == -1) { + printf("Error opening corpus\n"); + ret = -1; + goto out; + } + + stat(corpus, &stat_); + + int corpus_size = stat_.st_size; + printf("Corpus: %s | Size : %d\n", corpus, corpus_size); + + if (corpus_size > MAX_CORPUS_SIZE) { + printf("corpus size too big\n"); + ret = -1; + goto out; + } + + char * corpus_data = calloc(1, corpus_size); + int bsize = read(fd, corpus_data, corpus_size); + + if (bsize != corpus_size) { + printf("corpus size doesn't match readed size (%d != %d)!\n", bsize, corpus_size); + ret = -1; + goto out; + } + + ret = LLVMFuzzerTestOneInput((const uint8_t*)corpus_data, (size_t)corpus_size); + printf("ret=%d\n", ret); + + out: + + if (corpus_data) { + free(corpus_data); + } + + if (fd > 0) { + close(fd); + } + return ret; + +} + +// simply RECOMPILE WITH -DTEST_CORPUS=1 OR -DTEST_ALL_CORPUS=1 to test corpus_evolution or crashes directory. +#ifdef TEST_CORPUS +int main(int argc, char ** argv) { + + if (argc < 2) { + printf("Usage : %s /path/to/corpus\n", argv[0]); + return -1; + } + + char * corpus = argv[1]; + return test_one_corpus(corpus); +} +#endif + +#ifdef TEST_ALL_CORPUS +int main(int argc, char ** argv) { + + int ret; + struct dirent **namelist; + char path_corpus[200] = {0}; + + if (argc < 2) { + printf("Usage : %s /path/to/corpusdir\n", argv[0]); + return -1; + } + + int n = scandir(argv[1], &namelist, filter, alphasort); + if (n == -1) { + perror("scandir"); + exit(EXIT_FAILURE); + } + + while (n--) { + memset(path_corpus, 0, sizeof(path_corpus)); + + if ((strcmp(namelist[n]->d_name, ".") == 0) || strcmp(namelist[n]->d_name, "..") ==0) { + continue; + } + + if ((strstr(namelist[n]->d_name, ".metadata")) || strstr(namelist[n]->d_name, ".lafl_lock")) { + continue; + } + + snprintf(path_corpus, sizeof(path_corpus), "%s/%s", argv[1], namelist[n]->d_name); + ret = test_one_corpus(path_corpus); + } + + free(namelist); + return 0; + + +} +#endif \ No newline at end of file diff --git a/fuzzers/inprocess/libfuzzer_template_lib/libsrc/CMakeLists.txt b/fuzzers/inprocess/libfuzzer_template_lib/libsrc/CMakeLists.txt new file mode 100644 index 0000000000..c0030d6374 --- /dev/null +++ b/fuzzers/inprocess/libfuzzer_template_lib/libsrc/CMakeLists.txt @@ -0,0 +1,16 @@ +cmake_minimum_required(VERSION 3.15) + +project(template) + +add_library(${PROJECT_NAME} + template.c +) + +target_include_directories(${PROJECT_NAME} PUBLIC ${PROJECT_DIR}) + +set(CMAKE_BUILD_TYPE RELEASE) +set(CMAKE_C_FLAGS "${CMAKE_C_FLAGS} -Wall -D_GNU_SOURCE -O2 -std=c99") +set(CMAKE_C_FLAGS "${CMAKE_C_FLAGS} -I ${CMAKE_SOURCE_DIR}") + +set(CMAKE_C_COMPILER ${CC}) +set(CMAKE_CXX_COMPILER ${CCX}) diff --git a/fuzzers/inprocess/libfuzzer_template_lib/libsrc/template.c b/fuzzers/inprocess/libfuzzer_template_lib/libsrc/template.c new file mode 100644 index 0000000000..7a9f14cc54 --- /dev/null +++ b/fuzzers/inprocess/libfuzzer_template_lib/libsrc/template.c @@ -0,0 +1,16 @@ +#include "template.h" + +int template_run_some_data(char *data, size_t size) { + + if (data[0] == 'a') { + if (data[1] == 'b') { + if (data[2] == 'c') { + abort(); + } + return 3; + } + return 2; + } + + return 1; +} \ No newline at end of file diff --git a/fuzzers/inprocess/libfuzzer_template_lib/libsrc/template.h b/fuzzers/inprocess/libfuzzer_template_lib/libsrc/template.h new file mode 100644 index 0000000000..dc9a9a13e2 --- /dev/null +++ b/fuzzers/inprocess/libfuzzer_template_lib/libsrc/template.h @@ -0,0 +1,14 @@ +#pragma once + +#include +#include +#include +#include +#include +#include +#include +#include +#include + +//some template lib function +int template_run_some_data(char *data, size_t size); \ No newline at end of file diff --git a/fuzzers/inprocess/libfuzzer_template_lib/src/bin/libafl_cc.rs b/fuzzers/inprocess/libfuzzer_template_lib/src/bin/libafl_cc.rs new file mode 100644 index 0000000000..3777cab09d --- /dev/null +++ b/fuzzers/inprocess/libfuzzer_template_lib/src/bin/libafl_cc.rs @@ -0,0 +1,36 @@ +use std::env; + +use libafl_cc::{ClangWrapper, CompilerWrapper, ToolWrapper}; + +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++ wrapper was called. Expected {dir:?} to end with c or cxx"), + }; + + 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, "fuzzer_template") + .add_arg("-fsanitize-coverage=trace-pc-guard") + .run() + .expect("Failed to run the wrapped compiler") + { + std::process::exit(code); + } + } else { + panic!("LibAFL CC: No Arguments given"); + } +} diff --git a/fuzzers/inprocess/libfuzzer_template_lib/src/bin/libafl_cxx.rs b/fuzzers/inprocess/libfuzzer_template_lib/src/bin/libafl_cxx.rs new file mode 100644 index 0000000000..dabd22971a --- /dev/null +++ b/fuzzers/inprocess/libfuzzer_template_lib/src/bin/libafl_cxx.rs @@ -0,0 +1,5 @@ +pub mod libafl_cc; + +fn main() { + libafl_cc::main(); +} diff --git a/fuzzers/inprocess/libfuzzer_template_lib/src/lib.rs b/fuzzers/inprocess/libfuzzer_template_lib/src/lib.rs new file mode 100644 index 0000000000..3618980274 --- /dev/null +++ b/fuzzers/inprocess/libfuzzer_template_lib/src/lib.rs @@ -0,0 +1,191 @@ +use core::time::Duration; +use std::{env, path::PathBuf}; + +#[allow(unused_imports)] +use libafl::{ + corpus::{Corpus, InMemoryCorpus, OnDiskCorpus}, + events::{setup_restarting_mgr_std, EventConfig, EventRestarter}, + executors::{inprocess::InProcessExecutor, ExitKind}, + feedback_or, feedback_or_fast, feedbacks, + feedbacks::{CrashFeedback, MaxMapFeedback, TimeFeedback, TimeoutFeedback}, + fuzzer::{Fuzzer, StdFuzzer}, + inputs::{BytesInput, HasTargetBytes}, + monitors::MultiMonitor, + mutators::{ + havoc_mutations::havoc_mutations, + scheduled::{tokens_mutations, StdScheduledMutator}, + token_mutations::Tokens, + }, + observers::{CanTrack, HitcountsMapObserver, StdMapObserver, TimeObserver}, + schedulers::{ + powersched::PowerSchedule, IndexesLenTimeMinimizerScheduler, StdWeightedScheduler, + }, + stages::{calibrate::CalibrationStage, power::StdPowerMutationalStage}, + state::{HasCorpus, StdState}, + Error, HasMetadata, +}; +use libafl_bolts::{ + rands::StdRand, + tuples::{tuple_list, Merge}, + AsSlice, +}; +use libafl_targets::{libfuzzer_initialize, libfuzzer_test_one_input, EDGES_MAP, MAX_EDGES_FOUND}; +use mimalloc::MiMalloc; + +#[global_allocator] +static GLOBAL: MiMalloc = MiMalloc; + +/// The main fn, `no_mangle` as it is a C main +#[no_mangle] +pub extern "C" fn libafl_main() { + println!( + "Workdir: {:?}", + env::current_dir().unwrap().to_string_lossy().to_string() + ); + + fuzz( + &[PathBuf::from("./corpus")], + PathBuf::from("./crashes"), + 1337, + ) + .expect("An error occurred while fuzzing"); +} + +/// The actual fuzzer +fn fuzz(corpus_dirs: &[PathBuf], objective_dir: PathBuf, broker_port: u16) -> Result<(), Error> { + // 'While the stats are state, they are usually used in the broker - which is likely never restarted + let monitor = MultiMonitor::new(|s| println!("{s}")); + + let corpus_evolution_dir = PathBuf::from("./corpus_evolution"); + + // The restarting state will spawn the same process again as child, then restarted it each time it crashes. + let (state, mut restarting_mgr) = + match setup_restarting_mgr_std(monitor, broker_port, EventConfig::AlwaysUnique) { + Ok(res) => res, + Err(err) => match err { + Error::ShuttingDown => { + return Ok(()); + } + _ => { + panic!("Failed to setup the restarter: {err}"); + } + }, + }; + + // Create an observation channel using the coverage map + #[allow(static_mut_refs)] + let edges_observer = unsafe { + HitcountsMapObserver::new(StdMapObserver::from_mut_ptr( + "edges", + EDGES_MAP.as_mut_ptr(), + MAX_EDGES_FOUND, + )) + .track_indices() + }; + + let map_feedback = MaxMapFeedback::new(&edges_observer); + let calibration = CalibrationStage::new(&map_feedback); + let mut feedback = map_feedback; + // A feedback to choose if an input is a solution or not + let mut objective = CrashFeedback::new(); + + + // If not restarting, create a State from scratch + let mut state = state.unwrap_or_else(|| { + StdState::new( + // RNG + StdRand::new(), + // Corpus that will be evolved, we keep it in memory for performance + OnDiskCorpus::new(corpus_evolution_dir).unwrap(), + // Corpus in which we store solutions (crashes in this example), + // on disk so the user can get them after stopping the fuzzer + OnDiskCorpus::new(objective_dir).unwrap(), + // States of the feedbacks. + // The feedbacks can report the data that should persist in the State. + &mut feedback, + // Same for objective feedbacks + &mut objective, + ) + .unwrap() + }); + + println!("We're a client, let's fuzz :)"); + + // Setup a basic mutator with a mutational stage + + let mutator = StdScheduledMutator::new(havoc_mutations().merge(tokens_mutations())); + + let power: StdPowerMutationalStage<_, _, BytesInput, _, _> = + StdPowerMutationalStage::new(mutator); + + let mut stages = tuple_list!(calibration, power); + + // A minimization+queue policy to get testcasess from the corpus + let scheduler = IndexesLenTimeMinimizerScheduler::new( + &edges_observer, + StdWeightedScheduler::with_schedule( + &mut state, + &edges_observer, + Some(PowerSchedule::fast()), + ), + ); + + // 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(); + + unsafe { + 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 = InProcessExecutor::with_timeout( + &mut harness, + tuple_list!(edges_observer), + &mut fuzzer, + &mut state, + &mut restarting_mgr, + Duration::new(10, 0), + )?; + // 10 seconds timeout + + // The actual target run starts here. + // Call LLVMFUzzerInitialize() if present. + let args: Vec = env::args().collect(); + if unsafe { libfuzzer_initialize(&args) } == -1 { + println!("Warning: LLVMFuzzerInitialize failed with -1"); + } + + // In case the corpus is empty (on first run), reset + if state.must_load_initial_inputs() { + 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()); + } + + // This fuzzer restarts after 1 mio `fuzz_one` executions. + // Each fuzz_one will internally do many executions of the target. + // If your target is very instable, setting a low count here may help. + // However, you will lose a lot of performance that way. + let iters = 1_000_000; + fuzzer.fuzz_loop_for( + &mut stages, + &mut executor, + &mut state, + &mut restarting_mgr, + iters, + )?; + + // It's important, that we store the state before restarting! + // Else, the parent will not respawn a new child and quit. + restarting_mgr.on_restart(&mut state)?; + + Ok(()) +}