From e30a17be910e2edb88e38f99e183699f2bb182d6 Mon Sep 17 00:00:00 2001 From: vanhauser-thc Date: Sat, 26 Apr 2025 15:57:30 +0200 Subject: [PATCH 01/49] v4.33a init --- README.md | 2 +- docs/Changelog.md | 6 +++++- include/config.h | 2 +- 3 files changed, 7 insertions(+), 3 deletions(-) diff --git a/README.md b/README.md index b8c2d95e..f4c1439a 100644 --- a/README.md +++ b/README.md @@ -4,7 +4,7 @@ Release version: [4.32c](https://github.com/AFLplusplus/AFLplusplus/releases) -GitHub version: 4.32c +GitHub version: 4.33a Repository: [https://github.com/AFLplusplus/AFLplusplus](https://github.com/AFLplusplus/AFLplusplus) diff --git a/docs/Changelog.md b/docs/Changelog.md index 83fe673d..d6620ac5 100644 --- a/docs/Changelog.md +++ b/docs/Changelog.md @@ -3,6 +3,11 @@ This is the list of all noteworthy changes made in every public release of the tool. See README.md for the general instruction manual. + +### Version ++4.33a (dev) + - ... + + ### Version ++4.32c (release) - Fixed a bug where after a fast restart of a full fuzzed corpus afl-fuzz terminates with "need at least one valid input seed that does not crash" @@ -22,7 +27,6 @@ - frida_mode: - fixes for new MacOS + M4 hardware - ### Version ++4.31c (release) - SAND mode added (docs/SAND.md) for more effecient fuzzing with sanitizers (thanks to @wtdcode !) diff --git a/include/config.h b/include/config.h index 04e8efef..1ba436b5 100644 --- a/include/config.h +++ b/include/config.h @@ -26,7 +26,7 @@ /* Version string: */ // c = release, a = volatile github dev, e = experimental branch -#define VERSION "++4.32c" +#define VERSION "++4.33a" /****************************************************** * * From 30c93d132166f0d31751ba4eb1ed0e26e08252fe Mon Sep 17 00:00:00 2001 From: Kuang-che Wu Date: Sun, 27 Apr 2025 00:02:23 +0800 Subject: [PATCH 02/49] fix power schedules AFLFast power schedules regressed since v4.31c --- src/afl-fuzz-bitmap.c | 7 +++---- 1 file changed, 3 insertions(+), 4 deletions(-) diff --git a/src/afl-fuzz-bitmap.c b/src/afl-fuzz-bitmap.c index 3333a6d1..0dca34e6 100644 --- a/src/afl-fuzz-bitmap.c +++ b/src/afl-fuzz-bitmap.c @@ -469,8 +469,6 @@ void write_crash_readme(afl_state_t *afl) { u8 __attribute__((hot)) save_if_interesting(afl_state_t *afl, void *mem, u32 len, u8 fault) { - u8 classified = 0; - if (unlikely(len == 0)) { return 0; } if (unlikely(fault == FSRV_RUN_TMOUT && afl->afl_env.afl_ignore_timeouts)) { @@ -479,7 +477,6 @@ u8 __attribute__((hot)) save_if_interesting(afl_state_t *afl, void *mem, classify_counts(&afl->fsrv); u64 cksum = hash64(afl->fsrv.trace_bits, afl->fsrv.map_size, HASH_CONST); - classified = 1; // Saturated increment if (likely(afl->n_fuzz[cksum % N_FUZZ_SIZE] < 0xFFFFFFFF)) @@ -493,7 +490,8 @@ u8 __attribute__((hot)) save_if_interesting(afl_state_t *afl, void *mem, u8 fn[PATH_MAX]; u8 *queue_fn = ""; - u8 new_bits = 0, keeping = 0, res, is_timeout = 0, need_hash = 1; + u8 new_bits = 0, keeping = 0, res, is_timeout = 0, need_hash = 1, + classified = 0; s32 fd; u64 cksum = 0; u32 cksum_simplified = 0, cksum_unique = 0; @@ -511,6 +509,7 @@ u8 __attribute__((hot)) save_if_interesting(afl_state_t *afl, void *mem, classify_counts(&afl->fsrv); need_hash = 0; + classified = 1; cksum = hash64(afl->fsrv.trace_bits, afl->fsrv.map_size, HASH_CONST); From b0830163045fece89f989fed465908822f92ef0a Mon Sep 17 00:00:00 2001 From: Marian Buschsieweke Date: Mon, 28 Apr 2025 07:58:09 +0200 Subject: [PATCH 03/49] Define WORD_SIZE_64 for more 64-bit arches This enables 64-bit detection for the following additional systems: - [PowerPC64 (little endian)](https://en.wikipedia.org/wiki/Ppc64) - [S390x](https://en.wikipedia.org/wiki/S390x) - [LoongArch64](https://en.wikipedia.org/wiki/LoongArch64) --- include/config.h | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/include/config.h b/include/config.h index 1ba436b5..e5b14cb7 100644 --- a/include/config.h +++ b/include/config.h @@ -172,7 +172,8 @@ /* 64bit arch MACRO */ #if (defined(__x86_64__) || defined(__arm64__) || defined(__aarch64__) || \ - (defined(__riscv) && __riscv_xlen == 64)) + (defined(__riscv) && __riscv_xlen == 64) || defined(__powerpc64le__) || \ + defined(__s390x__) || defined(__loongarch64)) #define WORD_SIZE_64 1 #endif From 8a0e9c8915443de03c485abdf223046766f2a8a6 Mon Sep 17 00:00:00 2001 From: vanhauser-thc Date: Mon, 28 Apr 2025 14:03:12 +0200 Subject: [PATCH 04/49] minimum llvm 14 in docs --- docs/FAQ.md | 12 ++++++------ docs/INSTALL.md | 2 +- docs/features.md | 2 +- 3 files changed, 8 insertions(+), 8 deletions(-) diff --git a/docs/FAQ.md b/docs/FAQ.md index cc88cdc6..a3be128f 100644 --- a/docs/FAQ.md +++ b/docs/FAQ.md @@ -11,7 +11,7 @@ If you find an interesting or important question missing, submit it via AFL++ is a superior fork to Google's AFL - more speed, more and better mutations, more and better instrumentation, custom module support, etc. - American Fuzzy Lop (AFL) was developed by MichaƂ "lcamtuf" Zalewski starting + American Fuzzy Lop (AFL) was developed by Michal "lcamtuf" Zalewski starting in 2013/2014, and when he left Google end of 2017 he stopped developing it. At the end of 2019, the Google fuzzing team took over maintenance of AFL, @@ -284,14 +284,14 @@ If you find an interesting or important question missing, submit it via afl-cc/afl-clang-fast/afl-clang-lto: ``` - /prg/tmp/llvm-project/build/bin/clang-13: symbol lookup error: /usr/local/bin/../lib/afl//cmplog-instructions-pass.so: undefined symbol: _ZNK4llvm8TypeSizecvmEv - clang-13: error: unable to execute command: No such file or directory - clang-13: error: clang frontend command failed due to signal (use -v to see invocation) - clang version 13.0.0 (https://github.com/llvm/llvm-project 1d7cf550721c51030144f3cd295c5789d51c4aad) + /prg/tmp/llvm-project/build/bin/clang-18: symbol lookup error: /usr/local/bin/../lib/afl//cmplog-instructions-pass.so: undefined symbol: _ZNK4llvm8TypeSizecvmEv + clang-18: error: unable to execute command: No such file or directory + clang-18: error: clang frontend command failed due to signal (use -v to see invocation) + clang version 18.0.0 (https://github.com/llvm/llvm-project 1d7cf550721c51030144f3cd295c5789d51c4aad) Target: x86_64-unknown-linux-gnu Thread model: posix InstalledDir: /prg/tmp/llvm-project/build/bin - clang-13: note: diagnostic msg: + clang-18: note: diagnostic msg: ******************** ``` diff --git a/docs/INSTALL.md b/docs/INSTALL.md index 72510b51..b87068c9 100644 --- a/docs/INSTALL.md +++ b/docs/INSTALL.md @@ -21,7 +21,7 @@ If you want to build AFL++ yourself, you have many options. The easiest choice is to build and install everything: NOTE: depending on your Debian/Ubuntu/Kali/... release, replace `-14` with -whatever llvm version is available. We recommend llvm 13 or newer. +whatever llvm version is available. We recommend llvm 14 or newer. ```shell sudo apt-get update diff --git a/docs/features.md b/docs/features.md index e98d60fe..2732b907 100644 --- a/docs/features.md +++ b/docs/features.md @@ -45,7 +45,7 @@ E. CmpLog is our enhanced implementation, see [instrumentation/README.cmplog.md](../instrumentation/README.cmplog.md) -F. Similar and compatible to clang 13+ sancov sanitize-coverage-allow/deny but +F. Similar and compatible to clang 14+ sancov sanitize-coverage-allow/deny but for all llvm versions and all our compile modes, only instrument what should be instrumented, for more speed, directed fuzzing and less instability; see [instrumentation/README.instrument_list.md](../instrumentation/README.instrument_list.md) From f43116d9e05ce086583a6b6c522390f1851575ad Mon Sep 17 00:00:00 2001 From: vanhauser-thc Date: Mon, 28 Apr 2025 14:22:37 +0200 Subject: [PATCH 05/49] more classified count fixes --- src/afl-fuzz-bitmap.c | 31 ++++++++++++++++++++++--------- 1 file changed, 22 insertions(+), 9 deletions(-) diff --git a/src/afl-fuzz-bitmap.c b/src/afl-fuzz-bitmap.c index 0dca34e6..e7c79c69 100644 --- a/src/afl-fuzz-bitmap.c +++ b/src/afl-fuzz-bitmap.c @@ -490,14 +490,12 @@ u8 __attribute__((hot)) save_if_interesting(afl_state_t *afl, void *mem, u8 fn[PATH_MAX]; u8 *queue_fn = ""; - u8 new_bits = 0, keeping = 0, res, is_timeout = 0, need_hash = 1, - classified = 0; + u8 new_bits = 0, keeping = 0, res, is_timeout = 0, need_hash = 1; + u8 classified = 0; + u8 san_fault = 0, san_idx = 0, feed_san = 0; s32 fd; u64 cksum = 0; u32 cksum_simplified = 0, cksum_unique = 0; - u8 san_fault = 0; - u8 san_idx = 0; - u8 feed_san = 0; afl->san_case_status = 0; @@ -508,8 +506,8 @@ u8 __attribute__((hot)) save_if_interesting(afl_state_t *afl, void *mem, if (unlikely(afl->schedule >= FAST && afl->schedule <= RARE)) { classify_counts(&afl->fsrv); - need_hash = 0; classified = 1; + need_hash = 0; cksum = hash64(afl->fsrv.trace_bits, afl->fsrv.map_size, HASH_CONST); @@ -545,8 +543,17 @@ u8 __attribute__((hot)) save_if_interesting(afl_state_t *afl, void *mem, 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 (classified) { + + /* We could have classified the bits in SAND with COVERAGE_INCREASE */ + new_bits = has_new_bits(afl, afl->virgin_bits); + + } else { + + new_bits = has_new_bits_unclassified(afl, afl->virgin_bits); + classified = 1; + + } if (unlikely(new_bits)) { feed_san = 1; } @@ -635,6 +642,7 @@ u8 __attribute__((hot)) save_if_interesting(afl_state_t *afl, void *mem, } else { new_bits = has_new_bits_unclassified(afl, afl->virgin_bits); + classified = 1; } @@ -858,7 +866,12 @@ may_save_fault: } new_fault = fuzz_run_target(afl, &afl->fsrv, afl->hang_tmout); - classify_counts(&afl->fsrv); + if (!classified) { + + classify_counts(&afl->fsrv); + classified = 1; + + } /* A corner case that one user reported bumping into: increasing the timeout actually uncovers a crash. Make sure we don't discard it if From 5f7009d6e940d7f5b6c0a4f37a5d082853940695 Mon Sep 17 00:00:00 2001 From: vanhauser-thc Date: Mon, 28 Apr 2025 14:23:17 +0200 Subject: [PATCH 06/49] code format --- include/config.h | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/include/config.h b/include/config.h index e5b14cb7..b2a0764b 100644 --- a/include/config.h +++ b/include/config.h @@ -171,7 +171,7 @@ #define EXEC_TM_ROUND 20U /* 64bit arch MACRO */ -#if (defined(__x86_64__) || defined(__arm64__) || defined(__aarch64__) || \ +#if (defined(__x86_64__) || defined(__arm64__) || defined(__aarch64__) || \ (defined(__riscv) && __riscv_xlen == 64) || defined(__powerpc64le__) || \ defined(__s390x__) || defined(__loongarch64)) #define WORD_SIZE_64 1 From 6d5784e955438f1a33305ccdf209e9424a97bdcd Mon Sep 17 00:00:00 2001 From: vanhauser-thc Date: Mon, 28 Apr 2025 19:30:07 +0200 Subject: [PATCH 07/49] lower values for fuzzing state assessment --- src/afl-fuzz-stats.c | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/afl-fuzz-stats.c b/src/afl-fuzz-stats.c index 8c13cd65..40cd833f 100644 --- a/src/afl-fuzz-stats.c +++ b/src/afl-fuzz-stats.c @@ -54,13 +54,13 @@ char *get_fuzzing_state(afl_state_t *afl) { u64 percent_cur = last_find_100 / cur_run_time; u64 percent_total = last_find_100 / cur_total_run_time; - if (unlikely(percent_cur >= 80 && percent_total >= 80)) { + if (unlikely(percent_cur >= 75 && percent_total >= 75)) { if (unlikely(afl->afl_env.afl_exit_when_done)) { afl->stop_soon = 2; } return fuzzing_state[3]; - } else if (unlikely(percent_cur >= 55 && percent_total >= 55)) { + } else if (unlikely(percent_cur >= 50 && percent_total >= 50)) { return fuzzing_state[2]; From 6c70d68783fafb68a8f31cc89766ea0661c080db Mon Sep 17 00:00:00 2001 From: vanhauser-thc Date: Mon, 28 Apr 2025 22:09:58 +0200 Subject: [PATCH 08/49] update make flags --- GNUmakefile.gcc_plugin | 2 +- GNUmakefile.llvm | 2 +- utils/aflpp_driver/GNUmakefile | 9 +++++---- 3 files changed, 7 insertions(+), 6 deletions(-) diff --git a/GNUmakefile.gcc_plugin b/GNUmakefile.gcc_plugin index 0682c071..9cf16cfb 100644 --- a/GNUmakefile.gcc_plugin +++ b/GNUmakefile.gcc_plugin @@ -148,7 +148,7 @@ afl-common.o: ./src/afl-common.c $(PASSES): instrumentation/afl-gcc-common.h ./afl-gcc-pass.so: instrumentation/afl-gcc-pass.so.cc | test_deps - $(CXX) $(CXXEFLAGS) $(PLUGIN_FLAGS) -shared $< -o $@ + $(CXX) $(CXXEFLAGS) $(PLUGIN_FLAGS) -shared $< -o $@ $(LDFLAGS) ln -sf afl-cc afl-gcc-fast ln -sf afl-cc afl-g++-fast ln -sf afl-cc.8 afl-gcc-fast.8 diff --git a/GNUmakefile.llvm b/GNUmakefile.llvm index 73ec7cf7..ed91deaa 100644 --- a/GNUmakefile.llvm +++ b/GNUmakefile.llvm @@ -470,7 +470,7 @@ endif ./afl-ld-lto: src/afl-ld-lto.c ifeq "$(LLVM_LTO)" "1" - $(CC) $(CFLAGS) $(CPPFLAGS) $< -o $@ + $(CC) $(CFLAGS) $(CPPFLAGS) $< -o $@ $(LDFLAGS) ifdef IS_IOS @ldid -Sentitlements.plist $@ && echo "[+] Signed $@" || { echo "[-] Failed to sign $@"; } endif diff --git a/utils/aflpp_driver/GNUmakefile b/utils/aflpp_driver/GNUmakefile index fd39094c..3c8c637e 100644 --- a/utils/aflpp_driver/GNUmakefile +++ b/utils/aflpp_driver/GNUmakefile @@ -18,7 +18,8 @@ ifneq "" "$(LLVM_BINDIR)" endif endif -CFLAGS := -O3 -funroll-loops -g -fPIC +CFLAGS := -O3 -funroll-loops -g -fPIC -fno-lto +AR ?= ar ifdef IOS_SDK_PATH CFLAGS += -isysroot $(IOS_SDK_PATH) @@ -30,7 +31,7 @@ aflpp_driver.o: aflpp_driver.c -$(CC) -I. -I../../include $(CFLAGS) -c aflpp_driver.c libAFLDriver.a: aflpp_driver.o - @ar rc libAFLDriver.a aflpp_driver.o + @$(AR) rc libAFLDriver.a aflpp_driver.o @cp -vf libAFLDriver.a ../../ debug: @@ -38,13 +39,13 @@ debug: $(CC) -I../../include -D_DEBUG=\"1\" -g -funroll-loops -c aflpp_driver.c #$(CC) -S -emit-llvm -Wno-deprecated -I../../include $(CFLAGS) -D_DEBUG=\"1\" -c -o afl-performance.ll ../../src/afl-performance.c #$(CC) -S -emit-llvm -I../../include -D_DEBUG=\"1\" -g -funroll-loops -c aflpp_driver.c - ar rc libAFLDriver.a afl-performance.o aflpp_driver.o + $(AR) rc libAFLDriver.a afl-performance.o aflpp_driver.o aflpp_qemu_driver.o: aflpp_qemu_driver.c -$(CC) $(CFLAGS) -O0 -funroll-loops -c aflpp_qemu_driver.c libAFLQemuDriver.a: aflpp_qemu_driver.o - @-ar rc libAFLQemuDriver.a aflpp_qemu_driver.o + @-$(AR) rc libAFLQemuDriver.a aflpp_qemu_driver.o @-cp -vf libAFLQemuDriver.a ../../ aflpp_qemu_driver_hook.so: aflpp_qemu_driver_hook.o From 83a2a8aa141ee6a7937b670217d764862b8cb4aa Mon Sep 17 00:00:00 2001 From: Samuel Moelius Date: Mon, 28 Apr 2025 20:09:24 -0400 Subject: [PATCH 09/49] Color `AFL_NO_UI` output --- src/afl-fuzz-one.c | 5 +++-- src/afl-fuzz-stats.c | 9 +++++++-- 2 files changed, 10 insertions(+), 4 deletions(-) diff --git a/src/afl-fuzz-one.c b/src/afl-fuzz-one.c index dfd3c779..bccfe45f 100644 --- a/src/afl-fuzz-one.c +++ b/src/afl-fuzz-one.c @@ -411,11 +411,12 @@ u8 fuzz_one_original(afl_state_t *afl) { u_simplestring_time_diff(time_tmp, afl->prev_run_time + get_cur_time(), afl->start_time); ACTF( - "Fuzzing test case #%u (%u total, %llu crashes saved, state: %s, " + "Fuzzing test case #%u (%u total, %s%llu crashes saved%s, state: %s, " "mode=%s, " "perf_score=%0.0f, weight=%0.0f, favorite=%u, was_fuzzed=%u, " "exec_us=%llu, hits=%u, map=%u, ascii=%u, run_time=%s)...", - afl->current_entry, afl->queued_items, afl->saved_crashes, + afl->current_entry, afl->queued_items, + afl->saved_crashes != 0 ? cRED : "", afl->saved_crashes, cRST, get_fuzzing_state(afl), afl->fuzz_mode ? "exploit" : "explore", afl->queue_cur->perf_score, afl->queue_cur->weight, afl->queue_cur->favored, afl->queue_cur->was_fuzzed, diff --git a/src/afl-fuzz-stats.c b/src/afl-fuzz-stats.c index 8c13cd65..cfa87e48 100644 --- a/src/afl-fuzz-stats.c +++ b/src/afl-fuzz-stats.c @@ -28,8 +28,13 @@ #include "envs.h" #include -static char fuzzing_state[4][12] = {"started :-)", "in progress", "final phase", - "finished..."}; +// 7 is the number of characters in a color control code +// 11 is the number of characters in the fuzzing state itself +// 5 is the number of characters in `cRST` +// 1 is for the null character +static char fuzzing_state[4][7 + 11 + 5 + 1] = { + + "started :-)", "in progress", "final phase", cRED "finished..." cRST}; char *get_fuzzing_state(afl_state_t *afl) { From c4be2ec32f4a56720ff21595b5266670cc840e9d Mon Sep 17 00:00:00 2001 From: Scott Guest Date: Mon, 28 Apr 2025 20:52:15 -0700 Subject: [PATCH 10/49] utils/libdislocator/Makefile: Add missing override directive to CFLAGS+= --- README.md | 2 +- utils/libdislocator/Makefile | 5 ++--- 2 files changed, 3 insertions(+), 4 deletions(-) diff --git a/README.md b/README.md index f4c1439a..e1644f99 100644 --- a/README.md +++ b/README.md @@ -230,7 +230,7 @@ Thank you! (For people sending pull requests - please add yourself to this list fuzzah @intrigus-lgtm Yaakov Saxon Sergej Schumilo Ziqiao Kong Ryan Berger - Sangjun Park + Sangjun Park Scott Guest ``` diff --git a/utils/libdislocator/Makefile b/utils/libdislocator/Makefile index 6bfb79ec..b25346c6 100644 --- a/utils/libdislocator/Makefile +++ b/utils/libdislocator/Makefile @@ -19,11 +19,11 @@ HELPER_PATH = $(PREFIX)/lib/afl VERSION = $(shell grep '^\#define VERSION ' ../../config.h | cut -d '"' -f2) CFLAGS ?= -O3 -funroll-loops -D_FORTIFY_SOURCE=2 -CFLAGS += -I ../../include/ -Wall -g -Wno-pointer-sign +override CFLAGS += -I ../../include/ -Wall -g -Wno-pointer-sign CFLAGS_ADD=$(USEHUGEPAGE:1=-DUSEHUGEPAGE) CFLAGS_ADD += $(USENAMEDPAGE:1=-DUSENAMEDPAGE) -CFLAGS += $(CFLAGS_ADD) +override CFLAGS += $(CFLAGS_ADD) all: libdislocator.so @@ -41,4 +41,3 @@ install: all install -m 755 -d $${DESTDIR}$(HELPER_PATH) install -m 755 ../../libdislocator.so $${DESTDIR}$(HELPER_PATH) install -m 644 -T README.md $${DESTDIR}$(HELPER_PATH)/README.dislocator.md - From 04f2a2dd09ed76069ee281b6427ee27484860869 Mon Sep 17 00:00:00 2001 From: vanhauser-thc Date: Tue, 29 Apr 2025 15:55:14 +0200 Subject: [PATCH 11/49] ignore unnecessary warnings for tools --- custom_mutators/aflpp/standalone/Makefile | 2 +- custom_mutators/autotokens/standalone/Makefile | 17 +++++++++-------- .../standalone/autotokens-standalone.c | 17 +++++++++++++++-- 3 files changed, 25 insertions(+), 11 deletions(-) diff --git a/custom_mutators/aflpp/standalone/Makefile b/custom_mutators/aflpp/standalone/Makefile index d7bc840d..b01adf07 100644 --- a/custom_mutators/aflpp/standalone/Makefile +++ b/custom_mutators/aflpp/standalone/Makefile @@ -4,7 +4,7 @@ CFLAGS = -O3 -funroll-loops -fPIC all: aflpp-standalone aflpp-standalone: aflpp-standalone.c - $(CC) $(CFLAGS) -DBIN_PATH=\"foo\" -I../../../include -I. -o aflpp-standalone aflpp-standalone.c ../../../src/afl-performance.c ../../../src/afl-fuzz-extras.c ../../../src/afl-common.c + $(CC) $(CFLAGS) -w -DBIN_PATH=\"foo\" -I../../../include -I. -o aflpp-standalone aflpp-standalone.c ../../../src/afl-performance.c ../../../src/afl-fuzz-extras.c ../../../src/afl-common.c clean: rm -f *.o *~ aflpp-standalone core diff --git a/custom_mutators/autotokens/standalone/Makefile b/custom_mutators/autotokens/standalone/Makefile index 8d5baf13..36550b76 100644 --- a/custom_mutators/autotokens/standalone/Makefile +++ b/custom_mutators/autotokens/standalone/Makefile @@ -1,19 +1,20 @@ -CFLAGS = -g -O3 -funroll-loops -fPIC -D_STANDALONE_MODULE=1 -Wno-implicit-function-declaration +CFLAGS = -g -O3 -funroll-loops -fPIC -D_STANDALONE_MODULE=1 -Wno-pointer-sign CXXFLAGS= -g -O3 -funroll-loops -fPIC -D_STANDALONE_MODULE=1 all: autotokens-standalone autotokens.o: ../autotokens.cpp - $(CXX) $(CXXFLAGS) -I../../../include -I. -I../.. -c ../autotokens.cpp + $(CXX) $(CXXFLAGS) -g -I../../../include -I. -I../.. -c ../autotokens.cpp autotokens-standalone: autotokens-standalone.c autotokens.o - $(CC) $(CFLAGS) -DBIN_PATH=\"foo\" -I../../../include -I. -c autotokens-standalone.c - $(CC) $(CFLAGS) -DBIN_PATH=\"foo\" -I../../../include -I. -c ../../../src/afl-performance.c - $(CC) $(CFLAGS) -DBIN_PATH=\"foo\" -I../../../include -I. -c ../../../src/afl-fuzz-extras.c - $(CC) $(CFLAGS) -DBIN_PATH=\"foo\" -I../../../include -I. -c ../../../src/afl-fuzz-queue.c - $(CC) $(CFLAGS) -DBIN_PATH=\"foo\" -I../../../include -I. -c ../../../src/afl-common.c - $(CXX) $(CFLAGS) -DBIN_PATH=\"foo\" -I../../../include -I. -o autotokens-standalone *.o + $(CC) $(CFLAGS) -g -DBIN_PATH=\"foo\" -I../../../include -I. -c autotokens-standalone.c + $(CC) $(CFLAGS) -g -DBIN_PATH=\"foo\" -I../../../include -I. -c ../../../src/afl-performance.c + $(CC) $(CFLAGS) -g -DBIN_PATH=\"foo\" -I../../../include -I. -c ../../../src/afl-fuzz-extras.c + $(CC) $(CFLAGS) -g -DBIN_PATH=\"foo\" -I../../../include -I. -c ../../../src/afl-fuzz-queue.c + $(CC) $(CFLAGS) -g -DBIN_PATH=\"foo\" -I../../../include -I. -c ../../../src/afl-common.c + $(CXX) $(CFLAGS) -g -DBIN_PATH=\"foo\" -I../../../include -I. -o autotokens-standalone *.o + @rm -f ../../../src/afl-common.o ../../../src/afl-fuzz-queue.o ../../../src/afl-fuzz-extras.o ../../../src/afl-performance.o clean: rm -f *.o *~ autotokens-standalone core diff --git a/custom_mutators/autotokens/standalone/autotokens-standalone.c b/custom_mutators/autotokens/standalone/autotokens-standalone.c index e7a09cb3..794cbed6 100644 --- a/custom_mutators/autotokens/standalone/autotokens-standalone.c +++ b/custom_mutators/autotokens/standalone/autotokens-standalone.c @@ -1,15 +1,28 @@ #include "afl-fuzz.h" #include "afl-mutations.h" +#include "forkserver.h" #include #include static int max_havoc = 16, verbose; -static unsigned char *dict, *mh = "16"; +static char _mh[4] = "16"; +static char *dict, *mh = _mh; extern int module_disabled; void *afl_custom_init(afl_state_t *, unsigned int); +u8 afl_custom_queue_get(void *data, const u8 *filename); +size_t afl_custom_fuzz(void *data, u8 *buf, size_t buf_size, u8 **out_buf, + u8 *add_buf, size_t add_buf_size, size_t max_size); + +u32 write_to_testcase(afl_state_t *afl, void **mem, u32 a, u32 b) { + return 0; +} +fsrv_run_result_t fuzz_run_target(afl_state_t *afl, afl_forkserver_t *fsrv, + u32 i) { +return FSRV_RUN_OK; + } int main(int argc, char *argv[]) { @@ -144,7 +157,7 @@ int main(int argc, char *argv[]) { if (dict) { - load_extras(afl, dict); + load_extras(afl, (u8*)dict); if (verbose) fprintf(stderr, "Loaded dictionary: %s (%u entries)\n", dict, afl->extras_cnt); From b418a873403af922fd99ff20af688c6cb371557d Mon Sep 17 00:00:00 2001 From: Justus Perlwitz Date: Tue, 29 Apr 2025 11:40:21 +0900 Subject: [PATCH 12/49] Add test case for AFL_QEMU_PERSISTENT_EXITS Add a test case to `test/test-qemu-mode.sh` and make sure that AFL_QEMU_PERSISTENT_EXITS loops correctly. This works only on platforms for which `afl-qemu-trace` detects exit signals and resets the program counter. This commit updates `test-instr.c` to optionally call `exit(n)` instead of returning n to the operating system. This option can be activated using the `EXIT_AT_END` flag. This way, we can test the QEMU persistent exit mode without having to add a new test file. You can compile and run `test-instr.c` with the exit mode like so: ```bash gcc -o exit -DEXIT_AT_END test-instr.c AFL_QEMU_DEBUG_MAPS= \ AFL_DEBUG= \ AFL_QEMU_PERSISTENT_ADDR=$(readelf -a exit | grep 'main$' | awk '{ printf "0x%s", $2 }') \ AFL_QEMU_PERSISTENT_GPR=1 \ AFL_QEMU_PERSISTENT_EXITS=1 \ ./afl-qemu-trace exit ``` Press enter repeatedly and you will see an output like this: ``` ... Debug: Sending status 0xc201ffff test-instr: Neither one or zero? How quaint! test-instr: Neither one or zero? How quaint! test-instr: Neither one or zero? How quaint! test-instr: Neither one or zero? How quaint! test-instr: Neither one or zero? How quaint! ``` To make sure that persistent exits are detected correctly on x86_64, I've made the following changes to qemuafl: ``` linux-user/i386/cpu_loop.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/linux-user/i386/cpu_loop.c b/linux-user/i386/cpu_loop.c index 4509f46b95..46bdbaf94a 100644 --- a/linux-user/i386/cpu_loop.c +++ b/linux-user/i386/cpu_loop.c @@ -235,7 +235,7 @@ void cpu_loop(CPUX86State *env) #ifndef TARGET_ABI32 case EXCP_SYSCALL: /* linux syscall from syscall instruction */ - if (afl_fork_child && persistent_exits && + if (persistent_exits && env->regs[R_EAX] == TARGET_NR_exit_group) { env->eip = afl_persistent_addr; continue; ``` --- test-instr.c | 7 +++++ test/test-qemu-mode.sh | 59 +++++++++++++++++++++++++++++++++++++++--- 2 files changed, 63 insertions(+), 3 deletions(-) diff --git a/test-instr.c b/test-instr.c index 28552893..7ab342cf 100644 --- a/test-instr.c +++ b/test-instr.c @@ -49,7 +49,11 @@ int main(int argc, char **argv) { if ((cnt = read(fd, buf, sizeof(buf) - 1)) < 1) { printf("Hum?\n"); +#ifdef EXIT_AT_END + exit(1); +#else return 1; +#endif } @@ -76,6 +80,9 @@ int main(int argc, char **argv) { break; } +#ifdef EXIT_AT_END + exit(0); +#endif return 0; diff --git a/test/test-qemu-mode.sh b/test/test-qemu-mode.sh index 2ba81d02..00751ced 100755 --- a/test/test-qemu-mode.sh +++ b/test/test-qemu-mode.sh @@ -16,7 +16,8 @@ test -z "$AFL_CC" && { test -e ../afl-qemu-trace && { cc -pie -fPIE -o test-instr ../test-instr.c cc -o test-compcov test-compcov.c - test -e test-instr -a -e test-compcov && { + cc -pie -fPIE -o test-instr-exit-at-end -DEXIT_AT_END ../test-instr.c + test -e test-instr -a -e test-compcov -a -e test-instr-exit-at-end && { { mkdir -p in echo 00000 > in/in @@ -149,11 +150,63 @@ test -e ../afl-qemu-trace && { $ECHO "$RED[!] afl-fuzz is not working correctly with persistent qemu_mode" CODE=1 } - rm -rf in out errors + rm -rf out errors } || { $ECHO "$YELLOW[-] not an intel or arm platform, cannot test persistent qemu_mode" } + test "$SYS" = "i686" -o "$SYS" = "x86_64" -o "$SYS" = "amd64" -o "$SYS" = "i86pc" -o "$SYS" = "aarch64" -o ! "${SYS%%arm*}" && { + $ECHO "$GREY[*] running afl-fuzz for persistent qemu_mode with AFL_QEMU_PERSISTENT_EXITS, this will take approx 10 seconds" + { + IS_STATIC="" + file test-instr-exit-at-end | grep -q 'statically linked' && IS_STATIC=1 + test -z "$IS_STATIC" && { + if file test-instr-exit-at-end | grep -q "32-bit"; then + # for 32-bit reduce 8 nibbles to the lower 7 nibbles + ADDR_LOWER_PART=`nm test-instr-exit-at-end | grep "T main" | awk '{print $1}' | sed 's/^.//'` + else + # for 64-bit reduce 16 nibbles to the lower 9 nibbles + ADDR_LOWER_PART=`nm test-instr-exit-at-end | grep "T main" | awk '{print $1}' | sed 's/^.......//'` + fi + export AFL_QEMU_PERSISTENT_ADDR=`expr 0x4${ADDR_LOWER_PART}` + } + test -n "$IS_STATIC" && { + export AFL_QEMU_PERSISTENT_ADDR=0x`nm test-instr-exit-at-end | grep "T main" | awk '{print $1}'` + } + export AFL_QEMU_PERSISTENT_GPR=1 + $ECHO "Info: AFL_QEMU_PERSISTENT_ADDR=$AFL_QEMU_PERSISTENT_ADDR <= $(nm test-instr-exit-at-end | grep "T main" | awk '{print $1}')" + export AFL_QEMU_PERSISTENT_EXITS=1 + ../afl-fuzz -m ${MEM_LIMIT} -V07 -Q -i in -o out -- ./test-instr-exit-at-end + echo status "$?" + unset AFL_QEMU_PERSISTENT_ADDR + unset AFL_QEMU_PERSISTENT_GPR + unset AFL_QEMU_PERSISTENT_EXITS + } >>errors 2>&1 + test -n "$( ls out/default/queue/id:000000* 2>/dev/null )" && { + $ECHO "$GREEN[+] afl-fuzz is working correctly with persistent qemu_mode and AFL_QEMU_PERSISTENT_EXITS" + RUNTIMEP_EXIT=`grep execs_done out/default/fuzzer_stats | awk '{print$3}'` + test -n "$RUNTIME" -a -n "$RUNTIMEP_EXIT" && { + DIFF=`expr $RUNTIMEP_EXIT / $RUNTIME` + test "$DIFF" -gt 1 && { # must be at least twice as fast + $ECHO "$GREEN[+] persistent qemu_mode with AFL_QEMU_PERSISTENT_EXITS was noticeable faster than standard qemu_mode" + } || { + $ECHO "$YELLOW[-] persistent qemu_mode with AFL_QEMU_PERSISTENT_EXITS was not noticeable faster than standard qemu_mode" + } + } || { + $ECHO "$YELLOW[-] we got no data on executions performed? weird!" + } + } || { + echo CUT------------------------------------------------------------------CUT + cat errors + echo CUT------------------------------------------------------------------CUT + $ECHO "$RED[!] afl-fuzz is not working correctly with persistent qemu_mode and AFL_QEMU_PERSISTENT_EXITS" + CODE=1 + } + rm -rf in out errors + } || { + $ECHO "$YELLOW[-] not an intel or arm platform, cannot test persistent qemu_mode with AFL_QEMU_PERSISTENT_EXITS" + } + test -e ../qemu_mode/unsigaction/unsigaction32.so && { ${AFL_CC} -o test-unsigaction32 -m32 test-unsigaction.c >> errors 2>&1 && { ./test-unsigaction32 @@ -212,7 +265,7 @@ test -e ../afl-qemu-trace && { CODE=1 } - rm -f test-instr test-compcov + rm -f test-instr test-compcov test-instr-exit-at-end } || { $ECHO "$YELLOW[-] qemu_mode is not compiled, cannot test" INCOMPLETE=1 From d40f935b4e5fd2eaf15da4b7f317726b5adfe09a Mon Sep 17 00:00:00 2001 From: Scott Guest Date: Fri, 2 May 2025 17:25:16 -0700 Subject: [PATCH 13/49] Disable GCC instrumentation for AFL_SAN_NO_INST --- src/afl-cc.c | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/src/afl-cc.c b/src/afl-cc.c index 85642d7e..c735ef84 100644 --- a/src/afl-cc.c +++ b/src/afl-cc.c @@ -2600,6 +2600,13 @@ void add_assembler(aflcc_state_t *aflcc) { /* Add params to launch the gcc plugins for instrumentation. */ void add_gcc_plugin(aflcc_state_t *aflcc) { + if (getenv("AFL_SAN_NO_INST")) { + + if (!be_quiet) { DEBUGF("SAND: Coverage instrumentation disabled\n"); } + return; + + } + if (aflcc->cmplog_mode) { insert_object(aflcc, "afl-gcc-cmplog-pass.so", "-fplugin=%s", 0); From cd0cb1e731abc5f0148eadbaff460937b8e09e35 Mon Sep 17 00:00:00 2001 From: Alexandre DOYEN Date: Sun, 4 May 2025 11:36:01 +0200 Subject: [PATCH 14/49] Setting the AFL_I_AM_THE_FORKSERVER environment variable in the begining of the forkserver child process, and unsetting it when the target is launched --- instrumentation/afl-compiler-rt.o.c | 1 + src/afl-forkserver.c | 2 ++ 2 files changed, 3 insertions(+) diff --git a/instrumentation/afl-compiler-rt.o.c b/instrumentation/afl-compiler-rt.o.c index cba6436f..33899c84 100644 --- a/instrumentation/afl-compiler-rt.o.c +++ b/instrumentation/afl-compiler-rt.o.c @@ -1046,6 +1046,7 @@ static void __afl_start_forkserver(void) { /* In child process: close fds, resume execution. */ if (unlikely(!child_pid)) { // just to signal afl-fuzz faster + unsetenv("AFL_I_AM_THE_FORKSERVER"); //(void)nice(-20); diff --git a/src/afl-forkserver.c b/src/afl-forkserver.c index 3fc86b3b..17685529 100644 --- a/src/afl-forkserver.c +++ b/src/afl-forkserver.c @@ -878,6 +878,8 @@ void afl_fsrv_start(afl_forkserver_t *fsrv, char **argv, /* CHILD PROCESS */ + setenv("AFL_I_AM_THE_FORKSERVER", "1", 0); + // enable terminating on sigpipe in the children struct sigaction sa; memset((char *)&sa, 0, sizeof(sa)); From 0c4f8934c7ab79c645d2f016fc95d869d7937fdd Mon Sep 17 00:00:00 2001 From: Kuang-che Wu Date: Sun, 4 May 2025 19:00:36 +0800 Subject: [PATCH 15/49] add afl-cmin.py --- afl-cmin.py | 687 ++++++++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 687 insertions(+) create mode 100755 afl-cmin.py diff --git a/afl-cmin.py b/afl-cmin.py new file mode 100755 index 00000000..cbfe4aca --- /dev/null +++ b/afl-cmin.py @@ -0,0 +1,687 @@ +#!/usr/bin/env python3 +# Copyright 2016-2025 Google Inc. +# Copyright 2025 AFLplusplus Project. All rights reserved. +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. +# +import argparse +import array +import base64 +import collections +import glob +import hashlib +import itertools +import logging +import multiprocessing +import os +import shutil +import subprocess +import sys + +try: + from tqdm import tqdm +except ImportError: + print('Hint: install python module "tqdm" to show progress bar') + + class tqdm: + + def __init__(self, data=None, *args, **argd): + self.data = data + + def __iter__(self): + yield from self.data + + def __enter__(self): + return self + + def __exit__(self, exc_type, exc_value, traceback): + pass + + def update(self, *args): + pass + + +parser = argparse.ArgumentParser() + +cpu_count = multiprocessing.cpu_count() +group = parser.add_argument_group('Required parameters') +group.add_argument('-i', + dest='input', + action='append', + metavar='dir', + required=True, + help='input directory with the starting corpus') +group.add_argument('-o', + dest='output', + metavar='dir', + required=True, + help='output directory for minimized files') + +group = parser.add_argument_group('Execution control settings') +group.add_argument('-f', + dest='stdin_file', + metavar='file', + help='location read by the fuzzed program (stdin)') +group.add_argument( + '-m', + dest='memory_limit', + default='none', + metavar='megs', + type=lambda x: x if x == 'none' else int(x), + help='memory limit for child process (default: %(default)s)') +group.add_argument('-t', + dest='time_limit', + default=5000, + metavar='msec', + type=lambda x: x if x == 'none' else int(x), + help='timeout for each run (default: %(default)s)') +group.add_argument('-O', + dest='frida_mode', + action='store_true', + default=False, + help='use binary-only instrumentation (FRIDA mode)') +group.add_argument('-Q', + dest='qemu_mode', + action='store_true', + default=False, + help='use binary-only instrumentation (QEMU mode)') +group.add_argument('-U', + dest='unicorn_mode', + action='store_true', + default=False, + help='use unicorn-based instrumentation (Unicorn mode)') +group.add_argument('-X', + dest='nyx_mode', + action='store_true', + default=False, + help='use Nyx mode') + +group = parser.add_argument_group('Minimization settings') +group.add_argument('--crash-dir', + dest='crash_dir', + metavar='dir', + default=None, + help="move crashes to a separate dir, always deduplicated") +group.add_argument('-A', + dest='allow_any', + action='store_true', + help='allow crashes and timeouts (not recommended)') +group.add_argument('-C', + dest='crash_only', + action='store_true', + help='keep crashing inputs, reject everything else') +group.add_argument('-e', + dest='edge_mode', + action='store_true', + default=False, + help='solve for edge coverage only, ignore hit counts') + +group = parser.add_argument_group('Misc') +group.add_argument('-T', + dest='workers', + type=lambda x: cpu_count if x == 'all' else int(x), + default=1, + help='number of concurrent worker (default: %(default)d)') +group.add_argument('--as_queue', + action='store_true', + help='output file name like "id:000000,hash:value"') +group.add_argument('--no-dedup', + action='store_true', + help='skip deduplication step for corpus files') +group.add_argument('--debug', action='store_true') + +parser.add_argument('exe', metavar='/path/to/target_app') +parser.add_argument('args', nargs='*') + +args = parser.parse_args() +logger = None +afl_showmap_bin = None +tuple_index_type_code = 'I' +file_index_type_code = None + + +def init(): + global logger + log_level = logging.DEBUG if args.debug else logging.INFO + logging.basicConfig(level=log_level, + format='%(asctime)s - %(levelname)s - %(message)s') + logger = logging.getLogger(__name__) + + if args.stdin_file and args.workers > 1: + logger.error('-f is only supported with one worker (-T 1)') + sys.exit(1) + + if args.memory_limit != 'none' and args.memory_limit < 5: + logger.error('dangerously low memory limit') + sys.exit(1) + + if args.time_limit != 'none' and args.time_limit < 10: + logger.error('dangerously low timeout') + sys.exit(1) + + if not os.path.isfile(args.exe): + logger.error('binary "%s" not found or not regular file', args.exe) + sys.exit(1) + + if not os.environ.get('AFL_SKIP_BIN_CHECK') and not any( + [args.qemu_mode, args.frida_mode, args.unicorn_mode, args.nyx_mode]): + if b'__AFL_SHM_ID' not in open(args.exe, 'rb').read(): + logger.error("binary '%s' doesn't appear to be instrumented", + args.exe) + sys.exit(1) + + for dn in args.input: + if not os.path.isdir(dn) and not glob.glob(dn): + logger.error('directory "%s" not found', dn) + sys.exit(1) + + global afl_showmap_bin + searches = [ + None, + os.path.dirname(__file__), + os.getcwd(), + ] + if os.environ.get('AFL_PATH'): + searches.append(os.environ['AFL_PATH']) + + for search in searches: + afl_showmap_bin = shutil.which('afl-showmap', path=search) + if afl_showmap_bin: + break + if not afl_showmap_bin: + logger.fatal('cannot find afl-showmap, please set AFL_PATH') + sys.exit(1) + + trace_dir = os.path.join(args.output, '.traces') + shutil.rmtree(trace_dir, ignore_errors=True) + try: + os.rmdir(args.output) + except OSError: + pass + if os.path.exists(args.output): + logger.error( + 'directory "%s" exists and is not empty - delete it first', + args.output) + sys.exit(1) + if args.crash_dir and not os.path.exists(args.crash_dir): + os.makedirs(args.crash_dir) + os.makedirs(trace_dir) + + logger.info('use %d workers (-T)', args.workers) + + +def detect_type_code(size): + for type_code in ['B', 'H', 'I', 'L', 'Q']: + if 256**array.array(type_code).itemsize > size: + return type_code + + +def afl_showmap(input_path=None, batch=None, afl_map_size=None, first=False): + assert input_path or batch + # yapf: disable + cmd = [ + afl_showmap_bin, + '-m', str(args.memory_limit), + '-t', str(args.time_limit), + '-Z', # cmin mode + ] + # yapf: enable + found_atat = False + for arg in args.args: + if '@@' in arg: + found_atat = True + + if args.stdin_file: + assert args.workers == 1 + input_from_file = True + stdin_file = args.stdin_file + cmd += ['-H', stdin_file] + elif found_atat: + input_from_file = True + stdin_file = os.path.join(args.output, f'.input.{os.getpid()}') + cmd += ['-H', stdin_file] + else: + input_from_file = False + + if batch: + input_from_file = True + filelist = os.path.join(args.output, f'.filelist.{os.getpid()}') + with open(filelist, 'w') as f: + for _, path in batch: + f.write(path + '\n') + cmd += ['-I', filelist] + output_path = os.path.join(args.output, f'.showmap.{os.getpid()}') + cmd += ['-o', output_path] + else: + if input_from_file: + shutil.copy(input_path, stdin_file) + cmd += ['-o', '-'] + + if args.frida_mode: + cmd += ['-O'] + if args.qemu_mode: + cmd += ['-Q'] + if args.unicorn_mode: + cmd += ['-U'] + if args.nyx_mode: + cmd += ['-X'] + if args.edge_mode: + cmd += ['-e'] + cmd += ['--', args.exe] + args.args + + env = os.environ.copy() + env['AFL_QUIET'] = '1' + env['ASAN_OPTIONS'] = 'detect_leaks=0' + if first: + logger.debug('run command line: %s', subprocess.list2cmdline(cmd)) + env['AFL_CMIN_ALLOW_ANY'] = '1' + if afl_map_size: + env['AFL_MAP_SIZE'] = str(afl_map_size) + if args.crash_only: + env['AFL_CMIN_CRASHES_ONLY'] = '1' + if args.allow_any: + env['AFL_CMIN_ALLOW_ANY'] = '1' + + if input_from_file: + p = subprocess.Popen(cmd, + stdout=subprocess.PIPE, + env=env, + bufsize=1048576) + else: + p = subprocess.Popen(cmd, + stdin=open(input_path, 'rb'), + stdout=subprocess.PIPE, + env=env, + bufsize=1048576) + out = p.stdout.read() + p.wait() + + if batch: + result = [] + for idx, input_path in batch: + basename = os.path.basename(input_path) + values = [] + try: + trace_file = os.path.join(output_path, basename) + with open(trace_file, 'r') as f: + values = list(map(int, f)) + crashed = len(values) == 0 + os.unlink(trace_file) + except FileNotFoundError: + a = None + crashed = True + values = [(t // 1000) * 9 + t % 1000 for t in values] + a = array.array(tuple_index_type_code, values) + result.append((idx, a, crashed)) + os.unlink(filelist) + os.rmdir(output_path) + return result + else: + values = [] + for line in out.split(): + if not line.isdigit(): + continue + values.append(int(line)) + values = [(t // 1000) * 9 + t % 1000 for t in values] + a = array.array(tuple_index_type_code, values) + crashed = p.returncode in [2, 3] + if input_from_file and stdin_file != args.stdin_file: + os.unlink(stdin_file) + return a, crashed + + +class JobDispatcher(multiprocessing.Process): + + def __init__(self, job_queue, jobs): + super().__init__() + self.job_queue = job_queue + self.jobs = jobs + + def run(self): + for job in self.jobs: + self.job_queue.put(job) + self.job_queue.close() + + +class Worker(multiprocessing.Process): + + def __init__(self, idx, afl_map_size, q_in, p_out, r_out): + super().__init__() + self.idx = idx + self.afl_map_size = afl_map_size + self.q_in = q_in + self.p_out = p_out + self.r_out = r_out + + def run(self): + map_size = self.afl_map_size or 65536 + max_tuple = map_size * 9 + max_file_index = 256**array.array(file_index_type_code).itemsize - 1 + m = array.array(file_index_type_code, [max_file_index] * max_tuple) + counter = collections.Counter() + crashes = [] + + pack_name = os.path.join(args.output, '.traces', f'{self.idx}.pack') + pack_pos = 0 + with open(pack_name, 'wb') as trace_pack: + while True: + batch = self.q_in.get() + if batch is None: + break + + for idx, r, crash in afl_showmap( + batch=batch, afl_map_size=self.afl_map_size): + counter.update(r) + + used = False + + if crash: + crashes.append(idx) + + # If we aren't saving crashes to a separate dir, handle them + # the same as other inputs. However, unless AFL_CMIN_ALLOW_ANY=1, + # afl_showmap will not return any coverage for crashes so they will + # never be retained. + if not crash or not args.crash_dir: + for t in r: + if idx < m[t]: + m[t] = idx + used = True + + if used: + tuple_count = len(r) + r.tofile(trace_pack) + self.p_out.put((idx, self.idx, pack_pos, tuple_count)) + pack_pos += tuple_count * r.itemsize + else: + self.p_out.put(None) + + self.r_out.put((self.idx, m, counter, crashes)) + + +class CombineTraceWorker(multiprocessing.Process): + + def __init__(self, pack_name, jobs, r_out): + super().__init__() + self.pack_name = pack_name + self.jobs = jobs + self.r_out = r_out + + def run(self): + already_have = set() + with open(self.pack_name, 'rb') as f: + for pos, tuple_count in self.jobs: + f.seek(pos) + result = array.array(tuple_index_type_code) + result.fromfile(f, tuple_count) + already_have.update(result) + self.r_out.put(already_have) + + +def hash_file(path): + m = hashlib.sha1() + with open(path, 'rb') as f: + m.update(f.read()) + return m.digest() + + +def dedup(files): + with multiprocessing.Pool(args.workers) as pool: + seen_hash = set() + result = [] + hash_list = [] + # use large chunksize to reduce multiprocessing overhead + chunksize = max(1, min(256, len(files) // args.workers)) + for i, h in enumerate( + tqdm(pool.imap(hash_file, files, chunksize), + desc='dedup', + total=len(files), + ncols=0, + leave=(len(files) > 100000))): + if h in seen_hash: + continue + seen_hash.add(h) + result.append(files[i]) + hash_list.append(h) + return result, hash_list + + +def is_afl_dir(dirnames, filenames): + return ('queue' in dirnames and 'hangs' in dirnames + and 'crashes' in dirnames and 'fuzzer_setup' in filenames) + + +def collect_files(input_paths): + paths = [] + for s in input_paths: + paths += glob.glob(s) + + files = [] + with tqdm(desc='search', unit=' files', ncols=0) as pbar: + for path in paths: + for root, dirnames, filenames in os.walk(path, followlinks=True): + for dirname in dirnames: + if dirname.startswith('.'): + dirnames.remove(dirname) + + if not args.crash_only and is_afl_dir(dirnames, filenames): + continue + + for filename in filenames: + if filename.startswith('.'): + continue + pbar.update(1) + files.append(os.path.join(root, filename)) + return files + + +def main(): + init() + + files = collect_files(args.input) + if len(files) == 0: + logger.error('no inputs in the target directory - nothing to be done') + sys.exit(1) + logger.info('Found %d input files in %d directories', len(files), + len(args.input)) + + if not args.no_dedup: + files, hash_list = dedup(files) + logger.info('Remain %d files after dedup', len(files)) + else: + logger.info('Skipping file deduplication.') + + global file_index_type_code + file_index_type_code = detect_type_code(len(files)) + + logger.info('Sorting files.') + with multiprocessing.Pool(args.workers) as pool: + chunksize = max(1, min(512, len(files) // args.workers)) + size_list = list(pool.map(os.path.getsize, files, chunksize)) + idxes = sorted(range(len(files)), key=lambda x: size_list[x]) + files = [files[idx] for idx in idxes] + hash_list = [hash_list[idx] for idx in idxes] + + afl_map_size = None + if b'AFL_DUMP_MAP_SIZE' in open(args.exe, 'rb').read(): + output = subprocess.run([args.exe], + capture_output=True, + env={ + 'AFL_DUMP_MAP_SIZE': '1' + }).stdout + afl_map_size = int(output) + logger.info('Setting AFL_MAP_SIZE=%d', afl_map_size) + + global tuple_index_type_code + tuple_index_type_code = detect_type_code(afl_map_size * 9) + + logger.info('Testing the target binary') + tuples, _ = afl_showmap(files[0], afl_map_size=afl_map_size, first=True) + if tuples: + logger.info('ok, %d tuples recorded', len(tuples)) + else: + logger.error('no instrumentation output detected') + sys.exit(1) + + job_queue = multiprocessing.Queue() + progress_queue = multiprocessing.Queue() + result_queue = multiprocessing.Queue() + + workers = [] + for i in range(args.workers): + p = Worker(i, afl_map_size, job_queue, progress_queue, result_queue) + p.start() + workers.append(p) + + chunk = max(1, min(128, len(files) // args.workers)) + jobs = list(itertools.batched(enumerate(files), chunk)) + jobs += [None] * args.workers # sentinel + + dispatcher = JobDispatcher(job_queue, jobs) + dispatcher.start() + + logger.info('Processing traces') + effective = 0 + trace_info = {} + for _ in tqdm(files, ncols=0, smoothing=0.01): + r = progress_queue.get() + if r is not None: + idx, worker_idx, pos, tuple_count = r + trace_info[idx] = worker_idx, pos, tuple_count + effective += 1 + dispatcher.join() + + logger.info('Obtaining trace results') + ms = [] + crashes = [] + counter = collections.Counter() + for _ in tqdm(range(args.workers), ncols=0): + idx, m, c, crs = result_queue.get() + ms.append(m) + counter.update(c) + crashes.extend(crs) + workers[idx].join() + best_idxes = list(map(min, zip(*ms))) + + if not args.crash_dir: + logger.info('Found %d unique tuples across %d files (%d effective)', + len(counter), len(files), effective) + else: + logger.info( + 'Found %d unique tuples across %d files (%d effective, %d crashes)', + len(counter), len(files), effective, len(crashes)) + all_unique = counter.most_common() + + logger.info('Processing candidates and writing output') + already_have = set() + count = 0 + + def save_file(idx): + input_path = files[idx] + fn = (base64.b16encode(hash_list[idx]).decode('utf8').lower() + if not args.no_dedup else os.path.basename(input_path)) + if args.as_queue: + if args.no_dedup: + fn = 'id:%06d,orig:%s' % (count, fn) + else: + fn = 'id:%06d,hash:%s' % (count, fn) + output_path = os.path.join(args.output, fn) + try: + os.link(input_path, output_path) + except OSError: + shutil.copy(input_path, output_path) + + jobs = [[] for i in range(args.workers)] + saved = set() + for t, c in all_unique: + if c != 1: + continue + idx = best_idxes[t] + if idx in saved: + continue + save_file(idx) + saved.add(idx) + count += 1 + + worker_idx, pos, tuple_count = trace_info[idx] + job = (pos, tuple_count) + jobs[worker_idx].append(job) + + trace_packs = [] + workers = [] + for i in range(args.workers): + pack_name = os.path.join(args.output, '.traces', f'{i}.pack') + trace_f = open(pack_name, 'rb') + trace_packs.append(trace_f) + + p = CombineTraceWorker(pack_name, jobs[i], result_queue) + p.start() + workers.append(p) + + for _ in range(args.workers): + result = result_queue.get() + already_have.update(result) + + for t, c in tqdm(list(reversed(all_unique)), ncols=0): + if t in already_have: + continue + + idx = best_idxes[t] + save_file(idx) + count += 1 + + worker_idx, pos, tuple_count = trace_info[idx] + trace_pack = trace_packs[worker_idx] + trace_pack.seek(pos) + result = array.array(tuple_index_type_code) + result.fromfile(trace_pack, tuple_count) + + already_have.update(result) + + for f in trace_packs: + f.close() + + if args.crash_dir: + logger.info('Saving crashes to %s', args.crash_dir) + crash_files = [files[c] for c in crashes] + + if args.no_dedup: + # Unless we deduped previously, we have to dedup the crash files + # now. + crash_files, hash_list = dedup(crash_files) + + for idx, crash_path in enumerate(crash_files): + fn = base64.b16encode(hash_list[idx]).decode('utf8').lower() + output_path = os.path.join(args.crash_dir, fn) + try: + os.link(crash_path, output_path) + except OSError: + try: + shutil.copy(crash_path, output_path) + except shutil.Error: + # This error happens when src and dest are hardlinks of the + # same file. We have nothing to do in this case, but handle + # it gracefully. + pass + + if count == 1: + logger.warning('all test cases had the same traces, check syntax!') + logger.info('narrowed down to %s files, saved in "%s"', count, args.output) + if not os.environ.get('AFL_KEEP_TRACES'): + logger.info('Deleting trace files') + trace_dir = os.path.join(args.output, '.traces') + shutil.rmtree(trace_dir, ignore_errors=True) + + +if __name__ == '__main__': + main() From 7cb8ccc9605f7f815ce660b38ddf6aa690cb25f2 Mon Sep 17 00:00:00 2001 From: Kuang-che Wu Date: Sun, 4 May 2025 19:35:55 +0800 Subject: [PATCH 16/49] mention afl-cmin.py in afl-cmin --- afl-cmin | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/afl-cmin b/afl-cmin index b00b49ed..08dcbcae 100755 --- a/afl-cmin +++ b/afl-cmin @@ -8,7 +8,7 @@ export AFL_QUIET=1 export ASAN_OPTIONS=detect_leaks=0 THISPATH=`dirname ${0}` export PATH="${THISPATH}:$PATH" -awk -f - -- ${@+"$@"} <<'EOF' +awk -v script_dir="$THISPATH" -f - -- ${@+"$@"} <<'EOF' #!/usr/bin/awk -f # awk script to minimize a test corpus of input files # @@ -258,6 +258,10 @@ BEGIN { # sanity checks if (!prog_args[0] || !in_dir || !out_dir) usage() + if (0 == system( script_dir "/afl-cmin.py --help > /dev/null 2>&1" )) { + print "[*] Are you aware of the experimental afl-cmin.py that minimize corpus much faster?" + } + target_bin = prog_args[0] # Do a sanity check to discourage the use of /tmp, since we can't really From 24dc7b569cdb7114f0f4938cfb93b0473bdbb324 Mon Sep 17 00:00:00 2001 From: Kuang-che Wu Date: Sun, 4 May 2025 22:17:01 +0800 Subject: [PATCH 17/49] nit: simplify code "!target_hash" already cover "afl->fsrv.nyx_mode && target_hash == 0" --- src/afl-fuzz.c | 6 +----- 1 file changed, 1 insertion(+), 5 deletions(-) diff --git a/src/afl-fuzz.c b/src/afl-fuzz.c index 3574a598..8eefeada 100644 --- a/src/afl-fuzz.c +++ b/src/afl-fuzz.c @@ -2307,11 +2307,7 @@ int main(int argc, char **argv_orig, char **envp) { u64 target_hash = get_binary_hash(afl->fsrv.target_path); #endif - if ((!target_hash || prev_target_hash != target_hash) - #ifdef __linux__ - || (afl->fsrv.nyx_mode && target_hash == 0) - #endif - ) { + if (!target_hash || prev_target_hash != target_hash) { ACTF("Target binary is different, cannot perform FAST RESUME!"); From 90e929ea174cd29fdb77c8e6d8f86a2c6accf2fe Mon Sep 17 00:00:00 2001 From: Kuang-che Wu Date: Sun, 4 May 2025 22:59:38 +0800 Subject: [PATCH 18/49] only reinit shm map when make sense --- src/afl-showmap.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/afl-showmap.c b/src/afl-showmap.c index 6c6c2489..0392db0f 100644 --- a/src/afl-showmap.c +++ b/src/afl-showmap.c @@ -1589,7 +1589,7 @@ int main(int argc, char **argv_orig, char **envp) { // only reinitialize when it makes sense if (map_size < new_map_size || - (new_map_size > map_size && new_map_size - map_size > MAP_SIZE)) { + (new_map_size < map_size && map_size - new_map_size > MAP_SIZE)) { if (!be_quiet) ACTF("Acquired new map size for target: %u bytes\n", new_map_size); From 701299eefde05640b07d508c4886a0b45c888d2a Mon Sep 17 00:00:00 2001 From: Kuang-che Wu Date: Mon, 5 May 2025 08:08:03 +0800 Subject: [PATCH 19/49] remove dead code; we no longer use murmurhash --- include/hash.h | 89 -------------------------------------------------- 1 file changed, 89 deletions(-) diff --git a/include/hash.h b/include/hash.h index 5d56a108..edf4ba3b 100644 --- a/include/hash.h +++ b/include/hash.h @@ -2,18 +2,6 @@ american fuzzy lop++ - hashing function --------------------------------------- - The hash32() function is a variant of MurmurHash3, a good - non-cryptosafe hashing function developed by Austin Appleby. - - For simplicity, this variant does *NOT* accept buffer lengths - that are not divisible by 8 bytes. The 32-bit version is otherwise - similar to the original; the 64-bit one is a custom hack with - mostly-unproven properties. - - Austin's original code is public domain. - - Other code written by Michal Zalewski - Copyright 2016 Google Inc. All rights reserved. Copyright 2019-2024 AFLplusplus Project. All rights reserved. @@ -33,82 +21,5 @@ u32 hash32(u8 *key, u32 len, u32 seed); u64 hash64(u8 *key, u32 len, u64 seed); -#if 0 - -The following code is disabled because xxh3 is 30% faster - - #ifdef __x86_64__ - - #define ROL64(_x, _r) ((((u64)(_x)) << (_r)) | (((u64)(_x)) >> (64 - (_r)))) - -static inline u32 hash32(u8 *key, u32 len, u32 seed) { - - const u64 *data = (u64 *)key; - u64 h1 = seed ^ len; - - len >>= 3; - - while (len--) { - - u64 k1 = *data++; - - k1 *= 0x87c37b91114253d5ULL; - k1 = ROL64(k1, 31); - k1 *= 0x4cf5ad432745937fULL; - - h1 ^= k1; - h1 = ROL64(h1, 27); - h1 = h1 * 5 + 0x52dce729; - - } - - h1 ^= h1 >> 33; - h1 *= 0xff51afd7ed558ccdULL; - h1 ^= h1 >> 33; - h1 *= 0xc4ceb9fe1a85ec53ULL; - h1 ^= h1 >> 33; - - return h1; - -} - - #else - - #define ROL32(_x, _r) ((((u32)(_x)) << (_r)) | (((u32)(_x)) >> (32 - (_r)))) - -static inline u32 hash32(const void *key, u32 len, u32 seed) { - - const u32 *data = (u32 *)key; - u32 h1 = seed ^ len; - - len >>= 2; - - while (len--) { - - u32 k1 = *data++; - - k1 *= 0xcc9e2d51; - k1 = ROL32(k1, 15); - k1 *= 0x1b873593; - - h1 ^= k1; - h1 = ROL32(h1, 13); - h1 = h1 * 5 + 0xe6546b64; - - } - - h1 ^= h1 >> 16; - h1 *= 0x85ebca6b; - h1 ^= h1 >> 13; - h1 *= 0xc2b2ae35; - h1 ^= h1 >> 16; - - return h1; - -} - - #endif /* ^__x86_64__ */ -#endif - #endif /* !_HAVE_HASH_H */ From b1649f2fdbba1f7709e17e238f4af8ae2bb013d3 Mon Sep 17 00:00:00 2001 From: Kuang-che Wu Date: Mon, 5 May 2025 08:16:35 +0800 Subject: [PATCH 20/49] nyx nit --- src/afl-forkserver.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/afl-forkserver.c b/src/afl-forkserver.c index 3fc86b3b..07564673 100644 --- a/src/afl-forkserver.c +++ b/src/afl-forkserver.c @@ -140,7 +140,7 @@ nyx_plugin_handler_t *afl_load_libnyx_plugin(u8 *libnyx_binary) { if (plugin->nyx_get_target_hash64 == NULL) { goto fail; } plugin->nyx_config_free = dlsym(handle, "nyx_config_free"); - if (plugin->nyx_get_target_hash64 == NULL) { goto fail; } + if (plugin->nyx_config_free == NULL) { goto fail; } OKF("libnyx plugin is ready!"); return plugin; From 6876ab7901ab21e04607691b1c178e664f15ff54 Mon Sep 17 00:00:00 2001 From: Kuang-che Wu Date: Mon, 5 May 2025 08:43:58 +0800 Subject: [PATCH 21/49] remove dead prototype --- include/afl-fuzz.h | 2 -- 1 file changed, 2 deletions(-) diff --git a/include/afl-fuzz.h b/include/afl-fuzz.h index 4729c321..3636556a 100644 --- a/include/afl-fuzz.h +++ b/include/afl-fuzz.h @@ -1180,7 +1180,6 @@ u8 havoc_mutation_probability_py(void *); u8 queue_get_py(void *, const u8 *); const char *introspection_py(void *); u8 queue_new_entry_py(void *, const u8 *, const u8 *); -void splice_optout(void *); void deinit_py(void *); #endif @@ -1278,7 +1277,6 @@ u8 fuzz_one(afl_state_t *); #ifdef HAVE_AFFINITY void bind_to_free_cpu(afl_state_t *); #endif -void setup_post(afl_state_t *); void read_testcases(afl_state_t *, u8 *); void perform_dry_run(afl_state_t *); void pivot_inputs(afl_state_t *); From d10b85421d47e051596e8bab8fa7d9d33ac20caf Mon Sep 17 00:00:00 2001 From: vanhauser-thc Date: Mon, 5 May 2025 09:36:18 +0200 Subject: [PATCH 22/49] update qemuafl --- qemu_mode/QEMUAFL_VERSION | 2 +- qemu_mode/qemuafl | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/qemu_mode/QEMUAFL_VERSION b/qemu_mode/QEMUAFL_VERSION index af048aa5..71a0f4b2 100644 --- a/qemu_mode/QEMUAFL_VERSION +++ b/qemu_mode/QEMUAFL_VERSION @@ -1 +1 @@ -ef1cd9a8cb +c43dd6e036 diff --git a/qemu_mode/qemuafl b/qemu_mode/qemuafl index ef1cd9a8..c43dd6e0 160000 --- a/qemu_mode/qemuafl +++ b/qemu_mode/qemuafl @@ -1 +1 @@ -Subproject commit ef1cd9a8cb1522c918faab42805216f9a4054dda +Subproject commit c43dd6e0369cd5d2a2458f3bd7f4f58c8de53300 From 6f4767ea8155ab20e49d1fa341dea9a724fd0407 Mon Sep 17 00:00:00 2001 From: Alexandre DOYEN Date: Mon, 5 May 2025 09:42:33 +0200 Subject: [PATCH 23/49] AFL_I_AM_THE_FORKSERVER becomes AFL_FORKSERVER_PARENT --- instrumentation/afl-compiler-rt.o.c | 2 +- src/afl-forkserver.c | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/instrumentation/afl-compiler-rt.o.c b/instrumentation/afl-compiler-rt.o.c index 33899c84..46d76624 100644 --- a/instrumentation/afl-compiler-rt.o.c +++ b/instrumentation/afl-compiler-rt.o.c @@ -1046,7 +1046,7 @@ static void __afl_start_forkserver(void) { /* In child process: close fds, resume execution. */ if (unlikely(!child_pid)) { // just to signal afl-fuzz faster - unsetenv("AFL_I_AM_THE_FORKSERVER"); + unsetenv("AFL_FORKSERVER_PARENT"); //(void)nice(-20); diff --git a/src/afl-forkserver.c b/src/afl-forkserver.c index 17685529..0a1bd75f 100644 --- a/src/afl-forkserver.c +++ b/src/afl-forkserver.c @@ -878,7 +878,7 @@ void afl_fsrv_start(afl_forkserver_t *fsrv, char **argv, /* CHILD PROCESS */ - setenv("AFL_I_AM_THE_FORKSERVER", "1", 0); + setenv("AFL_FORKSERVER_PARENT", "1", 0); // enable terminating on sigpipe in the children struct sigaction sa; From e9f49527e912eb81e9e3c0a1b32c703f858cecda Mon Sep 17 00:00:00 2001 From: Alexandre DOYEN Date: Mon, 5 May 2025 09:49:56 +0200 Subject: [PATCH 24/49] We check before if the AFL_PRELOAD env variable is set --- instrumentation/afl-compiler-rt.o.c | 6 +++++- src/afl-forkserver.c | 6 +++++- 2 files changed, 10 insertions(+), 2 deletions(-) diff --git a/instrumentation/afl-compiler-rt.o.c b/instrumentation/afl-compiler-rt.o.c index 46d76624..2a563afc 100644 --- a/instrumentation/afl-compiler-rt.o.c +++ b/instrumentation/afl-compiler-rt.o.c @@ -1046,7 +1046,11 @@ static void __afl_start_forkserver(void) { /* In child process: close fds, resume execution. */ if (unlikely(!child_pid)) { // just to signal afl-fuzz faster - unsetenv("AFL_FORKSERVER_PARENT"); + if (unlikely(getenv("AFL_PRELOAD") != NULL)) { + + unsetenv("AFL_FORKSERVER_PARENT"); + + } //(void)nice(-20); diff --git a/src/afl-forkserver.c b/src/afl-forkserver.c index 0a1bd75f..d289ce8e 100644 --- a/src/afl-forkserver.c +++ b/src/afl-forkserver.c @@ -878,7 +878,11 @@ void afl_fsrv_start(afl_forkserver_t *fsrv, char **argv, /* CHILD PROCESS */ - setenv("AFL_FORKSERVER_PARENT", "1", 0); + if (unlikely(getenv("AFL_PRELOAD") != NULL)) { + + setenv("AFL_FORKSERVER_PARENT", "1", 0); + + } // enable terminating on sigpipe in the children struct sigaction sa; From a76ff5e79835210516d7b3dbaf650e6db86ce80e Mon Sep 17 00:00:00 2001 From: Alexandre DOYEN Date: Mon, 5 May 2025 09:54:53 +0200 Subject: [PATCH 25/49] Specific environment variable to choose if we want to be able to discriminate or not forkserver in preloaded libraries --- instrumentation/afl-compiler-rt.o.c | 2 +- src/afl-forkserver.c | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/instrumentation/afl-compiler-rt.o.c b/instrumentation/afl-compiler-rt.o.c index 2a563afc..8dfdc61c 100644 --- a/instrumentation/afl-compiler-rt.o.c +++ b/instrumentation/afl-compiler-rt.o.c @@ -1046,7 +1046,7 @@ static void __afl_start_forkserver(void) { /* In child process: close fds, resume execution. */ if (unlikely(!child_pid)) { // just to signal afl-fuzz faster - if (unlikely(getenv("AFL_PRELOAD") != NULL)) { + if (unlikely(getenv("AFL_PRELOAD_DISCRIMINATE_FORKSERVER_PARENT") != NULL)) { unsetenv("AFL_FORKSERVER_PARENT"); diff --git a/src/afl-forkserver.c b/src/afl-forkserver.c index d289ce8e..b5e1476d 100644 --- a/src/afl-forkserver.c +++ b/src/afl-forkserver.c @@ -878,7 +878,7 @@ void afl_fsrv_start(afl_forkserver_t *fsrv, char **argv, /* CHILD PROCESS */ - if (unlikely(getenv("AFL_PRELOAD") != NULL)) { + if (unlikely(getenv("AFL_PRELOAD_DISCRIMINATE_FORKSERVER_PARENT") != NULL)) { setenv("AFL_FORKSERVER_PARENT", "1", 0); From 062f8831603b12f4396b0d0eade2422cd48adfb8 Mon Sep 17 00:00:00 2001 From: Kuang-che Wu Date: Mon, 5 May 2025 16:16:42 +0800 Subject: [PATCH 26/49] add splice_optout_py prototype --- include/afl-fuzz.h | 1 + 1 file changed, 1 insertion(+) diff --git a/include/afl-fuzz.h b/include/afl-fuzz.h index 3636556a..22f80567 100644 --- a/include/afl-fuzz.h +++ b/include/afl-fuzz.h @@ -1180,6 +1180,7 @@ u8 havoc_mutation_probability_py(void *); u8 queue_get_py(void *, const u8 *); const char *introspection_py(void *); u8 queue_new_entry_py(void *, const u8 *, const u8 *); +void splice_optout_py(void *); void deinit_py(void *); #endif From 4d984d6e2b00f76406afea3b50fd5a06e25c8488 Mon Sep 17 00:00:00 2001 From: Alexandre DOYEN Date: Mon, 5 May 2025 10:44:34 +0200 Subject: [PATCH 27/49] getenv() call at the beginning of __afl_start_forkserver() --- instrumentation/afl-compiler-rt.o.c | 10 +++++++++- 1 file changed, 9 insertions(+), 1 deletion(-) diff --git a/instrumentation/afl-compiler-rt.o.c b/instrumentation/afl-compiler-rt.o.c index 8dfdc61c..18c776e2 100644 --- a/instrumentation/afl-compiler-rt.o.c +++ b/instrumentation/afl-compiler-rt.o.c @@ -119,6 +119,8 @@ u64 __afl_map_addr; u32 __afl_first_final_loc; u32 __afl_old_forkserver; +u8 __afl_forkserver_setenv = 0; + #ifdef __AFL_CODE_COVERAGE typedef struct afl_module_info_t afl_module_info_t; @@ -888,6 +890,12 @@ static void __afl_start_forkserver(void) { } + if (getenv("AFL_PRELOAD_DISCRIMINATE_FORKSERVER_PARENT") != NULL) { + + __afl_forkserver_setenv = 1; + + } + /* Phone home and tell the parent that we're OK. If parent isn't there, assume we're not running in forkserver mode and just execute program. */ @@ -1046,7 +1054,7 @@ static void __afl_start_forkserver(void) { /* In child process: close fds, resume execution. */ if (unlikely(!child_pid)) { // just to signal afl-fuzz faster - if (unlikely(getenv("AFL_PRELOAD_DISCRIMINATE_FORKSERVER_PARENT") != NULL)) { + if (unlikely(__afl_forkserver_setenv)) { unsetenv("AFL_FORKSERVER_PARENT"); From 7d29418db5b4a7c34039d3cc7f5a9641c0ef1d70 Mon Sep 17 00:00:00 2001 From: Alexandre DOYEN Date: Mon, 5 May 2025 10:50:13 +0200 Subject: [PATCH 28/49] Auxiliary variable for afl-forkserver.c too --- src/afl-forkserver.c | 8 +++++++- 1 file changed, 7 insertions(+), 1 deletion(-) diff --git a/src/afl-forkserver.c b/src/afl-forkserver.c index b5e1476d..c2bf3fcf 100644 --- a/src/afl-forkserver.c +++ b/src/afl-forkserver.c @@ -561,6 +561,7 @@ void afl_fsrv_start(afl_forkserver_t *fsrv, char **argv, u32 status; s32 rlen; char *ignore_autodict = getenv("AFL_NO_AUTODICT"); + u8 forkserver_setenv = 0; #ifdef __linux__ if (unlikely(fsrv->nyx_mode)) { @@ -867,6 +868,11 @@ void afl_fsrv_start(afl_forkserver_t *fsrv, char **argv, } + if (getenv("AFL_PRELOAD_DISCRIMINATE_FORKSERVER_PARENT") != NULL) + { + forkserver_setenv = 1; + } + if (pipe(st_pipe) || pipe(ctl_pipe)) { PFATAL("pipe() failed"); } fsrv->last_run_timed_out = 0; @@ -878,7 +884,7 @@ void afl_fsrv_start(afl_forkserver_t *fsrv, char **argv, /* CHILD PROCESS */ - if (unlikely(getenv("AFL_PRELOAD_DISCRIMINATE_FORKSERVER_PARENT") != NULL)) { + if (unlikely(forkserver_setenv)) { setenv("AFL_FORKSERVER_PARENT", "1", 0); From 19bd2984d52acbb985176df15c28df1cffc8efac Mon Sep 17 00:00:00 2001 From: Alexandre DOYEN Date: Mon, 5 May 2025 10:52:27 +0200 Subject: [PATCH 29/49] Writing style mistaske --- src/afl-forkserver.c | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/src/afl-forkserver.c b/src/afl-forkserver.c index c2bf3fcf..4265325e 100644 --- a/src/afl-forkserver.c +++ b/src/afl-forkserver.c @@ -868,9 +868,10 @@ void afl_fsrv_start(afl_forkserver_t *fsrv, char **argv, } - if (getenv("AFL_PRELOAD_DISCRIMINATE_FORKSERVER_PARENT") != NULL) - { + if (getenv("AFL_PRELOAD_DISCRIMINATE_FORKSERVER_PARENT") != NULL) { + forkserver_setenv = 1; + } if (pipe(st_pipe) || pipe(ctl_pipe)) { PFATAL("pipe() failed"); } From 320d4b7ef86a03680eb4c659f00132dd4034f417 Mon Sep 17 00:00:00 2001 From: Alexandre DOYEN Date: Mon, 5 May 2025 11:03:26 +0200 Subject: [PATCH 30/49] Requested changes --- include/forkserver.h | 2 ++ instrumentation/afl-compiler-rt.o.c | 12 +++++++----- src/afl-forkserver.c | 19 +++++++++++-------- 3 files changed, 20 insertions(+), 13 deletions(-) diff --git a/include/forkserver.h b/include/forkserver.h index 4f574aa1..db3bfcb3 100644 --- a/include/forkserver.h +++ b/include/forkserver.h @@ -159,6 +159,8 @@ typedef struct afl_forkserver { u8 uses_asan; /* Target uses ASAN/LSAN/MSAN? (bit 0/1/2 respectively) */ + bool setenv; /* setenv() to discriminate the forkserver? */ + bool debug; /* debug mode? */ u8 san_but_not_instrumented; /* Is it sanitizer enabled but not instrumented? diff --git a/instrumentation/afl-compiler-rt.o.c b/instrumentation/afl-compiler-rt.o.c index 18c776e2..24d2ed9e 100644 --- a/instrumentation/afl-compiler-rt.o.c +++ b/instrumentation/afl-compiler-rt.o.c @@ -1054,11 +1054,6 @@ static void __afl_start_forkserver(void) { /* In child process: close fds, resume execution. */ if (unlikely(!child_pid)) { // just to signal afl-fuzz faster - if (unlikely(__afl_forkserver_setenv)) { - - unsetenv("AFL_FORKSERVER_PARENT"); - - } //(void)nice(-20); @@ -1067,6 +1062,13 @@ static void __afl_start_forkserver(void) { close(FORKSRV_FD); close(FORKSRV_FD + 1); + + if (unlikely(__afl_forkserver_setenv)) { + + unsetenv("AFL_FORKSERVER_PARENT"); + + } + return; } diff --git a/src/afl-forkserver.c b/src/afl-forkserver.c index 4265325e..3eadedf1 100644 --- a/src/afl-forkserver.c +++ b/src/afl-forkserver.c @@ -250,6 +250,16 @@ void afl_fsrv_init(afl_forkserver_t *fsrv) { fsrv->child_kill_signal = SIGKILL; fsrv->max_length = MAX_FILE; + if (getenv("AFL_PRELOAD_DISCRIMINATE_FORKSERVER_PARENT") != NULL) { + + fsrv->setenv = 1; + + } else { + + fsrv->setenv = 0; + + } + /* exec related stuff */ fsrv->child_pid = -1; fsrv->map_size = get_map_size(); @@ -561,7 +571,6 @@ void afl_fsrv_start(afl_forkserver_t *fsrv, char **argv, u32 status; s32 rlen; char *ignore_autodict = getenv("AFL_NO_AUTODICT"); - u8 forkserver_setenv = 0; #ifdef __linux__ if (unlikely(fsrv->nyx_mode)) { @@ -868,12 +877,6 @@ void afl_fsrv_start(afl_forkserver_t *fsrv, char **argv, } - if (getenv("AFL_PRELOAD_DISCRIMINATE_FORKSERVER_PARENT") != NULL) { - - forkserver_setenv = 1; - - } - if (pipe(st_pipe) || pipe(ctl_pipe)) { PFATAL("pipe() failed"); } fsrv->last_run_timed_out = 0; @@ -885,7 +888,7 @@ void afl_fsrv_start(afl_forkserver_t *fsrv, char **argv, /* CHILD PROCESS */ - if (unlikely(forkserver_setenv)) { + if (unlikely(fsrv->setenv)) { setenv("AFL_FORKSERVER_PARENT", "1", 0); From f580fefc5ff61b0fbe267a4715d79b5526c677c6 Mon Sep 17 00:00:00 2001 From: Alexandre DOYEN Date: Mon, 5 May 2025 11:12:51 +0200 Subject: [PATCH 31/49] Doc --- docs/env_variables.md | 18 ++++++++++++++++++ include/envs.h | 3 ++- 2 files changed, 20 insertions(+), 1 deletion(-) diff --git a/docs/env_variables.md b/docs/env_variables.md index ed44c256..3052663a 100644 --- a/docs/env_variables.md +++ b/docs/env_variables.md @@ -664,6 +664,24 @@ checks or alter some of the more exotic semantics of the tool: Note that will not be exact and with slow targets it can take seconds until there is a slice for the time test. + - When using `AFL_PRELOAD` with a preload that disable `fork()` calls in + the target, the forkserver becomes unable to fork. + To overcome this issue, the `AFL_PRELOAD_DISCRIMINATE_FORKSERVER_PARENT` + permits to be able to check in the preloaded library if the environment + variable `AFL_FORKSERVER_PARENT` is set, to be able to use vanilla + `fork()` in the forkserver, and the placeholder in the target. + Here is a POC : + ```C + // AFL_PRELOAD_DISCRIMINATE_FORKSERVER_PARENT=1 afl-fuzz ... + pid_t fork(void) + { + if (getenv("AFL_FORKSERVER_PARENT") == NULL) + return 0; // We are in the target + else + return real_fork(); // We are in the forkserver + } + ``` + ## 6) Settings for afl-qemu-trace The QEMU wrapper used to instrument binary-only code supports several settings: diff --git a/include/envs.h b/include/envs.h index 7913e6b9..433b51a5 100644 --- a/include/envs.h +++ b/include/envs.h @@ -118,7 +118,8 @@ static char *afl_environment_variables[] = { "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", - "AFL_SAN_ABSTRACTION", "AFL_SAN_NO_INST", "AFL_SAN_RECOVER", NULL}; + "AFL_SAN_ABSTRACTION", "AFL_SAN_NO_INST", "AFL_SAN_RECOVER", + "AFL_PRELOAD_DISCRIMINATE_FORKSERVER_PARENT", NULL}; extern char *afl_environment_variables[]; From 6d45b286f81eb39763b0ce786ed690abc5310504 Mon Sep 17 00:00:00 2001 From: vanhauser-thc Date: Mon, 5 May 2025 14:35:52 +0200 Subject: [PATCH 32/49] nits --- docs/Changelog.md | 11 ++++++++++- src/afl-forkserver.c | 6 +----- test-instr.c | 1 + 3 files changed, 12 insertions(+), 6 deletions(-) diff --git a/docs/Changelog.md b/docs/Changelog.md index d6620ac5..c45e0fbe 100644 --- a/docs/Changelog.md +++ b/docs/Changelog.md @@ -5,7 +5,16 @@ ### Version ++4.33a (dev) - - ... + - afl-fuzz: + - Use `AFL_PRELOAD_DISCRIMINATE_FORKSERVER_PARENT` if you use AFL_PRELOAD + to disable fork, see docs (thanks to @alexandredoyen29) + - Fix for FAST power schedules (introduced in 4.32c) (thanks to @kcwu) + - Colors for NO_UI output (thanks to @smoelius) + - more 64 bit archicture support by @maribu + - afl-cc: + - Fix to make AFL_SAN_NO_INST work with gcc_plugin + - qemuafl: + - better MIPS persistent mode support ### Version ++4.32c (release) diff --git a/src/afl-forkserver.c b/src/afl-forkserver.c index 2d07ca4d..b5fec704 100644 --- a/src/afl-forkserver.c +++ b/src/afl-forkserver.c @@ -888,11 +888,7 @@ void afl_fsrv_start(afl_forkserver_t *fsrv, char **argv, /* CHILD PROCESS */ - if (unlikely(fsrv->setenv)) { - - setenv("AFL_FORKSERVER_PARENT", "1", 0); - - } + if (unlikely(fsrv->setenv)) { setenv("AFL_FORKSERVER_PARENT", "1", 0); } // enable terminating on sigpipe in the children struct sigaction sa; diff --git a/test-instr.c b/test-instr.c index 7ab342cf..61a2703a 100644 --- a/test-instr.c +++ b/test-instr.c @@ -80,6 +80,7 @@ int main(int argc, char **argv) { break; } + #ifdef EXIT_AT_END exit(0); #endif From b1730d99b672b75467ff6bb380629ab90da26c56 Mon Sep 17 00:00:00 2001 From: vanhauser-thc Date: Mon, 5 May 2025 17:46:01 +0200 Subject: [PATCH 33/49] new LLVM defaults! --- GNUmakefile.llvm | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/GNUmakefile.llvm b/GNUmakefile.llvm index ed91deaa..0e372e2c 100644 --- a/GNUmakefile.llvm +++ b/GNUmakefile.llvm @@ -32,8 +32,8 @@ VERSION = $(shell grep '^ *$(HASH)define VERSION ' ./config.h | cut -d '"' - SYS = $(shell uname -s) -override LLVM_TOO_NEW_DEFAULT := 19 -override LLVM_TOO_OLD_DEFAULT := 13 +override LLVM_TOO_NEW_DEFAULT := 20 +override LLVM_TOO_OLD_DEFAULT := 14 ifeq "$(SYS)" "OpenBSD" LLVM_CONFIG ?= $(BIN_PATH)/llvm-config From ec27e96486576aa908c9be97c3543e0bd8d771fc Mon Sep 17 00:00:00 2001 From: Kuang-che Wu Date: Tue, 6 May 2025 23:45:42 +0800 Subject: [PATCH 34/49] reformat by black --- afl-cmin.py | 427 ++++++++++++++++++++++++++++------------------------ 1 file changed, 234 insertions(+), 193 deletions(-) diff --git a/afl-cmin.py b/afl-cmin.py index cbfe4aca..aad7bc16 100755 --- a/afl-cmin.py +++ b/afl-cmin.py @@ -54,130 +54,156 @@ except ImportError: parser = argparse.ArgumentParser() cpu_count = multiprocessing.cpu_count() -group = parser.add_argument_group('Required parameters') -group.add_argument('-i', - dest='input', - action='append', - metavar='dir', - required=True, - help='input directory with the starting corpus') -group.add_argument('-o', - dest='output', - metavar='dir', - required=True, - help='output directory for minimized files') - -group = parser.add_argument_group('Execution control settings') -group.add_argument('-f', - dest='stdin_file', - metavar='file', - help='location read by the fuzzed program (stdin)') +group = parser.add_argument_group("Required parameters") group.add_argument( - '-m', - dest='memory_limit', - default='none', - metavar='megs', - type=lambda x: x if x == 'none' else int(x), - help='memory limit for child process (default: %(default)s)') -group.add_argument('-t', - dest='time_limit', - default=5000, - metavar='msec', - type=lambda x: x if x == 'none' else int(x), - help='timeout for each run (default: %(default)s)') -group.add_argument('-O', - dest='frida_mode', - action='store_true', - default=False, - help='use binary-only instrumentation (FRIDA mode)') -group.add_argument('-Q', - dest='qemu_mode', - action='store_true', - default=False, - help='use binary-only instrumentation (QEMU mode)') -group.add_argument('-U', - dest='unicorn_mode', - action='store_true', - default=False, - help='use unicorn-based instrumentation (Unicorn mode)') -group.add_argument('-X', - dest='nyx_mode', - action='store_true', - default=False, - help='use Nyx mode') + "-i", + dest="input", + action="append", + metavar="dir", + required=True, + help="input directory with the starting corpus", +) +group.add_argument( + "-o", + dest="output", + metavar="dir", + required=True, + help="output directory for minimized files", +) -group = parser.add_argument_group('Minimization settings') -group.add_argument('--crash-dir', - dest='crash_dir', - metavar='dir', - default=None, - help="move crashes to a separate dir, always deduplicated") -group.add_argument('-A', - dest='allow_any', - action='store_true', - help='allow crashes and timeouts (not recommended)') -group.add_argument('-C', - dest='crash_only', - action='store_true', - help='keep crashing inputs, reject everything else') -group.add_argument('-e', - dest='edge_mode', - action='store_true', - default=False, - help='solve for edge coverage only, ignore hit counts') +group = parser.add_argument_group("Execution control settings") +group.add_argument( + "-f", + dest="stdin_file", + metavar="file", + help="location read by the fuzzed program (stdin)", +) +group.add_argument( + "-m", + dest="memory_limit", + default="none", + metavar="megs", + type=lambda x: x if x == "none" else int(x), + help="memory limit for child process (default: %(default)s)", +) +group.add_argument( + "-t", + dest="time_limit", + default=5000, + metavar="msec", + type=lambda x: x if x == "none" else int(x), + help="timeout for each run (default: %(default)s)", +) +group.add_argument( + "-O", + dest="frida_mode", + action="store_true", + default=False, + help="use binary-only instrumentation (FRIDA mode)", +) +group.add_argument( + "-Q", + dest="qemu_mode", + action="store_true", + default=False, + help="use binary-only instrumentation (QEMU mode)", +) +group.add_argument( + "-U", + dest="unicorn_mode", + action="store_true", + default=False, + help="use unicorn-based instrumentation (Unicorn mode)", +) +group.add_argument( + "-X", dest="nyx_mode", action="store_true", default=False, help="use Nyx mode" +) -group = parser.add_argument_group('Misc') -group.add_argument('-T', - dest='workers', - type=lambda x: cpu_count if x == 'all' else int(x), - default=1, - help='number of concurrent worker (default: %(default)d)') -group.add_argument('--as_queue', - action='store_true', - help='output file name like "id:000000,hash:value"') -group.add_argument('--no-dedup', - action='store_true', - help='skip deduplication step for corpus files') -group.add_argument('--debug', action='store_true') +group = parser.add_argument_group("Minimization settings") +group.add_argument( + "--crash-dir", + dest="crash_dir", + metavar="dir", + default=None, + help="move crashes to a separate dir, always deduplicated", +) +group.add_argument( + "-A", + dest="allow_any", + action="store_true", + help="allow crashes and timeouts (not recommended)", +) +group.add_argument( + "-C", + dest="crash_only", + action="store_true", + help="keep crashing inputs, reject everything else", +) +group.add_argument( + "-e", + dest="edge_mode", + action="store_true", + default=False, + help="solve for edge coverage only, ignore hit counts", +) -parser.add_argument('exe', metavar='/path/to/target_app') -parser.add_argument('args', nargs='*') +group = parser.add_argument_group("Misc") +group.add_argument( + "-T", + dest="workers", + type=lambda x: cpu_count if x == "all" else int(x), + default=1, + help="number of concurrent worker (default: %(default)d)", +) +group.add_argument( + "--as_queue", + action="store_true", + help='output file name like "id:000000,hash:value"', +) +group.add_argument( + "--no-dedup", action="store_true", help="skip deduplication step for corpus files" +) +group.add_argument("--debug", action="store_true") + +parser.add_argument("exe", metavar="/path/to/target_app") +parser.add_argument("args", nargs="*") args = parser.parse_args() logger = None afl_showmap_bin = None -tuple_index_type_code = 'I' +tuple_index_type_code = "I" file_index_type_code = None def init(): global logger log_level = logging.DEBUG if args.debug else logging.INFO - logging.basicConfig(level=log_level, - format='%(asctime)s - %(levelname)s - %(message)s') + logging.basicConfig( + level=log_level, format="%(asctime)s - %(levelname)s - %(message)s" + ) logger = logging.getLogger(__name__) if args.stdin_file and args.workers > 1: - logger.error('-f is only supported with one worker (-T 1)') + logger.error("-f is only supported with one worker (-T 1)") sys.exit(1) - if args.memory_limit != 'none' and args.memory_limit < 5: - logger.error('dangerously low memory limit') + if args.memory_limit != "none" and args.memory_limit < 5: + logger.error("dangerously low memory limit") sys.exit(1) - if args.time_limit != 'none' and args.time_limit < 10: - logger.error('dangerously low timeout') + if args.time_limit != "none" and args.time_limit < 10: + logger.error("dangerously low timeout") sys.exit(1) if not os.path.isfile(args.exe): logger.error('binary "%s" not found or not regular file', args.exe) sys.exit(1) - if not os.environ.get('AFL_SKIP_BIN_CHECK') and not any( - [args.qemu_mode, args.frida_mode, args.unicorn_mode, args.nyx_mode]): - if b'__AFL_SHM_ID' not in open(args.exe, 'rb').read(): - logger.error("binary '%s' doesn't appear to be instrumented", - args.exe) + if not os.environ.get("AFL_SKIP_BIN_CHECK") and not any( + [args.qemu_mode, args.frida_mode, args.unicorn_mode, args.nyx_mode] + ): + if b"__AFL_SHM_ID" not in open(args.exe, "rb").read(): + logger.error("binary '%s' doesn't appear to be instrumented", args.exe) sys.exit(1) for dn in args.input: @@ -191,18 +217,18 @@ def init(): os.path.dirname(__file__), os.getcwd(), ] - if os.environ.get('AFL_PATH'): - searches.append(os.environ['AFL_PATH']) + if os.environ.get("AFL_PATH"): + searches.append(os.environ["AFL_PATH"]) for search in searches: - afl_showmap_bin = shutil.which('afl-showmap', path=search) + afl_showmap_bin = shutil.which("afl-showmap", path=search) if afl_showmap_bin: break if not afl_showmap_bin: - logger.fatal('cannot find afl-showmap, please set AFL_PATH') + logger.fatal("cannot find afl-showmap, please set AFL_PATH") sys.exit(1) - trace_dir = os.path.join(args.output, '.traces') + trace_dir = os.path.join(args.output, ".traces") shutil.rmtree(trace_dir, ignore_errors=True) try: os.rmdir(args.output) @@ -210,19 +236,19 @@ def init(): pass if os.path.exists(args.output): logger.error( - 'directory "%s" exists and is not empty - delete it first', - args.output) + 'directory "%s" exists and is not empty - delete it first', args.output + ) sys.exit(1) if args.crash_dir and not os.path.exists(args.crash_dir): os.makedirs(args.crash_dir) os.makedirs(trace_dir) - logger.info('use %d workers (-T)', args.workers) + logger.info("use %d workers (-T)", args.workers) def detect_type_code(size): - for type_code in ['B', 'H', 'I', 'L', 'Q']: - if 256**array.array(type_code).itemsize > size: + for type_code in ["B", "H", "I", "L", "Q"]: + if 256 ** array.array(type_code).itemsize > size: return type_code @@ -238,71 +264,70 @@ def afl_showmap(input_path=None, batch=None, afl_map_size=None, first=False): # yapf: enable found_atat = False for arg in args.args: - if '@@' in arg: + if "@@" in arg: found_atat = True if args.stdin_file: assert args.workers == 1 input_from_file = True stdin_file = args.stdin_file - cmd += ['-H', stdin_file] + cmd += ["-H", stdin_file] elif found_atat: input_from_file = True - stdin_file = os.path.join(args.output, f'.input.{os.getpid()}') - cmd += ['-H', stdin_file] + stdin_file = os.path.join(args.output, f".input.{os.getpid()}") + cmd += ["-H", stdin_file] else: input_from_file = False if batch: input_from_file = True - filelist = os.path.join(args.output, f'.filelist.{os.getpid()}') - with open(filelist, 'w') as f: + filelist = os.path.join(args.output, f".filelist.{os.getpid()}") + with open(filelist, "w") as f: for _, path in batch: - f.write(path + '\n') - cmd += ['-I', filelist] - output_path = os.path.join(args.output, f'.showmap.{os.getpid()}') - cmd += ['-o', output_path] + f.write(path + "\n") + cmd += ["-I", filelist] + output_path = os.path.join(args.output, f".showmap.{os.getpid()}") + cmd += ["-o", output_path] else: if input_from_file: shutil.copy(input_path, stdin_file) - cmd += ['-o', '-'] + cmd += ["-o", "-"] if args.frida_mode: - cmd += ['-O'] + cmd += ["-O"] if args.qemu_mode: - cmd += ['-Q'] + cmd += ["-Q"] if args.unicorn_mode: - cmd += ['-U'] + cmd += ["-U"] if args.nyx_mode: - cmd += ['-X'] + cmd += ["-X"] if args.edge_mode: - cmd += ['-e'] - cmd += ['--', args.exe] + args.args + cmd += ["-e"] + cmd += ["--", args.exe] + args.args env = os.environ.copy() - env['AFL_QUIET'] = '1' - env['ASAN_OPTIONS'] = 'detect_leaks=0' + env["AFL_QUIET"] = "1" + env["ASAN_OPTIONS"] = "detect_leaks=0" if first: - logger.debug('run command line: %s', subprocess.list2cmdline(cmd)) - env['AFL_CMIN_ALLOW_ANY'] = '1' + logger.debug("run command line: %s", subprocess.list2cmdline(cmd)) + env["AFL_CMIN_ALLOW_ANY"] = "1" if afl_map_size: - env['AFL_MAP_SIZE'] = str(afl_map_size) + env["AFL_MAP_SIZE"] = str(afl_map_size) if args.crash_only: - env['AFL_CMIN_CRASHES_ONLY'] = '1' + env["AFL_CMIN_CRASHES_ONLY"] = "1" if args.allow_any: - env['AFL_CMIN_ALLOW_ANY'] = '1' + env["AFL_CMIN_ALLOW_ANY"] = "1" if input_from_file: - p = subprocess.Popen(cmd, - stdout=subprocess.PIPE, - env=env, - bufsize=1048576) + p = subprocess.Popen(cmd, stdout=subprocess.PIPE, env=env, bufsize=1048576) else: - p = subprocess.Popen(cmd, - stdin=open(input_path, 'rb'), - stdout=subprocess.PIPE, - env=env, - bufsize=1048576) + p = subprocess.Popen( + cmd, + stdin=open(input_path, "rb"), + stdout=subprocess.PIPE, + env=env, + bufsize=1048576, + ) out = p.stdout.read() p.wait() @@ -313,7 +338,7 @@ def afl_showmap(input_path=None, batch=None, afl_map_size=None, first=False): values = [] try: trace_file = os.path.join(output_path, basename) - with open(trace_file, 'r') as f: + with open(trace_file, "r") as f: values = list(map(int, f)) crashed = len(values) == 0 os.unlink(trace_file) @@ -366,21 +391,22 @@ class Worker(multiprocessing.Process): def run(self): map_size = self.afl_map_size or 65536 max_tuple = map_size * 9 - max_file_index = 256**array.array(file_index_type_code).itemsize - 1 + max_file_index = 256 ** array.array(file_index_type_code).itemsize - 1 m = array.array(file_index_type_code, [max_file_index] * max_tuple) counter = collections.Counter() crashes = [] - pack_name = os.path.join(args.output, '.traces', f'{self.idx}.pack') + pack_name = os.path.join(args.output, ".traces", f"{self.idx}.pack") pack_pos = 0 - with open(pack_name, 'wb') as trace_pack: + with open(pack_name, "wb") as trace_pack: while True: batch = self.q_in.get() if batch is None: break for idx, r, crash in afl_showmap( - batch=batch, afl_map_size=self.afl_map_size): + batch=batch, afl_map_size=self.afl_map_size + ): counter.update(r) used = False @@ -419,7 +445,7 @@ class CombineTraceWorker(multiprocessing.Process): def run(self): already_have = set() - with open(self.pack_name, 'rb') as f: + with open(self.pack_name, "rb") as f: for pos, tuple_count in self.jobs: f.seek(pos) result = array.array(tuple_index_type_code) @@ -430,7 +456,7 @@ class CombineTraceWorker(multiprocessing.Process): def hash_file(path): m = hashlib.sha1() - with open(path, 'rb') as f: + with open(path, "rb") as f: m.update(f.read()) return m.digest() @@ -443,11 +469,14 @@ def dedup(files): # use large chunksize to reduce multiprocessing overhead chunksize = max(1, min(256, len(files) // args.workers)) for i, h in enumerate( - tqdm(pool.imap(hash_file, files, chunksize), - desc='dedup', - total=len(files), - ncols=0, - leave=(len(files) > 100000))): + tqdm( + pool.imap(hash_file, files, chunksize), + desc="dedup", + total=len(files), + ncols=0, + leave=(len(files) > 100000), + ) + ): if h in seen_hash: continue seen_hash.add(h) @@ -457,8 +486,12 @@ def dedup(files): def is_afl_dir(dirnames, filenames): - return ('queue' in dirnames and 'hangs' in dirnames - and 'crashes' in dirnames and 'fuzzer_setup' in filenames) + return ( + "queue" in dirnames + and "hangs" in dirnames + and "crashes" in dirnames + and "fuzzer_setup" in filenames + ) def collect_files(input_paths): @@ -467,18 +500,18 @@ def collect_files(input_paths): paths += glob.glob(s) files = [] - with tqdm(desc='search', unit=' files', ncols=0) as pbar: + with tqdm(desc="search", unit=" files", ncols=0) as pbar: for path in paths: for root, dirnames, filenames in os.walk(path, followlinks=True): for dirname in dirnames: - if dirname.startswith('.'): + if dirname.startswith("."): dirnames.remove(dirname) if not args.crash_only and is_afl_dir(dirnames, filenames): continue for filename in filenames: - if filename.startswith('.'): + if filename.startswith("."): continue pbar.update(1) files.append(os.path.join(root, filename)) @@ -490,21 +523,20 @@ def main(): files = collect_files(args.input) if len(files) == 0: - logger.error('no inputs in the target directory - nothing to be done') + logger.error("no inputs in the target directory - nothing to be done") sys.exit(1) - logger.info('Found %d input files in %d directories', len(files), - len(args.input)) + logger.info("Found %d input files in %d directories", len(files), len(args.input)) if not args.no_dedup: files, hash_list = dedup(files) - logger.info('Remain %d files after dedup', len(files)) + logger.info("Remain %d files after dedup", len(files)) else: - logger.info('Skipping file deduplication.') + logger.info("Skipping file deduplication.") global file_index_type_code file_index_type_code = detect_type_code(len(files)) - logger.info('Sorting files.') + logger.info("Sorting files.") with multiprocessing.Pool(args.workers) as pool: chunksize = max(1, min(512, len(files) // args.workers)) size_list = list(pool.map(os.path.getsize, files, chunksize)) @@ -513,24 +545,22 @@ def main(): hash_list = [hash_list[idx] for idx in idxes] afl_map_size = None - if b'AFL_DUMP_MAP_SIZE' in open(args.exe, 'rb').read(): - output = subprocess.run([args.exe], - capture_output=True, - env={ - 'AFL_DUMP_MAP_SIZE': '1' - }).stdout + if b"AFL_DUMP_MAP_SIZE" in open(args.exe, "rb").read(): + output = subprocess.run( + [args.exe], capture_output=True, env={"AFL_DUMP_MAP_SIZE": "1"} + ).stdout afl_map_size = int(output) - logger.info('Setting AFL_MAP_SIZE=%d', afl_map_size) + logger.info("Setting AFL_MAP_SIZE=%d", afl_map_size) global tuple_index_type_code tuple_index_type_code = detect_type_code(afl_map_size * 9) - logger.info('Testing the target binary') + logger.info("Testing the target binary") tuples, _ = afl_showmap(files[0], afl_map_size=afl_map_size, first=True) if tuples: - logger.info('ok, %d tuples recorded', len(tuples)) + logger.info("ok, %d tuples recorded", len(tuples)) else: - logger.error('no instrumentation output detected') + logger.error("no instrumentation output detected") sys.exit(1) job_queue = multiprocessing.Queue() @@ -550,7 +580,7 @@ def main(): dispatcher = JobDispatcher(job_queue, jobs) dispatcher.start() - logger.info('Processing traces') + logger.info("Processing traces") effective = 0 trace_info = {} for _ in tqdm(files, ncols=0, smoothing=0.01): @@ -561,7 +591,7 @@ def main(): effective += 1 dispatcher.join() - logger.info('Obtaining trace results') + logger.info("Obtaining trace results") ms = [] crashes = [] counter = collections.Counter() @@ -574,27 +604,38 @@ def main(): best_idxes = list(map(min, zip(*ms))) if not args.crash_dir: - logger.info('Found %d unique tuples across %d files (%d effective)', - len(counter), len(files), effective) + logger.info( + "Found %d unique tuples across %d files (%d effective)", + len(counter), + len(files), + effective, + ) else: logger.info( - 'Found %d unique tuples across %d files (%d effective, %d crashes)', - len(counter), len(files), effective, len(crashes)) + "Found %d unique tuples across %d files (%d effective, %d crashes)", + len(counter), + len(files), + effective, + len(crashes), + ) all_unique = counter.most_common() - logger.info('Processing candidates and writing output') + logger.info("Processing candidates and writing output") already_have = set() count = 0 def save_file(idx): input_path = files[idx] - fn = (base64.b16encode(hash_list[idx]).decode('utf8').lower() - if not args.no_dedup else os.path.basename(input_path)) + fn = ( + base64.b16encode(hash_list[idx]).decode("utf8").lower() + if not args.no_dedup + else os.path.basename(input_path) + ) if args.as_queue: if args.no_dedup: - fn = 'id:%06d,orig:%s' % (count, fn) + fn = "id:%06d,orig:%s" % (count, fn) else: - fn = 'id:%06d,hash:%s' % (count, fn) + fn = "id:%06d,hash:%s" % (count, fn) output_path = os.path.join(args.output, fn) try: os.link(input_path, output_path) @@ -620,8 +661,8 @@ def main(): trace_packs = [] workers = [] for i in range(args.workers): - pack_name = os.path.join(args.output, '.traces', f'{i}.pack') - trace_f = open(pack_name, 'rb') + pack_name = os.path.join(args.output, ".traces", f"{i}.pack") + trace_f = open(pack_name, "rb") trace_packs.append(trace_f) p = CombineTraceWorker(pack_name, jobs[i], result_queue) @@ -652,7 +693,7 @@ def main(): f.close() if args.crash_dir: - logger.info('Saving crashes to %s', args.crash_dir) + logger.info("Saving crashes to %s", args.crash_dir) crash_files = [files[c] for c in crashes] if args.no_dedup: @@ -661,7 +702,7 @@ def main(): crash_files, hash_list = dedup(crash_files) for idx, crash_path in enumerate(crash_files): - fn = base64.b16encode(hash_list[idx]).decode('utf8').lower() + fn = base64.b16encode(hash_list[idx]).decode("utf8").lower() output_path = os.path.join(args.crash_dir, fn) try: os.link(crash_path, output_path) @@ -675,13 +716,13 @@ def main(): pass if count == 1: - logger.warning('all test cases had the same traces, check syntax!') + logger.warning("all test cases had the same traces, check syntax!") logger.info('narrowed down to %s files, saved in "%s"', count, args.output) - if not os.environ.get('AFL_KEEP_TRACES'): - logger.info('Deleting trace files') - trace_dir = os.path.join(args.output, '.traces') + if not os.environ.get("AFL_KEEP_TRACES"): + logger.info("Deleting trace files") + trace_dir = os.path.join(args.output, ".traces") shutil.rmtree(trace_dir, ignore_errors=True) -if __name__ == '__main__': +if __name__ == "__main__": main() From 3f2e03aaf9725ed0ff1ffc6b03354a1ce45cf0c5 Mon Sep 17 00:00:00 2001 From: Kuang-che Wu Date: Thu, 8 May 2025 21:52:12 +0800 Subject: [PATCH 35/49] call afl-cmin.py if it can be executed successfully --- afl-cmin | 14 ++++++++------ 1 file changed, 8 insertions(+), 6 deletions(-) diff --git a/afl-cmin b/afl-cmin index 08dcbcae..baa003c5 100755 --- a/afl-cmin +++ b/afl-cmin @@ -4,11 +4,17 @@ test "$SYS" = "Darwin" && { echo Error: afl-cmin does not work on Apple currently. please use afl-cmin.bash instead. exit 1 } +THISPATH=`dirname ${0}` + +# call afl-cmin.py if it can be executed successfully. +if $THISPATH/afl-cmin.py --help > /dev/null 2>&1; then + exec $THISPATH/afl-cmin.py "$@" +fi + export AFL_QUIET=1 export ASAN_OPTIONS=detect_leaks=0 -THISPATH=`dirname ${0}` export PATH="${THISPATH}:$PATH" -awk -v script_dir="$THISPATH" -f - -- ${@+"$@"} <<'EOF' +awk -f - -- ${@+"$@"} <<'EOF' #!/usr/bin/awk -f # awk script to minimize a test corpus of input files # @@ -258,10 +264,6 @@ BEGIN { # sanity checks if (!prog_args[0] || !in_dir || !out_dir) usage() - if (0 == system( script_dir "/afl-cmin.py --help > /dev/null 2>&1" )) { - print "[*] Are you aware of the experimental afl-cmin.py that minimize corpus much faster?" - } - target_bin = prog_args[0] # Do a sanity check to discourage the use of /tmp, since we can't really From 61e97a8cebf579f582abc61fc363e698b3312663 Mon Sep 17 00:00:00 2001 From: Evian-Zhang Date: Fri, 9 May 2025 09:46:05 +0800 Subject: [PATCH 36/49] Do not match NUL when memmem --- src/afl-common.c | 4 ++-- src/afl-fuzz-init.c | 8 ++++---- 2 files changed, 6 insertions(+), 6 deletions(-) diff --git a/src/afl-common.c b/src/afl-common.c index 736c9f3d..f4eb54b2 100644 --- a/src/afl-common.c +++ b/src/afl-common.c @@ -179,7 +179,7 @@ u32 check_binary_signatures(u8 *fn) { if (f_data == MAP_FAILED) { PFATAL("Unable to mmap file '%s'", fn); } close(fd); - if (afl_memmem(f_data, f_len, PERSIST_SIG, strlen(PERSIST_SIG) + 1)) { + if (afl_memmem(f_data, f_len, PERSIST_SIG, strlen(PERSIST_SIG))) { if (!be_quiet) { OKF(cPIN "Persistent mode binary detected."); } setenv(PERSIST_ENV_VAR, "1", 1); @@ -204,7 +204,7 @@ u32 check_binary_signatures(u8 *fn) { } - if (afl_memmem(f_data, f_len, DEFER_SIG, strlen(DEFER_SIG) + 1)) { + if (afl_memmem(f_data, f_len, DEFER_SIG, strlen(DEFER_SIG))) { if (!be_quiet) { OKF(cPIN "Deferred forkserver binary detected."); } setenv(DEFER_ENV_VAR, "1", 1); diff --git a/src/afl-fuzz-init.c b/src/afl-fuzz-init.c index cf8d319c..645d2cf2 100644 --- a/src/afl-fuzz-init.c +++ b/src/afl-fuzz-init.c @@ -3116,7 +3116,7 @@ void check_binary(afl_state_t *afl, u8 *fname) { !afl->fsrv.nyx_mode && #endif !afl->fsrv.cs_mode && !afl->non_instrumented_mode && - !afl_memmem(f_data, f_len, SHM_ENV_VAR, strlen(SHM_ENV_VAR) + 1)) { + !afl_memmem(f_data, f_len, SHM_ENV_VAR, strlen(SHM_ENV_VAR))) { SAYF("\n" cLRD "[-] " cRST "Looks like the target binary is not instrumented! The fuzzer depends " @@ -3147,7 +3147,7 @@ void check_binary(afl_state_t *afl, u8 *fname) { } if ((afl->fsrv.cs_mode || afl->fsrv.qemu_mode || afl->fsrv.frida_mode) && - afl_memmem(f_data, f_len, SHM_ENV_VAR, strlen(SHM_ENV_VAR) + 1)) { + afl_memmem(f_data, f_len, SHM_ENV_VAR, strlen(SHM_ENV_VAR))) { SAYF("\n" cLRD "[-] " cRST "This program appears to be instrumented with AFL++ compilers, but is " @@ -3182,7 +3182,7 @@ void check_binary(afl_state_t *afl, u8 *fname) { /* Detect persistent & deferred init signatures in the binary. */ - if (afl_memmem(f_data, f_len, PERSIST_SIG, strlen(PERSIST_SIG) + 1)) { + if (afl_memmem(f_data, f_len, PERSIST_SIG, strlen(PERSIST_SIG))) { OKF(cPIN "Persistent mode binary detected."); setenv(PERSIST_ENV_VAR, "1", 1); @@ -3209,7 +3209,7 @@ void check_binary(afl_state_t *afl, u8 *fname) { } if (afl->fsrv.frida_mode || - afl_memmem(f_data, f_len, DEFER_SIG, strlen(DEFER_SIG) + 1)) { + afl_memmem(f_data, f_len, DEFER_SIG, strlen(DEFER_SIG))) { OKF(cPIN "Deferred forkserver binary detected."); setenv(DEFER_ENV_VAR, "1", 1); From 4d9d8aaf163e8d8546abf6b0d9e85eee3140c02b Mon Sep 17 00:00:00 2001 From: vanhauser-thc Date: Fri, 9 May 2025 09:12:37 +0200 Subject: [PATCH 37/49] afl-cmin.py nits --- GNUmakefile | 2 +- afl-cmin | 11 ++++++----- docs/Changelog.md | 3 +++ 3 files changed, 10 insertions(+), 6 deletions(-) diff --git a/GNUmakefile b/GNUmakefile index 6605c25c..9d50d5a8 100644 --- a/GNUmakefile +++ b/GNUmakefile @@ -31,7 +31,7 @@ PROGNAME = afl VERSION = $(shell grep '^$(HASH)define VERSION ' ../config.h | cut -d '"' -f2) PROGS = afl-fuzz afl-showmap afl-tmin afl-gotcpu afl-analyze -SH_PROGS = afl-plot afl-cmin afl-cmin.bash afl-whatsup afl-addseeds afl-system-config afl-persistent-config afl-cc +SH_PROGS = afl-plot afl-cmin afl-cmin.bash afl-cmin.py afl-whatsup afl-addseeds afl-system-config afl-persistent-config afl-cc HEADERS = include/afl-fuzz.h include/afl-mutations.h include/afl-persistent-replay.h include/afl-prealloc.h include/afl-record-compat.h include/alloc-inl.h include/android-ashmem.h include/cmplog.h include/common.h include/config.h include/coverage-32.h include/coverage-64.h include/debug.h include/envs.h include/forkserver.h include/hash.h include/list.h include/sharedmem.h include/snapshot-inl.h include/t1ha.h include/t1ha0_ia32aes_b.h include/t1ha_bits.h include/t1ha_selfcheck.h include/types.h include/xxhash.h MANPAGES=$(foreach p, $(PROGS) $(SH_PROGS), $(p).8) ASAN_OPTIONS=detect_leaks=0 diff --git a/afl-cmin b/afl-cmin index baa003c5..f7c54917 100755 --- a/afl-cmin +++ b/afl-cmin @@ -1,9 +1,4 @@ #!/usr/bin/env sh -SYS=$(uname -s) -test "$SYS" = "Darwin" && { - echo Error: afl-cmin does not work on Apple currently. please use afl-cmin.bash instead. - exit 1 -} THISPATH=`dirname ${0}` # call afl-cmin.py if it can be executed successfully. @@ -11,6 +6,12 @@ if $THISPATH/afl-cmin.py --help > /dev/null 2>&1; then exec $THISPATH/afl-cmin.py "$@" fi +SYS=$(uname -s) +test "$SYS" = "Darwin" && { + echo Error: afl-cmin does not work on Apple currently. please use afl-cmin.bash instead. + exit 1 +} + export AFL_QUIET=1 export ASAN_OPTIONS=detect_leaks=0 export PATH="${THISPATH}:$PATH" diff --git a/docs/Changelog.md b/docs/Changelog.md index c45e0fbe..c26aa148 100644 --- a/docs/Changelog.md +++ b/docs/Changelog.md @@ -15,6 +15,9 @@ - Fix to make AFL_SAN_NO_INST work with gcc_plugin - qemuafl: - better MIPS persistent mode support + - afl-cmin: + - new afl-cmin.py which is much faster, will be executed by default via + afl-cmin if it executes successfully (thanks to @kcwu!) ### Version ++4.32c (release) From f3995d5225a3c13ef62c44ddfed22a52cb6f5686 Mon Sep 17 00:00:00 2001 From: mio Date: Mon, 12 May 2025 14:43:08 +0800 Subject: [PATCH 38/49] rename AFL_SAN_NO_INST to AFL_FSRV_ONLY --- docs/SAND.md | 8 ++++---- docs/env_variables.md | 4 ++++ include/envs.h | 3 ++- instrumentation/SanitizerCoverageLTO.so.cc | 4 ++-- instrumentation/SanitizerCoveragePCGUARD.so.cc | 2 +- instrumentation/afl-compiler-rt.o.c | 5 +++++ instrumentation/afl-llvm-pass.so.cc | 4 ++-- src/afl-cc.c | 8 ++++---- src/afl-common.c | 10 ++++++++-- src/afl-fuzz-sanfuzz.c | 4 +++- 10 files changed, 35 insertions(+), 17 deletions(-) diff --git a/docs/SAND.md b/docs/SAND.md index b9d6647f..b110ad5b 100644 --- a/docs/SAND.md +++ b/docs/SAND.md @@ -21,7 +21,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 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. +2. Build target project with AFL_USE_ASAN=1 AFL_FSRV_ONLY=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: @@ -44,11 +44,11 @@ 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 +AFL_FSRV_ONLY=1 AFL_USE_UBSAN=1 AFL_USE_ASAN=1 afl-clang-fast test-instr.c -o ./asanubsan +AFL_FSRV_ONLY=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. +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_FSRV_ONLY=1`. This will mess up SAND execution pattern. 3. Start fuzzing diff --git a/docs/env_variables.md b/docs/env_variables.md index 3052663a..65ade8ee 100644 --- a/docs/env_variables.md +++ b/docs/env_variables.md @@ -111,6 +111,10 @@ fairly broad use of environment variables instead: - Note: both `AFL_CFISAN_VERBOSE=1` and `AFL_UBSAN_VERBOSE=1` are disabled by default as verbose output can significantly slow down fuzzing performance. Use these options only during debugging or when additional crash diagnostics are required + - `AFL_FSRV_ONLY` will inject forkserver but not pc instrumentation. Please note this is different compared to `AFL_LLVM_DISABLE_INSTRUMENTATION`, which will totally disable forkserver implementation. This env is pretty useful in two cases: + - [SAND](./SAND.md). In this case, the binaries built in this way will serve as extra oracles. Check the corresponding documents for details. + - Compatible with LibAFL ForkserverExecutor implementation and thus faster to run compared to simple CommandExecutor. + - `TMPDIR` is used by afl-as for temporary files; if this variable is not set, the tool defaults to /tmp. diff --git a/include/envs.h b/include/envs.h index 433b51a5..130d539d 100644 --- a/include/envs.h +++ b/include/envs.h @@ -10,6 +10,7 @@ static char *afl_environment_deprecated[] = { "AFL_DEFER_FORKSRV", "AFL_POST_LIBRARY", "AFL_PERSISTENT", + "AFL_SAN_NO_INST", NULL }; @@ -118,7 +119,7 @@ static char *afl_environment_variables[] = { "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", - "AFL_SAN_ABSTRACTION", "AFL_SAN_NO_INST", "AFL_SAN_RECOVER", + "AFL_SAN_ABSTRACTION", "AFL_FSRV_ONLY", "AFL_SAN_RECOVER", "AFL_PRELOAD_DISCRIMINATE_FORKSERVER_PARENT", NULL}; extern char *afl_environment_variables[]; diff --git a/instrumentation/SanitizerCoverageLTO.so.cc b/instrumentation/SanitizerCoverageLTO.so.cc index c27ff36a..407bb78c 100644 --- a/instrumentation/SanitizerCoverageLTO.so.cc +++ b/instrumentation/SanitizerCoverageLTO.so.cc @@ -327,7 +327,7 @@ class ModuleSanitizerCoverageLTOLegacyPass : public ModulePass { }; - if (!getenv("AFL_SAN_NO_INST")) { + if (!getenv("AFL_FSRV_ONLY")) { return ModuleSancov.instrumentModule(M, DTCallback, PDTCallback); @@ -389,7 +389,7 @@ PreservedAnalyses ModuleSanitizerCoverageLTO::run(Module &M, }; - if (!getenv("AFL_SAN_NO_INST")) { + if (!getenv("AFL_FSRV_ONLY")) { if (ModuleSancov.instrumentModule(M, DTCallback, PDTCallback)) return PreservedAnalyses::none(); diff --git a/instrumentation/SanitizerCoveragePCGUARD.so.cc b/instrumentation/SanitizerCoveragePCGUARD.so.cc index 670f06c1..6a0a7879 100644 --- a/instrumentation/SanitizerCoveragePCGUARD.so.cc +++ b/instrumentation/SanitizerCoveragePCGUARD.so.cc @@ -272,7 +272,7 @@ PreservedAnalyses ModuleSanitizerCoverageAFL::run(Module &M, // 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 (!getenv("AFL_FSRV_ONLY")) { if (ModuleSancov.instrumentModule(M, DTCallback, PDTCallback)) return PreservedAnalyses::none(); diff --git a/instrumentation/afl-compiler-rt.o.c b/instrumentation/afl-compiler-rt.o.c index 24d2ed9e..14ee0519 100644 --- a/instrumentation/afl-compiler-rt.o.c +++ b/instrumentation/afl-compiler-rt.o.c @@ -1241,6 +1241,11 @@ void __afl_manual_init(void) { } + if (getenv("AFL_FSRV_ONLY")) { + fprintf(stderr, "DEBUG: Overwrite area_ptr to dummy due to AFL_FSRV_ONLY\n"); + __afl_area_ptr = __afl_area_ptr_dummy; + } + if (!init_done) { __afl_start_forkserver(); diff --git a/instrumentation/afl-llvm-pass.so.cc b/instrumentation/afl-llvm-pass.so.cc index 3fb644af..1c6a1089 100644 --- a/instrumentation/afl-llvm-pass.so.cc +++ b/instrumentation/afl-llvm-pass.so.cc @@ -225,7 +225,7 @@ 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 (getenv("AFL_FSRV_ONLY")) { if (debug) { fprintf(stderr, "Instrument disabled\n"); } return PreservedAnalyses::all(); @@ -233,7 +233,7 @@ bool AFLCoverage::runOnModule(Module &M) { } #else - if (getenv("AFL_SAN_NO_INST")) { + if (getenv("AFL_FSRV_ONLY")) { if (debug) { fprintf(stderr, "Instrument disabled\n"); } return true; diff --git a/src/afl-cc.c b/src/afl-cc.c index c735ef84..59614456 100644 --- a/src/afl-cc.c +++ b/src/afl-cc.c @@ -244,7 +244,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 (getenv("AFL_FSRV_ONLY")) { if (!be_quiet) { DEBUGF("SAND: Coverage instrumentation disabled\n"); } return; @@ -2097,7 +2097,7 @@ void add_native_pcguard(aflcc_state_t *aflcc) { * anyway. */ if (aflcc->have_rust_asanrt) { return; } - if (getenv("AFL_SAN_NO_INST")) { + if (getenv("AFL_FSRV_ONLY")) { if (!be_quiet) { DEBUGF("SAND: Coverage instrumentation disabled\n"); } return; @@ -2138,7 +2138,7 @@ void add_native_pcguard(aflcc_state_t *aflcc) { */ void add_optimized_pcguard(aflcc_state_t *aflcc) { - if (getenv("AFL_SAN_NO_INST")) { + if (getenv("AFL_FSRV_ONLY")) { if (!be_quiet) { DEBUGF("SAND: Coverage instrumentation disabled\n"); } return; @@ -2600,7 +2600,7 @@ void add_assembler(aflcc_state_t *aflcc) { /* Add params to launch the gcc plugins for instrumentation. */ void add_gcc_plugin(aflcc_state_t *aflcc) { - if (getenv("AFL_SAN_NO_INST")) { + if (getenv("AFL_FSRV_ONLY")) { if (!be_quiet) { DEBUGF("SAND: Coverage instrumentation disabled\n"); } return; diff --git a/src/afl-common.c b/src/afl-common.c index f4eb54b2..ba36d372 100644 --- a/src/afl-common.c +++ b/src/afl-common.c @@ -819,8 +819,14 @@ void check_environment_vars(char **envp) { WARNF("AFL environment variable %s is deprecated!", afl_environment_deprecated[i]); - issue_detected = 1; - + + if (strncmp(afl_environment_deprecated[i], + "AFL_SAN_NO_INST", strlen(afl_environment_deprecated[i])) == 0 && !getenv("AFL_FSRV_ONLY")) { + WARNF("AFL_FSRV_ONLY is induced and set instead."); + setenv("AFL_FSRV_ONLY", "1", 0); + } else { + issue_detected = 1; + } } else { i++; diff --git a/src/afl-fuzz-sanfuzz.c b/src/afl-fuzz-sanfuzz.c index 3b25c57b..bae4dd2e 100644 --- a/src/afl-fuzz-sanfuzz.c +++ b/src/afl-fuzz-sanfuzz.c @@ -36,9 +36,11 @@ void sanfuzz_exec_child(afl_forkserver_t *fsrv, char **argv) { argv[0] != fsrv->asanfuzz_binary) { argv[0] = fsrv->asanfuzz_binary; - } + // In case users provide the normally instrumented binaries, this servers as the last + // resort to avoid collecting incorrect coverage. + setenv("AFL_FSRV_ONLY", "1", 0); execv(fsrv->target_path, argv); } From 2357daebe03337e0447ce183f49cf0ef6fc5a69f Mon Sep 17 00:00:00 2001 From: mio Date: Mon, 12 May 2025 14:46:29 +0800 Subject: [PATCH 39/49] update SAND docs accordingly --- docs/SAND.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/SAND.md b/docs/SAND.md index b110ad5b..72d8382b 100644 --- a/docs/SAND.md +++ b/docs/SAND.md @@ -48,7 +48,7 @@ AFL_FSRV_ONLY=1 AFL_USE_UBSAN=1 AFL_USE_ASAN=1 afl-clang-fast test-instr.c -o ./ AFL_FSRV_ONLY=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_FSRV_ONLY=1`. This will mess up SAND execution pattern. +Do note `AFL_FSRV_ONLY=1` is crucial, this enables forkservers but disables pc instrumentation. You are allowed to reuse sanitizers-enabled binaries, i.e. binaries built _without_ `AFL_FSRV_ONLY=1`, at a cost of reduced speed. 3. Start fuzzing From 19fc27a3f71399ec061cc98eed31f27520515116 Mon Sep 17 00:00:00 2001 From: mio Date: Mon, 12 May 2025 14:55:20 +0800 Subject: [PATCH 40/49] update docs --- docs/env_variables.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/env_variables.md b/docs/env_variables.md index 65ade8ee..96439e6b 100644 --- a/docs/env_variables.md +++ b/docs/env_variables.md @@ -113,7 +113,7 @@ fairly broad use of environment variables instead: - `AFL_FSRV_ONLY` will inject forkserver but not pc instrumentation. Please note this is different compared to `AFL_LLVM_DISABLE_INSTRUMENTATION`, which will totally disable forkserver implementation. This env is pretty useful in two cases: - [SAND](./SAND.md). In this case, the binaries built in this way will serve as extra oracles. Check the corresponding documents for details. - - Compatible with LibAFL ForkserverExecutor implementation and thus faster to run compared to simple CommandExecutor. + - Compatible with LibAFL ForkserverExecutor implementation and thus faster to repeatedly run, compared to simple CommandExecutor. - `TMPDIR` is used by afl-as for temporary files; if this variable is not set, the tool defaults to /tmp. From 8204bf69155b8909618b4a2567b0f485688c3bba Mon Sep 17 00:00:00 2001 From: mio Date: Tue, 13 May 2025 00:12:18 +0800 Subject: [PATCH 41/49] Allow afl-cmin.py for pre-3.12 by backport from more-itertools --- afl-cmin.py | 34 +++++++++++++++++++++++++++++++++- 1 file changed, 33 insertions(+), 1 deletion(-) diff --git a/afl-cmin.py b/afl-cmin.py index aad7bc16..5ceace8f 100755 --- a/afl-cmin.py +++ b/afl-cmin.py @@ -28,6 +28,38 @@ import shutil import subprocess import sys +# https://more-itertools.readthedocs.io/en/stable/_modules/more_itertools/recipes.html#batched +from sys import hexversion + +def _batched(iterable, n, *, strict=False): + """Batch data into tuples of length *n*. If the number of items in + *iterable* is not divisible by *n*: + * The last batch will be shorter if *strict* is ``False``. + * :exc:`ValueError` will be raised if *strict* is ``True``. + + >>> list(batched('ABCDEFG', 3)) + [('A', 'B', 'C'), ('D', 'E', 'F'), ('G',)] + + On Python 3.13 and above, this is an alias for :func:`itertools.batched`. + """ + if n < 1: + raise ValueError('n must be at least one') + iterator = iter(iterable) + while batch := tuple(itertools.islice(iterator, n)): + if strict and len(batch) != n: + raise ValueError('batched(): incomplete batch') + yield batch + + +if hexversion >= 0x30D00A2: # pragma: no cover + from itertools import batched as itertools_batched + def batched(iterable, n, *, strict=False): + return itertools_batched(iterable, n, strict=strict) +else: + batched = _batched + + batched.__doc__ = _batched.__doc__ + try: from tqdm import tqdm except ImportError: @@ -574,7 +606,7 @@ def main(): workers.append(p) chunk = max(1, min(128, len(files) // args.workers)) - jobs = list(itertools.batched(enumerate(files), chunk)) + jobs = list(batched(enumerate(files), chunk)) jobs += [None] * args.workers # sentinel dispatcher = JobDispatcher(job_queue, jobs) From 919108ee57528254348e58df75ef1db0bebbabce Mon Sep 17 00:00:00 2001 From: Kuang-che Wu Date: Tue, 13 May 2025 15:19:59 +0800 Subject: [PATCH 42/49] show stats more frequently when sync foreign otherwise, the stats might have no updates for hours for large foreign directory --- src/afl-fuzz-init.c | 8 ++------ 1 file changed, 2 insertions(+), 6 deletions(-) diff --git a/src/afl-fuzz-init.c b/src/afl-fuzz-init.c index 645d2cf2..5a86a74a 100644 --- a/src/afl-fuzz-init.c +++ b/src/afl-fuzz-init.c @@ -658,12 +658,8 @@ void read_foreign_testcases(afl_state_t *afl, int first) { munmap(mem, st.st_size); close(fd); - if (st.st_mtime > mtime_max) { - - mtime_max = st.st_mtime; - show_stats(afl); - - } + if (st.st_mtime > mtime_max) { mtime_max = st.st_mtime; } + show_stats(afl); } From 9476204da0aa3abe6c0f06e7f1e352738ca9097e Mon Sep 17 00:00:00 2001 From: mio Date: Tue, 13 May 2025 15:45:33 +0800 Subject: [PATCH 43/49] rename to AFL_LLVM_ONLY_FSRV --- docs/SAND.md | 8 ++++---- docs/env_variables.md | 2 +- include/envs.h | 2 +- instrumentation/SanitizerCoverageLTO.so.cc | 4 ++-- instrumentation/SanitizerCoveragePCGUARD.so.cc | 2 +- instrumentation/afl-compiler-rt.o.c | 4 ++-- instrumentation/afl-llvm-pass.so.cc | 4 ++-- src/afl-cc.c | 8 ++++---- src/afl-common.c | 6 +++--- src/afl-fuzz-sanfuzz.c | 2 +- 10 files changed, 21 insertions(+), 21 deletions(-) diff --git a/docs/SAND.md b/docs/SAND.md index 72d8382b..6012c956 100644 --- a/docs/SAND.md +++ b/docs/SAND.md @@ -21,7 +21,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 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_FSRV_ONLY=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. +2. Build target project with AFL_USE_ASAN=1 AFL_LLVM_ONLY_FSRV=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: @@ -44,11 +44,11 @@ Just like the normal building process, except using `afl-clang-fast` 2. Build the sanitizers-enabled binaries. ```bash -AFL_FSRV_ONLY=1 AFL_USE_UBSAN=1 AFL_USE_ASAN=1 afl-clang-fast test-instr.c -o ./asanubsan -AFL_FSRV_ONLY=1 AFL_USE_MSAN=1 afl-clang-fast test-instr.c -o ./msan +AFL_LLVM_ONLY_FSRV=1 AFL_USE_UBSAN=1 AFL_USE_ASAN=1 afl-clang-fast test-instr.c -o ./asanubsan +AFL_LLVM_ONLY_FSRV=1 AFL_USE_MSAN=1 afl-clang-fast test-instr.c -o ./msan ``` -Do note `AFL_FSRV_ONLY=1` is crucial, this enables forkservers but disables pc instrumentation. You are allowed to reuse sanitizers-enabled binaries, i.e. binaries built _without_ `AFL_FSRV_ONLY=1`, at a cost of reduced speed. +Do note `AFL_LLVM_ONLY_FSRV=1` is crucial, this enables forkservers but disables pc instrumentation. You are allowed to reuse sanitizers-enabled binaries, i.e. binaries built _without_ `AFL_LLVM_ONLY_FSRV=1`, at a cost of reduced speed. 3. Start fuzzing diff --git a/docs/env_variables.md b/docs/env_variables.md index 96439e6b..62fbef13 100644 --- a/docs/env_variables.md +++ b/docs/env_variables.md @@ -111,7 +111,7 @@ fairly broad use of environment variables instead: - Note: both `AFL_CFISAN_VERBOSE=1` and `AFL_UBSAN_VERBOSE=1` are disabled by default as verbose output can significantly slow down fuzzing performance. Use these options only during debugging or when additional crash diagnostics are required - - `AFL_FSRV_ONLY` will inject forkserver but not pc instrumentation. Please note this is different compared to `AFL_LLVM_DISABLE_INSTRUMENTATION`, which will totally disable forkserver implementation. This env is pretty useful in two cases: + - `AFL_LLVM_ONLY_FSRV` will inject forkserver but not pc instrumentation. Please note this is different compared to `AFL_LLVM_DISABLE_INSTRUMENTATION`, which will totally disable forkserver implementation. This env is pretty useful in two cases: - [SAND](./SAND.md). In this case, the binaries built in this way will serve as extra oracles. Check the corresponding documents for details. - Compatible with LibAFL ForkserverExecutor implementation and thus faster to repeatedly run, compared to simple CommandExecutor. diff --git a/include/envs.h b/include/envs.h index 130d539d..f5137247 100644 --- a/include/envs.h +++ b/include/envs.h @@ -119,7 +119,7 @@ static char *afl_environment_variables[] = { "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", - "AFL_SAN_ABSTRACTION", "AFL_FSRV_ONLY", "AFL_SAN_RECOVER", + "AFL_SAN_ABSTRACTION", "AFL_LLVM_ONLY_FSRV", "AFL_SAN_RECOVER", "AFL_PRELOAD_DISCRIMINATE_FORKSERVER_PARENT", NULL}; extern char *afl_environment_variables[]; diff --git a/instrumentation/SanitizerCoverageLTO.so.cc b/instrumentation/SanitizerCoverageLTO.so.cc index 407bb78c..7d2e45b3 100644 --- a/instrumentation/SanitizerCoverageLTO.so.cc +++ b/instrumentation/SanitizerCoverageLTO.so.cc @@ -327,7 +327,7 @@ class ModuleSanitizerCoverageLTOLegacyPass : public ModulePass { }; - if (!getenv("AFL_FSRV_ONLY")) { + if (!getenv("AFL_LLVM_ONLY_FSRV")) { return ModuleSancov.instrumentModule(M, DTCallback, PDTCallback); @@ -389,7 +389,7 @@ PreservedAnalyses ModuleSanitizerCoverageLTO::run(Module &M, }; - if (!getenv("AFL_FSRV_ONLY")) { + if (!getenv("AFL_LLVM_ONLY_FSRV")) { if (ModuleSancov.instrumentModule(M, DTCallback, PDTCallback)) return PreservedAnalyses::none(); diff --git a/instrumentation/SanitizerCoveragePCGUARD.so.cc b/instrumentation/SanitizerCoveragePCGUARD.so.cc index 6a0a7879..7c70a42e 100644 --- a/instrumentation/SanitizerCoveragePCGUARD.so.cc +++ b/instrumentation/SanitizerCoveragePCGUARD.so.cc @@ -272,7 +272,7 @@ PreservedAnalyses ModuleSanitizerCoverageAFL::run(Module &M, // TODO: Support LTO or llvm classic? // Note we still need afl-compiler-rt so we just disable the instrumentation // here. - if (!getenv("AFL_FSRV_ONLY")) { + if (!getenv("AFL_LLVM_ONLY_FSRV")) { if (ModuleSancov.instrumentModule(M, DTCallback, PDTCallback)) return PreservedAnalyses::none(); diff --git a/instrumentation/afl-compiler-rt.o.c b/instrumentation/afl-compiler-rt.o.c index 14ee0519..5dee57c8 100644 --- a/instrumentation/afl-compiler-rt.o.c +++ b/instrumentation/afl-compiler-rt.o.c @@ -1241,8 +1241,8 @@ void __afl_manual_init(void) { } - if (getenv("AFL_FSRV_ONLY")) { - fprintf(stderr, "DEBUG: Overwrite area_ptr to dummy due to AFL_FSRV_ONLY\n"); + if (getenv("AFL_LLVM_ONLY_FSRV")) { + fprintf(stderr, "DEBUG: Overwrite area_ptr to dummy due to AFL_LLVM_ONLY_FSRV\n"); __afl_area_ptr = __afl_area_ptr_dummy; } diff --git a/instrumentation/afl-llvm-pass.so.cc b/instrumentation/afl-llvm-pass.so.cc index 1c6a1089..e7839612 100644 --- a/instrumentation/afl-llvm-pass.so.cc +++ b/instrumentation/afl-llvm-pass.so.cc @@ -225,7 +225,7 @@ bool AFLCoverage::runOnModule(Module &M) { if (getenv("AFL_DEBUG")) debug = 1; #if LLVM_VERSION_MAJOR >= 11 /* use new pass manager */ - if (getenv("AFL_FSRV_ONLY")) { + if (getenv("AFL_LLVM_ONLY_FSRV")) { if (debug) { fprintf(stderr, "Instrument disabled\n"); } return PreservedAnalyses::all(); @@ -233,7 +233,7 @@ bool AFLCoverage::runOnModule(Module &M) { } #else - if (getenv("AFL_FSRV_ONLY")) { + if (getenv("AFL_LLVM_ONLY_FSRV")) { if (debug) { fprintf(stderr, "Instrument disabled\n"); } return true; diff --git a/src/afl-cc.c b/src/afl-cc.c index 59614456..e01484b8 100644 --- a/src/afl-cc.c +++ b/src/afl-cc.c @@ -244,7 +244,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_FSRV_ONLY")) { + if (getenv("AFL_LLVM_ONLY_FSRV")) { if (!be_quiet) { DEBUGF("SAND: Coverage instrumentation disabled\n"); } return; @@ -2097,7 +2097,7 @@ void add_native_pcguard(aflcc_state_t *aflcc) { * anyway. */ if (aflcc->have_rust_asanrt) { return; } - if (getenv("AFL_FSRV_ONLY")) { + if (getenv("AFL_LLVM_ONLY_FSRV")) { if (!be_quiet) { DEBUGF("SAND: Coverage instrumentation disabled\n"); } return; @@ -2138,7 +2138,7 @@ void add_native_pcguard(aflcc_state_t *aflcc) { */ void add_optimized_pcguard(aflcc_state_t *aflcc) { - if (getenv("AFL_FSRV_ONLY")) { + if (getenv("AFL_LLVM_ONLY_FSRV")) { if (!be_quiet) { DEBUGF("SAND: Coverage instrumentation disabled\n"); } return; @@ -2600,7 +2600,7 @@ void add_assembler(aflcc_state_t *aflcc) { /* Add params to launch the gcc plugins for instrumentation. */ void add_gcc_plugin(aflcc_state_t *aflcc) { - if (getenv("AFL_FSRV_ONLY")) { + if (getenv("AFL_LLVM_ONLY_FSRV")) { if (!be_quiet) { DEBUGF("SAND: Coverage instrumentation disabled\n"); } return; diff --git a/src/afl-common.c b/src/afl-common.c index ba36d372..e6e5a0ee 100644 --- a/src/afl-common.c +++ b/src/afl-common.c @@ -821,9 +821,9 @@ void check_environment_vars(char **envp) { afl_environment_deprecated[i]); if (strncmp(afl_environment_deprecated[i], - "AFL_SAN_NO_INST", strlen(afl_environment_deprecated[i])) == 0 && !getenv("AFL_FSRV_ONLY")) { - WARNF("AFL_FSRV_ONLY is induced and set instead."); - setenv("AFL_FSRV_ONLY", "1", 0); + "AFL_SAN_NO_INST", strlen(afl_environment_deprecated[i])) == 0 && !getenv("AFL_LLVM_ONLY_FSRV")) { + WARNF("AFL_LLVM_ONLY_FSRV is induced and set instead."); + setenv("AFL_LLVM_ONLY_FSRV", "1", 0); } else { issue_detected = 1; } diff --git a/src/afl-fuzz-sanfuzz.c b/src/afl-fuzz-sanfuzz.c index bae4dd2e..81c48449 100644 --- a/src/afl-fuzz-sanfuzz.c +++ b/src/afl-fuzz-sanfuzz.c @@ -40,7 +40,7 @@ void sanfuzz_exec_child(afl_forkserver_t *fsrv, char **argv) { // In case users provide the normally instrumented binaries, this servers as the last // resort to avoid collecting incorrect coverage. - setenv("AFL_FSRV_ONLY", "1", 0); + setenv("AFL_LLVM_ONLY_FSRV", "1", 0); execv(fsrv->target_path, argv); } From fca39a6ec36519d7c99bc7d1d50bf5810f0b66f0 Mon Sep 17 00:00:00 2001 From: mio Date: Tue, 13 May 2025 16:15:11 +0800 Subject: [PATCH 44/49] implement AFL_GCC_ONLY_FSRV --- docs/env_variables.md | 2 +- include/envs.h | 2 +- instrumentation/afl-compiler-rt.o.c | 4 ++-- instrumentation/afl-gcc-pass.so.cc | 16 ++++++++++++---- src/afl-cc.c | 2 +- src/afl-common.c | 5 +++-- 6 files changed, 20 insertions(+), 11 deletions(-) diff --git a/docs/env_variables.md b/docs/env_variables.md index 62fbef13..a492a1bc 100644 --- a/docs/env_variables.md +++ b/docs/env_variables.md @@ -111,7 +111,7 @@ fairly broad use of environment variables instead: - Note: both `AFL_CFISAN_VERBOSE=1` and `AFL_UBSAN_VERBOSE=1` are disabled by default as verbose output can significantly slow down fuzzing performance. Use these options only during debugging or when additional crash diagnostics are required - - `AFL_LLVM_ONLY_FSRV` will inject forkserver but not pc instrumentation. Please note this is different compared to `AFL_LLVM_DISABLE_INSTRUMENTATION`, which will totally disable forkserver implementation. This env is pretty useful in two cases: + - `AFL_LLVM_ONLY_FSRV`/`AFL_GCC_ONLY_FSRV` will inject forkserver but not pc instrumentation. Please note this is different compared to `AFL_LLVM_DISABLE_INSTRUMENTATION`, which will totally disable forkserver implementation. This env is pretty useful in two cases: - [SAND](./SAND.md). In this case, the binaries built in this way will serve as extra oracles. Check the corresponding documents for details. - Compatible with LibAFL ForkserverExecutor implementation and thus faster to repeatedly run, compared to simple CommandExecutor. diff --git a/include/envs.h b/include/envs.h index f5137247..eda9ceaf 100644 --- a/include/envs.h +++ b/include/envs.h @@ -119,7 +119,7 @@ static char *afl_environment_variables[] = { "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", - "AFL_SAN_ABSTRACTION", "AFL_LLVM_ONLY_FSRV", "AFL_SAN_RECOVER", + "AFL_SAN_ABSTRACTION", "AFL_LLVM_ONLY_FSRV", "AFL_GCC_ONLY_FRSV", "AFL_SAN_RECOVER", "AFL_PRELOAD_DISCRIMINATE_FORKSERVER_PARENT", NULL}; extern char *afl_environment_variables[]; diff --git a/instrumentation/afl-compiler-rt.o.c b/instrumentation/afl-compiler-rt.o.c index 5dee57c8..10a271cd 100644 --- a/instrumentation/afl-compiler-rt.o.c +++ b/instrumentation/afl-compiler-rt.o.c @@ -1241,8 +1241,8 @@ void __afl_manual_init(void) { } - if (getenv("AFL_LLVM_ONLY_FSRV")) { - fprintf(stderr, "DEBUG: Overwrite area_ptr to dummy due to AFL_LLVM_ONLY_FSRV\n"); + if (getenv("AFL_LLVM_ONLY_FSRV") || getenv("AFL_GCC_ONLY_FRSV")) { + fprintf(stderr, "DEBUG: Overwrite area_ptr to dummy due to AFL_LLVM_ONLY_FSRV/AFL_GCC_ONLY_FRSV\n"); __afl_area_ptr = __afl_area_ptr_dummy; } diff --git a/instrumentation/afl-gcc-pass.so.cc b/instrumentation/afl-gcc-pass.so.cc index 26f7bd19..8509d60f 100644 --- a/instrumentation/afl-gcc-pass.so.cc +++ b/instrumentation/afl-gcc-pass.so.cc @@ -462,6 +462,7 @@ static struct plugin_info afl_plugin = { .help = G_("AFL gcc plugin\n\ \n\ Set AFL_QUIET in the environment to silence it.\n\ +Set AFL_GCC_ONLY_FRSV in the environment to disable instrumentation.\n\ \n\ Set AFL_INST_RATIO in the environment to a number from 0 to 100\n\ to control how likely a block will be chosen for instrumentation.\n\ @@ -502,9 +503,12 @@ int plugin_init(struct plugin_name_args *info, case it was specified in the command line's -frandom-seed for reproducible instrumentation. */ srandom(get_random_seed(false)); + bool fsrv_only = !!getenv("AFL_GCC_ONLY_FRSV"); const char *name = info->base_name; - register_callback(name, PLUGIN_INFO, NULL, &afl_plugin); + if (!fsrv_only) { + register_callback(name, PLUGIN_INFO, NULL, &afl_plugin); + } afl_pass *aflp = new afl_pass(quiet, inst_ratio); struct register_pass_info pass_info = { @@ -516,14 +520,18 @@ int plugin_init(struct plugin_name_args *info, }; - register_callback(name, PLUGIN_PASS_MANAGER_SETUP, NULL, &pass_info); - register_callback(name, PLUGIN_FINISH, afl_pass::plugin_finalize, - pass_info.pass); + if (!fsrv_only) { + register_callback(name, PLUGIN_PASS_MANAGER_SETUP, NULL, &pass_info); + register_callback(name, PLUGIN_FINISH, afl_pass::plugin_finalize, + pass_info.pass); + } if (!quiet) ACTF(G_("%s instrumentation at ratio of %u%% in %s mode."), aflp->out_of_line ? G_("Call-based") : G_("Inline"), inst_ratio, getenv("AFL_HARDEN") ? G_("hardened") : G_("non-hardened")); + else if (fsrv_only) + ACTF("Instrumentation disabled due to AFL_GCC_ONLY_FRSV"); return 0; diff --git a/src/afl-cc.c b/src/afl-cc.c index e01484b8..2892db14 100644 --- a/src/afl-cc.c +++ b/src/afl-cc.c @@ -2600,7 +2600,7 @@ void add_assembler(aflcc_state_t *aflcc) { /* Add params to launch the gcc plugins for instrumentation. */ void add_gcc_plugin(aflcc_state_t *aflcc) { - if (getenv("AFL_LLVM_ONLY_FSRV")) { + if (getenv("AFL_GCC_ONLY_FSRV")) { if (!be_quiet) { DEBUGF("SAND: Coverage instrumentation disabled\n"); } return; diff --git a/src/afl-common.c b/src/afl-common.c index e6e5a0ee..e61b8388 100644 --- a/src/afl-common.c +++ b/src/afl-common.c @@ -821,8 +821,9 @@ void check_environment_vars(char **envp) { afl_environment_deprecated[i]); if (strncmp(afl_environment_deprecated[i], - "AFL_SAN_NO_INST", strlen(afl_environment_deprecated[i])) == 0 && !getenv("AFL_LLVM_ONLY_FSRV")) { - WARNF("AFL_LLVM_ONLY_FSRV is induced and set instead."); + "AFL_SAN_NO_INST", strlen(afl_environment_deprecated[i])) == 0) { + WARNF("AFL_LLVM_ONLY_FSRV/AFL_GCC_ONLY_FSRV is induced and set instead."); + setenv("AFL_GCC_ONLY_FSRV", "1", 0); setenv("AFL_LLVM_ONLY_FSRV", "1", 0); } else { issue_detected = 1; From 221439fc7a584e04e0091e6d1511b4fa2c7af693 Mon Sep 17 00:00:00 2001 From: vanhauser-thc Date: Tue, 13 May 2025 10:42:26 +0200 Subject: [PATCH 45/49] fix foreign sync naming --- include/afl-fuzz.h | 1 + src/afl-fuzz-bitmap.c | 10 +++++++++- src/afl-fuzz-init.c | 6 +++++- 3 files changed, 15 insertions(+), 2 deletions(-) diff --git a/include/afl-fuzz.h b/include/afl-fuzz.h index 22f80567..ab93d282 100644 --- a/include/afl-fuzz.h +++ b/include/afl-fuzz.h @@ -770,6 +770,7 @@ typedef struct afl_state { #define FOREIGN_SYNCS_MAX 32U u8 foreign_sync_cnt; struct foreign_sync foreign_syncs[FOREIGN_SYNCS_MAX]; + char *foreign_file; #ifdef _AFL_DOCUMENT_MUTATIONS u8 do_document; diff --git a/src/afl-fuzz-bitmap.c b/src/afl-fuzz-bitmap.c index e7c79c69..5d3cc71a 100644 --- a/src/afl-fuzz-bitmap.c +++ b/src/afl-fuzz-bitmap.c @@ -317,7 +317,15 @@ u8 *describe_op(afl_state_t *afl, u8 new_bits, size_t max_description_len) { if (unlikely(afl->syncing_party)) { - sprintf(ret, "sync:%s,src:%06u", afl->syncing_party, afl->syncing_case); + if (unlikely(afl->foreign_file)) { + + sprintf(ret, "sync:%s,src:%.20s", afl->syncing_party, afl->foreign_file); + + } else { + + sprintf(ret, "sync:%s,src:%06u", afl->syncing_party, afl->syncing_case); + + } } else { diff --git a/src/afl-fuzz-init.c b/src/afl-fuzz-init.c index 645d2cf2..382a3cd9 100644 --- a/src/afl-fuzz-init.c +++ b/src/afl-fuzz-init.c @@ -653,8 +653,9 @@ void read_foreign_testcases(afl_state_t *afl, int first) { u32 len = write_to_testcase(afl, (void **)&mem, st.st_size, 1); fault = fuzz_run_target(afl, &afl->fsrv, afl->fsrv.exec_tmout); afl->syncing_party = foreign_name; + afl->foreign_file = nl[i]->d_name; afl->queued_imported += save_if_interesting(afl, mem, len, fault); - afl->syncing_party = 0; + munmap(mem, st.st_size); close(fd); @@ -679,6 +680,9 @@ void read_foreign_testcases(afl_state_t *afl, int first) { } + afl->foreign_file = NULL; + afl->syncing_party = 0; + if (first) { afl->last_find_time = 0; From 9e4449bad2e98e129660ac0beecc2daa41f96f3d Mon Sep 17 00:00:00 2001 From: vanhauser-thc Date: Tue, 13 May 2025 10:51:56 +0200 Subject: [PATCH 46/49] code format --- include/envs.h | 4 ++-- instrumentation/afl-compiler-rt.o.c | 6 +++++- instrumentation/afl-gcc-pass.so.cc | 6 +++--- src/afl-common.c | 19 +++++++++++++------ src/afl-fuzz-sanfuzz.c | 5 +++-- 5 files changed, 26 insertions(+), 14 deletions(-) diff --git a/include/envs.h b/include/envs.h index eda9ceaf..25063b8a 100644 --- a/include/envs.h +++ b/include/envs.h @@ -119,8 +119,8 @@ static char *afl_environment_variables[] = { "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", - "AFL_SAN_ABSTRACTION", "AFL_LLVM_ONLY_FSRV", "AFL_GCC_ONLY_FRSV", "AFL_SAN_RECOVER", - "AFL_PRELOAD_DISCRIMINATE_FORKSERVER_PARENT", NULL}; + "AFL_SAN_ABSTRACTION", "AFL_LLVM_ONLY_FSRV", "AFL_GCC_ONLY_FRSV", + "AFL_SAN_RECOVER", "AFL_PRELOAD_DISCRIMINATE_FORKSERVER_PARENT", NULL}; extern char *afl_environment_variables[]; diff --git a/instrumentation/afl-compiler-rt.o.c b/instrumentation/afl-compiler-rt.o.c index 10a271cd..78826da8 100644 --- a/instrumentation/afl-compiler-rt.o.c +++ b/instrumentation/afl-compiler-rt.o.c @@ -1242,8 +1242,12 @@ void __afl_manual_init(void) { } if (getenv("AFL_LLVM_ONLY_FSRV") || getenv("AFL_GCC_ONLY_FRSV")) { - fprintf(stderr, "DEBUG: Overwrite area_ptr to dummy due to AFL_LLVM_ONLY_FSRV/AFL_GCC_ONLY_FRSV\n"); + + fprintf(stderr, + "DEBUG: Overwrite area_ptr to dummy due to " + "AFL_LLVM_ONLY_FSRV/AFL_GCC_ONLY_FRSV\n"); __afl_area_ptr = __afl_area_ptr_dummy; + } if (!init_done) { diff --git a/instrumentation/afl-gcc-pass.so.cc b/instrumentation/afl-gcc-pass.so.cc index 8509d60f..eeef4e02 100644 --- a/instrumentation/afl-gcc-pass.so.cc +++ b/instrumentation/afl-gcc-pass.so.cc @@ -506,9 +506,7 @@ int plugin_init(struct plugin_name_args *info, bool fsrv_only = !!getenv("AFL_GCC_ONLY_FRSV"); const char *name = info->base_name; - if (!fsrv_only) { - register_callback(name, PLUGIN_INFO, NULL, &afl_plugin); - } + if (!fsrv_only) { register_callback(name, PLUGIN_INFO, NULL, &afl_plugin); } afl_pass *aflp = new afl_pass(quiet, inst_ratio); struct register_pass_info pass_info = { @@ -521,9 +519,11 @@ int plugin_init(struct plugin_name_args *info, }; if (!fsrv_only) { + register_callback(name, PLUGIN_PASS_MANAGER_SETUP, NULL, &pass_info); register_callback(name, PLUGIN_FINISH, afl_pass::plugin_finalize, pass_info.pass); + } if (!quiet) diff --git a/src/afl-common.c b/src/afl-common.c index e61b8388..fbbf8e0d 100644 --- a/src/afl-common.c +++ b/src/afl-common.c @@ -819,15 +819,22 @@ void check_environment_vars(char **envp) { WARNF("AFL environment variable %s is deprecated!", afl_environment_deprecated[i]); - - if (strncmp(afl_environment_deprecated[i], - "AFL_SAN_NO_INST", strlen(afl_environment_deprecated[i])) == 0) { - WARNF("AFL_LLVM_ONLY_FSRV/AFL_GCC_ONLY_FSRV is induced and set instead."); - setenv("AFL_GCC_ONLY_FSRV", "1", 0); - setenv("AFL_LLVM_ONLY_FSRV", "1", 0); + + if (strncmp(afl_environment_deprecated[i], "AFL_SAN_NO_INST", + strlen(afl_environment_deprecated[i])) == 0) { + + WARNF( + "AFL_LLVM_ONLY_FSRV/AFL_GCC_ONLY_FSRV is induced and set " + "instead."); + setenv("AFL_GCC_ONLY_FSRV", "1", 0); + setenv("AFL_LLVM_ONLY_FSRV", "1", 0); + } else { + issue_detected = 1; + } + } else { i++; diff --git a/src/afl-fuzz-sanfuzz.c b/src/afl-fuzz-sanfuzz.c index 81c48449..330c9482 100644 --- a/src/afl-fuzz-sanfuzz.c +++ b/src/afl-fuzz-sanfuzz.c @@ -36,10 +36,11 @@ void sanfuzz_exec_child(afl_forkserver_t *fsrv, char **argv) { argv[0] != fsrv->asanfuzz_binary) { argv[0] = fsrv->asanfuzz_binary; + } - // In case users provide the normally instrumented binaries, this servers as the last - // resort to avoid collecting incorrect coverage. + // In case users provide the normally instrumented binaries, this servers as + // the last resort to avoid collecting incorrect coverage. setenv("AFL_LLVM_ONLY_FSRV", "1", 0); execv(fsrv->target_path, argv); From b6d1247e7d61ac1b3beb767c62445ccbceaeab15 Mon Sep 17 00:00:00 2001 From: Leon Date: Wed, 14 May 2025 16:31:31 +0800 Subject: [PATCH 47/49] fix incorrect allocation size for top_rated_candidates (#2424) --- 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 8eefeada..013e055b 100644 --- a/src/afl-fuzz.c +++ b/src/afl-fuzz.c @@ -1745,7 +1745,7 @@ int main(int argc, char **argv_orig, char **envp) { if (afl->cycle_schedules) { - afl->top_rated_candidates = ck_alloc(map_size * sizeof(u32)); + afl->top_rated_candidates = ck_alloc(map_size * sizeof(u32 *)); } From ef0c236427af21a3ad04dfa46c7fa65cb25ddb4b Mon Sep 17 00:00:00 2001 From: vanhauser-thc Date: Wed, 14 May 2025 16:45:23 +0200 Subject: [PATCH 48/49] update fuzzing_in_depth --- docs/fuzzing_in_depth.md | 14 +++++++++----- 1 file changed, 9 insertions(+), 5 deletions(-) diff --git a/docs/fuzzing_in_depth.md b/docs/fuzzing_in_depth.md index fea9ca0b..59a6d4bc 100644 --- a/docs/fuzzing_in_depth.md +++ b/docs/fuzzing_in_depth.md @@ -132,11 +132,15 @@ options are available: locations. This technique is very fast and good - if the target does not transform input data before comparison. Therefore, this technique is called `input to state` or `redqueen`. If you want to use this technique, then you - have to compile the target twice, once specifically with/for this mode by - setting `AFL_LLVM_CMPLOG=1`, and pass this binary to afl-fuzz via the `-c` - parameter. Note that you can compile also just a cmplog binary and use that - for both, however, there will be a performance penalty. You can read more - about this in + have to compile the target with `AFL_LLVM_CMPLOG=1`. + You could use the resulting binary for both normal fuzzing and `-c` CMPLOG + mode (with `-c 0`), however this will result in a performance loss of about + 20%. + It is therefore better to compile a specific CMPLOG target with + `AFL_LLVM_ONLY_FSRV=1 AFL_LLVM_CMPLOG=1` and pass this binary name via + `-c cmplog-fuzzing-target` and compile target again normally with `afl-cc` + and use this is the fuzzing target as usual. + You can read more about this in [instrumentation/README.cmplog.md](../instrumentation/README.cmplog.md). If you use LTO, LLVM, or GCC_PLUGIN mode From bedb38e2168c5a6abddd765ea354fb6e44604479 Mon Sep 17 00:00:00 2001 From: vanhauser-thc Date: Wed, 14 May 2025 21:05:38 +0200 Subject: [PATCH 49/49] fix UAF in -F --- src/afl-fuzz-init.c | 8 ++++++-- 1 file changed, 6 insertions(+), 2 deletions(-) diff --git a/src/afl-fuzz-init.c b/src/afl-fuzz-init.c index e71fa715..fd3fabf7 100644 --- a/src/afl-fuzz-init.c +++ b/src/afl-fuzz-init.c @@ -589,8 +589,6 @@ void read_foreign_testcases(afl_state_t *afl, int first) { u8 *fn2 = alloc_printf("%s/%s", afl->foreign_syncs[iter].dir, nl[i]->d_name); - free(nl[i]); /* not tracked */ - if (unlikely(lstat(fn2, &st) || access(fn2, R_OK))) { if (first) PFATAL("Unable to access '%s'", fn2); @@ -670,6 +668,12 @@ void read_foreign_testcases(afl_state_t *afl, int first) { } + for (i = 0; i < (u32)nl_cnt; ++i) { + + free(nl[i]); /* not tracked */ + + } + free(nl); /* not tracked */ }