From 1c9925c7d79eefa95870ab1bc8c6538816c9e45a Mon Sep 17 00:00:00 2001 From: mio Date: Sun, 19 Jan 2025 22:32:24 +0800 Subject: [PATCH 01/25] Initial integration --- include/afl-fuzz.h | 14 ++ include/asanfuzz.h | 50 ++++++ include/config.h | 9 ++ include/coverage-64.h | 16 ++ include/debug.h | 4 +- include/envs.h | 4 +- include/forkserver.h | 3 + include/hash.h | 2 + include/sharedmem.h | 1 + instrumentation/SanitizerCoverageLTO.so.cc | 22 ++- .../SanitizerCoveragePCGUARD.so.cc | 12 +- instrumentation/afl-llvm-pass.so.cc | 16 ++ src/afl-cc.c | 33 +++- src/afl-forkserver.c | 13 +- src/afl-fuzz-bitmap.c | 126 +++++++++++++-- src/afl-fuzz-init.c | 2 +- src/afl-fuzz-run.c | 1 + src/afl-fuzz-sanfuzz.c | 43 ++++++ src/afl-fuzz-stats.c | 26 +++- src/afl-fuzz.c | 145 +++++++++++++++++- src/afl-performance.c | 12 ++ 21 files changed, 512 insertions(+), 42 deletions(-) create mode 100644 include/asanfuzz.h create mode 100644 src/afl-fuzz-sanfuzz.c diff --git a/include/afl-fuzz.h b/include/afl-fuzz.h index b5f42d9f..c53d08b4 100644 --- a/include/afl-fuzz.h +++ b/include/afl-fuzz.h @@ -75,6 +75,7 @@ #include #include #include +#include "asanfuzz.h" #if defined(__APPLE__) || defined(__FreeBSD__) || defined(__OpenBSD__) || \ defined(__NetBSD__) || defined(__DragonFly__) @@ -610,7 +611,12 @@ typedef struct afl_state { u8 *var_bytes; /* Bytes that appear to be variable */ #define N_FUZZ_SIZE (1 << 21) +#define N_FUZZ_SIZE_BITMAP (1 << 29) u32 *n_fuzz; + u8 *n_fuzz_dup; + u8 *classified_n_fuzz; + u8 *simplitied_n_fuzz; + volatile u8 stop_soon, /* Ctrl-C pressed? */ clear_screen; /* Window resized? */ @@ -728,6 +734,14 @@ typedef struct afl_state { char *cmplog_binary; afl_forkserver_t cmplog_fsrv; /* cmplog has its own little forkserver */ + /* ASAN Fuzing */ + char *san_binary[MAX_EXTRA_SAN_BINARY]; + afl_forkserver_t san_fsrvs[MAX_EXTRA_SAN_BINARY]; + u8 san_binary_length; /* 0 means extra san binaries not given */ + u8 no_saving_crash_seed; + u32 san_case_status; + enum SanitizerAbstraction san_abstraction; + /* Custom mutators */ struct custom_mutator *mutator; diff --git a/include/asanfuzz.h b/include/asanfuzz.h new file mode 100644 index 00000000..ef25b992 --- /dev/null +++ b/include/asanfuzz.h @@ -0,0 +1,50 @@ +/* + american fuzzy lop++ - cmplog header + ------------------------------------ + + Originally written by Michal Zalewski + + Forkserver design by Jann Horn + + Now maintained by Marc Heuse , + Heiko Eißfeldt , + Andrea Fioraldi , + Dominik Maier + + Copyright 2016, 2017 Google Inc. All rights reserved. + Copyright 2019-2023 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: + + https://www.apache.org/licenses/LICENSE-2.0 + + Shared code to handle the shared memory. This is used by the fuzzer + as well the other components like afl-tmin, afl-showmap, etc... + + */ + +#ifndef _AFL_ASAMFUZZ_H +#define _AFL_ASAMFUZZ_H + +#include "config.h" + +// new_bits for describe_op +// new_bits value 1, 2 and 0x80 are already used! +#define SAN_CRASH_ONLY (1 << 4) +#define NON_COV_INCREASE_BUG (1 << 5) + +enum SanitizerAbstraction { + UNIQUE_TRACE = 0, // Feed all unique trace to sanitizers, the most sensitive + SIMPLIFY_TRACE, + COVERAGE_INCREASE // Feed all coverage increasing cases to sanitizers, the least sensitive +}; + +/* Execs the child */ + +struct afl_forkserver; +void sanfuzz_exec_child(struct afl_forkserver *fsrv, char **argv); + +#endif + diff --git a/include/config.h b/include/config.h index fd31f3c4..c43d984c 100644 --- a/include/config.h +++ b/include/config.h @@ -97,6 +97,12 @@ /* Maximum allowed fails per CMP value. Default: 96 */ #define CMPLOG_FAIL_MAX 96 +/* + * Effective fuzzing with selective feeding inputs + */ + +#define MAX_EXTRA_SAN_BINARY 4 + /* -------------------------------------*/ /* Now non-cmplog configuration options */ /* -------------------------------------*/ @@ -504,6 +510,9 @@ #define CMPLOG_SHM_ENV_VAR "__AFL_CMPLOG_SHM_ID" +/* ASAN SHM ID */ +#define AFL_ASAN_FUZZ_SHM_ENV_VAR "__AFL_ASAN_SHM_ID" + /* CPU Affinity lockfile env var */ #define CPU_AFFINITY_ENV_VAR "__AFL_LOCKFILE" diff --git a/include/coverage-64.h b/include/coverage-64.h index aab79d79..a855b5ab 100644 --- a/include/coverage-64.h +++ b/include/coverage-64.h @@ -72,6 +72,22 @@ inline void classify_counts(afl_forkserver_t *fsrv) { } +inline void classify_counts_mem(u64* mem, u32 size) { + + u32 i = (size >> 3); + + while (i--) { + + /* Optimize for sparse bitmaps. */ + + if (unlikely(*mem)) { *mem = classify_word(*mem); } + + mem++; + + } + +} + /* Updates the virgin bits, then reflects whether a new count or a new tuple is * seen in ret. */ inline void discover_word(u8 *ret, u64 *current, u64 *virgin) { diff --git a/include/debug.h b/include/debug.h index e7cbdb5c..95e46891 100644 --- a/include/debug.h +++ b/include/debug.h @@ -357,9 +357,9 @@ static inline const char *colorfilter(const char *x) { do { \ \ if (res < 0) \ - PFATAL(x); \ + ABORT(x); \ else \ - FATAL(x); \ + ABORT(x); \ \ } while (0) diff --git a/include/envs.h b/include/envs.h index 05fa2d3c..3ef78f05 100644 --- a/include/envs.h +++ b/include/envs.h @@ -117,8 +117,8 @@ static char *afl_environment_variables[] = { "AFL_USE_UBSAN", "AFL_UBSAN_VERBOSE", "AFL_USE_TSAN", "AFL_USE_CFISAN", "AFL_CFISAN_VERBOSE", "AFL_USE_LSAN", "AFL_WINE_PATH", "AFL_NO_SNAPSHOT", "AFL_EXPAND_HAVOC_NOW", "AFL_USE_FASAN", "AFL_USE_QASAN", - "AFL_PRINT_FILENAMES", "AFL_PIZZA_MODE", "AFL_NO_FASTRESUME", NULL - + "AFL_PRINT_FILENAMES", "AFL_PIZZA_MODE", "AFL_NO_FASTRESUME", + "AFL_SAN_ABSTRACTION", "AFL_SAN_NO_INST", "AFL_SAN_RECOVER", NULL }; extern char *afl_environment_variables[]; diff --git a/include/forkserver.h b/include/forkserver.h index d3d0e086..7b915c18 100644 --- a/include/forkserver.h +++ b/include/forkserver.h @@ -159,6 +159,8 @@ typedef struct afl_forkserver { bool debug; /* debug mode? */ + u8 san_but_not_instrumented; /* Is it sanitizer enabled but not instrumented? */ + bool uses_crash_exitcode; /* Custom crash exitcode specified? */ u8 crash_exitcode; /* The crash exitcode specified */ @@ -167,6 +169,7 @@ typedef struct afl_forkserver { u8 *shmem_fuzz; /* allocated memory for fuzzing */ char *cmplog_binary; /* the name of the cmplog binary */ + char *asanfuzz_binary; /* the name of the ASAN binary */ /* persistent mode replay functionality */ u32 persistent_record; /* persistent replay setting */ diff --git a/include/hash.h b/include/hash.h index 5d56a108..1a1b571a 100644 --- a/include/hash.h +++ b/include/hash.h @@ -33,6 +33,8 @@ u32 hash32(u8 *key, u32 len, u32 seed); u64 hash64(u8 *key, u32 len, u64 seed); +u32 hash32_xxh32(u8 *key, u32 len, u32 seed); + #if 0 The following code is disabled because xxh3 is 30% faster diff --git a/include/sharedmem.h b/include/sharedmem.h index 036fa560..140ee266 100644 --- a/include/sharedmem.h +++ b/include/sharedmem.h @@ -51,6 +51,7 @@ typedef struct sharedmem { size_t map_size; /* actual allocated size */ int cmplog_mode; + int sanfuzz_mode; int shmemfuzz_mode; struct cmp_map *cmp_map; diff --git a/instrumentation/SanitizerCoverageLTO.so.cc b/instrumentation/SanitizerCoverageLTO.so.cc index 6ec84dcd..25eabcda 100644 --- a/instrumentation/SanitizerCoverageLTO.so.cc +++ b/instrumentation/SanitizerCoverageLTO.so.cc @@ -326,9 +326,15 @@ class ModuleSanitizerCoverageLTOLegacyPass : public ModulePass { .getPostDomTree(); }; - - return ModuleSancov.instrumentModule(M, DTCallback, PDTCallback); - + + if (!getenv("AFL_SAN_NO_INST")) { + return ModuleSancov.instrumentModule(M, DTCallback, PDTCallback); + } else { + if (getenv("AFL_DEBUG")) { + DEBUGF("Instrument disabled\n"); + } + return false; + } } private: @@ -380,8 +386,14 @@ PreservedAnalyses ModuleSanitizerCoverageLTO::run(Module &M, }; - if (ModuleSancov.instrumentModule(M, DTCallback, PDTCallback)) - return PreservedAnalyses::none(); + if (!getenv("AFL_SAN_NO_INST")) { + if (ModuleSancov.instrumentModule(M, DTCallback, PDTCallback)) + return PreservedAnalyses::none(); + } else { + if (debug) { + DEBUGF("Instrument disabled\n"); + } + } return PreservedAnalyses::all(); diff --git a/instrumentation/SanitizerCoveragePCGUARD.so.cc b/instrumentation/SanitizerCoveragePCGUARD.so.cc index fae33d27..1706ed4f 100644 --- a/instrumentation/SanitizerCoveragePCGUARD.so.cc +++ b/instrumentation/SanitizerCoveragePCGUARD.so.cc @@ -261,8 +261,16 @@ PreservedAnalyses ModuleSanitizerCoverageAFL::run(Module &M, }; - if (ModuleSancov.instrumentModule(M, DTCallback, PDTCallback)) - return PreservedAnalyses::none(); + // TODO: Support LTO or llvm classic? + // Note we still need afl-compiler-rt so we just disable the instrumentation here. + if (!getenv("AFL_SAN_NO_INST")) { + if (ModuleSancov.instrumentModule(M, DTCallback, PDTCallback)) + return PreservedAnalyses::none(); + } else { + if (getenv("AFL_DEBUG")) { + DEBUGF("Instrument disabled\n"); + } + } return PreservedAnalyses::all(); } diff --git a/instrumentation/afl-llvm-pass.so.cc b/instrumentation/afl-llvm-pass.so.cc index c599e957..fd2b116b 100644 --- a/instrumentation/afl-llvm-pass.so.cc +++ b/instrumentation/afl-llvm-pass.so.cc @@ -223,6 +223,22 @@ bool AFLCoverage::runOnModule(Module &M) { if (getenv("AFL_DEBUG")) debug = 1; +#if LLVM_VERSION_MAJOR >= 11 /* use new pass manager */ + if (getenv("AFL_SAN_NO_INST")) { + if (debug) { + fprintf(stderr, "Intrument disabled\n"); + } + return PA; + } +#else + if (getenv("AFL_SAN_NO_INST")) { + if (debug) { + fprintf(stderr, "Intrument disabled\n"); + } + return true; + } +#endif + if ((isatty(2) && !getenv("AFL_QUIET")) || getenv("AFL_DEBUG") != NULL) { SAYF(cCYA "afl-llvm-pass" VERSION cRST diff --git a/src/afl-cc.c b/src/afl-cc.c index b793a6fc..38a0ad98 100644 --- a/src/afl-cc.c +++ b/src/afl-cc.c @@ -246,6 +246,13 @@ static inline void insert_object(aflcc_state_t *aflcc, u8 *obj, u8 *fmt, /* Insert params into the new argv, make clang load the pass. */ static inline void load_llvm_pass(aflcc_state_t *aflcc, u8 *pass) { + if (getenv("AFL_SAN_NO_INST")) { + if (debug) { + DEBUGF("Instrument disabled\n"); + } + return; + } + #if LLVM_MAJOR >= 11 /* use new pass manager */ #if LLVM_MAJOR < 16 @@ -2063,6 +2070,10 @@ void add_sanitizers(aflcc_state_t *aflcc, char **envp) { aflcc->have_cfisan = 1; + if (getenv("AFL_SAN_RECOVER")) { + cc_params[cc_par_cnt++] = "-fsanitize-recover=all"; + } + } } @@ -2079,7 +2090,12 @@ void add_native_pcguard(aflcc_state_t *aflcc) { * anyway. */ if (aflcc->have_rust_asanrt) { return; } - + if (getenv("AFL_SAN_NO_INST")) { + if (debug) { + DEBUGF("Instrument disabled\n"); + } + return; + } /* If llvm-config doesn't figure out LLVM_MAJOR, just go on anyway and let compiler complain if doesn't work. */ @@ -2091,10 +2107,11 @@ void add_native_pcguard(aflcc_state_t *aflcc) { "pcguard instrumentation with pc-table requires LLVM 6.0.1+" " otherwise the compiler will fail"); #endif + if (aflcc->instrument_opt_mode & INSTRUMENT_OPT_CODECOV) { insert_param(aflcc, - "-fsanitize-coverage=trace-pc-guard,bb,no-prune,pc-table"); + "-fsanitize-coverage=trace-pc-guard,bb,no-prune,pc-table"); } else { @@ -2113,11 +2130,17 @@ void add_native_pcguard(aflcc_state_t *aflcc) { */ void add_optimized_pcguard(aflcc_state_t *aflcc) { +if (getenv("AFL_SAN_NO_INST")) { + if (debug) { + DEBUGF("Instrument disabled\n"); + } + return; +} + #if LLVM_MAJOR >= 13 #if defined __ANDROID__ || ANDROID - - insert_param(aflcc, "-fsanitize-coverage=trace-pc-guard"); - aflcc->instrument_mode = INSTRUMENT_LLVMNATIVE; + insert_param(aflcc, "-fsanitize-coverage=trace-pc-guard"); + aflcc->instrument_mode = INSTRUMENT_LLVMNATIVE; #else diff --git a/src/afl-forkserver.c b/src/afl-forkserver.c index bee7f1bd..8ea58b39 100644 --- a/src/afl-forkserver.c +++ b/src/afl-forkserver.c @@ -1918,18 +1918,21 @@ fsrv_run_result_t __attribute__((hot)) afl_fsrv_run_target( must prevent any earlier operations from venturing into that territory. */ + /* If the binary is not instrumented, we don't care about the coverage. Make it a bit faster */ + if (!fsrv->san_but_not_instrumented) { #ifdef __linux__ if (likely(!fsrv->nyx_mode)) { - memset(fsrv->trace_bits, 0, fsrv->map_size); - MEM_BARRIER(); + memset(fsrv->trace_bits, 0, fsrv->map_size); + MEM_BARRIER(); - } + } #else - memset(fsrv->trace_bits, 0, fsrv->map_size); - MEM_BARRIER(); + memset(fsrv->trace_bits, 0, fsrv->map_size); + MEM_BARRIER(); #endif + } /* we have the fork server (or faux server) up and running First, tell it if the previous run timed out. */ diff --git a/src/afl-fuzz-bitmap.c b/src/afl-fuzz-bitmap.c index fd75a822..42f2741b 100644 --- a/src/afl-fuzz-bitmap.c +++ b/src/afl-fuzz-bitmap.c @@ -25,6 +25,9 @@ #include "afl-fuzz.h" #include +#include +#include +#include "asanfuzz.h" #if !defined NAME_MAX #define NAME_MAX _XOPEN_NAME_MAX #endif @@ -297,6 +300,9 @@ void minimize_bits(afl_state_t *afl, u8 *dst, u8 *src) { u8 *describe_op(afl_state_t *afl, u8 new_bits, size_t max_description_len) { u8 is_timeout = 0; + u8 san_crash_only = (afl->san_case_status & SAN_CRASH_ONLY); + u8 non_cov_incr = (afl->san_case_status & NON_COV_INCREASE_BUG); + if (new_bits & 0xf0) { @@ -388,6 +394,10 @@ u8 *describe_op(afl_state_t *afl, u8 new_bits, size_t max_description_len) { if (new_bits == 2) { strcat(ret, ",+cov"); } + if (san_crash_only) { strcat(ret, ",+san"); } + + if (non_cov_incr) { strcat(ret, ",+noncov"); } + if (unlikely(strlen(ret) >= max_description_len)) FATAL("describe string is too long"); @@ -452,6 +462,19 @@ void write_crash_readme(afl_state_t *afl) { } +static void bitmap_set(u8* map, u32 index) { + map[index / 8] |= (1u << ( index % 8 )); +} + +// static u8 bitmap_clear(u8* map, u32 index) { +// map[index / 8] &= ~(1u << (index % 8)); +// } + +static u8 bitmap_read(u8* map, u32 index) { + return (map[index / 8] >> (index % 8)) & 1; +} + + /* Check if the result of an execve() during routine fuzzing is interesting, save or queue the input test case for further analysis if so. Returns 1 if entry is saved, 0 otherwise. */ @@ -484,6 +507,22 @@ u8 __attribute__((hot)) save_if_interesting(afl_state_t *afl, void *mem, need_hash = 1; s32 fd; u64 cksum = 0; + u32 cksum_simplified = 0, cksum_unique = 0; + u8 san_fault = 0; + u8 san_idx = 0; + u8 feed_san = 0; + u8 crashed = 0; + + afl->san_case_status = 0; + + /* Mask out var bytes */ + if (unlikely(afl->san_binary_length)) { + for (u32 i = 0; i < afl->fsrv.map_size; i++) { + if (afl->var_bytes[i] && afl->fsrv.trace_bits[i]) { + afl->fsrv.trace_bits[i] = 1; + } + } + } /* Update path frequency. */ @@ -503,29 +542,96 @@ u8 __attribute__((hot)) save_if_interesting(afl_state_t *afl, void *mem, } + /* Only "normal" inputs seem interested to us */ if (likely(fault == afl->crash_mode)) { - /* Keep only if there are new bits in the map, add to queue for - future fuzzing, etc. */ + if (unlikely(afl->san_binary_length) && likely(afl->san_abstraction == SIMPLIFY_TRACE)) { + memcpy(afl->san_fsrvs[0].trace_bits, afl->fsrv.trace_bits, afl->fsrv.map_size); + classify_counts_mem((u64*)afl->san_fsrvs[0].trace_bits, afl->fsrv.map_size); + simplify_trace(afl, afl->san_fsrvs[0].trace_bits); - if (likely(classified)) { + // cksum_simplified = hash64(afl->san_fsrvs[0].trace_bits, afl->fsrv.map_size, HASH_CONST); + cksum_simplified = hash32_xxh32(afl->san_fsrvs[0].trace_bits, afl->fsrv.map_size, HASH_CONST); - new_bits = has_new_bits(afl, afl->virgin_bits); + if ( unlikely(!bitmap_read(afl->simplitied_n_fuzz, cksum_simplified))) { + feed_san = 1; + bitmap_set(afl->simplitied_n_fuzz, cksum_simplified); + } - } else { + } + if (unlikely(afl->san_binary_length) && unlikely(afl->san_abstraction == COVERAGE_INCREASE)) { + + /* Check if the input increase the coverage */ new_bits = has_new_bits_unclassified(afl, afl->virgin_bits); - if (unlikely(new_bits)) { classified = 1; } + if (unlikely(new_bits)) { + feed_san = 1; + } + } + if (unlikely(afl->san_binary_length) && likely(afl->san_abstraction == UNIQUE_TRACE)) { + cksum_unique = hash32_xxh32(afl->fsrv.trace_bits, afl->fsrv.map_size, HASH_CONST); + if (unlikely(!bitmap_read(afl->n_fuzz_dup, cksum) && fault == afl->crash_mode)) { + feed_san = 1; + bitmap_set(afl->n_fuzz_dup, cksum_unique); + } + } + + if (feed_san) { + /* The input seems interested to other sanitizers, feed it into extra binaries. */ + + for (san_idx = 0; san_idx < afl->san_binary_length; san_idx++) { + + len = write_to_testcase(afl, &mem, len, 0); + san_fault = fuzz_run_target(afl, &afl->san_fsrvs[san_idx], afl->san_fsrvs[san_idx].exec_tmout); + + // DEBUGF("ASAN Result: %hhd\n", asan_fault); + + if (unlikely(san_fault && fault == afl->crash_mode)) { + /* sanitizers discovers distinct bugs! */ + afl->san_case_status |= SAN_CRASH_ONLY; + } + + if (san_fault == FSRV_RUN_CRASH) { + /* Treat this execution as fault detected by ASAN */ + // fault = san_fault; + + /* That's pretty enough, break to avoid more overhead. */ + break; + } else { + // or keep san_fault as ok + san_fault = FSRV_RUN_OK; + } + } + } + } + + /* If there is no crash, everything is fine. */ + if (likely(fault == afl->crash_mode)) { + + /* Keep only if there are new bits in the map, add to queue for + future fuzzing, etc. */ + if (!unlikely(afl->san_abstraction == COVERAGE_INCREASE && feed_san)) { + /* If we are in coverage increasing abstraction and have fed input to sanitizers, we are + sure it has new bits.*/ + new_bits = has_new_bits_unclassified(afl, afl->virgin_bits); } if (likely(!new_bits)) { - if (unlikely(afl->crash_mode)) { ++afl->total_crashes; } - return 0; - + if (san_fault == FSRV_RUN_OK) { + if (unlikely(afl->crash_mode)) { ++afl->total_crashes; } + return 0; + } else { + afl->san_case_status |= NON_COV_INCREASE_BUG; + fault = san_fault; + classified = new_bits; + goto may_save_fault; + } } + fault = san_fault; + classified = new_bits; save_to_queue: @@ -653,7 +759,7 @@ u8 __attribute__((hot)) save_if_interesting(afl_state_t *afl, void *mem, keeping = 1; } - +may_save_fault: switch (fault) { case FSRV_RUN_TMOUT: diff --git a/src/afl-fuzz-init.c b/src/afl-fuzz-init.c index e90495f3..7e8fdb95 100644 --- a/src/afl-fuzz-init.c +++ b/src/afl-fuzz-init.c @@ -2332,7 +2332,7 @@ void setup_dirs_fds(afl_state_t *afl) { afl->fsrv.plot_file, "# relative_time, cycles_done, cur_item, corpus_count, " "pending_total, pending_favs, map_size, saved_crashes, " - "saved_hangs, max_depth, execs_per_sec, total_execs, edges_found\n"); + "saved_hangs, max_depth, execs_per_sec, total_execs, edges_found, total_crashes, servers_count, san1_exec...\n"); } else { diff --git a/src/afl-fuzz-run.c b/src/afl-fuzz-run.c index a3787e5c..c40af235 100644 --- a/src/afl-fuzz-run.c +++ b/src/afl-fuzz-run.c @@ -33,6 +33,7 @@ #endif #include "cmplog.h" +#include "asanfuzz.h" #ifdef PROFILING u64 time_spent_working = 0; diff --git a/src/afl-fuzz-sanfuzz.c b/src/afl-fuzz-sanfuzz.c new file mode 100644 index 00000000..87879138 --- /dev/null +++ b/src/afl-fuzz-sanfuzz.c @@ -0,0 +1,43 @@ +/* + american fuzzy lop++ - cmplog execution routines + ------------------------------------------------ + + Originally written by Michal Zalewski + + Forkserver design by Jann Horn + + Now maintained by by Marc Heuse , + Heiko Eißfeldt and + Andrea Fioraldi + + Copyright 2016, 2017 Google Inc. All rights reserved. + Copyright 2019-2023 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: + + https://www.apache.org/licenses/LICENSE-2.0 + + Shared code to handle the shared memory. This is used by the fuzzer + as well the other components like afl-tmin, afl-showmap, etc... + + */ + +/* This file roughly folows afl-fuzz-asanfuzz */ + +#include + +#include "afl-fuzz.h" + +void sanfuzz_exec_child(afl_forkserver_t *fsrv, char **argv) { + + if (!fsrv->qemu_mode && !fsrv->frida_mode && argv[0] != fsrv->asanfuzz_binary) { + + argv[0] = fsrv->asanfuzz_binary; + + } + + execv(fsrv->target_path, argv); + +} \ No newline at end of file diff --git a/src/afl-fuzz-stats.c b/src/afl-fuzz-stats.c index a5785eb8..3cd80e3f 100644 --- a/src/afl-fuzz-stats.c +++ b/src/afl-fuzz-stats.c @@ -386,6 +386,7 @@ void write_stats_file(afl_state_t *afl, u32 t_bytes, double bitmap_cvg, "bitmap_cvg : %0.02f%%\n" "saved_crashes : %llu\n" "saved_hangs : %llu\n" + "total_tmout : %llu\n" "last_find : %llu\n" "last_crash : %llu\n" "last_hang : %llu\n" @@ -424,7 +425,7 @@ void write_stats_file(afl_state_t *afl, u32 t_bytes, double bitmap_cvg, afl->queued_discovered, afl->queued_imported, afl->queued_variable, afl->max_depth, afl->current_entry, afl->pending_favored, afl->pending_not_fuzzed, stability, bitmap_cvg, afl->saved_crashes, - afl->saved_hangs, afl->last_find_time / 1000, afl->last_crash_time / 1000, + afl->saved_hangs, afl->total_tmouts, afl->last_find_time / 1000, afl->last_crash_time / 1000, afl->last_hang_time / 1000, afl->fsrv.total_execs - afl->last_crash_execs, afl->fsrv.exec_tmout, afl->slowest_exec_ms, #ifndef __HAIKU__ @@ -458,6 +459,16 @@ void write_stats_file(afl_state_t *afl, u32 t_bytes, double bitmap_cvg, : "default", afl->orig_cmdline); + if (afl->san_binary_length) { + for (u8 i = 0; i < afl->san_binary_length; i++) { + fprintf(f, + "extra_binary : %s\n" + "total_execs : %llu\n", + afl->san_binary[i], + afl->san_fsrvs[i].total_execs); + } + } + /* ignore errors */ if (afl->debug) { @@ -541,7 +552,7 @@ void maybe_update_plot_file(afl_state_t *afl, u32 t_bytes, double bitmap_cvg, afl->plot_prev_md == afl->max_depth && afl->plot_prev_ed == afl->fsrv.total_execs) || !afl->queue_cycle || - get_cur_time() - afl->start_time <= 60000))) { + get_cur_time() - afl->start_time <= 1000))) { return; @@ -565,12 +576,19 @@ void maybe_update_plot_file(afl_state_t *afl, u32 t_bytes, double bitmap_cvg, fprintf(afl->fsrv.plot_file, "%llu, %llu, %u, %u, %u, %u, %0.02f%%, %llu, %llu, %u, %0.02f, %llu, " - "%u\n", + "%u, %llu, %u", ((afl->prev_run_time + get_cur_time() - afl->start_time) / 1000), afl->queue_cycle - 1, afl->current_entry, afl->queued_items, afl->pending_not_fuzzed, afl->pending_favored, bitmap_cvg, afl->saved_crashes, afl->saved_hangs, afl->max_depth, eps, - afl->plot_prev_ed, t_bytes); /* ignore errors */ + afl->plot_prev_ed, t_bytes, afl->total_crashes, (u32)afl->san_binary_length); /* ignore errors */ + + + for (u32 i = 0; i < afl->san_binary_length; i++) { + fprintf(afl->fsrv.plot_file, ", %llu", afl->san_fsrvs[i].total_execs); + } + + fprintf(afl->fsrv.plot_file, "\n"); fflush(afl->fsrv.plot_file); diff --git a/src/afl-fuzz.c b/src/afl-fuzz.c index 27c928fa..829e0b26 100644 --- a/src/afl-fuzz.c +++ b/src/afl-fuzz.c @@ -25,7 +25,9 @@ */ #include "afl-fuzz.h" +#include "alloc-inl.h" #include "cmplog.h" +#include "asanfuzz.h" #include "common.h" #include #include @@ -252,13 +254,15 @@ static void usage(u8 *argv0, int more_help) { " X=extreme transform solving, R=random colorization " "bytes.\n\n" "Fuzzing behavior settings:\n" - " -Z - sequential queue selection instead of weighted " + " -Z - sequential queue selection instead of weighted " "random\n" - " -N - do not unlink the fuzzing input file (for devices " + " -N - do not unlink the fuzzing input file (for devices " "etc.)\n" - " -n - fuzz without instrumentation (non-instrumented mode)\n" - " -x dict_file - fuzzer dictionary (see README.md, specify up to 4 " - "times)\n\n" + " -n - fuzz without instrumentation (non-instrumented mode)\n" + " -x dict_file - fuzzer dictionary (see README.md, specify up to 4 " + "times)\n" + " -a san_binary - Specify the extra sanitizer instrumented binaries,\n" + " can be specified multiple times.\n\n" "Test settings:\n" " -s seed - use a fixed seed for the RNG\n" @@ -549,6 +553,7 @@ int main(int argc, char **argv_orig, char **envp) { u8 mem_limit_given = 0, exit_1 = 0, debug = 0, extras_dir_cnt = 0 /*, have_p = 0*/; char *afl_preload; + char *san_abstraction; char *frida_afl_preload = NULL; char **use_argv; @@ -607,7 +612,7 @@ int main(int argc, char **argv_orig, char **envp) { // still available: HjJkKqruvwz while ((opt = getopt(argc, argv, - "+a:Ab:B:c:CdDe:E:f:F:g:G:hi:I:l:L:m:M:nNo:Op:P:QRs:S:t:" + "+aw:Ab:B:c:CdDe:E:f:F:g:G:hi:I:l:L:m:M:nNo:Op:P:QRs:S:t:" "T:uUV:WXx:YzZ")) > 0) { switch (opt) { @@ -741,6 +746,17 @@ int main(int argc, char **argv_orig, char **envp) { } + case 'w' : { + + if (afl->san_binary_length == MAX_EXTRA_SAN_BINARY) { + FATAL("Only %d extra sanitizer instrumented binaries are supported.", MAX_EXTRA_SAN_BINARY); + } + + afl->shm.sanfuzz_mode = 1; + afl->san_binary[afl->san_binary_length++] = optarg; + break; + } + case 's': { if (optarg == NULL) { FATAL("No valid seed provided. Got NULL."); } @@ -1727,6 +1743,9 @@ int main(int argc, char **argv_orig, char **envp) { } + afl->n_fuzz_dup = ck_alloc(N_FUZZ_SIZE_BITMAP * sizeof(u8)); + afl->simplitied_n_fuzz = ck_alloc(N_FUZZ_SIZE_BITMAP * sizeof(u8)); + if (get_afl_env("AFL_NO_FORKSRV")) { afl->no_forkserver = 1; } if (get_afl_env("AFL_NO_CPU_RED")) { afl->no_cpu_meter_red = 1; } if (get_afl_env("AFL_NO_ARITH")) { afl->no_arith = 1; } @@ -2396,6 +2415,10 @@ int main(int argc, char **argv_orig, char **envp) { if (!afl->fsrv.out_file) { setup_stdio_file(afl); } + for (u8 i = 0; i < afl->san_binary_length; i++) { + check_binary(afl, afl->san_binary[i]); + } + if (afl->cmplog_binary) { if (afl->unicorn_mode) { @@ -2554,6 +2577,109 @@ int main(int argc, char **argv_orig, char **envp) { } + san_abstraction = getenv("AFL_SAN_ABSTRACTION"); + if (!san_abstraction || !strcmp(san_abstraction, "unique_trace")) { + afl->san_abstraction = UNIQUE_TRACE; + } else if (!strcmp(san_abstraction, "coverage_increase")) { + afl->san_abstraction = COVERAGE_INCREASE; + } else if (!strcmp(san_abstraction, "simplify_trace")) { + afl->san_abstraction = SIMPLIFY_TRACE; + } else { + WARNF("Unkown abstraction: %s, fallback to unique trace.\n", san_abstraction); + afl->san_abstraction = UNIQUE_TRACE; + } + + afl->no_saving_crash_seed = false; + + if (!afl->san_binary_length && san_abstraction) { + WARNF("No extra sanitizer instrumented binaries are given, do you forget -a?\n"); + } + + /* Maybe merge with cmplog but much cmplog code was already copy-paste style... */ + if (afl->san_binary_length) { + + for (u8 i = 0; i < afl->san_binary_length; i++) { + ACTF("Spawning forkserver for %s", afl->san_binary[i]); + afl_fsrv_init_dup(&afl->san_fsrvs[i], &afl->fsrv); + + /* + * We don't really collect trace bits for sanitizer instrumented binary so we just allocate + * some dummy memory here. + */ + afl->san_fsrvs[i].trace_bits = ck_alloc(afl->fsrv.map_size + 8); /* One more u64 according to afl_shm_init*/ + afl->san_fsrvs[i].map_size = afl->fsrv.map_size; + afl->san_fsrvs[i].san_but_not_instrumented = 1; + + afl->san_fsrvs[i].cs_mode = afl->fsrv.cs_mode; + afl->san_fsrvs[i].qemu_mode = afl->fsrv.qemu_mode; + afl->san_fsrvs[i].frida_mode = afl->fsrv.frida_mode; + afl->san_fsrvs[i].asanfuzz_binary = afl->san_binary[i]; + afl->san_fsrvs[i].target_path = afl->san_binary[i]; + afl->san_fsrvs[i].init_child_func = sanfuzz_exec_child; + + afl->san_fsrvs[i].child_kill_signal = afl->fsrv.child_kill_signal; // I believe cmplog also needs this. + afl->san_fsrvs[i].fsrv_kill_signal = afl->fsrv.fsrv_kill_signal; + + if ((map_size <= DEFAULT_SHMEM_SIZE || + afl->san_fsrvs[i].map_size < map_size) && + !afl->non_instrumented_mode && !afl->fsrv.qemu_mode && + !afl->fsrv.frida_mode && !afl->unicorn_mode && !afl->fsrv.cs_mode && + !afl->afl_env.afl_skip_bin_check) { + + afl->san_fsrvs[i].map_size = MAX(map_size, (u32)DEFAULT_SHMEM_SIZE); + char vbuf[16]; + snprintf(vbuf, sizeof(vbuf), "%u", afl->san_fsrvs[i].map_size); + setenv("AFL_MAP_SIZE", vbuf, 1); + + } + + u32 new_map_size = + afl_fsrv_get_mapsize(&afl->san_fsrvs[i], afl->argv, &afl->stop_soon, + afl->afl_env.afl_debug_child); + + // only reinitialize when it needs to be larger + if (map_size < new_map_size) { + + OKF("Re-initializing maps to %u bytes due to SAN instrumented binary", new_map_size); + + afl->virgin_bits = ck_realloc(afl->virgin_bits, new_map_size); + afl->virgin_tmout = ck_realloc(afl->virgin_tmout, new_map_size); + afl->virgin_crash = ck_realloc(afl->virgin_crash, new_map_size); + afl->var_bytes = ck_realloc(afl->var_bytes, new_map_size); + afl->top_rated = + ck_realloc(afl->top_rated, new_map_size * sizeof(void *)); + afl->clean_trace = ck_realloc(afl->clean_trace, new_map_size); + afl->clean_trace_custom = + ck_realloc(afl->clean_trace_custom, new_map_size); + afl->first_trace = ck_realloc(afl->first_trace, new_map_size); + afl->map_tmp_buf = ck_realloc(afl->map_tmp_buf, new_map_size); + + afl_fsrv_kill(&afl->fsrv); + afl_fsrv_kill(&afl->san_fsrvs[i]); + afl_shm_deinit(&afl->shm); + + afl->san_fsrvs[i].map_size = new_map_size; // non-cmplog stays the same + map_size = new_map_size; + + setenv("AFL_NO_AUTODICT", "1", 1); // loaded already + afl->fsrv.trace_bits = + afl_shm_init(&afl->shm, new_map_size, afl->non_instrumented_mode); + ck_free(afl->san_fsrvs[i].trace_bits); + afl->san_fsrvs[i].trace_bits = ck_alloc(afl->fsrv.map_size + 8); + afl->san_fsrvs[i].map_size = afl->fsrv.map_size; + afl_fsrv_start(&afl->fsrv, afl->argv, &afl->stop_soon, + afl->afl_env.afl_debug_child); + afl_fsrv_start(&afl->san_fsrvs[i], afl->argv, &afl->stop_soon, + afl->afl_env.afl_debug_child); + + } + + OKF("forkserver for %s successfully started", afl->san_binary[i]); + } + + OKF("All forkservers for extra sanitizers instrumented binares are up and we have abstraction = %d", afl->san_abstraction); + } + if (afl->cmplog_binary) { ACTF("Spawning cmplog forkserver"); @@ -3450,6 +3576,13 @@ stop_fuzzing: afl_fsrv_deinit(&afl->fsrv); + + + for (u8 i = 0; i < afl->san_binary_length; i++) { + ck_free(afl->san_fsrvs[i].trace_bits); + afl_fsrv_deinit(&afl->san_fsrvs[i]); // TODO: Is this necessary? cmplog fksrv seems never deinit-ed? + } + /* remove tmpfile */ if (!afl->in_place_resume && afl->fsrv.out_file) { diff --git a/src/afl-performance.c b/src/afl-performance.c index b824fd35..3a2bc4b0 100644 --- a/src/afl-performance.c +++ b/src/afl-performance.c @@ -425,3 +425,15 @@ char *sha1_hex_for_file(const char *fname, u32 len) { } +#ifdef _DEBUG +u32 hash32_xxh32(u8 *key, u32 len, u32 seed) { + +#else +inline u32 hash32_xxh32(u8 *key, u32 len, u32 seed) { + +#endif + + (void)seed; + return (u32)XXH32(key, len, seed); + +} From a60003e3cfff2e76dcc4cf50a7f1fb2181586613 Mon Sep 17 00:00:00 2001 From: mio Date: Sun, 19 Jan 2025 23:51:44 +0800 Subject: [PATCH 02/25] Fix --- src/afl-cc.c | 6 +++--- src/afl-fuzz-bitmap.c | 1 - 2 files changed, 3 insertions(+), 4 deletions(-) diff --git a/src/afl-cc.c b/src/afl-cc.c index 38a0ad98..773e556b 100644 --- a/src/afl-cc.c +++ b/src/afl-cc.c @@ -247,7 +247,7 @@ static inline void insert_object(aflcc_state_t *aflcc, u8 *obj, u8 *fmt, /* Insert params into the new argv, make clang load the pass. */ static inline void load_llvm_pass(aflcc_state_t *aflcc, u8 *pass) { if (getenv("AFL_SAN_NO_INST")) { - if (debug) { + if (!be_quiet) { DEBUGF("Instrument disabled\n"); } return; @@ -2091,7 +2091,7 @@ void add_native_pcguard(aflcc_state_t *aflcc) { */ if (aflcc->have_rust_asanrt) { return; } if (getenv("AFL_SAN_NO_INST")) { - if (debug) { + if (!be_quiet) { DEBUGF("Instrument disabled\n"); } return; @@ -2131,7 +2131,7 @@ void add_native_pcguard(aflcc_state_t *aflcc) { void add_optimized_pcguard(aflcc_state_t *aflcc) { if (getenv("AFL_SAN_NO_INST")) { - if (debug) { + if (!be_quiet) { DEBUGF("Instrument disabled\n"); } return; diff --git a/src/afl-fuzz-bitmap.c b/src/afl-fuzz-bitmap.c index 42f2741b..5b489c67 100644 --- a/src/afl-fuzz-bitmap.c +++ b/src/afl-fuzz-bitmap.c @@ -511,7 +511,6 @@ u8 __attribute__((hot)) save_if_interesting(afl_state_t *afl, void *mem, u8 san_fault = 0; u8 san_idx = 0; u8 feed_san = 0; - u8 crashed = 0; afl->san_case_status = 0; From f4346e423dd6a2285e85717843b246879a871304 Mon Sep 17 00:00:00 2001 From: mio Date: Sun, 19 Jan 2025 23:53:03 +0800 Subject: [PATCH 03/25] No AFL_SAN_RECOVER --- src/afl-cc.c | 5 ----- 1 file changed, 5 deletions(-) diff --git a/src/afl-cc.c b/src/afl-cc.c index 773e556b..8e140a66 100644 --- a/src/afl-cc.c +++ b/src/afl-cc.c @@ -2069,11 +2069,6 @@ void add_sanitizers(aflcc_state_t *aflcc, char **envp) { } aflcc->have_cfisan = 1; - - if (getenv("AFL_SAN_RECOVER")) { - cc_params[cc_par_cnt++] = "-fsanitize-recover=all"; - } - } } From 967b81736d9e3d8cdb27f1dc8299875e28185301 Mon Sep 17 00:00:00 2001 From: mio Date: Sun, 19 Jan 2025 23:55:44 +0800 Subject: [PATCH 04/25] Fix pass --- instrumentation/afl-llvm-pass.so.cc | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/instrumentation/afl-llvm-pass.so.cc b/instrumentation/afl-llvm-pass.so.cc index fd2b116b..29146b70 100644 --- a/instrumentation/afl-llvm-pass.so.cc +++ b/instrumentation/afl-llvm-pass.so.cc @@ -228,7 +228,7 @@ bool AFLCoverage::runOnModule(Module &M) { if (debug) { fprintf(stderr, "Intrument disabled\n"); } - return PA; + return PreservedAnalyses::all(); } #else if (getenv("AFL_SAN_NO_INST")) { From efa2120935a5105d1a0561bacf9f2fb3b7b92156 Mon Sep 17 00:00:00 2001 From: mio Date: Mon, 20 Jan 2025 00:09:32 +0800 Subject: [PATCH 05/25] Update help --- src/afl-fuzz.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/afl-fuzz.c b/src/afl-fuzz.c index 829e0b26..c2b66966 100644 --- a/src/afl-fuzz.c +++ b/src/afl-fuzz.c @@ -261,7 +261,7 @@ static void usage(u8 *argv0, int more_help) { " -n - fuzz without instrumentation (non-instrumented mode)\n" " -x dict_file - fuzzer dictionary (see README.md, specify up to 4 " "times)\n" - " -a san_binary - Specify the extra sanitizer instrumented binaries,\n" + " -w san_binary - Specify the extra sanitizer instrumented binaries,\n" " can be specified multiple times.\n\n" "Test settings:\n" From c4d576b4e0b3ac351cdd0371321cc45b1084a5cf Mon Sep 17 00:00:00 2001 From: mio Date: Thu, 23 Jan 2025 19:04:53 +0800 Subject: [PATCH 06/25] Add myself to contributor --- README.md | 1 + 1 file changed, 1 insertion(+) diff --git a/README.md b/README.md index 78a14077..2e40f6d4 100644 --- a/README.md +++ b/README.md @@ -230,6 +230,7 @@ Thank you! (For people sending pull requests - please add yourself to this list Ruben ten Hove Joey Jiao fuzzah @intrigus-lgtm Yaakov Saxon Sergej Schumilo + Ziqiao Kong ``` From 5c239d9207668351e3e3e239e149f0195e523241 Mon Sep 17 00:00:00 2001 From: mio Date: Thu, 23 Jan 2025 19:11:45 +0800 Subject: [PATCH 07/25] nit with code formatt-ed --- include/afl-fuzz.h | 19 ++-- include/debug.h | 4 +- src/afl-cc.c | 36 ++++---- src/afl-fuzz-bitmap.c | 108 +++++++++++++++------- src/afl-fuzz-stats.c | 206 ++++++++++++++++++++++-------------------- src/afl-fuzz.c | 85 +++++++++++------ 6 files changed, 268 insertions(+), 190 deletions(-) diff --git a/include/afl-fuzz.h b/include/afl-fuzz.h index c53d08b4..b88fefbd 100644 --- a/include/afl-fuzz.h +++ b/include/afl-fuzz.h @@ -613,10 +613,9 @@ typedef struct afl_state { #define N_FUZZ_SIZE (1 << 21) #define N_FUZZ_SIZE_BITMAP (1 << 29) u32 *n_fuzz; - u8 *n_fuzz_dup; - u8 *classified_n_fuzz; - u8 *simplitied_n_fuzz; - + u8 *n_fuzz_dup; + u8 *classified_n_fuzz; + u8 *simplified_n_fuzz; volatile u8 stop_soon, /* Ctrl-C pressed? */ clear_screen; /* Window resized? */ @@ -735,12 +734,12 @@ typedef struct afl_state { afl_forkserver_t cmplog_fsrv; /* cmplog has its own little forkserver */ /* ASAN Fuzing */ - char *san_binary[MAX_EXTRA_SAN_BINARY]; - afl_forkserver_t san_fsrvs[MAX_EXTRA_SAN_BINARY]; - u8 san_binary_length; /* 0 means extra san binaries not given */ - u8 no_saving_crash_seed; - u32 san_case_status; - enum SanitizerAbstraction san_abstraction; + char *san_binary[MAX_EXTRA_SAN_BINARY]; + afl_forkserver_t san_fsrvs[MAX_EXTRA_SAN_BINARY]; + u8 san_binary_length; /* 0 means extra san binaries not given */ + u8 no_saving_crash_seed; + u32 san_case_status; + enum SanitizerAbstraction san_abstraction; /* Custom mutators */ struct custom_mutator *mutator; diff --git a/include/debug.h b/include/debug.h index 95e46891..e7cbdb5c 100644 --- a/include/debug.h +++ b/include/debug.h @@ -357,9 +357,9 @@ static inline const char *colorfilter(const char *x) { do { \ \ if (res < 0) \ - ABORT(x); \ + PFATAL(x); \ else \ - ABORT(x); \ + FATAL(x); \ \ } while (0) diff --git a/src/afl-cc.c b/src/afl-cc.c index 8e140a66..56627566 100644 --- a/src/afl-cc.c +++ b/src/afl-cc.c @@ -246,13 +246,13 @@ static inline void insert_object(aflcc_state_t *aflcc, u8 *obj, u8 *fmt, /* Insert params into the new argv, make clang load the pass. */ static inline void load_llvm_pass(aflcc_state_t *aflcc, u8 *pass) { - if (getenv("AFL_SAN_NO_INST")) { - if (!be_quiet) { - DEBUGF("Instrument disabled\n"); - } - return; - } + if (getenv("AFL_SAN_NO_INST")) { + + if (!be_quiet) { DEBUGF("SAND: Coverage instrumentation disabled\n"); } + return; + + } #if LLVM_MAJOR >= 11 /* use new pass manager */ #if LLVM_MAJOR < 16 @@ -2069,6 +2069,7 @@ void add_sanitizers(aflcc_state_t *aflcc, char **envp) { } aflcc->have_cfisan = 1; + } } @@ -2086,11 +2087,12 @@ void add_native_pcguard(aflcc_state_t *aflcc) { */ if (aflcc->have_rust_asanrt) { return; } if (getenv("AFL_SAN_NO_INST")) { - if (!be_quiet) { - DEBUGF("Instrument disabled\n"); - } + + if (!be_quiet) { DEBUGF("SAND: Coverage instrumentation disabled\n"); } return; + } + /* If llvm-config doesn't figure out LLVM_MAJOR, just go on anyway and let compiler complain if doesn't work. */ @@ -2106,7 +2108,7 @@ void add_native_pcguard(aflcc_state_t *aflcc) { if (aflcc->instrument_opt_mode & INSTRUMENT_OPT_CODECOV) { insert_param(aflcc, - "-fsanitize-coverage=trace-pc-guard,bb,no-prune,pc-table"); + "-fsanitize-coverage=trace-pc-guard,bb,no-prune,pc-table"); } else { @@ -2125,17 +2127,17 @@ void add_native_pcguard(aflcc_state_t *aflcc) { */ void add_optimized_pcguard(aflcc_state_t *aflcc) { -if (getenv("AFL_SAN_NO_INST")) { - if (!be_quiet) { - DEBUGF("Instrument disabled\n"); + if (getenv("AFL_SAN_NO_INST")) { + + if (!be_quiet) { DEBUGF("SAND: Coverage instrumentation disabled\n"); } + return; + } - return; -} #if LLVM_MAJOR >= 13 #if defined __ANDROID__ || ANDROID - insert_param(aflcc, "-fsanitize-coverage=trace-pc-guard"); - aflcc->instrument_mode = INSTRUMENT_LLVMNATIVE; + insert_param(aflcc, "-fsanitize-coverage=trace-pc-guard"); + aflcc->instrument_mode = INSTRUMENT_LLVMNATIVE; #else diff --git a/src/afl-fuzz-bitmap.c b/src/afl-fuzz-bitmap.c index 5b489c67..5b0966e7 100644 --- a/src/afl-fuzz-bitmap.c +++ b/src/afl-fuzz-bitmap.c @@ -303,7 +303,6 @@ u8 *describe_op(afl_state_t *afl, u8 new_bits, size_t max_description_len) { u8 san_crash_only = (afl->san_case_status & SAN_CRASH_ONLY); u8 non_cov_incr = (afl->san_case_status & NON_COV_INCREASE_BUG); - if (new_bits & 0xf0) { new_bits -= 0x80; @@ -462,18 +461,17 @@ void write_crash_readme(afl_state_t *afl) { } -static void bitmap_set(u8* map, u32 index) { - map[index / 8] |= (1u << ( index % 8 )); +static void bitmap_set(u8 *map, u32 index) { + + map[index / 8] |= (1u << (index % 8)); + } -// static u8 bitmap_clear(u8* map, u32 index) { -// map[index / 8] &= ~(1u << (index % 8)); -// } +static u8 bitmap_read(u8 *map, u32 index) { -static u8 bitmap_read(u8* map, u32 index) { return (map[index / 8] >> (index % 8)) & 1; -} +} /* Check if the result of an execve() during routine fuzzing is interesting, save or queue the input test case for further analysis if so. Returns 1 if @@ -508,19 +506,25 @@ u8 __attribute__((hot)) save_if_interesting(afl_state_t *afl, void *mem, s32 fd; u64 cksum = 0; u32 cksum_simplified = 0, cksum_unique = 0; - u8 san_fault = 0; - u8 san_idx = 0; - u8 feed_san = 0; + u8 san_fault = 0; + u8 san_idx = 0; + u8 feed_san = 0; afl->san_case_status = 0; /* Mask out var bytes */ if (unlikely(afl->san_binary_length)) { + for (u32 i = 0; i < afl->fsrv.map_size; i++) { + if (afl->var_bytes[i] && afl->fsrv.trace_bits[i]) { + afl->fsrv.trace_bits[i] = 1; + } + } + } /* Update path frequency. */ @@ -544,91 +548,126 @@ u8 __attribute__((hot)) save_if_interesting(afl_state_t *afl, void *mem, /* Only "normal" inputs seem interested to us */ if (likely(fault == afl->crash_mode)) { - if (unlikely(afl->san_binary_length) && likely(afl->san_abstraction == SIMPLIFY_TRACE)) { - memcpy(afl->san_fsrvs[0].trace_bits, afl->fsrv.trace_bits, afl->fsrv.map_size); - classify_counts_mem((u64*)afl->san_fsrvs[0].trace_bits, afl->fsrv.map_size); + if (unlikely(afl->san_binary_length) && + likely(afl->san_abstraction == SIMPLIFY_TRACE)) { + + memcpy(afl->san_fsrvs[0].trace_bits, afl->fsrv.trace_bits, + afl->fsrv.map_size); + classify_counts_mem((u64 *)afl->san_fsrvs[0].trace_bits, + afl->fsrv.map_size); simplify_trace(afl, afl->san_fsrvs[0].trace_bits); - // cksum_simplified = hash64(afl->san_fsrvs[0].trace_bits, afl->fsrv.map_size, HASH_CONST); - cksum_simplified = hash32_xxh32(afl->san_fsrvs[0].trace_bits, afl->fsrv.map_size, HASH_CONST); + // cksum_simplified = hash64(afl->san_fsrvs[0].trace_bits, + // afl->fsrv.map_size, HASH_CONST); + cksum_simplified = hash32_xxh32(afl->san_fsrvs[0].trace_bits, + afl->fsrv.map_size, HASH_CONST); + + if (unlikely(!bitmap_read(afl->simplified_n_fuzz, cksum_simplified))) { - if ( unlikely(!bitmap_read(afl->simplitied_n_fuzz, cksum_simplified))) { feed_san = 1; - bitmap_set(afl->simplitied_n_fuzz, cksum_simplified); + bitmap_set(afl->simplified_n_fuzz, cksum_simplified); + } } - if (unlikely(afl->san_binary_length) && unlikely(afl->san_abstraction == COVERAGE_INCREASE)) { + if (unlikely(afl->san_binary_length) && + unlikely(afl->san_abstraction == COVERAGE_INCREASE)) { /* Check if the input increase the coverage */ new_bits = has_new_bits_unclassified(afl, afl->virgin_bits); - if (unlikely(new_bits)) { - feed_san = 1; - } + if (unlikely(new_bits)) { feed_san = 1; } + } - if (unlikely(afl->san_binary_length) && likely(afl->san_abstraction == UNIQUE_TRACE)) { - cksum_unique = hash32_xxh32(afl->fsrv.trace_bits, afl->fsrv.map_size, HASH_CONST); - if (unlikely(!bitmap_read(afl->n_fuzz_dup, cksum) && fault == afl->crash_mode)) { - feed_san = 1; - bitmap_set(afl->n_fuzz_dup, cksum_unique); - } + if (unlikely(afl->san_binary_length) && + likely(afl->san_abstraction == UNIQUE_TRACE)) { + + cksum_unique = + hash32_xxh32(afl->fsrv.trace_bits, afl->fsrv.map_size, HASH_CONST); + if (unlikely(!bitmap_read(afl->n_fuzz_dup, cksum) && + fault == afl->crash_mode)) { + + feed_san = 1; + bitmap_set(afl->n_fuzz_dup, cksum_unique); + + } + } if (feed_san) { - /* The input seems interested to other sanitizers, feed it into extra binaries. */ + + /* The input seems interested to other sanitizers, feed it into extra + * binaries. */ for (san_idx = 0; san_idx < afl->san_binary_length; san_idx++) { len = write_to_testcase(afl, &mem, len, 0); - san_fault = fuzz_run_target(afl, &afl->san_fsrvs[san_idx], afl->san_fsrvs[san_idx].exec_tmout); + san_fault = fuzz_run_target(afl, &afl->san_fsrvs[san_idx], + afl->san_fsrvs[san_idx].exec_tmout); // DEBUGF("ASAN Result: %hhd\n", asan_fault); if (unlikely(san_fault && fault == afl->crash_mode)) { + /* sanitizers discovers distinct bugs! */ afl->san_case_status |= SAN_CRASH_ONLY; + } if (san_fault == FSRV_RUN_CRASH) { + /* Treat this execution as fault detected by ASAN */ // fault = san_fault; /* That's pretty enough, break to avoid more overhead. */ break; + } else { + // or keep san_fault as ok san_fault = FSRV_RUN_OK; + } + } + } + } /* If there is no crash, everything is fine. */ - if (likely(fault == afl->crash_mode)) { - + if (likely(fault == afl->crash_mode)) { + /* Keep only if there are new bits in the map, add to queue for future fuzzing, etc. */ if (!unlikely(afl->san_abstraction == COVERAGE_INCREASE && feed_san)) { - /* If we are in coverage increasing abstraction and have fed input to sanitizers, we are - sure it has new bits.*/ + + /* If we are in coverage increasing abstraction and have fed input to + sanitizers, we are sure it has new bits.*/ new_bits = has_new_bits_unclassified(afl, afl->virgin_bits); + } if (likely(!new_bits)) { if (san_fault == FSRV_RUN_OK) { + if (unlikely(afl->crash_mode)) { ++afl->total_crashes; } return 0; + } else { + afl->san_case_status |= NON_COV_INCREASE_BUG; fault = san_fault; classified = new_bits; goto may_save_fault; + } + } + fault = san_fault; classified = new_bits; @@ -758,6 +797,7 @@ u8 __attribute__((hot)) save_if_interesting(afl_state_t *afl, void *mem, keeping = 1; } + may_save_fault: switch (fault) { diff --git a/src/afl-fuzz-stats.c b/src/afl-fuzz-stats.c index 3cd80e3f..3a43373f 100644 --- a/src/afl-fuzz-stats.c +++ b/src/afl-fuzz-stats.c @@ -356,117 +356,121 @@ void write_stats_file(afl_state_t *afl, u32 t_bytes, double bitmap_cvg, 1000; if (!runtime_ms) { runtime_ms = 1; } - fprintf( - f, - "start_time : %llu\n" - "last_update : %llu\n" - "run_time : %llu\n" - "fuzzer_pid : %u\n" - "cycles_done : %llu\n" - "cycles_wo_finds : %llu\n" - "time_wo_finds : %llu\n" - "fuzz_time : %llu\n" - "calibration_time : %llu\n" - "cmplog_time : %llu\n" - "sync_time : %llu\n" - "trim_time : %llu\n" - "execs_done : %llu\n" - "execs_per_sec : %0.02f\n" - "execs_ps_last_min : %0.02f\n" - "corpus_count : %u\n" - "corpus_favored : %u\n" - "corpus_found : %u\n" - "corpus_imported : %u\n" - "corpus_variable : %u\n" - "max_depth : %u\n" - "cur_item : %u\n" - "pending_favs : %u\n" - "pending_total : %u\n" - "stability : %0.02f%%\n" - "bitmap_cvg : %0.02f%%\n" - "saved_crashes : %llu\n" - "saved_hangs : %llu\n" - "total_tmout : %llu\n" - "last_find : %llu\n" - "last_crash : %llu\n" - "last_hang : %llu\n" - "execs_since_crash : %llu\n" - "exec_timeout : %u\n" - "slowest_exec_ms : %u\n" - "peak_rss_mb : %lu\n" - "cpu_affinity : %d\n" - "edges_found : %u\n" - "total_edges : %u\n" - "var_byte_count : %u\n" - "havoc_expansion : %u\n" - "auto_dict_entries : %u\n" - "testcache_size : %llu\n" - "testcache_count : %u\n" - "testcache_evict : %u\n" - "afl_banner : %s\n" - "afl_version : " VERSION - "\n" - "target_mode : %s%s%s%s%s%s%s%s%s%s\n" - "command_line : %s\n", - (afl->start_time /*- afl->prev_run_time*/) / 1000, cur_time / 1000, - runtime_ms / 1000, (u32)getpid(), - afl->queue_cycle ? (afl->queue_cycle - 1) : 0, afl->cycles_wo_finds, - afl->longest_find_time > cur_time - afl->last_find_time - ? afl->longest_find_time / 1000 - : ((afl->start_time == 0 || afl->last_find_time == 0) - ? 0 - : (cur_time - afl->last_find_time) / 1000), - (runtime_ms - MIN(runtime_ms, overhead_ms)) / 1000, - afl->calibration_time_us / 1000000, afl->cmplog_time_us / 1000000, - afl->sync_time_us / 1000000, afl->trim_time_us / 1000000, - afl->fsrv.total_execs, - afl->fsrv.total_execs / ((double)(runtime_ms) / 1000), - afl->last_avg_execs_saved, afl->queued_items, afl->queued_favored, - afl->queued_discovered, afl->queued_imported, afl->queued_variable, - afl->max_depth, afl->current_entry, afl->pending_favored, - afl->pending_not_fuzzed, stability, bitmap_cvg, afl->saved_crashes, - afl->saved_hangs, afl->total_tmouts, afl->last_find_time / 1000, afl->last_crash_time / 1000, - afl->last_hang_time / 1000, afl->fsrv.total_execs - afl->last_crash_execs, - afl->fsrv.exec_tmout, afl->slowest_exec_ms, + fprintf(f, + "start_time : %llu\n" + "last_update : %llu\n" + "run_time : %llu\n" + "fuzzer_pid : %u\n" + "cycles_done : %llu\n" + "cycles_wo_finds : %llu\n" + "time_wo_finds : %llu\n" + "fuzz_time : %llu\n" + "calibration_time : %llu\n" + "cmplog_time : %llu\n" + "sync_time : %llu\n" + "trim_time : %llu\n" + "execs_done : %llu\n" + "execs_per_sec : %0.02f\n" + "execs_ps_last_min : %0.02f\n" + "corpus_count : %u\n" + "corpus_favored : %u\n" + "corpus_found : %u\n" + "corpus_imported : %u\n" + "corpus_variable : %u\n" + "max_depth : %u\n" + "cur_item : %u\n" + "pending_favs : %u\n" + "pending_total : %u\n" + "stability : %0.02f%%\n" + "bitmap_cvg : %0.02f%%\n" + "saved_crashes : %llu\n" + "saved_hangs : %llu\n" + "total_tmout : %llu\n" + "last_find : %llu\n" + "last_crash : %llu\n" + "last_hang : %llu\n" + "execs_since_crash : %llu\n" + "exec_timeout : %u\n" + "slowest_exec_ms : %u\n" + "peak_rss_mb : %lu\n" + "cpu_affinity : %d\n" + "edges_found : %u\n" + "total_edges : %u\n" + "var_byte_count : %u\n" + "havoc_expansion : %u\n" + "auto_dict_entries : %u\n" + "testcache_size : %llu\n" + "testcache_count : %u\n" + "testcache_evict : %u\n" + "afl_banner : %s\n" + "afl_version : " VERSION + "\n" + "target_mode : %s%s%s%s%s%s%s%s%s%s\n" + "command_line : %s\n", + (afl->start_time /*- afl->prev_run_time*/) / 1000, cur_time / 1000, + runtime_ms / 1000, (u32)getpid(), + afl->queue_cycle ? (afl->queue_cycle - 1) : 0, afl->cycles_wo_finds, + afl->longest_find_time > cur_time - afl->last_find_time + ? afl->longest_find_time / 1000 + : ((afl->start_time == 0 || afl->last_find_time == 0) + ? 0 + : (cur_time - afl->last_find_time) / 1000), + (runtime_ms - MIN(runtime_ms, overhead_ms)) / 1000, + afl->calibration_time_us / 1000000, afl->cmplog_time_us / 1000000, + afl->sync_time_us / 1000000, afl->trim_time_us / 1000000, + afl->fsrv.total_execs, + afl->fsrv.total_execs / ((double)(runtime_ms) / 1000), + afl->last_avg_execs_saved, afl->queued_items, afl->queued_favored, + afl->queued_discovered, afl->queued_imported, afl->queued_variable, + afl->max_depth, afl->current_entry, afl->pending_favored, + afl->pending_not_fuzzed, stability, bitmap_cvg, afl->saved_crashes, + afl->saved_hangs, afl->total_tmouts, afl->last_find_time / 1000, + afl->last_crash_time / 1000, afl->last_hang_time / 1000, + afl->fsrv.total_execs - afl->last_crash_execs, afl->fsrv.exec_tmout, + afl->slowest_exec_ms, #ifndef __HAIKU__ #ifdef __APPLE__ - (unsigned long int)(rus.ru_maxrss >> 20), + (unsigned long int)(rus.ru_maxrss >> 20), #else - (unsigned long int)(rus.ru_maxrss >> 10), + (unsigned long int)(rus.ru_maxrss >> 10), #endif #else - -1UL, + -1UL, #endif #ifdef HAVE_AFFINITY - afl->cpu_aff, + afl->cpu_aff, #else - -1, + -1, #endif - t_bytes, afl->fsrv.real_map_size, afl->var_byte_count, afl->expand_havoc, - afl->a_extras_cnt, afl->q_testcase_cache_size, - afl->q_testcase_cache_count, afl->q_testcase_evictions, afl->use_banner, - afl->unicorn_mode ? "unicorn" : "", afl->fsrv.qemu_mode ? "qemu " : "", - afl->fsrv.cs_mode ? "coresight" : "", - afl->non_instrumented_mode ? " non_instrumented " : "", - afl->no_forkserver ? "no_fsrv " : "", afl->crash_mode ? "crash " : "", - afl->persistent_mode ? "persistent " : "", - afl->shmem_testcase_mode ? "shmem_testcase " : "", - afl->deferred_mode ? "deferred " : "", - (afl->unicorn_mode || afl->fsrv.qemu_mode || afl->fsrv.cs_mode || - afl->non_instrumented_mode || afl->no_forkserver || afl->crash_mode || - afl->persistent_mode || afl->deferred_mode) - ? "" - : "default", - afl->orig_cmdline); + t_bytes, afl->fsrv.real_map_size, afl->var_byte_count, + afl->expand_havoc, afl->a_extras_cnt, afl->q_testcase_cache_size, + afl->q_testcase_cache_count, afl->q_testcase_evictions, + afl->use_banner, afl->unicorn_mode ? "unicorn" : "", + afl->fsrv.qemu_mode ? "qemu " : "", + afl->fsrv.cs_mode ? "coresight" : "", + afl->non_instrumented_mode ? " non_instrumented " : "", + afl->no_forkserver ? "no_fsrv " : "", afl->crash_mode ? "crash " : "", + afl->persistent_mode ? "persistent " : "", + afl->shmem_testcase_mode ? "shmem_testcase " : "", + afl->deferred_mode ? "deferred " : "", + (afl->unicorn_mode || afl->fsrv.qemu_mode || afl->fsrv.cs_mode || + afl->non_instrumented_mode || afl->no_forkserver || + afl->crash_mode || afl->persistent_mode || afl->deferred_mode) + ? "" + : "default", + afl->orig_cmdline); if (afl->san_binary_length) { + for (u8 i = 0; i < afl->san_binary_length; i++) { + fprintf(f, - "extra_binary : %s\n" - "total_execs : %llu\n", - afl->san_binary[i], - afl->san_fsrvs[i].total_execs); + "extra_binary : %s\n" + "total_execs : %llu\n", + afl->san_binary[i], afl->san_fsrvs[i].total_execs); + } + } /* ignore errors */ @@ -552,7 +556,7 @@ void maybe_update_plot_file(afl_state_t *afl, u32 t_bytes, double bitmap_cvg, afl->plot_prev_md == afl->max_depth && afl->plot_prev_ed == afl->fsrv.total_execs) || !afl->queue_cycle || - get_cur_time() - afl->start_time <= 1000))) { + get_cur_time() - afl->start_time <= 60000))) { return; @@ -581,11 +585,13 @@ void maybe_update_plot_file(afl_state_t *afl, u32 t_bytes, double bitmap_cvg, afl->queue_cycle - 1, afl->current_entry, afl->queued_items, afl->pending_not_fuzzed, afl->pending_favored, bitmap_cvg, afl->saved_crashes, afl->saved_hangs, afl->max_depth, eps, - afl->plot_prev_ed, t_bytes, afl->total_crashes, (u32)afl->san_binary_length); /* ignore errors */ - + afl->plot_prev_ed, t_bytes, afl->total_crashes, + (u32)afl->san_binary_length); /* ignore errors */ for (u32 i = 0; i < afl->san_binary_length; i++) { + fprintf(afl->fsrv.plot_file, ", %llu", afl->san_fsrvs[i].total_execs); + } fprintf(afl->fsrv.plot_file, "\n"); diff --git a/src/afl-fuzz.c b/src/afl-fuzz.c index c2b66966..3c7042e5 100644 --- a/src/afl-fuzz.c +++ b/src/afl-fuzz.c @@ -258,7 +258,8 @@ static void usage(u8 *argv0, int more_help) { "random\n" " -N - do not unlink the fuzzing input file (for devices " "etc.)\n" - " -n - fuzz without instrumentation (non-instrumented mode)\n" + " -n - fuzz without instrumentation (non-instrumented " + "mode)\n" " -x dict_file - fuzzer dictionary (see README.md, specify up to 4 " "times)\n" " -w san_binary - Specify the extra sanitizer instrumented binaries,\n" @@ -611,9 +612,10 @@ int main(int argc, char **argv_orig, char **envp) { afl->shmem_testcase_mode = 1; // we always try to perform shmem fuzzing // still available: HjJkKqruvwz - while ((opt = getopt(argc, argv, - "+aw:Ab:B:c:CdDe:E:f:F:g:G:hi:I:l:L:m:M:nNo:Op:P:QRs:S:t:" - "T:uUV:WXx:YzZ")) > 0) { + while ( + (opt = getopt(argc, argv, + "+aw:Ab:B:c:CdDe:E:f:F:g:G:hi:I:l:L:m:M:nNo:Op:P:QRs:S:t:" + "T:uUV:WXx:YzZ")) > 0) { switch (opt) { @@ -746,15 +748,19 @@ int main(int argc, char **argv_orig, char **envp) { } - case 'w' : { - + case 'w': { + if (afl->san_binary_length == MAX_EXTRA_SAN_BINARY) { - FATAL("Only %d extra sanitizer instrumented binaries are supported.", MAX_EXTRA_SAN_BINARY); + + FATAL("Only %d extra sanitizer instrumented binaries are supported.", + MAX_EXTRA_SAN_BINARY); + } - + afl->shm.sanfuzz_mode = 1; afl->san_binary[afl->san_binary_length++] = optarg; break; + } case 's': { @@ -1744,7 +1750,7 @@ int main(int argc, char **argv_orig, char **envp) { } afl->n_fuzz_dup = ck_alloc(N_FUZZ_SIZE_BITMAP * sizeof(u8)); - afl->simplitied_n_fuzz = ck_alloc(N_FUZZ_SIZE_BITMAP * sizeof(u8)); + afl->simplified_n_fuzz = ck_alloc(N_FUZZ_SIZE_BITMAP * sizeof(u8)); if (get_afl_env("AFL_NO_FORKSRV")) { afl->no_forkserver = 1; } if (get_afl_env("AFL_NO_CPU_RED")) { afl->no_cpu_meter_red = 1; } @@ -2416,7 +2422,9 @@ int main(int argc, char **argv_orig, char **envp) { if (!afl->fsrv.out_file) { setup_stdio_file(afl); } for (u8 i = 0; i < afl->san_binary_length; i++) { + check_binary(afl, afl->san_binary[i]); + } if (afl->cmplog_binary) { @@ -2579,34 +2587,50 @@ int main(int argc, char **argv_orig, char **envp) { san_abstraction = getenv("AFL_SAN_ABSTRACTION"); if (!san_abstraction || !strcmp(san_abstraction, "unique_trace")) { + afl->san_abstraction = UNIQUE_TRACE; + } else if (!strcmp(san_abstraction, "coverage_increase")) { + afl->san_abstraction = COVERAGE_INCREASE; + } else if (!strcmp(san_abstraction, "simplify_trace")) { + afl->san_abstraction = SIMPLIFY_TRACE; + } else { - WARNF("Unkown abstraction: %s, fallback to unique trace.\n", san_abstraction); + + WARNF("Unkown abstraction: %s, fallback to unique trace.\n", + san_abstraction); afl->san_abstraction = UNIQUE_TRACE; + } afl->no_saving_crash_seed = false; if (!afl->san_binary_length && san_abstraction) { - WARNF("No extra sanitizer instrumented binaries are given, do you forget -a?\n"); + + WARNF( + "No extra sanitizer instrumented binaries are given, do you forget " + "-a?\n"); + } - /* Maybe merge with cmplog but much cmplog code was already copy-paste style... */ + /* Maybe merge with cmplog but much cmplog code was already copy-paste + * style... */ if (afl->san_binary_length) { for (u8 i = 0; i < afl->san_binary_length; i++) { + ACTF("Spawning forkserver for %s", afl->san_binary[i]); afl_fsrv_init_dup(&afl->san_fsrvs[i], &afl->fsrv); - /* - * We don't really collect trace bits for sanitizer instrumented binary so we just allocate - * some dummy memory here. + /* + * We don't really collect trace bits for sanitizer instrumented binary so + * we just allocate some dummy memory here. */ - afl->san_fsrvs[i].trace_bits = ck_alloc(afl->fsrv.map_size + 8); /* One more u64 according to afl_shm_init*/ + afl->san_fsrvs[i].trace_bits = ck_alloc( + afl->fsrv.map_size + 8); /* One more u64 according to afl_shm_init*/ afl->san_fsrvs[i].map_size = afl->fsrv.map_size; afl->san_fsrvs[i].san_but_not_instrumented = 1; @@ -2616,12 +2640,13 @@ int main(int argc, char **argv_orig, char **envp) { afl->san_fsrvs[i].asanfuzz_binary = afl->san_binary[i]; afl->san_fsrvs[i].target_path = afl->san_binary[i]; afl->san_fsrvs[i].init_child_func = sanfuzz_exec_child; - - afl->san_fsrvs[i].child_kill_signal = afl->fsrv.child_kill_signal; // I believe cmplog also needs this. + + afl->san_fsrvs[i].child_kill_signal = + afl->fsrv.child_kill_signal; // I believe cmplog also needs this. afl->san_fsrvs[i].fsrv_kill_signal = afl->fsrv.fsrv_kill_signal; if ((map_size <= DEFAULT_SHMEM_SIZE || - afl->san_fsrvs[i].map_size < map_size) && + afl->san_fsrvs[i].map_size < map_size) && !afl->non_instrumented_mode && !afl->fsrv.qemu_mode && !afl->fsrv.frida_mode && !afl->unicorn_mode && !afl->fsrv.cs_mode && !afl->afl_env.afl_skip_bin_check) { @@ -2635,12 +2660,13 @@ int main(int argc, char **argv_orig, char **envp) { u32 new_map_size = afl_fsrv_get_mapsize(&afl->san_fsrvs[i], afl->argv, &afl->stop_soon, - afl->afl_env.afl_debug_child); + afl->afl_env.afl_debug_child); // only reinitialize when it needs to be larger if (map_size < new_map_size) { - OKF("Re-initializing maps to %u bytes due to SAN instrumented binary", new_map_size); + OKF("Re-initializing maps to %u bytes due to SAN instrumented binary", + new_map_size); afl->virgin_bits = ck_realloc(afl->virgin_bits, new_map_size); afl->virgin_tmout = ck_realloc(afl->virgin_tmout, new_map_size); @@ -2668,16 +2694,20 @@ int main(int argc, char **argv_orig, char **envp) { afl->san_fsrvs[i].trace_bits = ck_alloc(afl->fsrv.map_size + 8); afl->san_fsrvs[i].map_size = afl->fsrv.map_size; afl_fsrv_start(&afl->fsrv, afl->argv, &afl->stop_soon, - afl->afl_env.afl_debug_child); + afl->afl_env.afl_debug_child); afl_fsrv_start(&afl->san_fsrvs[i], afl->argv, &afl->stop_soon, - afl->afl_env.afl_debug_child); + afl->afl_env.afl_debug_child); } OKF("forkserver for %s successfully started", afl->san_binary[i]); + } - OKF("All forkservers for extra sanitizers instrumented binares are up and we have abstraction = %d", afl->san_abstraction); + OKF("All forkservers for extra sanitizers instrumented binares are up and " + "we have abstraction = %d", + afl->san_abstraction); + } if (afl->cmplog_binary) { @@ -3576,11 +3606,12 @@ stop_fuzzing: afl_fsrv_deinit(&afl->fsrv); - - for (u8 i = 0; i < afl->san_binary_length; i++) { + ck_free(afl->san_fsrvs[i].trace_bits); - afl_fsrv_deinit(&afl->san_fsrvs[i]); // TODO: Is this necessary? cmplog fksrv seems never deinit-ed? + afl_fsrv_deinit(&afl->san_fsrvs[i]); // TODO: Is this necessary? cmplog + // fksrv seems never deinit-ed? + } /* remove tmpfile */ From 99cf15bd30b7e858c8c88742a691512da5e8761f Mon Sep 17 00:00:00 2001 From: mio Date: Thu, 23 Jan 2025 19:12:48 +0800 Subject: [PATCH 08/25] Fix building --- include/coverage-64.h | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/include/coverage-64.h b/include/coverage-64.h index a855b5ab..6f658460 100644 --- a/include/coverage-64.h +++ b/include/coverage-64.h @@ -72,7 +72,7 @@ inline void classify_counts(afl_forkserver_t *fsrv) { } -inline void classify_counts_mem(u64* mem, u32 size) { +inline static void classify_counts_mem(u64 *mem, u32 size) { u32 i = (size >> 3); From f905087e8e6c50a07bd37743d1d1bc373f77b842 Mon Sep 17 00:00:00 2001 From: mio Date: Thu, 23 Jan 2025 19:17:44 +0800 Subject: [PATCH 09/25] Remove var bytes makeups --- src/afl-fuzz-bitmap.c | 15 --------------- 1 file changed, 15 deletions(-) diff --git a/src/afl-fuzz-bitmap.c b/src/afl-fuzz-bitmap.c index 5b0966e7..d089b696 100644 --- a/src/afl-fuzz-bitmap.c +++ b/src/afl-fuzz-bitmap.c @@ -512,21 +512,6 @@ u8 __attribute__((hot)) save_if_interesting(afl_state_t *afl, void *mem, afl->san_case_status = 0; - /* Mask out var bytes */ - if (unlikely(afl->san_binary_length)) { - - for (u32 i = 0; i < afl->fsrv.map_size; i++) { - - if (afl->var_bytes[i] && afl->fsrv.trace_bits[i]) { - - afl->fsrv.trace_bits[i] = 1; - - } - - } - - } - /* Update path frequency. */ /* Generating a hash on every input is super expensive. Bad idea and should From be3c665eeefebe388545df44edac68a527a60117 Mon Sep 17 00:00:00 2001 From: mio Date: Thu, 23 Jan 2025 23:18:35 +0800 Subject: [PATCH 10/25] Fix integration --- include/asanfuzz.h | 4 ++-- src/afl-fuzz.c | 28 +++++++++++++++------------- 2 files changed, 17 insertions(+), 15 deletions(-) diff --git a/include/asanfuzz.h b/include/asanfuzz.h index ef25b992..a2423e49 100644 --- a/include/asanfuzz.h +++ b/include/asanfuzz.h @@ -36,8 +36,8 @@ #define NON_COV_INCREASE_BUG (1 << 5) enum SanitizerAbstraction { - UNIQUE_TRACE = 0, // Feed all unique trace to sanitizers, the most sensitive - SIMPLIFY_TRACE, + SIMPLIFY_TRACE = 0, // Feed all unique trace to sanitizers, the most sensitive + UNIQUE_TRACE, COVERAGE_INCREASE // Feed all coverage increasing cases to sanitizers, the least sensitive }; diff --git a/src/afl-fuzz.c b/src/afl-fuzz.c index 3c7042e5..a295bab4 100644 --- a/src/afl-fuzz.c +++ b/src/afl-fuzz.c @@ -2243,6 +2243,14 @@ int main(int argc, char **argv_orig, char **envp) { } setup_cmdline_file(afl, argv + optind); + + // Let's check SAND sanitizers binaries a bit earlier + // so that we won't overwrite target_path. + // Lazymio: why does cmplog fsrv even work?! + for (u8 i = 0; i < afl->san_binary_length; i++) { + check_binary(afl, afl->san_binary[i]); + } + check_binary(afl, argv[optind]); u64 prev_target_hash = 0; @@ -2421,12 +2429,6 @@ int main(int argc, char **argv_orig, char **envp) { if (!afl->fsrv.out_file) { setup_stdio_file(afl); } - for (u8 i = 0; i < afl->san_binary_length; i++) { - - check_binary(afl, afl->san_binary[i]); - - } - if (afl->cmplog_binary) { if (afl->unicorn_mode) { @@ -2586,23 +2588,23 @@ int main(int argc, char **argv_orig, char **envp) { } san_abstraction = getenv("AFL_SAN_ABSTRACTION"); - if (!san_abstraction || !strcmp(san_abstraction, "unique_trace")) { + if (!san_abstraction || !strcmp(san_abstraction, "simplify_trace")) { - afl->san_abstraction = UNIQUE_TRACE; + afl->san_abstraction = SIMPLIFY_TRACE; } else if (!strcmp(san_abstraction, "coverage_increase")) { afl->san_abstraction = COVERAGE_INCREASE; - } else if (!strcmp(san_abstraction, "simplify_trace")) { + } else if (!strcmp(san_abstraction, "unique_trace")) { - afl->san_abstraction = SIMPLIFY_TRACE; + afl->san_abstraction = UNIQUE_TRACE; } else { WARNF("Unkown abstraction: %s, fallback to unique trace.\n", san_abstraction); - afl->san_abstraction = UNIQUE_TRACE; + afl->san_abstraction = SIMPLIFY_TRACE; } @@ -2622,7 +2624,7 @@ int main(int argc, char **argv_orig, char **envp) { for (u8 i = 0; i < afl->san_binary_length; i++) { - ACTF("Spawning forkserver for %s", afl->san_binary[i]); + ACTF("Spawning SAND forkserver for %s", afl->san_binary[i]); afl_fsrv_init_dup(&afl->san_fsrvs[i], &afl->fsrv); /* @@ -2700,7 +2702,7 @@ int main(int argc, char **argv_orig, char **envp) { } - OKF("forkserver for %s successfully started", afl->san_binary[i]); + OKF("SAND forkserver for %s successfully started", afl->san_binary[i]); } From 12a88c52dff110d204c0c40866084386e9c2e8e1 Mon Sep 17 00:00:00 2001 From: mio Date: Fri, 24 Jan 2025 22:24:10 +0800 Subject: [PATCH 11/25] Update docs --- docs/SAND.md | 133 +++++++++++++++++++++++++++++++++++++++ docs/fuzzing_in_depth.md | 3 + 2 files changed, 136 insertions(+) create mode 100644 docs/SAND.md diff --git a/docs/SAND.md b/docs/SAND.md new file mode 100644 index 00000000..74b9e790 --- /dev/null +++ b/docs/SAND.md @@ -0,0 +1,133 @@ +# SAND + +This repo will contain the implementation and paper for "SAND: Decoupling Sanitization from Fuzzing for Low Overhead". + +Preprint is available [here](./paper.pdf) and we will have camera-reday version uploaded shortly. + +The original AFL++ README is available [here](./README.AFLpp.md). + +## Branch organization + +- [sand](https://github.com/wtdcode/sand-aflpp/tree/sand): Forked from 4.05c, this branch contains the reference implementation used in our paper evaluation. Please use this for any evaluation against SAND. +- [upstream](https://github.com/wtdcode/sand-aflpp/tree/upstream): Forked from AFLplusplus mainstream, this branch contains our efforts to port SAND to the latest AFLplusplus. + +Track the upstream progress [here](https://github.com/AFLplusplus/AFLplusplus/pull/2288). + +## Basic Usage + +To use SAND, two binaries need to be built like cmplog: + +- The native binary without any sanitizer instrumented. SAND will run it during every loop and collect coverage. +- The sanitizer instrumented binary but _without AFL instrumentation_. SAND use this binary to check if an interesting input triggers sanitizers. Note, this binary must have fork servers enabled so `afl-clang-fast` is still needed for compilation. We will explain how to build this below. + +### Build + +Docker is highly recommened to reproduce the build. Simply do: + +``` +docker build -t sand . +``` + +If docker is not available, follow [The original AFL++ README](./README.AFLpp.md) to build SAND. + +### Simple Example + +The following steps assume you have built the docker image and started a container. If not, do it with: + +``` +docker run --rm -it sand +``` + +Take `test_instr.c` as an example, firstly build the native binary: + +``` +afl-clang-fast test-instr.c -o ./native +``` + +Then build the sanitizer instrumented binary but without AFL instrumentation: + +``` +AFL_SAN_NO_INST=1 AFL_USE_ASAN=1 afl-clang-fast test-instr.c -o ./san +``` + +Run SAND: + +``` +mkdir /tmp/test +echo "a" > /tmp/test/a +# Note the "-a ./san" parameter. +AFL_NO_UI=1 AFL_SKIP_CPUFREQ=1 afl-fuzz -i /tmp/test -o /tmp/out -a ./san -- ./native -f @@ +``` + +That's it! We also have detailed usage and caveats [here](./docs/SAND.md). + +## Evaluation Reproduction + +Building the evaluation images might take ~10-30 hours depending on your CPU and memory configuration. + +Some building process might has a dependency on the host kernel version and our evaluation environment is: + +```bash +> uname -a +Linux 5.4.0-200-generic #220-Ubuntu SMP Fri Sep 27 13:19:16 UTC 2024 x86_64 x86_64 x86_64 GNU/Linux +> cat /etc/issue + Ubuntu 20.04 (x86_64) +``` + +Please note, a recent kernel introduces changes that [might break ASAN instrumentation](https://github.com/google/sanitizers/issues/1614). This causes issues because the build process of some programs contains bootstraping, e.g. firstly building a helper program and further building other code by the helper program. ASAN instrumentation could crash these helper programs. A quick workaround is: `sudo sysctl vm.mmap_rnd_bits=28` + +### Build UNIFUZZ Image + +For easy experiment, we bundle all targets within the SAND image. Build it via: + +```bash +docker build -t sand-unifuzz -f Dockerfile.UNIFUZZ . +``` + +### ASAN-- baseline + +Firstly, build ASAN-- with: + +```bash +git clone https://github.com/wtdcode/ASAN-- debloat12 +cd debloat12 && docker build -t debloat12 -f Dockerfile_ASAN-- . +``` + +Likewise, build another all-in-one image for the base line: + +```bash +docker build -t sand-debloat12 -f Dockerfile.ASAN-- . +``` + +### Merge Image + +For ease of experiment, it is possible to merge the two images to a single image. We provide a script [merge.sh](./merge.sh) to do so: + +```bash +./merge.sh sand-debloat12 sand-unifuzz +``` + +This will produce an image `sand-debloat12-sand-unifuzz-merged`. + +### Start Experiments + +Assume you have built the two images above, you could refer to [experiments](./experiments/) to reproduce our fuzzing experiments. + +## Other AFLplusplus Schedule + +We use the default schedule of AFLplusplus but other schedule should be agnostic to our approach. We also evaluated SAND on the `mmopt` schedule of AFLplusplus and confirmed the similar performance. + +## Port SAND to other fuzzers + +The approach of SAND is rather simple and easy to port to other fuzzers. We once applied SAND on [Fuzzilli](https://github.com/wtdcode/sand_fuzzilli). Due to time and pages limitation, we didn't spend too much time exploring this direction. + +## Cite + +```bib +@inproceedings{sand, + author = {Ziqiao Kong, Shaohua Li, Heqing Huang, Zhendong Su}, + title = {SAND: Decoupling Sanitization from Fuzzing for LowOverhead}, + booktitle = {IEEE/ACM International Conference on Software Engineering (ICSE)}, + year = {2025}, +} +``` \ No newline at end of file diff --git a/docs/fuzzing_in_depth.md b/docs/fuzzing_in_depth.md index 39be12a7..8cd640bb 100644 --- a/docs/fuzzing_in_depth.md +++ b/docs/fuzzing_in_depth.md @@ -203,6 +203,9 @@ instances, so running more than one address sanitized target would be a waste. *IF* you are running a saturated corpus, then you can run up to half of the instances with sanitizers. +An alternative approach, [SAND](./SAND.md) could combine different sanitizers +while keeping high throughput with a few caveats. + The following sanitizers have built-in support in AFL++: * ASAN = Address SANitizer, finds memory corruption vulnerabilities like From 60b92dcef309e90c5c99ddf9c68e37b1f81c92d6 Mon Sep 17 00:00:00 2001 From: mio Date: Fri, 24 Jan 2025 22:25:15 +0800 Subject: [PATCH 12/25] Fix incorrect docs --- docs/SAND.md | 140 ++++++++++++--------------------------------------- 1 file changed, 31 insertions(+), 109 deletions(-) diff --git a/docs/SAND.md b/docs/SAND.md index 74b9e790..fb225380 100644 --- a/docs/SAND.md +++ b/docs/SAND.md @@ -1,133 +1,55 @@ -# SAND +# SAND: Decoupling Sanitization from Fuzzing for Low Overhead -This repo will contain the implementation and paper for "SAND: Decoupling Sanitization from Fuzzing for Low Overhead". +- Authors: Ziqiao Kong, Shaohua Li, Heqing Huang, Zhendong Su +- Maintainer: [Ziqiao Kong](https://github.com/wtdcode) +- Preprint: [arXiv](https://arxiv.org/abs/2402.16497), accepted by ICSE 2025 +- Main repo (for paper, reproduction, reference or cite): https://github.com/wtdcode/sand-aflpp -Preprint is available [here](./paper.pdf) and we will have camera-reday version uploaded shortly. +## Motivation -The original AFL++ README is available [here](./README.AFLpp.md). +SAND introduces a new fuzzing workflow greatly reduce (or even eliminate) sanitizer overhead and combine different sanitizers in one fuzzing compaign. -## Branch organization +The key point of SAND is that: sanitizing all inputs is wasting fuzzing power, because bug-triggering inputs are extremely rare (~1%). Obviously, not all inputs worth going through sanitizers. There, if we can somehow "predict" if an input could trigger bugs (defined as "execution pattern"), we could greatly save fuzzing power by only sanitizing a small propotion of all inputs. That's exactly how SAND works. -- [sand](https://github.com/wtdcode/sand-aflpp/tree/sand): Forked from 4.05c, this branch contains the reference implementation used in our paper evaluation. Please use this for any evaluation against SAND. -- [upstream](https://github.com/wtdcode/sand-aflpp/tree/upstream): Forked from AFLplusplus mainstream, this branch contains our efforts to port SAND to the latest AFLplusplus. +## Usage -Track the upstream progress [here](https://github.com/AFLplusplus/AFLplusplus/pull/2288). +For a normal fuzzing workflow, we have: -## Basic Usage +1. Build target project with AFL_USE_ASAN=1 to get `target_asan` +2. Fuzz the target with `afl-fuzz -i seeds -o out -- ./target_asan` -To use SAND, two binaries need to be built like cmplog: +For SAND fuzzing workflow, this is slightly different: -- The native binary without any sanitizer instrumented. SAND will run it during every loop and collect coverage. -- The sanitizer instrumented binary but _without AFL instrumentation_. SAND use this binary to check if an interesting input triggers sanitizers. Note, this binary must have fork servers enabled so `afl-clang-fast` is still needed for compilation. We will explain how to build this below. +1. Build target project _without_ any sanitizers to get `target_native`, which we will define as "native binary". +2. Build target project with AFL_USE_ASAN=1 AFL_SAN_NO_INST=1 to get `target_asan` +3. Fuzz the target with `afl-fuzz -i seeds -o out -w ./target_asan -- ./target_native` -### Build +Then you get: -Docker is highly recommened to reproduce the build. Simply do: +- almost the same performance as `afl-fuzz -i seeds -o out -- ./target_native` +- and the same bug-finding capability as `afl-fuzz -i seeds -o out -- ./target_asan` -``` -docker build -t sand . -``` +## Tips -If docker is not available, follow [The original AFL++ README](./README.AFLpp.md) to build SAND. +### Alternative execution patterns -### Simple Example +By default, SAND use the hash value of the simplified coverage map as execution pattern, i.e. if an input has a unique execution pattern, it will be sent to sanitizers for inspection. This shall work for most cases. However, if you are strongly worried about missing bugs, try `AFL_SAN_ABSTRACTION=unique_trace afl-fuzz ...`. Alternatively, you can also have `AFL_SAN_ABSTRACTION=coverage_increase`, which essentially equals to runing sanitizers on the corpus. -The following steps assume you have built the docker image and started a container. If not, do it with: +### Run as many sanitizers as possible -``` -docker run --rm -it sand -``` +Though we just used ASAN as an example, SAND works best if you provide more sanitizers, for example, UBSAN and MSAN. -Take `test_instr.c` as an example, firstly build the native binary: +You might do it via `afl-fuzz -i seeds -o out -w ./target_asan -w ./target_msan -w ./target_ubsan -- ./target_native`. Don't worry about the slow sanitizers like MSAN, SAND could still run very fast because only rather a few inputs are sanitized. -``` -afl-clang-fast test-instr.c -o ./native -``` +### Bugs types -Then build the sanitizer instrumented binary but without AFL instrumentation: +The execution pattern evaluated in our papers is targeting the common bugs, as ASAN/MSAN/UBSAN catches. For other bug types, you probably need to define new execution patterns and re-evaluate. -``` -AFL_SAN_NO_INST=1 AFL_USE_ASAN=1 afl-clang-fast test-instr.c -o ./san -``` +### My throughput is greatly impacted -Run SAND: +Generally, this is due to too many inputs going through sanitizers, for example, because of unstable targets. You could check stats from `plot_file` to confrim this. Try to switch execution patterns as stated above. -``` -mkdir /tmp/test -echo "a" > /tmp/test/a -# Note the "-a ./san" parameter. -AFL_NO_UI=1 AFL_SKIP_CPUFREQ=1 afl-fuzz -i /tmp/test -o /tmp/out -a ./san -- ./native -f @@ -``` +### Cmplog Compatibility -That's it! We also have detailed usage and caveats [here](./docs/SAND.md). +At this moment, SAND probably is not compatible with cmplog and we will fix this soon. -## Evaluation Reproduction - -Building the evaluation images might take ~10-30 hours depending on your CPU and memory configuration. - -Some building process might has a dependency on the host kernel version and our evaluation environment is: - -```bash -> uname -a -Linux 5.4.0-200-generic #220-Ubuntu SMP Fri Sep 27 13:19:16 UTC 2024 x86_64 x86_64 x86_64 GNU/Linux -> cat /etc/issue - Ubuntu 20.04 (x86_64) -``` - -Please note, a recent kernel introduces changes that [might break ASAN instrumentation](https://github.com/google/sanitizers/issues/1614). This causes issues because the build process of some programs contains bootstraping, e.g. firstly building a helper program and further building other code by the helper program. ASAN instrumentation could crash these helper programs. A quick workaround is: `sudo sysctl vm.mmap_rnd_bits=28` - -### Build UNIFUZZ Image - -For easy experiment, we bundle all targets within the SAND image. Build it via: - -```bash -docker build -t sand-unifuzz -f Dockerfile.UNIFUZZ . -``` - -### ASAN-- baseline - -Firstly, build ASAN-- with: - -```bash -git clone https://github.com/wtdcode/ASAN-- debloat12 -cd debloat12 && docker build -t debloat12 -f Dockerfile_ASAN-- . -``` - -Likewise, build another all-in-one image for the base line: - -```bash -docker build -t sand-debloat12 -f Dockerfile.ASAN-- . -``` - -### Merge Image - -For ease of experiment, it is possible to merge the two images to a single image. We provide a script [merge.sh](./merge.sh) to do so: - -```bash -./merge.sh sand-debloat12 sand-unifuzz -``` - -This will produce an image `sand-debloat12-sand-unifuzz-merged`. - -### Start Experiments - -Assume you have built the two images above, you could refer to [experiments](./experiments/) to reproduce our fuzzing experiments. - -## Other AFLplusplus Schedule - -We use the default schedule of AFLplusplus but other schedule should be agnostic to our approach. We also evaluated SAND on the `mmopt` schedule of AFLplusplus and confirmed the similar performance. - -## Port SAND to other fuzzers - -The approach of SAND is rather simple and easy to port to other fuzzers. We once applied SAND on [Fuzzilli](https://github.com/wtdcode/sand_fuzzilli). Due to time and pages limitation, we didn't spend too much time exploring this direction. - -## Cite - -```bib -@inproceedings{sand, - author = {Ziqiao Kong, Shaohua Li, Heqing Huang, Zhendong Su}, - title = {SAND: Decoupling Sanitization from Fuzzing for LowOverhead}, - booktitle = {IEEE/ACM International Conference on Software Engineering (ICSE)}, - year = {2025}, -} -``` \ No newline at end of file From 96dc77e4102aa7546e0e1be923e56d0607af0a0f Mon Sep 17 00:00:00 2001 From: mio Date: Fri, 24 Jan 2025 22:31:21 +0800 Subject: [PATCH 13/25] Fix typos --- docs/SAND.md | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/docs/SAND.md b/docs/SAND.md index fb225380..a2cd9451 100644 --- a/docs/SAND.md +++ b/docs/SAND.md @@ -9,7 +9,7 @@ SAND introduces a new fuzzing workflow greatly reduce (or even eliminate) sanitizer overhead and combine different sanitizers in one fuzzing compaign. -The key point of SAND is that: sanitizing all inputs is wasting fuzzing power, because bug-triggering inputs are extremely rare (~1%). Obviously, not all inputs worth going through sanitizers. There, if we can somehow "predict" if an input could trigger bugs (defined as "execution pattern"), we could greatly save fuzzing power by only sanitizing a small propotion of all inputs. That's exactly how SAND works. +The key point of SAND is that: sanitizing all inputs is wasting fuzzing power, because bug-triggering inputs are extremely rare (~1%). Obviously, not all inputs worth going through sanitizers. There, if we can somehow "predict" if an input could trigger bugs (defined as "execution pattern"), we could greatly save fuzzing power by only sanitizing a small proportion of all inputs. That's exactly how SAND works. ## Usage @@ -33,7 +33,7 @@ Then you get: ### Alternative execution patterns -By default, SAND use the hash value of the simplified coverage map as execution pattern, i.e. if an input has a unique execution pattern, it will be sent to sanitizers for inspection. This shall work for most cases. However, if you are strongly worried about missing bugs, try `AFL_SAN_ABSTRACTION=unique_trace afl-fuzz ...`. Alternatively, you can also have `AFL_SAN_ABSTRACTION=coverage_increase`, which essentially equals to runing sanitizers on the corpus. +By default, SAND use the hash value of the simplified coverage map as execution pattern, i.e. if an input has a unique execution pattern, it will be sent to sanitizers for inspection. This shall work for most cases. However, if you are strongly worried about missing bugs, try `AFL_SAN_ABSTRACTION=unique_trace afl-fuzz ...`. Alternatively, you can also have `AFL_SAN_ABSTRACTION=coverage_increase`, which essentially equals to running sanitizers on the corpus. ### Run as many sanitizers as possible @@ -47,7 +47,7 @@ The execution pattern evaluated in our papers is targeting the common bugs, as A ### My throughput is greatly impacted -Generally, this is due to too many inputs going through sanitizers, for example, because of unstable targets. You could check stats from `plot_file` to confrim this. Try to switch execution patterns as stated above. +Generally, this is due to too many inputs going through sanitizers, for example, because of unstable targets. You could check stats from `plot_file` to confirm this. Try to switch execution patterns as stated above. ### Cmplog Compatibility From 40991801bdd3b44bdba857be4132ac3cb5f87462 Mon Sep 17 00:00:00 2001 From: mio Date: Sun, 26 Jan 2025 15:28:56 +0800 Subject: [PATCH 14/25] Fix cmplog srv not deinit --- src/afl-fuzz.c | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/src/afl-fuzz.c b/src/afl-fuzz.c index a295bab4..904efd9a 100644 --- a/src/afl-fuzz.c +++ b/src/afl-fuzz.c @@ -3611,9 +3611,11 @@ stop_fuzzing: for (u8 i = 0; i < afl->san_binary_length; i++) { ck_free(afl->san_fsrvs[i].trace_bits); - afl_fsrv_deinit(&afl->san_fsrvs[i]); // TODO: Is this necessary? cmplog - // fksrv seems never deinit-ed? + afl_fsrv_deinit(&afl->san_fsrvs[i]); + } + if (afl->cmplog_binary) { + afl_fsrv_deinit(&afl->cmplog_fsrv); } /* remove tmpfile */ From c7c66bd0d6eff75ca12a04528a2cd0b0a441c6eb Mon Sep 17 00:00:00 2001 From: mio Date: Sun, 26 Jan 2025 15:34:56 +0800 Subject: [PATCH 15/25] Fix plot_file header --- src/afl-fuzz-init.c | 10 +++++++++- 1 file changed, 9 insertions(+), 1 deletion(-) diff --git a/src/afl-fuzz-init.c b/src/afl-fuzz-init.c index 7e8fdb95..1c0411ee 100644 --- a/src/afl-fuzz-init.c +++ b/src/afl-fuzz-init.c @@ -2332,7 +2332,15 @@ void setup_dirs_fds(afl_state_t *afl) { afl->fsrv.plot_file, "# relative_time, cycles_done, cur_item, corpus_count, " "pending_total, pending_favs, map_size, saved_crashes, " - "saved_hangs, max_depth, execs_per_sec, total_execs, edges_found, total_crashes, servers_count, san1_exec...\n"); + "saved_hangs, max_depth, execs_per_sec, total_execs, edges_found, total_crashes, servers_count"); + + if (afl->san_binary_length) { + for (u8 i = 0; i < afl->san_binary_length; i++) { + fprintf(afl->fsrv.plot_file, ", sand_fsrv%u_exec", i); + } + } + + fprintf(afl->fsrv.plot_file, "\n"); } else { From 604cf2cf80a034a31a3469db492f3240f18b58ec Mon Sep 17 00:00:00 2001 From: mio Date: Sun, 26 Jan 2025 15:46:25 +0800 Subject: [PATCH 16/25] Use AFL hash32 --- include/hash.h | 2 -- src/afl-fuzz-bitmap.c | 7 +++---- src/afl-performance.c | 15 +-------------- 3 files changed, 4 insertions(+), 20 deletions(-) diff --git a/include/hash.h b/include/hash.h index 1a1b571a..5d56a108 100644 --- a/include/hash.h +++ b/include/hash.h @@ -33,8 +33,6 @@ u32 hash32(u8 *key, u32 len, u32 seed); u64 hash64(u8 *key, u32 len, u64 seed); -u32 hash32_xxh32(u8 *key, u32 len, u32 seed); - #if 0 The following code is disabled because xxh3 is 30% faster diff --git a/src/afl-fuzz-bitmap.c b/src/afl-fuzz-bitmap.c index d089b696..4ea14bd3 100644 --- a/src/afl-fuzz-bitmap.c +++ b/src/afl-fuzz-bitmap.c @@ -542,9 +542,8 @@ u8 __attribute__((hot)) save_if_interesting(afl_state_t *afl, void *mem, afl->fsrv.map_size); simplify_trace(afl, afl->san_fsrvs[0].trace_bits); - // cksum_simplified = hash64(afl->san_fsrvs[0].trace_bits, - // afl->fsrv.map_size, HASH_CONST); - cksum_simplified = hash32_xxh32(afl->san_fsrvs[0].trace_bits, + // Note: Original SAND implementation used XXHASH32 + cksum_simplified = hash32(afl->san_fsrvs[0].trace_bits, afl->fsrv.map_size, HASH_CONST); if (unlikely(!bitmap_read(afl->simplified_n_fuzz, cksum_simplified))) { @@ -570,7 +569,7 @@ u8 __attribute__((hot)) save_if_interesting(afl_state_t *afl, void *mem, likely(afl->san_abstraction == UNIQUE_TRACE)) { cksum_unique = - hash32_xxh32(afl->fsrv.trace_bits, afl->fsrv.map_size, HASH_CONST); + hash32(afl->fsrv.trace_bits, afl->fsrv.map_size, HASH_CONST); if (unlikely(!bitmap_read(afl->n_fuzz_dup, cksum) && fault == afl->crash_mode)) { diff --git a/src/afl-performance.c b/src/afl-performance.c index 3a2bc4b0..2bb787e4 100644 --- a/src/afl-performance.c +++ b/src/afl-performance.c @@ -423,17 +423,4 @@ char *sha1_hex_for_file(const char *fname, u32 len) { ck_free(tmp); return hex; -} - -#ifdef _DEBUG -u32 hash32_xxh32(u8 *key, u32 len, u32 seed) { - -#else -inline u32 hash32_xxh32(u8 *key, u32 len, u32 seed) { - -#endif - - (void)seed; - return (u32)XXH32(key, len, seed); - -} +} \ No newline at end of file From b96047d7b0c7777342ec7681665e4a5cb70efde6 Mon Sep 17 00:00:00 2001 From: mio Date: Sun, 26 Jan 2025 15:51:35 +0800 Subject: [PATCH 17/25] Fix typo --- docs/SAND.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/SAND.md b/docs/SAND.md index a2cd9451..9306e491 100644 --- a/docs/SAND.md +++ b/docs/SAND.md @@ -7,7 +7,7 @@ ## Motivation -SAND introduces a new fuzzing workflow greatly reduce (or even eliminate) sanitizer overhead and combine different sanitizers in one fuzzing compaign. +SAND introduces a new fuzzing workflow that can greatly reduce (or even eliminate) sanitizer overhead and combine different sanitizers in one fuzzing compaign. The key point of SAND is that: sanitizing all inputs is wasting fuzzing power, because bug-triggering inputs are extremely rare (~1%). Obviously, not all inputs worth going through sanitizers. There, if we can somehow "predict" if an input could trigger bugs (defined as "execution pattern"), we could greatly save fuzzing power by only sanitizing a small proportion of all inputs. That's exactly how SAND works. From c64813b7d5d29ae08f8ac63a28ff92574e0c8e95 Mon Sep 17 00:00:00 2001 From: mio Date: Sun, 26 Jan 2025 15:52:29 +0800 Subject: [PATCH 18/25] Update more instructions --- docs/SAND.md | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/docs/SAND.md b/docs/SAND.md index 9306e491..94999eac 100644 --- a/docs/SAND.md +++ b/docs/SAND.md @@ -21,8 +21,8 @@ For a normal fuzzing workflow, we have: For SAND fuzzing workflow, this is slightly different: 1. Build target project _without_ any sanitizers to get `target_native`, which we will define as "native binary". -2. Build target project with AFL_USE_ASAN=1 AFL_SAN_NO_INST=1 to get `target_asan` -3. Fuzz the target with `afl-fuzz -i seeds -o out -w ./target_asan -- ./target_native` +2. Build target project with AFL_USE_ASAN=1 AFL_SAN_NO_INST=1 to get `target_asan`. Do note this step can be repeated for multiple sanitizers, like MSAN, UBSAN etc. +3. Fuzz the target with `afl-fuzz -i seeds -o out -w ./target_asan -- ./target_native`. Note `-w` can be specified multiple times. Then you get: From c7e919333ecbe0058d7419003b53e1e934560790 Mon Sep 17 00:00:00 2001 From: mio Date: Sun, 26 Jan 2025 18:40:37 +0800 Subject: [PATCH 19/25] Update help usage of afl-fuzz --- src/afl-fuzz.c | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/src/afl-fuzz.c b/src/afl-fuzz.c index 904efd9a..5d69f031 100644 --- a/src/afl-fuzz.c +++ b/src/afl-fuzz.c @@ -263,7 +263,8 @@ static void usage(u8 *argv0, int more_help) { " -x dict_file - fuzzer dictionary (see README.md, specify up to 4 " "times)\n" " -w san_binary - Specify the extra sanitizer instrumented binaries,\n" - " can be specified multiple times.\n\n" + " can be specified multiple times.\n" + " Read docs/SAND.md for details.\n\n" "Test settings:\n" " -s seed - use a fixed seed for the RNG\n" From 522da5e9b56fe445bd9dcfdd7f9ff09361420af1 Mon Sep 17 00:00:00 2001 From: mio Date: Mon, 27 Jan 2025 19:23:21 +0800 Subject: [PATCH 20/25] Update docs accordingly --- docs/SAND.md | 6 +++--- docs/fuzzing_in_depth.md | 4 ++-- 2 files changed, 5 insertions(+), 5 deletions(-) diff --git a/docs/SAND.md b/docs/SAND.md index 94999eac..15a7e094 100644 --- a/docs/SAND.md +++ b/docs/SAND.md @@ -20,8 +20,8 @@ For a normal fuzzing workflow, we have: For SAND fuzzing workflow, this is slightly different: -1. Build target project _without_ any sanitizers to get `target_native`, which we will define as "native binary". -2. Build target project with AFL_USE_ASAN=1 AFL_SAN_NO_INST=1 to get `target_asan`. Do note this step can be repeated for multiple sanitizers, like MSAN, UBSAN etc. +1. Build target project _without_ any sanitizers to get `target_native`, which we will define as "native binary". It is usually done by using `afl-clang-fast/lto(++)` to compile your project _without_ `AFL_USE_ASAN/UBSAN/MSAN`. +2. Build target project with AFL_USE_ASAN=1 AFL_SAN_NO_INST=1 to get `target_asan`. Do note this step can be repeated for multiple sanitizers, like MSAN, UBSAN etc. It is also possible to have ASAN and UBSAN to build together. 3. Fuzz the target with `afl-fuzz -i seeds -o out -w ./target_asan -- ./target_native`. Note `-w` can be specified multiple times. Then you get: @@ -33,7 +33,7 @@ Then you get: ### Alternative execution patterns -By default, SAND use the hash value of the simplified coverage map as execution pattern, i.e. if an input has a unique execution pattern, it will be sent to sanitizers for inspection. This shall work for most cases. However, if you are strongly worried about missing bugs, try `AFL_SAN_ABSTRACTION=unique_trace afl-fuzz ...`. Alternatively, you can also have `AFL_SAN_ABSTRACTION=coverage_increase`, which essentially equals to running sanitizers on the corpus. +By default, SAND uses the hash value of the simplified coverage map as execution pattern, i.e. if an input has a unique simplefied coverage map, it will be sent to sanitizers for inspection. This shall work for most cases. However, if you are strongly worried about missing bugs, try `AFL_SAN_ABSTRACTION=unique_trace afl-fuzz ...`, which filters inputs having a _unique coverage map_. Do note this significantly increases the number of inputs by 4-10 times, leading to much lower throughput. Alternatively, SAND also supports `AFL_SAN_ABSTRACTION=coverage_increase`, which essentially equals to running sanitizers on the corpus and thus having almost zero overhead, but at a cost of missing ~15% bugs in our evaluation. ### Run as many sanitizers as possible diff --git a/docs/fuzzing_in_depth.md b/docs/fuzzing_in_depth.md index 8cd640bb..45a5d382 100644 --- a/docs/fuzzing_in_depth.md +++ b/docs/fuzzing_in_depth.md @@ -203,8 +203,8 @@ instances, so running more than one address sanitized target would be a waste. *IF* you are running a saturated corpus, then you can run up to half of the instances with sanitizers. -An alternative approach, [SAND](./SAND.md) could combine different sanitizers -while keeping high throughput with a few caveats. +An alternative but more effective approach is to use [SAND](./SAND.md) which could +combine different sanitizers at a much higher throughput. The following sanitizers have built-in support in AFL++: From 5fa1a9c36541e08eb47475d214c9ba78bdee6d86 Mon Sep 17 00:00:00 2001 From: mio Date: Mon, 27 Jan 2025 19:24:31 +0800 Subject: [PATCH 21/25] Add inline --- src/afl-fuzz-bitmap.c | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/afl-fuzz-bitmap.c b/src/afl-fuzz-bitmap.c index 4ea14bd3..03475914 100644 --- a/src/afl-fuzz-bitmap.c +++ b/src/afl-fuzz-bitmap.c @@ -461,13 +461,13 @@ void write_crash_readme(afl_state_t *afl) { } -static void bitmap_set(u8 *map, u32 index) { +static inline void bitmap_set(u8 *map, u32 index) { map[index / 8] |= (1u << (index % 8)); } -static u8 bitmap_read(u8 *map, u32 index) { +static inline u8 bitmap_read(u8 *map, u32 index) { return (map[index / 8] >> (index % 8)) & 1; From 80e1a953785d5905b77863c949da133f423602b8 Mon Sep 17 00:00:00 2001 From: mio Date: Mon, 27 Jan 2025 19:24:46 +0800 Subject: [PATCH 22/25] Remove the unused field --- include/afl-fuzz.h | 1 - src/afl-fuzz.c | 2 -- 2 files changed, 3 deletions(-) diff --git a/include/afl-fuzz.h b/include/afl-fuzz.h index b88fefbd..dae2fc56 100644 --- a/include/afl-fuzz.h +++ b/include/afl-fuzz.h @@ -737,7 +737,6 @@ typedef struct afl_state { char *san_binary[MAX_EXTRA_SAN_BINARY]; afl_forkserver_t san_fsrvs[MAX_EXTRA_SAN_BINARY]; u8 san_binary_length; /* 0 means extra san binaries not given */ - u8 no_saving_crash_seed; u32 san_case_status; enum SanitizerAbstraction san_abstraction; diff --git a/src/afl-fuzz.c b/src/afl-fuzz.c index 5d69f031..39f13d6d 100644 --- a/src/afl-fuzz.c +++ b/src/afl-fuzz.c @@ -2609,8 +2609,6 @@ int main(int argc, char **argv_orig, char **envp) { } - afl->no_saving_crash_seed = false; - if (!afl->san_binary_length && san_abstraction) { WARNF( From 0b53a5a8aa655b2fa0845044b857dec8aa9a47c6 Mon Sep 17 00:00:00 2001 From: mio Date: Mon, 27 Jan 2025 19:30:52 +0800 Subject: [PATCH 23/25] Fix typo --- docs/SAND.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/SAND.md b/docs/SAND.md index 15a7e094..dbf99a82 100644 --- a/docs/SAND.md +++ b/docs/SAND.md @@ -9,7 +9,7 @@ SAND introduces a new fuzzing workflow that can greatly reduce (or even eliminate) sanitizer overhead and combine different sanitizers in one fuzzing compaign. -The key point of SAND is that: sanitizing all inputs is wasting fuzzing power, because bug-triggering inputs are extremely rare (~1%). Obviously, not all inputs worth going through sanitizers. There, if we can somehow "predict" if an input could trigger bugs (defined as "execution pattern"), we could greatly save fuzzing power by only sanitizing a small proportion of all inputs. That's exactly how SAND works. +The key point of SAND is that: sanitizing all inputs is wasting fuzzing power, because bug-triggering inputs are extremely rare (~1%). Obviously, not all inputs worth going through sanitizers. Therefore, if we can somehow "predict" if an input could trigger bugs (defined as "execution pattern"), we could greatly save fuzzing power by only sanitizing a small proportion of all inputs. That's exactly how SAND works. ## Usage From c78643f56642c24087f7ea2709cb395d9054b0c0 Mon Sep 17 00:00:00 2001 From: mio Date: Mon, 27 Jan 2025 19:37:21 +0800 Subject: [PATCH 24/25] Add an example --- docs/SAND.md | 33 ++++++++++++++++++++++++++++++++- 1 file changed, 32 insertions(+), 1 deletion(-) diff --git a/docs/SAND.md b/docs/SAND.md index dbf99a82..2157dbba 100644 --- a/docs/SAND.md +++ b/docs/SAND.md @@ -20,7 +20,7 @@ For a normal fuzzing workflow, we have: For SAND fuzzing workflow, this is slightly different: -1. Build target project _without_ any sanitizers to get `target_native`, which we will define as "native binary". It is usually done by using `afl-clang-fast/lto(++)` to compile your project _without_ `AFL_USE_ASAN/UBSAN/MSAN`. +1. Build target project _without_ any sanitizers to get `target_native`, which we will define as a "native binary". It is usually done by using `afl-clang-fast/lto(++)` to compile your project _without_ `AFL_USE_ASAN/UBSAN/MSAN`. 2. Build target project with AFL_USE_ASAN=1 AFL_SAN_NO_INST=1 to get `target_asan`. Do note this step can be repeated for multiple sanitizers, like MSAN, UBSAN etc. It is also possible to have ASAN and UBSAN to build together. 3. Fuzz the target with `afl-fuzz -i seeds -o out -w ./target_asan -- ./target_native`. Note `-w` can be specified multiple times. @@ -29,6 +29,37 @@ Then you get: - almost the same performance as `afl-fuzz -i seeds -o out -- ./target_native` - and the same bug-finding capability as `afl-fuzz -i seeds -o out -- ./target_asan` +## Example Workflow + +Take [test-instr.c](../test-instr.c) as an example. + +1. Build the native binary + +```bash +afl-clang-fast test-instr.c -o ./native +``` + +Just like the normal building process, except using `afl-clang-fast` + +2. Build the sanitizers-enabled binaries. + +```bash +AFL_SAN_NO_INST=1 AFL_USE_UBSAN=1 AFL_USE_ASAN=1 afl-clang-fast test-instr.c -o ./asanubsan +AFL_SAN_NO_INST=1 AFL_USE_MSAN=1 afl-clang-fast test-instr.c -o ./msan +``` + +Do note `AFL_SAN_NO_INST=1` is crucial, this enables forkservers but disables pc instrumentation. Do not reuse sanitizers-enabled binaries built _without_ `AFL_SAN_NO_INST=1`. This will mess up SAND execution pattern. + +3. Start fuzzing + +```bash +mkdir /tmp/test +echo "a" > /tmp/test/a +AFL_NO_UI=1 AFL_SKIP_CPUFREQ=1 afl-fuzz -i /tmp/test -o /tmp/out -w ./asanubsan -w ./msan -- ./native @@ +``` + +That's it! + ## Tips ### Alternative execution patterns From 72d248ae57ca45bb194a483c62252ca81be8aa4e Mon Sep 17 00:00:00 2001 From: mio Date: Mon, 27 Jan 2025 19:41:33 +0800 Subject: [PATCH 25/25] cmplog is working! --- docs/SAND.md | 7 +------ 1 file changed, 1 insertion(+), 6 deletions(-) diff --git a/docs/SAND.md b/docs/SAND.md index 2157dbba..38f88084 100644 --- a/docs/SAND.md +++ b/docs/SAND.md @@ -78,9 +78,4 @@ The execution pattern evaluated in our papers is targeting the common bugs, as A ### My throughput is greatly impacted -Generally, this is due to too many inputs going through sanitizers, for example, because of unstable targets. You could check stats from `plot_file` to confirm this. Try to switch execution patterns as stated above. - -### Cmplog Compatibility - -At this moment, SAND probably is not compatible with cmplog and we will fix this soon. - +Generally, this is due to too many inputs going through sanitizers, for example, because of unstable targets. You could check stats from `plot_file` to confirm this. Try to switch execution patterns as stated above. \ No newline at end of file