From 5ee5403c66a2fe92bc15d53c532ec7bdc134fe2a Mon Sep 17 00:00:00 2001 From: van Hauser Date: Sun, 23 Jun 2019 00:26:00 +0200 Subject: [PATCH 01/39] QoL patch: also use @@ instead of AFL_FILE --- README.md | 11 ++++++++--- afl-cov | 3 +++ 2 files changed, 11 insertions(+), 3 deletions(-) diff --git a/README.md b/README.md index 9990aeb..49b0c03 100644 --- a/README.md +++ b/README.md @@ -82,8 +82,12 @@ the output directory used by `afl-fuzz`, and the command to execute along with associated arguments. This command and arguments should closely resemble the manner in which `afl-fuzz` executes the targeted binary during the fuzzing cycle. If there is already an existing directory of AFL fuzzing results, then -just omit the `--live` argument to process the existing results. Here is an -example: +just omit the `--live` argument to process the existing results. + +NOTE: you can use both afl's "@@" or afl-cov's original "AFL_FILE" placeholder for +the --coverage-cmd line parameter. + +Here is an example: ```bash $ cd /path/to/project-gcov/ @@ -98,6 +102,7 @@ The `AFL_FILE` string above refers to the test case file that AFL will build in the `queue/` directory under `/path/to/afl-fuzz-output`. Just leave this string as-is since `afl-cov` will automatically substitute it with each AFL `queue/id:NNNNNN*` in succession as it builds the code coverage reports. +You can also use @@ instead of AFL_FILE, both notations work. Also, in the above command, this handles the case where the AFL fuzzing cycle is fuzzing the targeted binary via stdin. This explains the @@ -107,7 +112,7 @@ fuzzing with AFL where a file is read from the filesystem, here is an example: ```bash $ cd /path/to/project-gcov/ $ afl-cov -d /path/to/afl-fuzz-output/ --live --coverage-cmd \ -"LD_LIBRARY_PATH=./lib/.libs ./bin/.libs/somebin -f AFL_FILE -a -b -c" \ +"LD_LIBRARY_PATH=./lib/.libs ./bin/.libs/somebin -f @@ -a -b -c" \ --code-dir . ``` diff --git a/afl-cov b/afl-cov index 32318d3..5728aaa 100755 --- a/afl-cov +++ b/afl-cov @@ -57,6 +57,9 @@ def main(): cargs = parse_cmdline() + if cargs.coverage_cmd: + cargs.coverage_cmd = cargs.coverage_cmd.replace('@@', 'AFL_FILE') + if cargs.version: print "afl-cov-" + __version__ return exit_success From b8b90073a3b564f7e390b05aa5b075a8ae7a9e26 Mon Sep 17 00:00:00 2001 From: van Hauser Date: Tue, 27 Aug 2019 16:40:16 +0200 Subject: [PATCH 02/39] version bump --- afl-cov | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/afl-cov b/afl-cov index 5728aaa..fe15f51 100755 --- a/afl-cov +++ b/afl-cov @@ -44,7 +44,7 @@ try: except ImportError: import subprocess -__version__ = '0.6.2' +__version__ = '0.6.2.1' NO_OUTPUT = 0 WANT_OUTPUT = 1 From 3d709e572a811d7fc44e15f707ec277ce86acc27 Mon Sep 17 00:00:00 2001 From: van Hauser Date: Wed, 13 May 2020 12:30:33 +0200 Subject: [PATCH 03/39] add helper scripts --- README.md | 15 +++++++++++++++ afl-cov-build.sh | 15 +++++++++++++++ afl-cov.sh | 20 ++++++++++++++++++++ 3 files changed, 50 insertions(+) create mode 100755 afl-cov-build.sh create mode 100755 afl-cov.sh diff --git a/README.md b/README.md index 49b0c03..86eb8d2 100644 --- a/README.md +++ b/README.md @@ -1,5 +1,6 @@ # afl-cov - AFL Fuzzing Code Coverage +- [Preface](#preface) - [Introduction](#introduction) - [Prerequisites](#prerequisites) - [Workflow](#workflow) @@ -10,6 +11,20 @@ - [License](#license) - [Contact](#contact) +## Preface + +This is a modified afl-cov fork because the original author's account is +inactive :-( + +It has three changes: + * afl-cov now accepts "@@" like AFL++ in the command line + * afl-cov.sh makes using afl-cov easier (just needs two parameters) + * afl-cov-build.sh makes builing a target for coverage easier + +Enjoy! + +Marc "vanHauser" Heuse + ## Introduction `afl-cov` uses test case files produced by the [AFL fuzzer](http://lcamtuf.coredump.cx/afl/) `afl-fuzz` to generate gcov code diff --git a/afl-cov-build.sh b/afl-cov-build.sh new file mode 100755 index 0000000..ff4c5f7 --- /dev/null +++ b/afl-cov-build.sh @@ -0,0 +1,15 @@ +#!/bin/sh + +test -z "$1" -o "$1" = "-h" && { + echo "Syntax: $0 [options]" + echo Sets build options for coverage instrumentation with gcov/lcov. + echo Example: "$0 ./configure --disable-shared" +} + +test -z "$CC" && export CC=gcc +test -z "$CXX" && export CXX=g++ +export CFLAGS="-fprofile-arcs -ftest-coverage" +export CXXFLAGS="$CFLAGS" +export LDFLAGS="-lgcov --coverage" + +$* diff --git a/afl-cov.sh b/afl-cov.sh new file mode 100755 index 0000000..b9d1ad7 --- /dev/null +++ b/afl-cov.sh @@ -0,0 +1,20 @@ +#!/bin/sh +# +# easy wrapper script for afl-cov +# +test "$1" = "-h" -o -z "$1" && { + echo Syntax: $0 out-dir \"exec cmd --foo @@\" + echo Generates the coverage information for an AFL run. + echo Must be run from the top directory of the coverage build. + echo Example: $0 ../target/out \"tools/target @@\" + exit 1 +} +test -d "$1" || { echo Error: not a directory: $1 ; exit 1 ; } +test -e "$1"/queue || { echo Error: not an afl-fuzz -o out directory ; exit 1 ; } +DST=`realpath "$1"` +afl-cov -v -d "$DST" --cover-corpus --coverage-cmd "$2" --code-dir . --overwrite +test -e "$1"/fuzzer_stats && { + echo "runtime :" $(expr `grep last_update "$DST"/fuzzer_stats|awk '{print$3}'` - `grep start_time "$DST"/fuzzer_stats|awk '{print$3}'`) seconds + egrep 'execs_done|paths_total|^unique_|stability' "$DST"/fuzzer_stats +} +echo open "file://$DST/cov/web/index.html" From b0cdf594001711f9720b31bae7aa196e93f67a00 Mon Sep 17 00:00:00 2001 From: van Hauser Date: Wed, 13 May 2020 13:36:58 +0200 Subject: [PATCH 04/39] add helper script, bump version to 0.6.3 --- ChangeLog | 4 ++++ README.md | 3 +++ VERSION | 2 +- afl-cov | 4 ++-- afl-stat.sh | 17 +++++++++++++++++ 5 files changed, 27 insertions(+), 3 deletions(-) create mode 100755 afl-stat.sh diff --git a/ChangeLog b/ChangeLog index 5597ec2..5affe19 100644 --- a/ChangeLog +++ b/ChangeLog @@ -1,3 +1,7 @@ +afl-cov-0.6.3 (2020-05-13): + - Allow @@ additionally to AFL_FILE + - added three helper scripts + afl-cov-0.6.2 (12/26/2018): - (Tim Strazzere) Add support for llvm-cov compiled binaries. Add a check when ensuring the binary was instrumented with gcov to catch an llvm-cov diff --git a/README.md b/README.md index 86eb8d2..f8a8ec4 100644 --- a/README.md +++ b/README.md @@ -1,5 +1,7 @@ # afl-cov - AFL Fuzzing Code Coverage +Version: 0.6.3 + - [Preface](#preface) - [Introduction](#introduction) - [Prerequisites](#prerequisites) @@ -20,6 +22,7 @@ It has three changes: * afl-cov now accepts "@@" like AFL++ in the command line * afl-cov.sh makes using afl-cov easier (just needs two parameters) * afl-cov-build.sh makes builing a target for coverage easier + * afl-stat.sh shows the statistics of a run (in progress or completed) Enjoy! diff --git a/VERSION b/VERSION index b616048..844f6a9 100644 --- a/VERSION +++ b/VERSION @@ -1 +1 @@ -0.6.2 +0.6.3 diff --git a/afl-cov b/afl-cov index fe15f51..55213e2 100755 --- a/afl-cov +++ b/afl-cov @@ -2,7 +2,7 @@ # # File: afl-cov # -# Version: 0.6.2 +# Version: 0.6.3 # # Purpose: Perform lcov coverage diff's against each AFL queue file to see # new functions and line coverage evolve from an AFL fuzzing cycle. @@ -44,7 +44,7 @@ try: except ImportError: import subprocess -__version__ = '0.6.2.1' +__version__ = '0.6.3' NO_OUTPUT = 0 WANT_OUTPUT = 1 diff --git a/afl-stat.sh b/afl-stat.sh new file mode 100755 index 0000000..02c77af --- /dev/null +++ b/afl-stat.sh @@ -0,0 +1,17 @@ +#!/bin/sh +test "$1" = "-h" -o -z "$1" -o -z "$1" && { + echo Syntax: $0 out-dir + echo "Shows statistics of a run (in progress or done)" + exit 1 +} +test -n "$AFL_PATH" && PATH=$AFL_PATH:$PATH +while [ -n "$1" ]; do + test -d "$1" || { echo Error: not a directory: $1 ; } + test -e "$1"/fuzzer_stats || { echo Error: not an afl-fuzz -o out directory ; } + echo File: `realpath "$1"` + egrep 'run_time|execs_done|execs_per_sec|paths_total|^unique_|stability' "$1"/fuzzer_stats | sort | tee -a "$1"/stats.out + LINES= + test -e "$1"/cov/afl-cov.log && LINES=`grep -w lines "$1"/cov/afl-cov.log|tail -n 1|sed 's/.*(//'|sed 's/ .*//'` + echo "coverage : $LINES" | tee -a "$1"/stats.out + shift +done From 89801bb525e79d769838ad5cddd774c2b066d774 Mon Sep 17 00:00:00 2001 From: van Hauser Date: Thu, 14 May 2020 10:17:34 +0200 Subject: [PATCH 05/39] display time as clock too --- afl-stat.sh | 7 ++++++- 1 file changed, 6 insertions(+), 1 deletion(-) diff --git a/afl-stat.sh b/afl-stat.sh index 02c77af..e942faf 100755 --- a/afl-stat.sh +++ b/afl-stat.sh @@ -9,7 +9,12 @@ while [ -n "$1" ]; do test -d "$1" || { echo Error: not a directory: $1 ; } test -e "$1"/fuzzer_stats || { echo Error: not an afl-fuzz -o out directory ; } echo File: `realpath "$1"` - egrep 'run_time|execs_done|execs_per_sec|paths_total|^unique_|stability' "$1"/fuzzer_stats | sort | tee -a "$1"/stats.out + { + egrep 'run_time|execs_done|execs_per_sec|paths_total|^unique_|stability' "$1"/fuzzer_stats + SECONDS=`egrep run_time "$1"/fuzzer_stats | awk '{print$3}'` + TIME=`date -u -d "@$SECONDS" +"%T"` + echo "run_clock : $TIME" + } | sort | tee -a "$1"/stats.out LINES= test -e "$1"/cov/afl-cov.log && LINES=`grep -w lines "$1"/cov/afl-cov.log|tail -n 1|sed 's/.*(//'|sed 's/ .*//'` echo "coverage : $LINES" | tee -a "$1"/stats.out From 6699b5004e85e1624bf17736690d66b1ea1a6b29 Mon Sep 17 00:00:00 2001 From: van Hauser Date: Thu, 14 May 2020 22:43:32 +0200 Subject: [PATCH 06/39] fix writing to stat file --- afl-stat.sh | 8 +++++--- 1 file changed, 5 insertions(+), 3 deletions(-) diff --git a/afl-stat.sh b/afl-stat.sh index e942faf..1b211e8 100755 --- a/afl-stat.sh +++ b/afl-stat.sh @@ -14,9 +14,11 @@ while [ -n "$1" ]; do SECONDS=`egrep run_time "$1"/fuzzer_stats | awk '{print$3}'` TIME=`date -u -d "@$SECONDS" +"%T"` echo "run_clock : $TIME" - } | sort | tee -a "$1"/stats.out + } | sort | tee "$1"/stats.out LINES= - test -e "$1"/cov/afl-cov.log && LINES=`grep -w lines "$1"/cov/afl-cov.log|tail -n 1|sed 's/.*(//'|sed 's/ .*//'` - echo "coverage : $LINES" | tee -a "$1"/stats.out + test -e "$1"/cov/afl-cov.log && { + LINES=`grep -w lines "$1"/cov/afl-cov.log|tail -n 1|sed 's/.*(//'|sed 's/ .*//'` + echo "coverage : $LINES" | tee -a "$1"/stats.out + } shift done From 71ce66cce7f1decbcd8aa6870afcaecdcf4776a5 Mon Sep 17 00:00:00 2001 From: van Hauser Date: Fri, 22 May 2020 21:38:19 +0200 Subject: [PATCH 07/39] fix script --- afl-cov.sh | 12 +++++++++++- 1 file changed, 11 insertions(+), 1 deletion(-) diff --git a/afl-cov.sh b/afl-cov.sh index b9d1ad7..ba8becf 100755 --- a/afl-cov.sh +++ b/afl-cov.sh @@ -9,12 +9,22 @@ test "$1" = "-h" -o -z "$1" && { echo Example: $0 ../target/out \"tools/target @@\" exit 1 } + test -d "$1" || { echo Error: not a directory: $1 ; exit 1 ; } test -e "$1"/queue || { echo Error: not an afl-fuzz -o out directory ; exit 1 ; } + +HOMEPATH=`dirname $0` DST=`realpath "$1"` +export PATH=$HOMEPATH:$PATH + afl-cov -v -d "$DST" --cover-corpus --coverage-cmd "$2" --code-dir . --overwrite + test -e "$1"/fuzzer_stats && { echo "runtime :" $(expr `grep last_update "$DST"/fuzzer_stats|awk '{print$3}'` - `grep start_time "$DST"/fuzzer_stats|awk '{print$3}'`) seconds egrep 'execs_done|paths_total|^unique_|stability' "$DST"/fuzzer_stats -} + LINES= + test -e "$1"/cov/afl-cov.log && LINES=`grep -w lines "$1"/cov/afl-cov.log|tail -n 1|sed 's/.*(//'|sed 's/ .*//'` + echo "coverage : $LINES" +} | tee "$1"/stats.out + echo open "file://$DST/cov/web/index.html" From c0da127b670e90159a52ff5b01575e408ed67006 Mon Sep 17 00:00:00 2001 From: van Hauser Date: Sat, 23 May 2020 11:22:23 +0200 Subject: [PATCH 08/39] add support for stdin --- ChangeLog | 4 ++++ README.md | 7 ++++--- afl-cov | 47 ++++++++++++++++++++++++----------------------- 3 files changed, 32 insertions(+), 26 deletions(-) diff --git a/ChangeLog b/ChangeLog index 5affe19..9dd5304 100644 --- a/ChangeLog +++ b/ChangeLog @@ -1,3 +1,7 @@ +afl-cov-0.6.4 (2020-05-23): + - afl-cov now supports stdin targets (just omit @@/AFL_FILE) + - enhance scripts + afl-cov-0.6.3 (2020-05-13): - Allow @@ additionally to AFL_FILE - added three helper scripts diff --git a/README.md b/README.md index f8a8ec4..abf51c8 100644 --- a/README.md +++ b/README.md @@ -1,6 +1,6 @@ # afl-cov - AFL Fuzzing Code Coverage -Version: 0.6.3 +Version: 0.6.4 - [Preface](#preface) - [Introduction](#introduction) @@ -18,8 +18,9 @@ Version: 0.6.3 This is a modified afl-cov fork because the original author's account is inactive :-( -It has three changes: - * afl-cov now accepts "@@" like AFL++ in the command line +It has four changes: + * afl-cov now accepts "@@" like AFL++ in the target command parameters + * afl-cov now can send to targets that read on stdin (just omit @@) * afl-cov.sh makes using afl-cov easier (just needs two parameters) * afl-cov-build.sh makes builing a target for coverage easier * afl-stat.sh shows the statistics of a run (in progress or completed) diff --git a/afl-cov b/afl-cov index 55213e2..bb4fd93 100755 --- a/afl-cov +++ b/afl-cov @@ -2,12 +2,13 @@ # # File: afl-cov # -# Version: 0.6.3 +# Version: 0.6.4 # # Purpose: Perform lcov coverage diff's against each AFL queue file to see # new functions and line coverage evolve from an AFL fuzzing cycle. # # Copyright (C) 2015-2016 Michael Rash (mbr@cipherdyne.org) +# Copyright (C) 2018-2020 Marc "vanHauser" Heuse (mh@mh-sec.de) # # License (GNU General Public License version 2 or any later version): # @@ -44,7 +45,7 @@ try: except ImportError: import subprocess -__version__ = '0.6.3' +__version__ = '0.6.4' NO_OUTPUT = 0 WANT_OUTPUT = 1 @@ -185,10 +186,10 @@ def process_afl_test_cases(cargs): ### for the current AFL test case file if run_once: run_cmd(cargs.coverage_cmd.replace('AFL_FILE', f), - cov_paths['log_file'], cargs, NO_OUTPUT) + cov_paths['log_file'], cargs, NO_OUTPUT, True, f) else: out_lines = run_cmd(cargs.coverage_cmd.replace('AFL_FILE', f), - cov_paths['log_file'], cargs, WANT_OUTPUT)[1] + cov_paths['log_file'], cargs, WANT_OUTPUT, True, f)[1] run_once = True if cargs.afl_queue_id_limit \ @@ -584,7 +585,7 @@ def lcov_gen_coverage(cov_paths, cargs): + " --no-checksum --capture --directory " \ + cargs.code_dir + " --output-file " \ + cov_paths['lcov_info'], \ - cov_paths['log_file'], cargs, LOG_ERRORS) + cov_paths['log_file'], cargs, LOG_ERRORS, False, "") if (cargs.disable_lcov_exclude_pattern): out_lines = run_cmd(cargs.lcov_path \ @@ -592,7 +593,7 @@ def lcov_gen_coverage(cov_paths, cargs): + " --no-checksum -a " + cov_paths['lcov_base'] \ + " -a " + cov_paths['lcov_info'] \ + " --output-file " + cov_paths['lcov_info_final'], \ - cov_paths['log_file'], cargs, WANT_OUTPUT)[1] + cov_paths['log_file'], cargs, WANT_OUTPUT, False, "")[1] else: tmp_file = NamedTemporaryFile(delete=False) run_cmd(cargs.lcov_path \ @@ -600,13 +601,13 @@ def lcov_gen_coverage(cov_paths, cargs): + " --no-checksum -a " + cov_paths['lcov_base'] \ + " -a " + cov_paths['lcov_info'] \ + " --output-file " + tmp_file.name, \ - cov_paths['log_file'], cargs, LOG_ERRORS) + cov_paths['log_file'], cargs, LOG_ERRORS, False, "") out_lines = run_cmd(cargs.lcov_path \ + lcov_opts + " --no-checksum -r " + tmp_file.name \ + " " + cargs.lcov_exclude_pattern + " --output-file " \ + cov_paths['lcov_info_final'], - cov_paths['log_file'], cargs, WANT_OUTPUT)[1] + cov_paths['log_file'], cargs, WANT_OUTPUT, False, "")[1] if os.path.exists(tmp_file.name): os.unlink(tmp_file.name) @@ -643,7 +644,7 @@ def gen_web_cov_report(fuzz_dir, cov_paths, cargs): + " --output-directory " \ + cov_paths['web_dir'] + " " \ + cov_paths['lcov_info_final'], \ - cov_paths['log_file'], cargs, LOG_ERRORS) + cov_paths['log_file'], cargs, LOG_ERRORS, False, "") logr("[+] Final lcov web report: %s/%s" % \ (cov_paths['web_dir'], 'index.html'), cov_paths['log_file'], cargs) @@ -691,16 +692,10 @@ def get_running_pid(stats_file, pid_re): break return pid -def run_cmd(cmd, log_file, cargs, collect): +def run_cmd(cmd, log_file, cargs, collect, aflrun, fn): out = [] - if cargs.verbose: - if log_file: - logr(" CMD: %s" % cmd, log_file, cargs) - else: - print " CMD: %s" % cmd - fh = None if cargs.disable_cmd_redirection or collect == WANT_OUTPUT \ or collect == LOG_ERRORS: @@ -708,6 +703,15 @@ def run_cmd(cmd, log_file, cargs, collect): else: fh = open(os.devnull, 'w') + if aflrun == True and len(fn) > 0: + cmd = 'cat ' + fn + ' | ' + cmd + + if cargs.verbose: + if log_file: + logr(" CMD: %s" % cmd, log_file, cargs) + else: + print " CMD: %s" % cmd + es = subprocess.call(cmd, stdin=None, stdout=fh, stderr=subprocess.STDOUT, shell=True) @@ -809,7 +813,7 @@ def init_tracking(cov_paths, cargs): run_cmd(cargs.lcov_path \ + lcov_opts \ + " --no-checksum --zerocounters --directory " \ - + cargs.code_dir, cov_paths['log_file'], cargs, LOG_ERRORS) + + cargs.code_dir, cov_paths['log_file'], cargs, LOG_ERRORS, False, "") run_cmd(cargs.lcov_path \ + lcov_opts @@ -817,7 +821,7 @@ def init_tracking(cov_paths, cargs): + " --directory " + cargs.code_dir \ + " --output-file " \ + cov_paths['lcov_base'], \ - cov_paths['log_file'], cargs, LOG_ERRORS) + cov_paths['log_file'], cargs, LOG_ERRORS, False, "") return True @@ -832,7 +836,7 @@ def is_bin_gcov_enabled(binary, cargs): ### run readelf against the binary to see if it contains gcov support for line in run_cmd("%s -a %s" % (cargs.readelf_path, binary), - False, cargs, WANT_OUTPUT)[1]: + False, cargs, WANT_OUTPUT, False, "")[1]: if ' __gcov' in line: if cargs.validate_args or cargs.gcov_check or cargs.gcov_check_bin: print "[+] Binary '%s' is compiled with code coverage support via gcc." % binary @@ -897,12 +901,9 @@ def is_gcov_enabled(cargs): return False if cargs.coverage_cmd: - if 'AFL_FILE' not in cargs.coverage_cmd: - print "[*] --coverage-cmd must contain AFL_FILE" - return False - ### make sure at least one component of the command is an ### executable and is compiled with code coverage support + found_exec = False found_code_cov_binary = False From 9b9d121b5c6e5155a0315d089203ab50d262dfc0 Mon Sep 17 00:00:00 2001 From: van Hauser Date: Sat, 23 May 2020 14:37:59 +0200 Subject: [PATCH 09/39] -v option --- afl-cov.sh | 8 ++++++-- 1 file changed, 6 insertions(+), 2 deletions(-) diff --git a/afl-cov.sh b/afl-cov.sh index ba8becf..30209aa 100755 --- a/afl-cov.sh +++ b/afl-cov.sh @@ -3,9 +3,12 @@ # easy wrapper script for afl-cov # test "$1" = "-h" -o -z "$1" && { - echo Syntax: $0 out-dir \"exec cmd --foo @@\" + echo "Syntax: $0 [-v] out-dir \"exec cmd --foo @@\"" + echo echo Generates the coverage information for an AFL run. echo Must be run from the top directory of the coverage build. + echo The -v option enables verbose output + echo echo Example: $0 ../target/out \"tools/target @@\" exit 1 } @@ -17,7 +20,8 @@ HOMEPATH=`dirname $0` DST=`realpath "$1"` export PATH=$HOMEPATH:$PATH -afl-cov -v -d "$DST" --cover-corpus --coverage-cmd "$2" --code-dir . --overwrite +test "$1" = "-v" && { OPT="-v" ; shift ; } +afl-cov $OPT -d "$DST" --cover-corpus --coverage-cmd "$2" --code-dir . --overwrite test -e "$1"/fuzzer_stats && { echo "runtime :" $(expr `grep last_update "$DST"/fuzzer_stats|awk '{print$3}'` - `grep start_time "$DST"/fuzzer_stats|awk '{print$3}'`) seconds From 7567328478f35df4b9f953a2477836e73dc3e1ad Mon Sep 17 00:00:00 2001 From: van Hauser Date: Wed, 27 May 2020 12:20:26 +0200 Subject: [PATCH 10/39] better stat output --- afl-cov.sh | 7 +++++-- afl-stat.sh | 8 +++++--- 2 files changed, 10 insertions(+), 5 deletions(-) diff --git a/afl-cov.sh b/afl-cov.sh index 30209aa..b44e358 100755 --- a/afl-cov.sh +++ b/afl-cov.sh @@ -24,11 +24,14 @@ test "$1" = "-v" && { OPT="-v" ; shift ; } afl-cov $OPT -d "$DST" --cover-corpus --coverage-cmd "$2" --code-dir . --overwrite test -e "$1"/fuzzer_stats && { - echo "runtime :" $(expr `grep last_update "$DST"/fuzzer_stats|awk '{print$3}'` - `grep start_time "$DST"/fuzzer_stats|awk '{print$3}'`) seconds + DIFF=$(expr `grep last_update "$DST"/fuzzer_stats|awk '{print$3}'` - `grep start_time "$DST"/fuzzer_stats|awk '{print$3}'`) + echo "runtime : $DIFF seconds" + TIME=`date -u -d "@$SECONDS" +"%T"` + echo "run_clock : $TIME" egrep 'execs_done|paths_total|^unique_|stability' "$DST"/fuzzer_stats LINES= test -e "$1"/cov/afl-cov.log && LINES=`grep -w lines "$1"/cov/afl-cov.log|tail -n 1|sed 's/.*(//'|sed 's/ .*//'` - echo "coverage : $LINES" + echo "coverage : $LINES lines" } | tee "$1"/stats.out echo open "file://$DST/cov/web/index.html" diff --git a/afl-stat.sh b/afl-stat.sh index 1b211e8..9e0c2fa 100755 --- a/afl-stat.sh +++ b/afl-stat.sh @@ -12,13 +12,15 @@ while [ -n "$1" ]; do { egrep 'run_time|execs_done|execs_per_sec|paths_total|^unique_|stability' "$1"/fuzzer_stats SECONDS=`egrep run_time "$1"/fuzzer_stats | awk '{print$3}'` - TIME=`date -u -d "@$SECONDS" +"%T"` - echo "run_clock : $TIME" + test -n "$SECONDS" && { + TIME=`date -u -d "@$SECONDS" +"%T"` + echo "run_clock : $TIME" + } } | sort | tee "$1"/stats.out LINES= test -e "$1"/cov/afl-cov.log && { LINES=`grep -w lines "$1"/cov/afl-cov.log|tail -n 1|sed 's/.*(//'|sed 's/ .*//'` - echo "coverage : $LINES" | tee -a "$1"/stats.out + echo "coverage : $LINES lines" | tee -a "$1"/stats.out } shift done From e6a6fb9cccffdf53e178d563cd8d87e7f434a62c Mon Sep 17 00:00:00 2001 From: van Hauser Date: Wed, 27 May 2020 12:22:30 +0200 Subject: [PATCH 11/39] add Makefile --- Makefile | 5 +++++ 1 file changed, 5 insertions(+) create mode 100644 Makefile diff --git a/Makefile b/Makefile new file mode 100644 index 0000000..5c452a0 --- /dev/null +++ b/Makefile @@ -0,0 +1,5 @@ +all: + @echo nothing to do, just run \"sudo make install\" + +install: + install -m 0755 afl-* /usr/local/bin From 0a750c0a84389d7ca2852fb7e0fdcc004b60329e Mon Sep 17 00:00:00 2001 From: van Hauser Date: Thu, 28 May 2020 14:13:49 +0200 Subject: [PATCH 12/39] switch to python3 --- ChangeLog | 3 ++ README.md | 2 +- afl-cov | 100 +++++++++++++++++++++++++++--------------------------- 3 files changed, 54 insertions(+), 51 deletions(-) diff --git a/ChangeLog b/ChangeLog index 9dd5304..d1dee24 100644 --- a/ChangeLog +++ b/ChangeLog @@ -1,3 +1,6 @@ +afl-cov-0.6.5 (2020-05-28) + - switched to python3 + afl-cov-0.6.4 (2020-05-23): - afl-cov now supports stdin targets (just omit @@/AFL_FILE) - enhance scripts diff --git a/README.md b/README.md index abf51c8..0b6552f 100644 --- a/README.md +++ b/README.md @@ -1,6 +1,6 @@ # afl-cov - AFL Fuzzing Code Coverage -Version: 0.6.4 +Version: 0.6.5 - [Preface](#preface) - [Introduction](#introduction) diff --git a/afl-cov b/afl-cov index bb4fd93..a6f77fa 100755 --- a/afl-cov +++ b/afl-cov @@ -1,8 +1,8 @@ -#!/usr/bin/env python2 +#!/usr/bin/env python3 # # File: afl-cov # -# Version: 0.6.4 +# Version: 0.6.5 # # Purpose: Perform lcov coverage diff's against each AFL queue file to see # new functions and line coverage evolve from an AFL fuzzing cycle. @@ -45,7 +45,7 @@ try: except ImportError: import subprocess -__version__ = '0.6.4' +__version__ = '0.6.5' NO_OUTPUT = 0 WANT_OUTPUT = 1 @@ -62,7 +62,7 @@ def main(): cargs.coverage_cmd = cargs.coverage_cmd.replace('@@', 'AFL_FILE') if cargs.version: - print "afl-cov-" + __version__ + print("afl-cov-" + __version__) return exit_success if cargs.gcov_check or cargs.gcov_check_bin: @@ -100,7 +100,7 @@ def run_in_background(): ### capability anyway pid = os.fork() if (pid < 0): - print "[*] fork() error, exiting." + print("[*] fork() error, exiting.") os._exit() elif (pid > 0): os._exit(0) @@ -710,7 +710,7 @@ def run_cmd(cmd, log_file, cargs, collect, aflrun, fn): if log_file: logr(" CMD: %s" % cmd, log_file, cargs) else: - print " CMD: %s" % cmd + print(" CMD: %s" % cmd) es = subprocess.call(cmd, stdin=None, stdout=fh, stderr=subprocess.STDOUT, shell=True) @@ -731,14 +731,14 @@ def run_cmd(cmd, log_file, cargs, collect, aflrun, fn): for line in out: logr(line, log_file, cargs) else: - print " Non-zero exit status '%d' for CMD: %s" % (es, cmd) + print(" Non-zero exit status '%d' for CMD: %s" % (es, cmd)) return es, out def import_fuzzing_dirs(cov_paths, cargs): if not cargs.afl_fuzzing_dir: - print "[*] Must specify AFL fuzzing dir with --afl-fuzzing-dir or -d" + print("[*] Must specify AFL fuzzing dir with --afl-fuzzing-dir or -d") return False if 'top_dir' not in cov_paths: @@ -794,8 +794,8 @@ def init_tracking(cov_paths, cargs): else: if is_dir(cov_paths['top_dir']): if not cargs.func_search and not cargs.line_search: - print "[*] Existing coverage dir %s found, use --overwrite to " \ - "re-calculate coverage" % (cov_paths['top_dir']) + print("[*] Existing coverage dir %s found, use --overwrite to " \ + "re-calculate coverage" % (cov_paths['top_dir'])) return False else: mkdirs(cov_paths, cargs) @@ -839,18 +839,18 @@ def is_bin_gcov_enabled(binary, cargs): False, cargs, WANT_OUTPUT, False, "")[1]: if ' __gcov' in line: if cargs.validate_args or cargs.gcov_check or cargs.gcov_check_bin: - print "[+] Binary '%s' is compiled with code coverage support via gcc." % binary + print("[+] Binary '%s' is compiled with code coverage support via gcc." % binary) rv = True break if '__llvm_gcov' in line: if cargs.validate_args or cargs.gcov_check or cargs.gcov_check_bin: - print "[+] Binary '%s' is compiled with code coverage support via llvm." % binary + print("[+] Binary '%s' is compiled with code coverage support via llvm." % binary) rv = True break if not rv and cargs.gcov_check_bin: - print "[*] Binary '%s' is not compiled with code coverage support." % binary + print("[*] Binary '%s' is not compiled with code coverage support." % binary) return rv @@ -878,26 +878,26 @@ def check_requirements(cargs): genhtml = which ( cargs.genhtml_path ) if ( lcov == None or gcov == None): - print "Required command not found :" + print("Required command not found :") else: if (genhtml == None and not cargs.disable_lcov_web): - print "Required command not found :" + print("Required command not found :") else: return True if ( lcov == None ): - print "[*] lcov command does not exist : %s" % (cargs.lcov_path) + print("[*] lcov command does not exist : %s" % (cargs.lcov_path)) if ( genhtml == None and not cargs.disable_lcov_web): - print "[*] genhtml command does not exist : %s" % (cargs.genhtml_path) + print("[*] genhtml command does not exist : %s" % (cargs.genhtml_path)) if ( gcov == None ): - print "[*] gcov command does not exist : %s" % (cargs.gcov_path) + print("[*] gcov command does not exist : %s" % (cargs.gcov_path)) return False def is_gcov_enabled(cargs): if not is_exe(cargs.readelf_path): - print "[*] Need a valid path to readelf, use --readelf-path" + print("[*] Need a valid path to readelf, use --readelf-path") return False if cargs.coverage_cmd: @@ -917,21 +917,21 @@ def is_gcov_enabled(cargs): break if not found_exec: - print "[*] Could not find an executable binary " \ - "--coverage-cmd '%s'" % cargs.coverage_cmd + print("[*] Could not find an executable binary " \ + "--coverage-cmd '%s'" % cargs.coverage_cmd) return False if not cargs.disable_gcov_check and not found_code_cov_binary: - print "[*] Could not find an executable binary with code " \ + print("[*] Could not find an executable binary with code " \ "coverage support ('-fprofile-arcs -ftest-coverage') " \ - "in --coverage-cmd '%s'" % cargs.coverage_cmd + "in --coverage-cmd '%s'" % cargs.coverage_cmd) return False elif cargs.gcov_check_bin: if not is_bin_gcov_enabled(cargs.gcov_check_bin, cargs): return False elif cargs.gcov_check: - print "[*] Either --coverage-cmd or --gcov-check-bin required in --gcov-check mode" + print("[*] Either --coverage-cmd or --gcov-check-bin required in --gcov-check mode") return False return True @@ -943,12 +943,12 @@ def validate_cargs(cargs): return False else: if not cargs.func_search and not cargs.line_search: - print "[*] Must set --coverage-cmd or --func-search/--line-search" + print("[*] Must set --coverage-cmd or --func-search/--line-search") return False if cargs.code_dir: if not is_dir(cargs.code_dir): - print "[*] --code-dir path does not exist" + print("[*] --code-dir path does not exist") return False ### make sure code coverage support is compiled in @@ -957,18 +957,18 @@ def validate_cargs(cargs): else: if not cargs.func_search and not cargs.line_search: - print "[*] Must set --code-dir unless using --func-search " \ - "against existing afl-cov directory" + print("[*] Must set --code-dir unless using --func-search " \ + "against existing afl-cov directory") return False if cargs.func_search or cargs.line_search: if not cargs.afl_fuzzing_dir: - print "[*] Must set --afl-fuzzing-dir" + print("[*] Must set --afl-fuzzing-dir") return False if cargs.func_search and '()' not in cargs.func_search: cargs.func_search += '()' if cargs.line_search and not cargs.src_file: - print "[*] Must set --src-file in --line-search mode" + print("[*] Must set --src-file in --line-search mode") return False if cargs.live and not cargs.ignore_core_pattern: @@ -976,12 +976,12 @@ def validate_cargs(cargs): return False if not cargs.live and not is_dir(cargs.afl_fuzzing_dir): - print "[*] It doesn't look like directory '%s' exists" \ - % (cargs.afl_fuzzing_dir) + print("[*] It doesn't look like directory '%s' exists" \ + % (cargs.afl_fuzzing_dir)) return False if cargs.disable_lcov_web and cargs.lcov_web_all: - print "[*] --disable-lcov-web and --lcov-web-all are incompatible" + print("[*] --disable-lcov-web and --lcov-web-all are incompatible") return False return True @@ -997,24 +997,24 @@ def gcno_files_exist(cargs): if filename[-5:] == '.gcno': found_code_coverage_support = True if not found_code_coverage_support: - print "[*] Could not find any *.gcno files in --code-dir " \ + print("[*] Could not find any *.gcno files in --code-dir " \ "'%s', is code coverage ('-fprofile-arcs -ftest-coverage') " \ - "compiled in?" % cargs.code_dir + "compiled in?" % cargs.code_dir) return False return True def is_afl_running(cargs): while not is_dir(cargs.afl_fuzzing_dir): if not cargs.background: - print "[-] Sleep for %d seconds for AFL fuzzing directory to be created..." \ - % cargs.sleep + print("[-] Sleep for %d seconds for AFL fuzzing directory to be created..." \ + % cargs.sleep) time.sleep(cargs.sleep) ### if we make it here then afl-fuzz is presumably running while not is_afl_fuzz_running(cargs): if not cargs.background: - print "[-] Sleep for %d seconds waiting for afl-fuzz to be started...." \ - % cargs.sleep + print("[-] Sleep for %d seconds waiting for afl-fuzz to be started...." \ + % cargs.sleep) time.sleep(cargs.sleep) return @@ -1053,7 +1053,7 @@ def is_dir(dpath): def logr(pstr, log_file, cargs): if not cargs.background and not cargs.quiet: - print " " + pstr + print(" " + pstr) append_file(pstr, log_file) return @@ -1066,22 +1066,22 @@ def stop_afl(cargs): ### is also stopped. if not cargs.afl_fuzzing_dir: - print "[*] Must set --afl-fuzzing-dir" + print("[*] Must set --afl-fuzzing-dir") return False if not is_dir(cargs.afl_fuzzing_dir): - print "[*] Doesn't look like AFL fuzzing directory '%s' exists." \ - % cargs.afl_fuzzing_dir + print("[*] Doesn't look like AFL fuzzing directory '%s' exists." \ + % cargs.afl_fuzzing_dir) return False if os.path.exists(cargs.afl_fuzzing_dir + '/fuzzer_stats'): afl_pid = get_running_pid(cargs.afl_fuzzing_dir + '/fuzzer_stats', 'fuzzer_pid\s+\:\s+(\d+)') if afl_pid: - print "[+] Stopping running afl-fuzz instance, PID: %d" % afl_pid + print("[+] Stopping running afl-fuzz instance, PID: %d" % afl_pid) os.kill(afl_pid, signal.SIGTERM) else: - print "[-] No running afl-fuzz instance" + print("[-] No running afl-fuzz instance") rv = False else: found = False @@ -1090,12 +1090,12 @@ def stop_afl(cargs): if os.path.exists(stats_file): afl_pid = get_running_pid(stats_file, 'fuzzer_pid\s+\:\s+(\d+)') if afl_pid: - print "[+] Stopping running afl-fuzz instance, PID: %d" \ - % afl_pid + print("[+] Stopping running afl-fuzz instance, PID: %d" \ + % afl_pid) os.kill(afl_pid, signal.SIGTERM) found = True if not found: - print "[-] No running afl-fuzz instance" + print("[-] No running afl-fuzz instance") rv = False return rv @@ -1112,8 +1112,8 @@ def check_core_pattern(): with open(core_pattern_file, 'r') as f: if f.readline().rstrip()[0] == '|': ### same logic as implemented by afl-fuzz itself - print "[*] afl-fuzz requires 'echo core >%s'" \ - % core_pattern_file + print("[*] afl-fuzz requires 'echo core >%s'" \ + % core_pattern_file) rv = False return rv From fd7ba2bdc824a3368dac1b7e3f53d3756c6ca7dd Mon Sep 17 00:00:00 2001 From: van Hauser Date: Fri, 29 May 2020 22:32:45 +0200 Subject: [PATCH 13/39] small change to build script --- afl-cov-build.sh | 2 ++ 1 file changed, 2 insertions(+) diff --git a/afl-cov-build.sh b/afl-cov-build.sh index ff4c5f7..1c54bb2 100755 --- a/afl-cov-build.sh +++ b/afl-cov-build.sh @@ -3,6 +3,7 @@ test -z "$1" -o "$1" = "-h" && { echo "Syntax: $0 [options]" echo Sets build options for coverage instrumentation with gcov/lcov. + echo Set CC/CXX environment variables if you do not want gcc/g++. echo Example: "$0 ./configure --disable-shared" } @@ -10,6 +11,7 @@ test -z "$CC" && export CC=gcc test -z "$CXX" && export CXX=g++ export CFLAGS="-fprofile-arcs -ftest-coverage" export CXXFLAGS="$CFLAGS" +export CPPFLAGS="$CFLAGS" export LDFLAGS="-lgcov --coverage" $* From 0c582d33754c9d54802fea866b6aed5a87b30e8a Mon Sep 17 00:00:00 2001 From: van Hauser Date: Fri, 29 May 2020 22:33:18 +0200 Subject: [PATCH 14/39] small change to build script --- afl-cov-build.sh | 1 + 1 file changed, 1 insertion(+) diff --git a/afl-cov-build.sh b/afl-cov-build.sh index 1c54bb2..dfce10e 100755 --- a/afl-cov-build.sh +++ b/afl-cov-build.sh @@ -5,6 +5,7 @@ test -z "$1" -o "$1" = "-h" && { echo Sets build options for coverage instrumentation with gcov/lcov. echo Set CC/CXX environment variables if you do not want gcc/g++. echo Example: "$0 ./configure --disable-shared" + exit 1 } test -z "$CC" && export CC=gcc From ec8b03dc590556e53bea834e6d5e1a2309bd2101 Mon Sep 17 00:00:00 2001 From: van Hauser Date: Thu, 11 Jun 2020 09:15:01 +0200 Subject: [PATCH 15/39] libfuzzer_driver for fuzzbench --- libfuzzer_driver.cpp | 247 +++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 247 insertions(+) create mode 100644 libfuzzer_driver.cpp diff --git a/libfuzzer_driver.cpp b/libfuzzer_driver.cpp new file mode 100644 index 0000000..847090e --- /dev/null +++ b/libfuzzer_driver.cpp @@ -0,0 +1,247 @@ +//===- afl_driver.cpp - a glue between AFL and libFuzzer --------*- C++ -* ===// +// +// Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions. +// See https://llvm.org/LICENSE.txt for license information. +// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception +//===----------------------------------------------------------------------===// + +/* This file allows to fuzz libFuzzer-style target functions + (LLVMFuzzerTestOneInput) with AFL using AFL's persistent (in-process) mode. + +Usage: +################################################################################ +cat << EOF > test_fuzzer.cc +#include +#include +extern "C" int LLVMFuzzerTestOneInput(const uint8_t *data, size_t size) { + if (size > 0 && data[0] == 'H') + if (size > 1 && data[1] == 'I') + if (size > 2 && data[2] == '!') + __builtin_trap(); + return 0; +} +EOF +# Build your target with -fsanitize-coverage=trace-pc-guard using fresh clang. +clang -g -fsanitize-coverage=trace-pc-guard test_fuzzer.cc -c +# Build afl-llvm-rt.o.c from the AFL distribution. +clang -c -w $AFL_HOME/llvm_mode/afl-llvm-rt.o.c +# Build this file, link it with afl-llvm-rt.o.o and the target code. +clang++ afl_driver.cpp test_fuzzer.o afl-llvm-rt.o.o +# Run AFL: +rm -rf IN OUT; mkdir IN OUT; echo z > IN/z; +$AFL_HOME/afl-fuzz -i IN -o OUT ./a.out +################################################################################ +AFL_DRIVER_STDERR_DUPLICATE_FILENAME: Setting this *appends* stderr to the file +specified. If the file does not exist, it is created. This is useful for getting +stack traces (when using ASAN for example) or original error messages on hard +to reproduce bugs. Note that any content written to stderr will be written to +this file instead of stderr's usual location. + +AFL_DRIVER_CLOSE_FD_MASK: Similar to libFuzzer's -close_fd_mask behavior option. +If 1, close stdout at startup. If 2 close stderr; if 3 close both. + +*/ +#include +#include +#include +#include +#include +#include +#include +#include + +#include +#include +#include + +// Platform detection. Copied from FuzzerInternal.h +#ifdef __linux__ +#define LIBFUZZER_LINUX 1 +#define LIBFUZZER_APPLE 0 +#define LIBFUZZER_NETBSD 0 +#define LIBFUZZER_FREEBSD 0 +#define LIBFUZZER_OPENBSD 0 +#elif __APPLE__ +#define LIBFUZZER_LINUX 0 +#define LIBFUZZER_APPLE 1 +#define LIBFUZZER_NETBSD 0 +#define LIBFUZZER_FREEBSD 0 +#define LIBFUZZER_OPENBSD 0 +#elif __NetBSD__ +#define LIBFUZZER_LINUX 0 +#define LIBFUZZER_APPLE 0 +#define LIBFUZZER_NETBSD 1 +#define LIBFUZZER_FREEBSD 0 +#define LIBFUZZER_OPENBSD 0 +#elif __FreeBSD__ +#define LIBFUZZER_LINUX 0 +#define LIBFUZZER_APPLE 0 +#define LIBFUZZER_NETBSD 0 +#define LIBFUZZER_FREEBSD 1 +#define LIBFUZZER_OPENBSD 0 +#elif __OpenBSD__ +#define LIBFUZZER_LINUX 0 +#define LIBFUZZER_APPLE 0 +#define LIBFUZZER_NETBSD 0 +#define LIBFUZZER_FREEBSD 0 +#define LIBFUZZER_OPENBSD 1 +#else +#error "Support for your platform has not been implemented" +#endif + +// libFuzzer interface is thin, so we don't include any libFuzzer headers. +extern "C" { +int LLVMFuzzerTestOneInput(const uint8_t *Data, size_t Size); +__attribute__((weak)) int LLVMFuzzerInitialize(int *argc, char ***argv); +} + +// Input buffer. +static const size_t kMaxAflInputSize = 1 << 20; +static uint8_t AflInputBuf[kMaxAflInputSize]; + +// Keep track of where stderr content is being written to, so that +// dup_and_close_stderr can use the correct one. +static FILE *output_file = stderr; + +// If the user asks us to duplicate stderr, then do it. +static void maybe_duplicate_stderr() { + char *stderr_duplicate_filename = + getenv("AFL_DRIVER_STDERR_DUPLICATE_FILENAME"); + + if (!stderr_duplicate_filename) + return; + + FILE *stderr_duplicate_stream = + freopen(stderr_duplicate_filename, "a+", stderr); + + if (!stderr_duplicate_stream) { + fprintf( + stderr, + "Failed to duplicate stderr to AFL_DRIVER_STDERR_DUPLICATE_FILENAME"); + abort(); + } + output_file = stderr_duplicate_stream; +} + +// Most of these I/O functions were inspired by/copied from libFuzzer's code. +static void discard_output(int fd) { + FILE *temp = fopen("/dev/null", "w"); + if (!temp) + abort(); + dup2(fileno(temp), fd); + fclose(temp); +} + +static void close_stdout() { discard_output(STDOUT_FILENO); } + +// Prevent the targeted code from writing to "stderr" but allow sanitizers and +// this driver to do so. +static void dup_and_close_stderr() { + int output_fileno = fileno(output_file); + int output_fd = dup(output_fileno); + if (output_fd <= 0) + abort(); + FILE *new_output_file = fdopen(output_fd, "w"); + if (!new_output_file) + abort(); + discard_output(output_fileno); +} + +static void Printf(const char *Fmt, ...) { + va_list ap; + va_start(ap, Fmt); + vfprintf(output_file, Fmt, ap); + va_end(ap); + fflush(output_file); +} + +// Close stdout and/or stderr if user asks for it. +static void maybe_close_fd_mask() { + char *fd_mask_str = getenv("AFL_DRIVER_CLOSE_FD_MASK"); + if (!fd_mask_str) + return; + int fd_mask = atoi(fd_mask_str); + if (fd_mask & 2) + dup_and_close_stderr(); + if (fd_mask & 1) + close_stdout(); +} + +// Define LLVMFuzzerMutate to avoid link failures for targets that use it +// with libFuzzer's LLVMFuzzerCustomMutator. +extern "C" size_t LLVMFuzzerMutate(uint8_t *Data, size_t Size, size_t MaxSize) { + assert(false && "LLVMFuzzerMutate should not be called from afl_driver"); + return 0; +} + +// Execute any files provided as parameters. +static int ExecuteFilesOnyByOne(int argc, char **argv) { + for (int i = 1; i < argc; i++) { + std::ifstream in(argv[i], std::ios::binary); + in.seekg(0, in.end); + size_t length = in.tellg(); + in.seekg (0, in.beg); + std::cout << "Reading " << length << " bytes from " << argv[i] << std::endl; + // Allocate exactly length bytes so that we reliably catch buffer overflows. + std::vector bytes(length); + in.read(bytes.data(), bytes.size()); + assert(in); + LLVMFuzzerTestOneInput(reinterpret_cast(bytes.data()), + bytes.size()); + std::cout << "Execution successful" << std::endl; + } + return 0; +} + +int main(int argc, char **argv) { + Printf( + "======================= INFO =========================\n" + "This binary is built for AFL-fuzz.\n" + "To run the target function on individual input(s) execute this:\n" + " %s < INPUT_FILE\n" + "or\n" + " %s INPUT_FILE1 [INPUT_FILE2 ... ]\n" + "To fuzz with afl-fuzz execute this:\n" + " afl-fuzz [afl-flags] %s [-N]\n" + "afl-fuzz will run N iterations before " + "re-spawning the process (default: 1000)\n" + "======================================================\n", + argv[0], argv[0], argv[0]); + + maybe_duplicate_stderr(); + maybe_close_fd_mask(); + if (LLVMFuzzerInitialize) + LLVMFuzzerInitialize(&argc, &argv); + // Do any other expensive one-time initialization here. + + int N = 1000; + if (argc == 2 && argv[1][0] == '-') + N = atoi(argv[1] + 1); + else if(argc == 2 && (N = atoi(argv[1])) > 0) + Printf("WARNING: using the deprecated call style `%s %d`\n", argv[0], N); + else if (argc > 1) + return ExecuteFilesOnyByOne(argc, argv); + + assert(N > 0); + + // Call LLVMFuzzerTestOneInput here so that coverage caused by initialization + // on the first execution of LLVMFuzzerTestOneInput is ignored. + uint8_t dummy_input[1] = {0}; + LLVMFuzzerTestOneInput(dummy_input, 1); + + int num_runs = 0; + ssize_t n_read = 0; + while (n_read >= 0) { + n_read = read(0, AflInputBuf, kMaxAflInputSize); + if (n_read > 0) { + // Copy AflInputBuf into a separate buffer to let asan find buffer + // overflows. Don't use unique_ptr/etc to avoid extra dependencies. + uint8_t *copy = new uint8_t[n_read]; + memcpy(copy, AflInputBuf, n_read); + num_runs++; + LLVMFuzzerTestOneInput(copy, n_read); + delete[] copy; + } + } + Printf("%s: successfully executed %d input(s)\n", argv[0], num_runs); +} From e67f2c43b4b31ba5cb1f0d1004dbcfc77936e982 Mon Sep 17 00:00:00 2001 From: van Hauser Date: Thu, 11 Jun 2020 11:19:20 +0200 Subject: [PATCH 16/39] add clang support --- README.md | 4 +++- VERSION | 2 +- afl-clang-cov.sh | 2 ++ afl-cov | 11 +++++++++-- afl-cov-build.sh | 18 +++++++++++++----- afl-cov.sh | 12 ++++++++---- 6 files changed, 36 insertions(+), 13 deletions(-) create mode 100755 afl-clang-cov.sh diff --git a/README.md b/README.md index 0b6552f..4644bce 100644 --- a/README.md +++ b/README.md @@ -1,6 +1,6 @@ # afl-cov - AFL Fuzzing Code Coverage -Version: 0.6.5 +Version: 0.6.6 - [Preface](#preface) - [Introduction](#introduction) @@ -23,6 +23,8 @@ It has four changes: * afl-cov now can send to targets that read on stdin (just omit @@) * afl-cov.sh makes using afl-cov easier (just needs two parameters) * afl-cov-build.sh makes builing a target for coverage easier + * afl-cov/afl-cov.sh/afl-cov-build.sh now support clang coverage, just add + -c to afl-cov.sh/afl-cov-build.sh (and --clang to afl-cov) * afl-stat.sh shows the statistics of a run (in progress or completed) Enjoy! diff --git a/VERSION b/VERSION index 844f6a9..05e8a45 100644 --- a/VERSION +++ b/VERSION @@ -1 +1 @@ -0.6.3 +0.6.6 diff --git a/afl-clang-cov.sh b/afl-clang-cov.sh new file mode 100755 index 0000000..2a3a05f --- /dev/null +++ b/afl-clang-cov.sh @@ -0,0 +1,2 @@ +#!/bin/sh +llvm-cov gcov $* diff --git a/afl-cov b/afl-cov index a6f77fa..00ab8be 100755 --- a/afl-cov +++ b/afl-cov @@ -2,7 +2,7 @@ # # File: afl-cov # -# Version: 0.6.5 +# Version: 0.6.6 # # Purpose: Perform lcov coverage diff's against each AFL queue file to see # new functions and line coverage evolve from an AFL fuzzing cycle. @@ -45,7 +45,7 @@ try: except ImportError: import subprocess -__version__ = '0.6.5' +__version__ = '0.6.6' NO_OUTPUT = 0 WANT_OUTPUT = 1 @@ -579,6 +579,8 @@ def lcov_gen_coverage(cov_paths, cargs): lcov_opts += ' --rc lcov_branch_coverage=1' if cargs.follow: lcov_opts += ' --follow' + if cargs.clang: + lcov_opts += ' --gcov-tool afl-clang-cov.sh' run_cmd(cargs.lcov_path \ + lcov_opts @@ -807,6 +809,8 @@ def init_tracking(cov_paths, cargs): lcov_opts = '' if cargs.enable_branch_coverage: lcov_opts += ' --rc lcov_branch_coverage=1 ' + if cargs.clang: + lcov_opts += ' --gcov-tool afl-clang-cov.sh' ### reset code coverage counters - this is done only once as ### afl-cov is spinning up even if AFL is running in parallel mode @@ -1149,6 +1153,9 @@ def parse_cmdline(): p.add_argument("--live", action='store_true', help="Process a live AFL directory, and afl-cov will exit when it appears afl-fuzz has been stopped", default=False) + p.add_argument("--clang", action='store_true', + help="If clang was used for coverage compilation instead of gcc"", + default=False) p.add_argument("--cover-corpus", action='store_true', help="Measure coverage after running all available tests instead of individually per queue file", default=False) diff --git a/afl-cov-build.sh b/afl-cov-build.sh index dfce10e..46214ce 100755 --- a/afl-cov-build.sh +++ b/afl-cov-build.sh @@ -1,18 +1,26 @@ -#!/bin/sh +#!/bin/bash test -z "$1" -o "$1" = "-h" && { - echo "Syntax: $0 [options]" + echo "Syntax: $0 [-c] [options]" echo Sets build options for coverage instrumentation with gcov/lcov. echo Set CC/CXX environment variables if you do not want gcc/g++. + echo Specify the -c parameter if you want to use clang/clang++ instead. echo Example: "$0 ./configure --disable-shared" exit 1 } -test -z "$CC" && export CC=gcc -test -z "$CXX" && export CXX=g++ +CLANG= +test "$1" = "-c" && { CLANG=yes ; shift ; } + +test -z "$CC" -a -z "$CLANG" && export CC=gcc +test -z "$CXX" -a -z "$CLANG" && export CXX=g++ +test -z "$CC" -a -n "$CLANG" && export CC=clang +test -z "$CXX" -a -n "$CLANG" && export CXX=clang++ + export CFLAGS="-fprofile-arcs -ftest-coverage" export CXXFLAGS="$CFLAGS" export CPPFLAGS="$CFLAGS" -export LDFLAGS="-lgcov --coverage" +test -z "$CLANG" && export LDFLAGS="-lgcov --coverage" +test -n "$CLANG" && export LDFLAGS="--coverage" $* diff --git a/afl-cov.sh b/afl-cov.sh index b44e358..e8b5fee 100755 --- a/afl-cov.sh +++ b/afl-cov.sh @@ -3,16 +3,21 @@ # easy wrapper script for afl-cov # test "$1" = "-h" -o -z "$1" && { - echo "Syntax: $0 [-v] out-dir \"exec cmd --foo @@\"" + echo "Syntax: $0 [-v] [-c] out-dir \"exec cmd --foo @@\"" echo echo Generates the coverage information for an AFL run. echo Must be run from the top directory of the coverage build. - echo The -v option enables verbose output + echo The -v option enables verbose output. + echo The option -c specifies that clang was used for the coverage build echo echo Example: $0 ../target/out \"tools/target @@\" exit 1 } +test "$1" = "-v" && { OPT1="-v" ; shift ; } +test "$1" = "-c" && { OPT2="--clang" ; shift ; } +test "$1" = "-v" && { OPT1="-v" ; shift ; } + test -d "$1" || { echo Error: not a directory: $1 ; exit 1 ; } test -e "$1"/queue || { echo Error: not an afl-fuzz -o out directory ; exit 1 ; } @@ -20,8 +25,7 @@ HOMEPATH=`dirname $0` DST=`realpath "$1"` export PATH=$HOMEPATH:$PATH -test "$1" = "-v" && { OPT="-v" ; shift ; } -afl-cov $OPT -d "$DST" --cover-corpus --coverage-cmd "$2" --code-dir . --overwrite +afl-cov $OPT1 $OPT2 -d "$DST" --cover-corpus --coverage-cmd "$2" --code-dir . --overwrite test -e "$1"/fuzzer_stats && { DIFF=$(expr `grep last_update "$DST"/fuzzer_stats|awk '{print$3}'` - `grep start_time "$DST"/fuzzer_stats|awk '{print$3}'`) From c83749cfecd6211fbdb8d5503861ddfc804303fb Mon Sep 17 00:00:00 2001 From: van Hauser Date: Sun, 21 Jun 2020 09:53:00 +0200 Subject: [PATCH 17/39] display days on afl-stat --- afl-stat.sh | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/afl-stat.sh b/afl-stat.sh index 9e0c2fa..4c9b7f2 100755 --- a/afl-stat.sh +++ b/afl-stat.sh @@ -13,8 +13,10 @@ while [ -n "$1" ]; do egrep 'run_time|execs_done|execs_per_sec|paths_total|^unique_|stability' "$1"/fuzzer_stats SECONDS=`egrep run_time "$1"/fuzzer_stats | awk '{print$3}'` test -n "$SECONDS" && { + DAYS=`expr $SECONDS / 86400` + SECONDS=`expr $SECONDS % 86400` TIME=`date -u -d "@$SECONDS" +"%T"` - echo "run_clock : $TIME" + echo "run_clock : $DAYS:$TIME" } } | sort | tee "$1"/stats.out LINES= From ce1e72d786d5beec1658906da98092a44fc3141b Mon Sep 17 00:00:00 2001 From: van Hauser Date: Wed, 24 Jun 2020 16:49:29 +0200 Subject: [PATCH 18/39] fix afl-cov --- afl-cov | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/afl-cov b/afl-cov index 00ab8be..a34b600 100755 --- a/afl-cov +++ b/afl-cov @@ -1154,7 +1154,7 @@ def parse_cmdline(): help="Process a live AFL directory, and afl-cov will exit when it appears afl-fuzz has been stopped", default=False) p.add_argument("--clang", action='store_true', - help="If clang was used for coverage compilation instead of gcc"", + help="If clang was used for coverage compilation instead of gcc", default=False) p.add_argument("--cover-corpus", action='store_true', help="Measure coverage after running all available tests instead of individually per queue file", From e4f3a979e803d0566402a85f825a67004a3fb12a Mon Sep 17 00:00:00 2001 From: vanhauser-thc Date: Tue, 9 Feb 2021 09:53:20 +0100 Subject: [PATCH 19/39] timeout option, auto use default/ if necessary --- README.md | 8 +++++--- afl-cov | 11 ++++++++--- afl-cov.sh | 12 ++++++++++-- 3 files changed, 23 insertions(+), 8 deletions(-) diff --git a/README.md b/README.md index 4644bce..e67bfa5 100644 --- a/README.md +++ b/README.md @@ -18,13 +18,15 @@ Version: 0.6.6 This is a modified afl-cov fork because the original author's account is inactive :-( -It has four changes: +It has several improvements: * afl-cov now accepts "@@" like AFL++ in the target command parameters * afl-cov now can send to targets that read on stdin (just omit @@) + * afl-cov has a timeout -T option to hangs are not an issue, default 5s * afl-cov.sh makes using afl-cov easier (just needs two parameters) - * afl-cov-build.sh makes builing a target for coverage easier + * afl-cov-build.sh makes builing a target for coverage easier, just type + `afl-cov-build.sh make` * afl-cov/afl-cov.sh/afl-cov-build.sh now support clang coverage, just add - -c to afl-cov.sh/afl-cov-build.sh (and --clang to afl-cov) + -c to afl-cov.sh/afl-cov-build.sh (--clang for afl-cov) * afl-stat.sh shows the statistics of a run (in progress or completed) Enjoy! diff --git a/afl-cov b/afl-cov index a34b600..df60c8d 100755 --- a/afl-cov +++ b/afl-cov @@ -186,10 +186,10 @@ def process_afl_test_cases(cargs): ### for the current AFL test case file if run_once: run_cmd(cargs.coverage_cmd.replace('AFL_FILE', f), - cov_paths['log_file'], cargs, NO_OUTPUT, True, f) + cov_paths['log_file'], cargs, NO_OUTPUT, True, f, cargs.timeout) else: out_lines = run_cmd(cargs.coverage_cmd.replace('AFL_FILE', f), - cov_paths['log_file'], cargs, WANT_OUTPUT, True, f)[1] + cov_paths['log_file'], cargs, WANT_OUTPUT, True, f, cargs.timeout)[1] run_once = True if cargs.afl_queue_id_limit \ @@ -694,7 +694,7 @@ def get_running_pid(stats_file, pid_re): break return pid -def run_cmd(cmd, log_file, cargs, collect, aflrun, fn): +def run_cmd(cmd, log_file, cargs, collect, aflrun, fn, timeout=None): out = [] @@ -714,6 +714,9 @@ def run_cmd(cmd, log_file, cargs, collect, aflrun, fn): else: print(" CMD: %s" % cmd) + if timeout: + cmd = 'timeout -s KILL %s %s' % (timeout, cmd) + es = subprocess.call(cmd, stdin=None, stdout=fh, stderr=subprocess.STDOUT, shell=True) @@ -1215,6 +1218,8 @@ def parse_cmdline(): help="Print version and exit", default=False) p.add_argument("-q", "--quiet", action='store_true', help="Quiet mode", default=False) + p.add_argument("-T", "--timeout", type=str, + help="timeout (default 5 seconds)", default='5') return p.parse_args() diff --git a/afl-cov.sh b/afl-cov.sh index e8b5fee..cfc9854 100755 --- a/afl-cov.sh +++ b/afl-cov.sh @@ -19,10 +19,18 @@ test "$1" = "-c" && { OPT2="--clang" ; shift ; } test "$1" = "-v" && { OPT1="-v" ; shift ; } test -d "$1" || { echo Error: not a directory: $1 ; exit 1 ; } -test -e "$1"/queue || { echo Error: not an afl-fuzz -o out directory ; exit 1 ; } -HOMEPATH=`dirname $0` + DST=`realpath "$1"` +test -e "$DST"/queue || { + DST="$DST/default" + test -e "$DST"/queue || { + echo Error: not an afl-fuzz -o out directory + exit 1 + } +} + +HOMEPATH=`dirname $0` export PATH=$HOMEPATH:$PATH afl-cov $OPT1 $OPT2 -d "$DST" --cover-corpus --coverage-cmd "$2" --code-dir . --overwrite From 572c75be70f62908f87206e6af614d00e7c9dca3 Mon Sep 17 00:00:00 2001 From: vanhauser-thc Date: Sun, 21 Feb 2021 23:05:50 +0100 Subject: [PATCH 20/39] fix open call --- afl-cov | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/afl-cov b/afl-cov index df60c8d..949b883 100755 --- a/afl-cov +++ b/afl-cov @@ -724,7 +724,7 @@ def run_cmd(cmd, log_file, cargs, collect, aflrun, fn, timeout=None): if cargs.disable_cmd_redirection or collect == WANT_OUTPUT \ or collect == LOG_ERRORS: - with open(fh.name, 'r') as f: + with open(fh.name, 'r', 'rb') as f: for line in f: out.append(line.rstrip('\n')) os.unlink(fh.name) From db64d33dc0baba1fe483ef9d5371a6419fd33bf6 Mon Sep 17 00:00:00 2001 From: vanhauser-thc Date: Mon, 22 Feb 2021 09:14:17 +0100 Subject: [PATCH 21/39] fix the fix --- afl-cov | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/afl-cov b/afl-cov index 949b883..55fd5a6 100755 --- a/afl-cov +++ b/afl-cov @@ -724,7 +724,7 @@ def run_cmd(cmd, log_file, cargs, collect, aflrun, fn, timeout=None): if cargs.disable_cmd_redirection or collect == WANT_OUTPUT \ or collect == LOG_ERRORS: - with open(fh.name, 'r', 'rb') as f: + with open(fh.name, 'r', encoding='ISO-8859-1') as f: for line in f: out.append(line.rstrip('\n')) os.unlink(fh.name) From ba95180c28702f8f019672bbe4b90f7b86db146a Mon Sep 17 00:00:00 2001 From: vanhauser-thc Date: Mon, 22 Feb 2021 13:47:13 +0100 Subject: [PATCH 22/39] fix afl-cov for python3. python sucks. --- afl-cov | 18 +++++++++--------- 1 file changed, 9 insertions(+), 9 deletions(-) diff --git a/afl-cov b/afl-cov index 55fd5a6..dc08c79 100755 --- a/afl-cov +++ b/afl-cov @@ -224,7 +224,7 @@ def process_afl_test_cases(cargs): logr("\n\n++++++ BEGIN - first exec output for CMD: %s" % \ (cargs.coverage_cmd.replace('AFL_FILE', f)), cov_paths['log_file'], cargs) - for line in out_lines: + for line in out_lines.decode("ascii"): logr(" %s" % (line), cov_paths['log_file'], cargs) logr("++++++ END\n", cov_paths['log_file'], cargs) @@ -619,16 +619,16 @@ def lcov_gen_coverage(cov_paths, cargs): def log_coverage(out_lines, log_file, cargs): for line in out_lines: - m = re.search('^\s+(lines\.\..*\:\s.*)', line) + m = re.search('^\s+(lines\.\..*\:\s.*)', line.decode("ascii")) if m and m.group(1): logr(" " + m.group(1), log_file, cargs) else: - m = re.search('^\s+(functions\.\..*\:\s.*)', line) + m = re.search('^\s+(functions\.\..*\:\s.*)', line.decode("ascii")) if m and m.group(1): logr(" " + m.group(1), log_file, cargs) else: if cargs.enable_branch_coverage: - m = re.search('^\s+(branches\.\..*\:\s.*)', line) + m = re.search('^\s+(branches\.\..*\:\s.*)', line.decode("ascii")) if m and m.group(1): logr(" " + m.group(1), log_file, cargs) @@ -724,9 +724,9 @@ def run_cmd(cmd, log_file, cargs, collect, aflrun, fn, timeout=None): if cargs.disable_cmd_redirection or collect == WANT_OUTPUT \ or collect == LOG_ERRORS: - with open(fh.name, 'r', encoding='ISO-8859-1') as f: + with open(fh.name, 'rb') as f: for line in f: - out.append(line.rstrip('\n')) + out.append(line.rstrip(b'\n')) os.unlink(fh.name) if (es != 0) and (collect == LOG_ERRORS or collect == WANT_OUTPUT): @@ -734,7 +734,7 @@ def run_cmd(cmd, log_file, cargs, collect, aflrun, fn, timeout=None): logr(" Non-zero exit status '%d' for CMD: %s" % (es, cmd), log_file, cargs) for line in out: - logr(line, log_file, cargs) + logr(line.decode("ascii"), log_file, cargs) else: print(" Non-zero exit status '%d' for CMD: %s" % (es, cmd)) @@ -844,13 +844,13 @@ def is_bin_gcov_enabled(binary, cargs): ### run readelf against the binary to see if it contains gcov support for line in run_cmd("%s -a %s" % (cargs.readelf_path, binary), False, cargs, WANT_OUTPUT, False, "")[1]: - if ' __gcov' in line: + if b' __gcov' in line: if cargs.validate_args or cargs.gcov_check or cargs.gcov_check_bin: print("[+] Binary '%s' is compiled with code coverage support via gcc." % binary) rv = True break - if '__llvm_gcov' in line: + if b'__llvm_gcov' in line: if cargs.validate_args or cargs.gcov_check or cargs.gcov_check_bin: print("[+] Binary '%s' is compiled with code coverage support via llvm." % binary) rv = True From 14124eb43b4261ca46dd22add73c5abe806eb956 Mon Sep 17 00:00:00 2001 From: vanhauser-thc Date: Mon, 22 Feb 2021 19:07:50 +0100 Subject: [PATCH 23/39] fix --- afl-cov | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/afl-cov b/afl-cov index dc08c79..f205a73 100755 --- a/afl-cov +++ b/afl-cov @@ -224,8 +224,8 @@ def process_afl_test_cases(cargs): logr("\n\n++++++ BEGIN - first exec output for CMD: %s" % \ (cargs.coverage_cmd.replace('AFL_FILE', f)), cov_paths['log_file'], cargs) - for line in out_lines.decode("ascii"): - logr(" %s" % (line), cov_paths['log_file'], cargs) + for line in out_lines: + logr(" %s" % (line.decode("ascii")), cov_paths['log_file'], cargs) logr("++++++ END\n", cov_paths['log_file'], cargs) cov_paths['id_file'] = "%s" % os.path.basename(f) From c315486679dbcfb5a589c97686c81c145163e01f Mon Sep 17 00:00:00 2001 From: vanhauser-thc Date: Thu, 25 Feb 2021 09:31:25 +0100 Subject: [PATCH 24/39] fix --- afl-cov | 1275 ++++++++++++++++++++++++++++++++++--------------------- 1 file changed, 783 insertions(+), 492 deletions(-) diff --git a/afl-cov b/afl-cov index f205a73..02bc7e1 100755 --- a/afl-cov +++ b/afl-cov @@ -45,11 +45,12 @@ try: except ImportError: import subprocess -__version__ = '0.6.6' +__version__ = "0.6.6" -NO_OUTPUT = 0 +NO_OUTPUT = 0 WANT_OUTPUT = 1 -LOG_ERRORS = 2 +LOG_ERRORS = 2 + def main(): @@ -59,7 +60,7 @@ def main(): cargs = parse_cmdline() if cargs.coverage_cmd: - cargs.coverage_cmd = cargs.coverage_cmd.replace('@@', 'AFL_FILE') + cargs.coverage_cmd = cargs.coverage_cmd.replace("@@", "AFL_FILE") if cargs.version: print("afl-cov-" + __version__) @@ -94,35 +95,37 @@ def main(): return not process_afl_test_cases(cargs) + def run_in_background(): - ### could use the python 'daemon' module, but it isn't always - ### installed, and we just need a basic backgrounding - ### capability anyway + # could use the python 'daemon' module, but it isn't always + # installed, and we just need a basic backgrounding + # capability anyway pid = os.fork() - if (pid < 0): + if pid < 0: print("[*] fork() error, exiting.") os._exit() - elif (pid > 0): + elif pid > 0: os._exit(0) else: os.setsid() return + def process_afl_test_cases(cargs): - rv = True - run_once = False + rv = True + run_once = False tot_files = 0 - fuzz_dir = '' - curr_file = '' + fuzz_dir = "" + curr_file = "" afl_files = [] cov_paths = {} - ### main coverage tracking dictionary - cov = {} - cov['zero'] = {} - cov['pos'] = {} + # main coverage tracking dictionary + cov = {} + cov["zero"] = {} + cov["pos"] = {} while True: @@ -130,24 +133,24 @@ def process_afl_test_cases(cargs): rv = False break - dir_ctr = 0 + dir_ctr = 0 last_dir = False do_coverage = True if cargs.cover_corpus: do_coverage = False - for fuzz_dir in cov_paths['dirs']: + for fuzz_dir in cov_paths["dirs"]: - do_break = False + do_break = False last_file = False num_files = 0 new_files = [] - tmp_files = import_test_cases(fuzz_dir + '/queue') - dir_ctr += 1 - f_ctr = 0 + tmp_files = import_test_cases(fuzz_dir + "/queue") + dir_ctr += 1 + f_ctr = 0 - if dir_ctr == len(cov_paths['dirs']): + if dir_ctr == len(cov_paths["dirs"]): last_dir = True for f in tmp_files: @@ -156,9 +159,12 @@ def process_afl_test_cases(cargs): new_files.append(f) if new_files: - logr("\n*** Imported %d new test cases from: %s\n" \ - % (len(new_files), (fuzz_dir + '/queue')), - cov_paths['log_file'], cargs) + logr( + "\n*** Imported %d new test cases from: %s\n" + % (len(new_files), (fuzz_dir + "/queue")), + cov_paths["log_file"], + cargs, + ) for f in new_files: @@ -167,68 +173,98 @@ def process_afl_test_cases(cargs): last_file = True if cargs.cover_corpus and last_dir and last_file: - ### in --cover-corpus mode, only run lcov after all AFL - ### test cases have been processed + # in --cover-corpus mode, only run lcov after all AFL + # test cases have been processed do_coverage = True out_lines = [] curr_cycle = get_cycle_num(num_files, cargs) - logr("[+] AFL test case: %s (%d / %d), cycle: %d" \ - % (os.path.basename(f), num_files, len(afl_files), - curr_cycle), cov_paths['log_file'], cargs) - - cov_paths['diff'] = "%s/%s" % \ - (cov_paths['diff_dir'], os.path.basename(f)) + logr( + "[+] AFL test case: %s (%d / %d), cycle: %d" + % (os.path.basename(f), num_files, len(afl_files), curr_cycle), + cov_paths["log_file"], + cargs, + ) + + cov_paths["diff"] = "%s/%s" % ( + cov_paths["diff_dir"], + os.path.basename(f), + ) id_range_update(f, cov_paths) - ### execute the command to generate code coverage stats - ### for the current AFL test case file + # execute the command to generate code coverage stats + # for the current AFL test case file if run_once: - run_cmd(cargs.coverage_cmd.replace('AFL_FILE', f), - cov_paths['log_file'], cargs, NO_OUTPUT, True, f, cargs.timeout) + run_cmd( + cargs.coverage_cmd.replace("AFL_FILE", f), + cov_paths["log_file"], + cargs, + NO_OUTPUT, + True, + f, + cargs.timeout, + ) else: - out_lines = run_cmd(cargs.coverage_cmd.replace('AFL_FILE', f), - cov_paths['log_file'], cargs, WANT_OUTPUT, True, f, cargs.timeout)[1] + out_lines = run_cmd( + cargs.coverage_cmd.replace("AFL_FILE", f), + cov_paths["log_file"], + cargs, + WANT_OUTPUT, + True, + f, + cargs.timeout, + )[1] run_once = True - if cargs.afl_queue_id_limit \ - and num_files >= cargs.afl_queue_id_limit - 1: - logr("[+] queue/ id limit of %d reached..." \ - % cargs.afl_queue_id_limit, - cov_paths['log_file'], cargs) + if ( + cargs.afl_queue_id_limit + and num_files >= cargs.afl_queue_id_limit - 1 + ): + logr( + "[+] queue/ id limit of %d reached..." + % cargs.afl_queue_id_limit, + cov_paths["log_file"], + cargs, + ) do_break = True if cargs.cover_corpus and last_dir: do_coverage = True if do_coverage and not cargs.coverage_at_exit: - ### generate the code coverage stats for this test case + # generate the code coverage stats for this test case lcov_gen_coverage(cov_paths, cargs) - ### diff to the previous code coverage, look for new - ### lines/functions, and write out results - coverage_diff(curr_cycle, fuzz_dir, cov_paths, f, - cov, cargs) + # diff to the previous code coverage, look for new + # lines/functions, and write out results + coverage_diff(curr_cycle, fuzz_dir, cov_paths, f, cov, cargs) if cargs.cover_corpus: - ### reset the range values - cov_paths['id_min'] = cov_paths['id_max'] = -1 + # reset the range values + cov_paths["id_min"] = cov_paths["id_max"] = -1 if cargs.lcov_web_all: gen_web_cov_report(fuzz_dir, cov_paths, cargs) - ### log the output of the very first coverage command to - ### assist in troubleshooting - if len(out_lines): - logr("\n\n++++++ BEGIN - first exec output for CMD: %s" % \ - (cargs.coverage_cmd.replace('AFL_FILE', f)), - cov_paths['log_file'], cargs) - for line in out_lines: - logr(" %s" % (line.decode("ascii")), cov_paths['log_file'], cargs) - logr("++++++ END\n", cov_paths['log_file'], cargs) - - cov_paths['id_file'] = "%s" % os.path.basename(f) + # log the output of the very first coverage command to + # assist in troubleshooting + #if len(out_lines): + # logr( + # "\n\n++++++ BEGIN - first exec output for CMD: %s" + # % (cargs.coverage_cmd.replace("AFL_FILE", f)), + # cov_paths["log_file"], + # cargs, + # ) + # for line in out_lines: + # logr( + # " %s" % (line.decode("ascii")), + # cov_paths["log_file"], + # cargs, + # ) + # logr("++++++ END\n", cov_paths["log_file"], cargs) + + cov_paths["id_file"] = "%s" % os.path.basename(f) num_files += 1 tot_files += 1 @@ -239,35 +275,45 @@ def process_afl_test_cases(cargs): if cargs.live: if is_afl_fuzz_running(cargs): if not len(new_files): - logr("[-] No new AFL test cases, sleeping for %d seconds" \ - % cargs.sleep, cov_paths['log_file'], cargs) + logr( + "[-] No new AFL test cases, sleeping for %d seconds" + % cargs.sleep, + cov_paths["log_file"], + cargs, + ) time.sleep(cargs.sleep) continue else: - logr("[+] afl-fuzz appears to be stopped...", - cov_paths['log_file'], cargs) + logr( + "[+] afl-fuzz appears to be stopped...", + cov_paths["log_file"], + cargs, + ) break - ### only go once through the loop unless we are in --live mode + # only go once through the loop unless we are in --live mode else: break if tot_files > 0: - logr("[+] Processed %d / %d test cases.\n" \ - % (tot_files, len(afl_files)), - cov_paths['log_file'], cargs) + logr( + "[+] Processed %d / %d test cases.\n" % (tot_files, len(afl_files)), + cov_paths["log_file"], + cargs, + ) if cargs.coverage_at_exit: - ### generate the code coverage stats for this test case + # generate the code coverage stats for this test case lcov_gen_coverage(cov_paths, cargs) - ### diff to the previous code coverage, look for new - ### lines/functions, and write out results - coverage_diff(curr_cycle, fuzz_dir, cov_paths, - cov_paths['id_file'], cov, cargs) + # diff to the previous code coverage, look for new + # lines/functions, and write out results + coverage_diff( + curr_cycle, fuzz_dir, cov_paths, cov_paths["id_file"], cov, cargs + ) - ### write out the final zero coverage and positive coverage reports - write_zero_cov(cov['zero'], cov_paths, cargs) - write_pos_cov(cov['pos'], cov_paths, cargs) + # write out the final zero coverage and positive coverage reports + write_zero_cov(cov["zero"], cov_paths, cargs) + write_pos_cov(cov["pos"], cov_paths, cargs) if not cargs.disable_lcov_web: lcov_gen_coverage(cov_paths, cargs) @@ -275,144 +321,153 @@ def process_afl_test_cases(cargs): else: if rv: - logr("[*] Did not find any AFL test cases, exiting.\n", - cov_paths['log_file'], cargs) + logr( + "[*] Did not find any AFL test cases, exiting.\n", + cov_paths["log_file"], + cargs, + ) rv = False return rv + def id_range_update(afl_file, cov_paths): - id_val = int(os.path.basename(afl_file).split(',')[0].split(':')[1]) + id_val = int(os.path.basename(afl_file).split(",")[0].split(":")[1]) - if cov_paths['id_min'] == -1: - cov_paths['id_min'] = id_val - elif id_val < cov_paths['id_min']: - cov_paths['id_min'] = id_val + if cov_paths["id_min"] == -1: + cov_paths["id_min"] = id_val + elif id_val < cov_paths["id_min"]: + cov_paths["id_min"] = id_val - if cov_paths['id_max'] == -1: - cov_paths['id_max'] = id_val - elif id_val > cov_paths['id_max']: - cov_paths['id_max'] = id_val + if cov_paths["id_max"] == -1: + cov_paths["id_max"] = id_val + elif id_val > cov_paths["id_max"]: + cov_paths["id_max"] = id_val return + def coverage_diff(cycle_num, fuzz_dir, cov_paths, afl_file, cov, cargs): - log_lines = [] - delta_log_lines = [] + log_lines = [] + delta_log_lines = [] print_diff_header = True - ### defaults - a_file = '(init)' - if cov_paths['id_file']: - a_file = cov_paths['id_file'] + # defaults + a_file = "(init)" + if cov_paths["id_file"]: + a_file = cov_paths["id_file"] delta_file = b_file = os.path.basename(afl_file) if cargs.cover_corpus or cargs.coverage_at_exit: - a_file = 'id:%d...' % cov_paths['id_min'] - b_file = 'id:%d...' % cov_paths['id_max'] - delta_file = 'id:[%d-%d]...' % \ - (cov_paths['id_min'], cov_paths['id_max']) + a_file = "id:%d..." % cov_paths["id_min"] + b_file = "id:%d..." % cov_paths["id_max"] + delta_file = "id:[%d-%d]..." % (cov_paths["id_min"], cov_paths["id_max"]) - new_cov = extract_coverage(cov_paths['lcov_info_final'], - cov_paths['log_file'], cargs) + new_cov = extract_coverage( + cov_paths["lcov_info_final"], cov_paths["log_file"], cargs + ) if not new_cov: return - ### We aren't interested in the number of times AFL has executed - ### a line or function (since we can't really get this anyway because - ### gcov stats aren't influenced by AFL directly) - what we want is - ### simply whether a new line or function has been executed at all by - ### this test case. So, we look for new positive coverage. - for f in new_cov['pos']: + # We aren't interested in the number of times AFL has executed + # a line or function (since we can't really get this anyway because + # gcov stats aren't influenced by AFL directly) - what we want is + # simply whether a new line or function has been executed at all by + # this test case. So, we look for new positive coverage. + for f in new_cov["pos"]: print_filename = True - if f not in cov['zero'] and f not in cov['pos']: ### completely new file + if f not in cov["zero"] and f not in cov["pos"]: # completely new file cov_init(f, cov) if print_diff_header: - log_lines.append("diff %s -> %s" % \ - (a_file, b_file)) + log_lines.append("diff %s -> %s" % (a_file, b_file)) print_diff_header = False - for ctype in new_cov['pos'][f]: - for val in sorted(new_cov['pos'][f][ctype]): - cov['pos'][f][ctype][val] = '' + for ctype in new_cov["pos"][f]: + for val in sorted(new_cov["pos"][f][ctype]): + cov["pos"][f][ctype][val] = "" if print_filename: log_lines.append("New src file: " + f) print_filename = False log_lines.append(" New '" + ctype + "' coverage: " + val) - if ctype == 'line': + if ctype == "line": if cargs.coverage_include_lines: - delta_log_lines.append("%s, %s, %s, %s, %s\n" \ - % (delta_file, cycle_num, f, ctype, val)) + delta_log_lines.append( + "%s, %s, %s, %s, %s\n" + % (delta_file, cycle_num, f, ctype, val) + ) else: - delta_log_lines.append("%s, %s, %s, %s, %s\n" \ - % (delta_file, cycle_num, f, ctype, val)) - elif f in cov['zero'] and f in cov['pos']: - for ctype in new_cov['pos'][f]: - for val in sorted(new_cov['pos'][f][ctype]): - if val not in cov['pos'][f][ctype]: - cov['pos'][f][ctype][val] = '' + delta_log_lines.append( + "%s, %s, %s, %s, %s\n" + % (delta_file, cycle_num, f, ctype, val) + ) + elif f in cov["zero"] and f in cov["pos"]: + for ctype in new_cov["pos"][f]: + for val in sorted(new_cov["pos"][f][ctype]): + if val not in cov["pos"][f][ctype]: + cov["pos"][f][ctype][val] = "" if print_diff_header: - log_lines.append("diff %s -> %s" % \ - (a_file, b_file)) + log_lines.append("diff %s -> %s" % (a_file, b_file)) print_diff_header = False if print_filename: log_lines.append("Src file: " + f) print_filename = False log_lines.append(" New '" + ctype + "' coverage: " + val) - if ctype == 'line': + if ctype == "line": if cargs.coverage_include_lines: - delta_log_lines.append("%s, %s, %s, %s, %s\n" \ - % (delta_file, cycle_num, f, \ - ctype, val)) + delta_log_lines.append( + "%s, %s, %s, %s, %s\n" + % (delta_file, cycle_num, f, ctype, val) + ) else: - delta_log_lines.append("%s, %s, %s, %s, %s\n" \ - % (delta_file, cycle_num, f, \ - ctype, val)) + delta_log_lines.append( + "%s, %s, %s, %s, %s\n" + % (delta_file, cycle_num, f, ctype, val) + ) - ### now that new positive coverage has been added, reset zero - ### coverage to the current new zero coverage - cov['zero'] = {} - cov['zero'] = new_cov['zero'].copy() + # now that new positive coverage has been added, reset zero + # coverage to the current new zero coverage + cov["zero"] = {} + cov["zero"] = new_cov["zero"].copy() if len(log_lines): - logr("\n Coverage diff %s %s" \ - % (a_file, b_file), - cov_paths['log_file'], cargs) + logr( + "\n Coverage diff %s %s" % (a_file, b_file), cov_paths["log_file"], cargs + ) for l in log_lines: - logr(l, cov_paths['log_file'], cargs) - append_file(l, cov_paths['diff']) - logr("", cov_paths['log_file'], cargs) + logr(l, cov_paths["log_file"], cargs) + append_file(l, cov_paths["diff"]) + logr("", cov_paths["log_file"], cargs) if len(delta_log_lines): - cfile = open(cov_paths['id_delta_cov'], 'a') + cfile = open(cov_paths["id_delta_cov"], "a") for l in delta_log_lines: cfile.write(l) cfile.close() return + def write_zero_cov(zero_cov, cov_paths, cargs): - cpath = cov_paths['zero_cov'] + cpath = cov_paths["zero_cov"] - logr("[+] Final zero coverage report: %s" % cpath, - cov_paths['log_file'], cargs) - cfile = open(cpath, 'w') + logr("[+] Final zero coverage report: %s" % cpath, cov_paths["log_file"], cargs) + cfile = open(cpath, "w") cfile.write("# All functions / lines in this file were never executed by any\n") cfile.write("# AFL test case.\n") cfile.close() write_cov(cpath, zero_cov, cargs) return + def write_pos_cov(pos_cov, cov_paths, cargs): - cpath = cov_paths['pos_cov'] + cpath = cov_paths["pos_cov"] - logr("[+] Final positive coverage report: %s" % cpath, - cov_paths['log_file'], cargs) - cfile = open(cpath, 'w') + logr("[+] Final positive coverage report: %s" % cpath, cov_paths["log_file"], cargs) + cfile = open(cpath, "w") cfile.write("# All functions / lines in this file were executed by at\n") cfile.write("# least one AFL test case. See the cov/id-delta-cov file\n") cfile.write("# for more information.\n") @@ -420,15 +475,16 @@ def write_pos_cov(pos_cov, cov_paths, cargs): write_cov(cpath, pos_cov, cargs) return + def write_cov(cpath, cov, cargs): - cfile = open(cpath, 'a') + cfile = open(cpath, "a") for f in cov: cfile.write("File: %s\n" % f) for ctype in sorted(cov[f]): - if ctype == 'function': + if ctype == "function": for val in sorted(cov[f][ctype]): cfile.write(" %s: %s\n" % (ctype, val)) - elif ctype == 'line': + elif ctype == "line": if cargs.coverage_include_lines: for val in sorted(cov[f][ctype], key=int): cfile.write(" %s: %s\n" % (ctype, val)) @@ -436,251 +492,322 @@ def write_cov(cpath, cov, cargs): return + def write_status(status_file): - f = open(status_file, 'w') + f = open(status_file, "w") f.write("afl_cov_pid : %d\n" % os.getpid()) f.write("afl_cov_version : %s\n" % __version__) - f.write("command_line : %s\n" % ' '.join(argv)) + f.write("command_line : %s\n" % " ".join(argv)) f.close() return + def append_file(pstr, path): - f = open(path, 'a') + f = open(path, "a") f.write("%s\n" % pstr) f.close() return + def cov_init(cfile, cov): - for k in ['zero', 'pos']: + for k in ["zero", "pos"]: if k not in cov: cov[k] = {} if cfile not in cov[k]: cov[k][cfile] = {} - cov[k][cfile]['function'] = {} - cov[k][cfile]['line'] = {} + cov[k][cfile]["function"] = {} + cov[k][cfile]["line"] = {} return + def extract_coverage(lcov_file, log_file, cargs): search_rv = False tmp_cov = {} if not os.path.exists(lcov_file): - logr("[-] Coverage file '%s' does not exist, skipping." % lcov_file, - log_file, cargs) + logr( + "[-] Coverage file '%s' does not exist, skipping." % lcov_file, + log_file, + cargs, + ) return tmp_cov - ### populate old lcov output for functions/lines that were called - ### zero times - with open(lcov_file, 'r') as f: - current_file = '' + # populate old lcov output for functions/lines that were called + # zero times + with open(lcov_file, "r") as f: + current_file = "" for line in f: line = line.strip() - m = re.search('SF:(\S+)', line) + m = re.search("SF:(\S+)", line) if m and m.group(1): current_file = m.group(1) cov_init(current_file, tmp_cov) continue if current_file: - m = re.search('^FNDA:(\d+),(\S+)', line) + m = re.search("^FNDA:(\d+),(\S+)", line) if m and m.group(2): - fcn = m.group(2) + '()' - if m.group(1) == '0': - ### the function was never called - tmp_cov['zero'][current_file]['function'][fcn] = '' + fcn = m.group(2) + "()" + if m.group(1) == "0": + # the function was never called + tmp_cov["zero"][current_file]["function"][fcn] = "" else: - tmp_cov['pos'][current_file]['function'][fcn] = '' + tmp_cov["pos"][current_file]["function"][fcn] = "" continue - ### look for lines that were never called - m = re.search('^DA:(\d+),(\d+)', line) + # look for lines that were never called + m = re.search("^DA:(\d+),(\d+)", line) if m and m.group(1): lnum = m.group(1) - if m.group(2) == '0': - ### the line was never executed - tmp_cov['zero'][current_file]['line'][lnum] = '' + if m.group(2) == "0": + # the line was never executed + tmp_cov["zero"][current_file]["line"][lnum] = "" else: - tmp_cov['pos'][current_file]['line'][lnum] = '' + tmp_cov["pos"][current_file]["line"][lnum] = "" return tmp_cov + def search_cov(cargs): search_rv = False - id_delta_file = cargs.afl_fuzzing_dir + '/cov/id-delta-cov' - log_file = cargs.afl_fuzzing_dir + '/cov/afl-cov.log' + id_delta_file = cargs.afl_fuzzing_dir + "/cov/id-delta-cov" + log_file = cargs.afl_fuzzing_dir + "/cov/afl-cov.log" - with open(id_delta_file, 'r') as f: + with open(id_delta_file, "r") as f: for line in f: line = line.strip() - ### id:NNNNNN*_file, cycle, src_file, cov_type, fcn/line\n") - [id_file, cycle_num, src_file, cov_type, val] = line.split(', ') - - if cargs.func_search and cov_type == 'function' and val == cargs.func_search: + # id:NNNNNN*_file, cycle, src_file, cov_type, fcn/line\n") + [id_file, cycle_num, src_file, cov_type, val] = line.split(", ") + + if ( + cargs.func_search + and cov_type == "function" + and val == cargs.func_search + ): if cargs.src_file: if cargs.src_file == src_file: - logr("[+] Function '%s' in file: '%s' executed by: '%s', cycle: %s" \ - % (val, src_file, id_file, cycle_num), - log_file, cargs) + logr( + "[+] Function '%s' in file: '%s' executed by: '%s', cycle: %s" + % (val, src_file, id_file, cycle_num), + log_file, + cargs, + ) search_rv = True else: - logr("[+] Function '%s' executed by: '%s', cycle: %s" \ - % (val, id_file, cycle_num), - log_file, cargs) + logr( + "[+] Function '%s' executed by: '%s', cycle: %s" + % (val, id_file, cycle_num), + log_file, + cargs, + ) search_rv = True - if cargs.src_file == src_file \ - and cargs.line_search and val == cargs.line_search: + if ( + cargs.src_file == src_file + and cargs.line_search + and val == cargs.line_search + ): if cargs.src_file == src_file: - logr("[+] Line '%s' in file: '%s' executed by: '%s', cycle: %s" \ - % (val, src_file, id_file, cycle_num), - log_file, cargs) + logr( + "[+] Line '%s' in file: '%s' executed by: '%s', cycle: %s" + % (val, src_file, id_file, cycle_num), + log_file, + cargs, + ) search_rv = True if not search_rv: if cargs.func_search: - logr("[-] Function '%s' not found..." % cargs.func_search, - log_file, cargs) + logr("[-] Function '%s' not found..." % cargs.func_search, log_file, cargs) elif cargs.line_search: - logr("[-] Line %s not found..." % cargs.line_search, - log_file, cargs) + logr("[-] Line %s not found..." % cargs.line_search, log_file, cargs) return search_rv + def get_cycle_num(id_num, cargs): - ### default cycle + # default cycle cycle_num = 0 - if not is_dir(cargs.afl_fuzzing_dir + '/plot_data'): + if not is_dir(cargs.afl_fuzzing_dir + "/plot_data"): return cycle_num - with open(cargs.afl_fuzzing_dir + '/plot_data') as f: + with open(cargs.afl_fuzzing_dir + "/plot_data") as f: for line in f: - ### unix_time, cycles_done, cur_path, paths_total, pending_total,... - ### 1427742641, 11, 54, 419, 45, 0, 2.70%, 0, 0, 9, 1645.47 - vals = line.split(', ') - ### test the id number against the current path + # unix_time, cycles_done, cur_path, paths_total, pending_total,... + # 1427742641, 11, 54, 419, 45, 0, 2.70%, 0, 0, 9, 1645.47 + vals = line.split(", ") + # test the id number against the current path if vals[2] == str(id_num): cycle_num = int(vals[1]) break return cycle_num + def lcov_gen_coverage(cov_paths, cargs): out_lines = [] - lcov_opts = '' + lcov_opts = "" if cargs.enable_branch_coverage: - lcov_opts += ' --rc lcov_branch_coverage=1' + lcov_opts += " --rc lcov_branch_coverage=1" if cargs.follow: - lcov_opts += ' --follow' + lcov_opts += " --follow" if cargs.clang: - lcov_opts += ' --gcov-tool afl-clang-cov.sh' - - run_cmd(cargs.lcov_path \ + lcov_opts += " --gcov-tool afl-clang-cov.sh" + + run_cmd( + cargs.lcov_path + + lcov_opts + + " --no-checksum --capture --directory " + + cargs.code_dir + + " --output-file " + + cov_paths["lcov_info"], + cov_paths["log_file"], + cargs, + LOG_ERRORS, + False, + "", + ) + + if cargs.disable_lcov_exclude_pattern: + out_lines = run_cmd( + cargs.lcov_path + lcov_opts - + " --no-checksum --capture --directory " \ - + cargs.code_dir + " --output-file " \ - + cov_paths['lcov_info'], \ - cov_paths['log_file'], cargs, LOG_ERRORS, False, "") - - if (cargs.disable_lcov_exclude_pattern): - out_lines = run_cmd(cargs.lcov_path \ - + lcov_opts - + " --no-checksum -a " + cov_paths['lcov_base'] \ - + " -a " + cov_paths['lcov_info'] \ - + " --output-file " + cov_paths['lcov_info_final'], \ - cov_paths['log_file'], cargs, WANT_OUTPUT, False, "")[1] + + " --no-checksum -a " + + cov_paths["lcov_base"] + + " -a " + + cov_paths["lcov_info"] + + " --output-file " + + cov_paths["lcov_info_final"], + cov_paths["log_file"], + cargs, + WANT_OUTPUT, + False, + "", + )[1] else: tmp_file = NamedTemporaryFile(delete=False) - run_cmd(cargs.lcov_path \ - + lcov_opts - + " --no-checksum -a " + cov_paths['lcov_base'] \ - + " -a " + cov_paths['lcov_info'] \ - + " --output-file " + tmp_file.name, \ - cov_paths['log_file'], cargs, LOG_ERRORS, False, "") - out_lines = run_cmd(cargs.lcov_path \ - + lcov_opts - + " --no-checksum -r " + tmp_file.name \ - + " " + cargs.lcov_exclude_pattern + " --output-file " \ - + cov_paths['lcov_info_final'], - cov_paths['log_file'], cargs, WANT_OUTPUT, False, "")[1] + run_cmd( + cargs.lcov_path + + lcov_opts + + " --no-checksum -a " + + cov_paths["lcov_base"] + + " -a " + + cov_paths["lcov_info"] + + " --output-file " + + tmp_file.name, + cov_paths["log_file"], + cargs, + LOG_ERRORS, + False, + "", + ) + out_lines = run_cmd( + cargs.lcov_path + + lcov_opts + + " --no-checksum -r " + + tmp_file.name + + " " + + cargs.lcov_exclude_pattern + + " --output-file " + + cov_paths["lcov_info_final"], + cov_paths["log_file"], + cargs, + WANT_OUTPUT, + False, + "", + )[1] if os.path.exists(tmp_file.name): os.unlink(tmp_file.name) - log_coverage(out_lines, cov_paths['log_file'], cargs) + log_coverage(out_lines, cov_paths["log_file"], cargs) return + def log_coverage(out_lines, log_file, cargs): for line in out_lines: - m = re.search('^\s+(lines\.\..*\:\s.*)', line.decode("ascii")) + m = re.search("^\s+(lines\.\..*\:\s.*)", line.decode("ascii")) if m and m.group(1): logr(" " + m.group(1), log_file, cargs) else: - m = re.search('^\s+(functions\.\..*\:\s.*)', line.decode("ascii")) + m = re.search("^\s+(functions\.\..*\:\s.*)", line.decode("ascii")) if m and m.group(1): logr(" " + m.group(1), log_file, cargs) else: if cargs.enable_branch_coverage: - m = re.search('^\s+(branches\.\..*\:\s.*)', line.decode("ascii")) + m = re.search("^\s+(branches\.\..*\:\s.*)", line.decode("ascii")) if m and m.group(1): - logr(" " + m.group(1), - log_file, cargs) + logr(" " + m.group(1), log_file, cargs) return + def gen_web_cov_report(fuzz_dir, cov_paths, cargs): - genhtml_opts = '' + genhtml_opts = "" if cargs.enable_branch_coverage: - genhtml_opts += ' --branch-coverage' - - run_cmd(cargs.genhtml_path \ - + genhtml_opts - + " --output-directory " \ - + cov_paths['web_dir'] + " " \ - + cov_paths['lcov_info_final'], \ - cov_paths['log_file'], cargs, LOG_ERRORS, False, "") - - logr("[+] Final lcov web report: %s/%s" % \ - (cov_paths['web_dir'], 'index.html'), cov_paths['log_file'], cargs) + genhtml_opts += " --branch-coverage" + + run_cmd( + cargs.genhtml_path + + genhtml_opts + + " --output-directory " + + cov_paths["web_dir"] + + " " + + cov_paths["lcov_info_final"], + cov_paths["log_file"], + cargs, + LOG_ERRORS, + False, + "", + ) + + logr( + "[+] Final lcov web report: %s/%s" % (cov_paths["web_dir"], "index.html"), + cov_paths["log_file"], + cargs, + ) return + def is_afl_fuzz_running(cargs): pid = None - stats_file = cargs.afl_fuzzing_dir + '/fuzzer_stats' + stats_file = cargs.afl_fuzzing_dir + "/fuzzer_stats" if os.path.exists(stats_file): - pid = get_running_pid(stats_file, 'fuzzer_pid\s+\:\s+(\d+)') + pid = get_running_pid(stats_file, "fuzzer_pid\s+\:\s+(\d+)") else: for p in os.listdir(cargs.afl_fuzzing_dir): stats_file = "%s/%s/fuzzer_stats" % (cargs.afl_fuzzing_dir, p) if os.path.exists(stats_file): - ### allow a single running AFL instance in parallel mode - ### to mean that AFL is running (and may be generating - ### new code coverage) - pid = get_running_pid(stats_file, 'fuzzer_pid\s+\:\s+(\d+)') + # allow a single running AFL instance in parallel mode + # to mean that AFL is running (and may be generating + # new code coverage) + pid = get_running_pid(stats_file, "fuzzer_pid\s+\:\s+(\d+)") if pid: break return pid + def get_running_pid(stats_file, pid_re): pid = None if not os.path.exists(stats_file): return pid - with open(stats_file, 'r') as f: + with open(stats_file, "r") as f: for line in f: line = line.strip() - ### fuzzer_pid : 13238 + # fuzzer_pid : 13238 m = re.search(pid_re, line) if m and m.group(1): is_running = int(m.group(1)) @@ -694,19 +821,19 @@ def get_running_pid(stats_file, pid_re): break return pid + def run_cmd(cmd, log_file, cargs, collect, aflrun, fn, timeout=None): out = [] fh = None - if cargs.disable_cmd_redirection or collect == WANT_OUTPUT \ - or collect == LOG_ERRORS: + if cargs.disable_cmd_redirection or collect == WANT_OUTPUT or collect == LOG_ERRORS: fh = NamedTemporaryFile(delete=False) else: - fh = open(os.devnull, 'w') + fh = open(os.devnull, "w") if aflrun == True and len(fn) > 0: - cmd = 'cat ' + fn + ' | ' + cmd + cmd = "cat " + fn + " | " + cmd if cargs.verbose: if log_file: @@ -715,24 +842,25 @@ def run_cmd(cmd, log_file, cargs, collect, aflrun, fn, timeout=None): print(" CMD: %s" % cmd) if timeout: - cmd = 'timeout -s KILL %s %s' % (timeout, cmd) + cmd = "timeout -s KILL %s %s" % (timeout, cmd) - es = subprocess.call(cmd, stdin=None, - stdout=fh, stderr=subprocess.STDOUT, shell=True) + es = subprocess.call( + cmd, stdin=None, stdout=fh, stderr=subprocess.STDOUT, shell=True + ) fh.close() - if cargs.disable_cmd_redirection or collect == WANT_OUTPUT \ - or collect == LOG_ERRORS: - with open(fh.name, 'rb') as f: + if cargs.disable_cmd_redirection or collect == WANT_OUTPUT or collect == LOG_ERRORS: + with open(fh.name, "rb") as f: for line in f: - out.append(line.rstrip(b'\n')) + out.append(line.rstrip(b"\n")) os.unlink(fh.name) if (es != 0) and (collect == LOG_ERRORS or collect == WANT_OUTPUT): if log_file: - logr(" Non-zero exit status '%d' for CMD: %s" % (es, cmd), - log_file, cargs) + logr( + " Non-zero exit status '%d' for CMD: %s" % (es, cmd), log_file, cargs + ) for line in out: logr(line.decode("ascii"), log_file, cargs) else: @@ -740,119 +868,147 @@ def run_cmd(cmd, log_file, cargs, collect, aflrun, fn, timeout=None): return es, out + def import_fuzzing_dirs(cov_paths, cargs): if not cargs.afl_fuzzing_dir: print("[*] Must specify AFL fuzzing dir with --afl-fuzzing-dir or -d") return False - if 'top_dir' not in cov_paths: + if "top_dir" not in cov_paths: if not init_tracking(cov_paths, cargs): return False def_dir = cargs.afl_fuzzing_dir if is_dir("%s/queue" % def_dir): - if def_dir not in cov_paths['dirs']: + if def_dir not in cov_paths["dirs"]: add_dir(def_dir, cov_paths) else: for p in os.listdir(def_dir): fuzz_dir = "%s/%s" % (def_dir, p) if is_dir(fuzz_dir): if is_dir("%s/queue" % fuzz_dir): - ### found an AFL fuzzing directory instance from - ### parallel AFL execution - if fuzz_dir not in cov_paths['dirs']: + # found an AFL fuzzing directory instance from + # parallel AFL execution + if fuzz_dir not in cov_paths["dirs"]: add_dir(fuzz_dir, cov_paths) return True + def import_test_cases(qdir): return sorted(glob.glob(qdir + "/id:*")) + def init_tracking(cov_paths, cargs): - cov_paths['dirs'] = {} + cov_paths["dirs"] = {} - cov_paths['top_dir'] = "%s/cov" % cargs.afl_fuzzing_dir - cov_paths['web_dir'] = "%s/web" % cov_paths['top_dir'] - cov_paths['lcov_dir'] = "%s/lcov" % cov_paths['top_dir'] - cov_paths['diff_dir'] = "%s/diff" % cov_paths['top_dir'] - cov_paths['log_file'] = "%s/afl-cov.log" % cov_paths['top_dir'] + cov_paths["top_dir"] = "%s/cov" % cargs.afl_fuzzing_dir + cov_paths["web_dir"] = "%s/web" % cov_paths["top_dir"] + cov_paths["lcov_dir"] = "%s/lcov" % cov_paths["top_dir"] + cov_paths["diff_dir"] = "%s/diff" % cov_paths["top_dir"] + cov_paths["log_file"] = "%s/afl-cov.log" % cov_paths["top_dir"] - ### global coverage results - cov_paths['id_delta_cov'] = "%s/id-delta-cov" % cov_paths['top_dir'] - cov_paths['zero_cov'] = "%s/zero-cov" % cov_paths['top_dir'] - cov_paths['pos_cov'] = "%s/pos-cov" % cov_paths['top_dir'] - cov_paths['diff'] = '' - cov_paths['id_file'] = '' - cov_paths['id_min'] = -1 ### used in --cover-corpus mode - cov_paths['id_max'] = -1 ### used in --cover-corpus mode + # global coverage results + cov_paths["id_delta_cov"] = "%s/id-delta-cov" % cov_paths["top_dir"] + cov_paths["zero_cov"] = "%s/zero-cov" % cov_paths["top_dir"] + cov_paths["pos_cov"] = "%s/pos-cov" % cov_paths["top_dir"] + cov_paths["diff"] = "" + cov_paths["id_file"] = "" + cov_paths["id_min"] = -1 # used in --cover-corpus mode + cov_paths["id_max"] = -1 # used in --cover-corpus mode - ### raw lcov files - cov_paths['lcov_base'] = "%s/trace.lcov_base" % cov_paths['lcov_dir'] - cov_paths['lcov_info'] = "%s/trace.lcov_info" % cov_paths['lcov_dir'] - cov_paths['lcov_info_final'] = "%s/trace.lcov_info_final" % cov_paths['lcov_dir'] + # raw lcov files + cov_paths["lcov_base"] = "%s/trace.lcov_base" % cov_paths["lcov_dir"] + cov_paths["lcov_info"] = "%s/trace.lcov_info" % cov_paths["lcov_dir"] + cov_paths["lcov_info_final"] = "%s/trace.lcov_info_final" % cov_paths["lcov_dir"] if cargs.overwrite: mkdirs(cov_paths, cargs) else: - if is_dir(cov_paths['top_dir']): + if is_dir(cov_paths["top_dir"]): if not cargs.func_search and not cargs.line_search: - print("[*] Existing coverage dir %s found, use --overwrite to " \ - "re-calculate coverage" % (cov_paths['top_dir'])) + print( + "[*] Existing coverage dir %s found, use --overwrite to " + "re-calculate coverage" % (cov_paths["top_dir"]) + ) return False else: mkdirs(cov_paths, cargs) - write_status("%s/afl-cov-status" % cov_paths['top_dir']) + write_status("%s/afl-cov-status" % cov_paths["top_dir"]) if not cargs.disable_coverage_init and cargs.coverage_cmd: - lcov_opts = '' + lcov_opts = "" if cargs.enable_branch_coverage: - lcov_opts += ' --rc lcov_branch_coverage=1 ' + lcov_opts += " --rc lcov_branch_coverage=1 " if cargs.clang: - lcov_opts += ' --gcov-tool afl-clang-cov.sh' - - ### reset code coverage counters - this is done only once as - ### afl-cov is spinning up even if AFL is running in parallel mode - run_cmd(cargs.lcov_path \ - + lcov_opts \ - + " --no-checksum --zerocounters --directory " \ - + cargs.code_dir, cov_paths['log_file'], cargs, LOG_ERRORS, False, "") - - run_cmd(cargs.lcov_path \ - + lcov_opts - + " --no-checksum --capture --initial" \ - + " --directory " + cargs.code_dir \ - + " --output-file " \ - + cov_paths['lcov_base'], \ - cov_paths['log_file'], cargs, LOG_ERRORS, False, "") + lcov_opts += " --gcov-tool afl-clang-cov.sh" + + # reset code coverage counters - this is done only once as + # afl-cov is spinning up even if AFL is running in parallel mode + run_cmd( + cargs.lcov_path + + lcov_opts + + " --no-checksum --zerocounters --directory " + + cargs.code_dir, + cov_paths["log_file"], + cargs, + LOG_ERRORS, + False, + "", + ) + + run_cmd( + cargs.lcov_path + + lcov_opts + + " --no-checksum --capture --initial" + + " --directory " + + cargs.code_dir + + " --output-file " + + cov_paths["lcov_base"], + cov_paths["log_file"], + cargs, + LOG_ERRORS, + False, + "", + ) return True -### credit: -### http://stackoverflow.com/questions/377017/test-if-executable-exists-in-python + +# credit: +# http://stackoverflow.com/questions/377017/test-if-executable-exists-in-python def is_exe(fpath): return os.path.isfile(fpath) and os.access(fpath, os.X_OK) + def is_bin_gcov_enabled(binary, cargs): rv = False - ### run readelf against the binary to see if it contains gcov support - for line in run_cmd("%s -a %s" % (cargs.readelf_path, binary), - False, cargs, WANT_OUTPUT, False, "")[1]: - if b' __gcov' in line: + # run readelf against the binary to see if it contains gcov support + for line in run_cmd( + "%s -a %s" % (cargs.readelf_path, binary), False, cargs, WANT_OUTPUT, False, "" + )[1]: + if b" __gcov" in line: if cargs.validate_args or cargs.gcov_check or cargs.gcov_check_bin: - print("[+] Binary '%s' is compiled with code coverage support via gcc." % binary) + print( + "[+] Binary '%s' is compiled with code coverage support via gcc." + % binary + ) rv = True break - if b'__llvm_gcov' in line: + if b"__llvm_gcov" in line: if cargs.validate_args or cargs.gcov_check or cargs.gcov_check_bin: - print("[+] Binary '%s' is compiled with code coverage support via llvm." % binary) + print( + "[+] Binary '%s' is compiled with code coverage support via llvm." + % binary + ) rv = True break @@ -861,6 +1017,7 @@ def is_bin_gcov_enabled(binary, cargs): return rv + def which(prog): fpath, fname = os.path.split(prog) if fpath: @@ -874,33 +1031,35 @@ def which(prog): return exe_file return None + def check_requirements(cargs): - lcov = which( "lcov" ); - gcov = which( "gcov" ); - genhtml = which( "genhtml" ); + lcov = which("lcov") + gcov = which("gcov") + genhtml = which("genhtml") - if ( lcov == None ): - lcov = which( cargs.lcov_path ) - if ( genhtml == None ): - genhtml = which ( cargs.genhtml_path ) + if lcov == None: + lcov = which(cargs.lcov_path) + if genhtml == None: + genhtml = which(cargs.genhtml_path) - if ( lcov == None or gcov == None): + if lcov == None or gcov == None: print("Required command not found :") else: - if (genhtml == None and not cargs.disable_lcov_web): + if genhtml == None and not cargs.disable_lcov_web: print("Required command not found :") else: return True - if ( lcov == None ): + if lcov == None: print("[*] lcov command does not exist : %s" % (cargs.lcov_path)) - if ( genhtml == None and not cargs.disable_lcov_web): + if genhtml == None and not cargs.disable_lcov_web: print("[*] genhtml command does not exist : %s" % (cargs.genhtml_path)) - if ( gcov == None ): + if gcov == None: print("[*] gcov command does not exist : %s" % (cargs.gcov_path)) return False + def is_gcov_enabled(cargs): if not is_exe(cargs.readelf_path): @@ -908,41 +1067,48 @@ def is_gcov_enabled(cargs): return False if cargs.coverage_cmd: - ### make sure at least one component of the command is an - ### executable and is compiled with code coverage support + # make sure at least one component of the command is an + # executable and is compiled with code coverage support found_exec = False found_code_cov_binary = False - for part in cargs.coverage_cmd.split(' '): - if not part or part[0] == ' ' or part[0] == '-': + for part in cargs.coverage_cmd.split(" "): + if not part or part[0] == " " or part[0] == "-": continue - if (which(part)): + if which(part): found_exec = True if not cargs.disable_gcov_check and is_bin_gcov_enabled(part, cargs): found_code_cov_binary = True break if not found_exec: - print("[*] Could not find an executable binary " \ - "--coverage-cmd '%s'" % cargs.coverage_cmd) + print( + "[*] Could not find an executable binary " + "--coverage-cmd '%s'" % cargs.coverage_cmd + ) return False if not cargs.disable_gcov_check and not found_code_cov_binary: - print("[*] Could not find an executable binary with code " \ - "coverage support ('-fprofile-arcs -ftest-coverage') " \ - "in --coverage-cmd '%s'" % cargs.coverage_cmd) + print( + "[*] Could not find an executable binary with code " + "coverage support ('-fprofile-arcs -ftest-coverage') " + "in --coverage-cmd '%s'" % cargs.coverage_cmd + ) return False elif cargs.gcov_check_bin: if not is_bin_gcov_enabled(cargs.gcov_check_bin, cargs): return False elif cargs.gcov_check: - print("[*] Either --coverage-cmd or --gcov-check-bin required in --gcov-check mode") + print( + "[*] Either --coverage-cmd or --gcov-check-bin required in --gcov-check mode" + ) return False return True + def validate_cargs(cargs): if cargs.coverage_cmd: @@ -958,22 +1124,24 @@ def validate_cargs(cargs): print("[*] --code-dir path does not exist") return False - ### make sure code coverage support is compiled in + # make sure code coverage support is compiled in if not gcno_files_exist(cargs): return False else: if not cargs.func_search and not cargs.line_search: - print("[*] Must set --code-dir unless using --func-search " \ - "against existing afl-cov directory") + print( + "[*] Must set --code-dir unless using --func-search " + "against existing afl-cov directory" + ) return False if cargs.func_search or cargs.line_search: if not cargs.afl_fuzzing_dir: print("[*] Must set --afl-fuzzing-dir") return False - if cargs.func_search and '()' not in cargs.func_search: - cargs.func_search += '()' + if cargs.func_search and "()" not in cargs.func_search: + cargs.func_search += "()" if cargs.line_search and not cargs.src_file: print("[*] Must set --src-file in --line-search mode") return False @@ -983,8 +1151,9 @@ def validate_cargs(cargs): return False if not cargs.live and not is_dir(cargs.afl_fuzzing_dir): - print("[*] It doesn't look like directory '%s' exists" \ - % (cargs.afl_fuzzing_dir)) + print( + "[*] It doesn't look like directory '%s' exists" % (cargs.afl_fuzzing_dir) + ) return False if cargs.disable_lcov_web and cargs.lcov_web_all: @@ -996,57 +1165,65 @@ def validate_cargs(cargs): def gcno_files_exist(cargs): - ### make sure the code has been compiled with code coverage support, - ### so *.gcno files should exist + # make sure the code has been compiled with code coverage support, + # so *.gcno files should exist found_code_coverage_support = False for root, dirs, files in os.walk(cargs.code_dir): for filename in files: - if filename[-5:] == '.gcno': + if filename[-5:] == ".gcno": found_code_coverage_support = True if not found_code_coverage_support: - print("[*] Could not find any *.gcno files in --code-dir " \ - "'%s', is code coverage ('-fprofile-arcs -ftest-coverage') " \ - "compiled in?" % cargs.code_dir) + print( + "[*] Could not find any *.gcno files in --code-dir " + "'%s', is code coverage ('-fprofile-arcs -ftest-coverage') " + "compiled in?" % cargs.code_dir + ) return False return True + def is_afl_running(cargs): while not is_dir(cargs.afl_fuzzing_dir): if not cargs.background: - print("[-] Sleep for %d seconds for AFL fuzzing directory to be created..." \ - % cargs.sleep) + print( + "[-] Sleep for %d seconds for AFL fuzzing directory to be created..." + % cargs.sleep + ) time.sleep(cargs.sleep) - ### if we make it here then afl-fuzz is presumably running + # if we make it here then afl-fuzz is presumably running while not is_afl_fuzz_running(cargs): if not cargs.background: - print("[-] Sleep for %d seconds waiting for afl-fuzz to be started...." \ - % cargs.sleep) + print( + "[-] Sleep for %d seconds waiting for afl-fuzz to be started...." + % cargs.sleep + ) time.sleep(cargs.sleep) return def add_dir(fdir, cov_paths): - cov_paths['dirs'][fdir] = {} + cov_paths["dirs"][fdir] = {} return + def mkdirs(cov_paths, cargs): create_cov_dirs = False - if is_dir(cov_paths['top_dir']): + if is_dir(cov_paths["top_dir"]): if cargs.overwrite: - rmtree(cov_paths['top_dir']) + rmtree(cov_paths["top_dir"]) create_cov_dirs = True else: create_cov_dirs = True if create_cov_dirs: - for k in ['top_dir', 'web_dir', 'lcov_dir', 'diff_dir']: + for k in ["top_dir", "web_dir", "lcov_dir", "diff_dir"]: if not is_dir(cov_paths[k]): os.mkdir(cov_paths[k]) - ### write coverage results in the following format - cfile = open(cov_paths['id_delta_cov'], 'w') + # write coverage results in the following format + cfile = open(cov_paths["id_delta_cov"], "w") if cargs.cover_corpus or cargs.coverage_at_exit: cfile.write("# id:[range]..., cycle, src_file, coverage_type, fcn/line\n") else: @@ -1055,35 +1232,41 @@ def mkdirs(cov_paths, cargs): return + def is_dir(dpath): return os.path.exists(dpath) and os.path.isdir(dpath) + def logr(pstr, log_file, cargs): if not cargs.background and not cargs.quiet: print(" " + pstr) append_file(pstr, log_file) return + def stop_afl(cargs): rv = True - ### note that this function only looks for afl-fuzz processes - it does not - ### stop afl-cov processes since they will stop on their own after afl-fuzz - ### is also stopped. + # note that this function only looks for afl-fuzz processes - it does not + # stop afl-cov processes since they will stop on their own after afl-fuzz + # is also stopped. if not cargs.afl_fuzzing_dir: print("[*] Must set --afl-fuzzing-dir") return False if not is_dir(cargs.afl_fuzzing_dir): - print("[*] Doesn't look like AFL fuzzing directory '%s' exists." \ - % cargs.afl_fuzzing_dir) + print( + "[*] Doesn't look like AFL fuzzing directory '%s' exists." + % cargs.afl_fuzzing_dir + ) return False - if os.path.exists(cargs.afl_fuzzing_dir + '/fuzzer_stats'): - afl_pid = get_running_pid(cargs.afl_fuzzing_dir + '/fuzzer_stats', - 'fuzzer_pid\s+\:\s+(\d+)') + if os.path.exists(cargs.afl_fuzzing_dir + "/fuzzer_stats"): + afl_pid = get_running_pid( + cargs.afl_fuzzing_dir + "/fuzzer_stats", "fuzzer_pid\s+\:\s+(\d+)" + ) if afl_pid: print("[+] Stopping running afl-fuzz instance, PID: %d" % afl_pid) os.kill(afl_pid, signal.SIGTERM) @@ -1093,12 +1276,11 @@ def stop_afl(cargs): else: found = False for p in os.listdir(cargs.afl_fuzzing_dir): - stats_file = cargs.afl_fuzzing_dir + '/' + p + '/fuzzer_stats' + stats_file = cargs.afl_fuzzing_dir + "/" + p + "/fuzzer_stats" if os.path.exists(stats_file): - afl_pid = get_running_pid(stats_file, 'fuzzer_pid\s+\:\s+(\d+)') + afl_pid = get_running_pid(stats_file, "fuzzer_pid\s+\:\s+(\d+)") if afl_pid: - print("[+] Stopping running afl-fuzz instance, PID: %d" \ - % afl_pid) + print("[+] Stopping running afl-fuzz instance, PID: %d" % afl_pid) os.kill(afl_pid, signal.SIGTERM) found = True if not found: @@ -1107,121 +1289,230 @@ def stop_afl(cargs): return rv + def check_core_pattern(): rv = True - core_pattern_file = '/proc/sys/kernel/core_pattern' + core_pattern_file = "/proc/sys/kernel/core_pattern" - ### check /proc/sys/kernel/core_pattern to see if afl-fuzz will - ### accept it + # check /proc/sys/kernel/core_pattern to see if afl-fuzz will + # accept it if os.path.exists(core_pattern_file): - with open(core_pattern_file, 'r') as f: - if f.readline().rstrip()[0] == '|': - ### same logic as implemented by afl-fuzz itself - print("[*] afl-fuzz requires 'echo core >%s'" \ - % core_pattern_file) + with open(core_pattern_file, "r") as f: + if f.readline().rstrip()[0] == "|": + # same logic as implemented by afl-fuzz itself + print("[*] afl-fuzz requires 'echo core >%s'" % core_pattern_file) rv = False return rv + def parse_cmdline(): p = argparse.ArgumentParser() - p.add_argument("-e", "--coverage-cmd", type=str, - help="Set command to exec (including args, and assumes code coverage support)") - p.add_argument("-d", "--afl-fuzzing-dir", type=str, - help="top level AFL fuzzing directory") - p.add_argument("-c", "--code-dir", type=str, - help="Directory where the code lives (compiled with code coverage support)") - p.add_argument("-f", "--follow", action='store_true', - help="Follow links when searching .da files", default=False) - p.add_argument("-O", "--overwrite", action='store_true', - help="Overwrite existing coverage results", default=False) - p.add_argument("--disable-cmd-redirection", action='store_true', - help="Disable redirection of command results to /dev/null", - default=False) - p.add_argument("--disable-lcov-web", action='store_true', - help="Disable generation of all lcov web code coverage reports", - default=False) - p.add_argument("--disable-coverage-init", action='store_true', - help="Disable initialization of code coverage counters at afl-cov startup", - default=False) - p.add_argument("--coverage-include-lines", action='store_true', - help="Include lines in zero-coverage status files", - default=False) - p.add_argument("--enable-branch-coverage", action='store_true', - help="Include branch coverage in code coverage reports (may be slow)", - default=False) - p.add_argument("--live", action='store_true', - help="Process a live AFL directory, and afl-cov will exit when it appears afl-fuzz has been stopped", - default=False) - p.add_argument("--clang", action='store_true', - help="If clang was used for coverage compilation instead of gcc", - default=False) - p.add_argument("--cover-corpus", action='store_true', - help="Measure coverage after running all available tests instead of individually per queue file", - default=False) - p.add_argument("--coverage-at-exit", action='store_true', - help="Only calculate coverage just before afl-cov exit.", - default=False) - p.add_argument("--sleep", type=int, - help="In --live mode, # of seconds to sleep between checking for new queue files", - default=60) - p.add_argument("--gcov-check", action='store_true', - help="Check to see if there is a binary in --coverage-cmd (or in --gcov-check-bin) has coverage support", - default=False) - p.add_argument("--gcov-check-bin", type=str, - help="Test a specific binary for code coverage support", - default=False) - p.add_argument("--disable-gcov-check", type=str, - help="Disable check for code coverage support", - default=False) - p.add_argument("--background", action='store_true', - help="Background mode - if also in --live mode, will exit when the alf-fuzz process is finished", - default=False) - p.add_argument("--lcov-web-all", action='store_true', - help="Generate lcov web reports for all id:NNNNNN* files instead of just the last one", - default=False) - p.add_argument("--disable-lcov-exclude-pattern", action='store_true', - help="Allow default /usr/include/* pattern to be included in lcov results", - default=False) - p.add_argument("--lcov-exclude-pattern", type=str, - help="Set exclude pattern for lcov results", - default="/usr/include/\*") - p.add_argument("--func-search", type=str, - help="Search for coverage of a specific function") - p.add_argument("--line-search", type=str, - help="Search for coverage of a specific line number (requires --src-file)") - p.add_argument("--src-file", type=str, - help="Restrict function or line search to a specific source file") - p.add_argument("--afl-queue-id-limit", type=int, - help="Limit the number of id:NNNNNN* files processed in the AFL queue/ directory", - default=0) - p.add_argument("--ignore-core-pattern", action='store_true', - help="Ignore the /proc/sys/kernel/core_pattern setting in --live mode", - default=False) - p.add_argument("--lcov-path", type=str, - help="Path to lcov command", default="/usr/bin/lcov") - p.add_argument("--genhtml-path", type=str, - help="Path to genhtml command", default="/usr/bin/genhtml") - p.add_argument("--readelf-path", type=str, - help="Path to readelf command", default="/usr/bin/readelf") - p.add_argument("--stop-afl", action='store_true', - help="Stop all running afl-fuzz instances associated with --afl-fuzzing-dir ", - default=False) - p.add_argument("--validate-args", action='store_true', - help="Validate args and exit", default=False) - p.add_argument("-v", "--verbose", action='store_true', - help="Verbose mode", default=False) - p.add_argument("-V", "--version", action='store_true', - help="Print version and exit", default=False) - p.add_argument("-q", "--quiet", action='store_true', - help="Quiet mode", default=False) - p.add_argument("-T", "--timeout", type=str, - help="timeout (default 5 seconds)", default='5') + p.add_argument( + "-e", + "--coverage-cmd", + type=str, + help="Set command to exec (including args, and assumes code coverage support)", + ) + p.add_argument( + "-d", "--afl-fuzzing-dir", type=str, help="top level AFL fuzzing directory" + ) + p.add_argument( + "-c", + "--code-dir", + type=str, + help="Directory where the code lives (compiled with code coverage support)", + ) + p.add_argument( + "-f", + "--follow", + action="store_true", + help="Follow links when searching .da files", + default=False, + ) + p.add_argument( + "-O", + "--overwrite", + action="store_true", + help="Overwrite existing coverage results", + default=False, + ) + p.add_argument( + "--disable-cmd-redirection", + action="store_true", + help="Disable redirection of command results to /dev/null", + default=False, + ) + p.add_argument( + "--disable-lcov-web", + action="store_true", + help="Disable generation of all lcov web code coverage reports", + default=False, + ) + p.add_argument( + "--disable-coverage-init", + action="store_true", + help="Disable initialization of code coverage counters at afl-cov startup", + default=False, + ) + p.add_argument( + "--coverage-include-lines", + action="store_true", + help="Include lines in zero-coverage status files", + default=False, + ) + p.add_argument( + "--enable-branch-coverage", + action="store_true", + help="Include branch coverage in code coverage reports (may be slow)", + default=False, + ) + p.add_argument( + "--live", + action="store_true", + help="Process a live AFL directory, and afl-cov will exit when it appears afl-fuzz has been stopped", + default=False, + ) + p.add_argument( + "--clang", + action="store_true", + help="If clang was used for coverage compilation instead of gcc", + default=False, + ) + p.add_argument( + "--cover-corpus", + action="store_true", + help="Measure coverage after running all available tests instead of individually per queue file", + default=False, + ) + p.add_argument( + "--coverage-at-exit", + action="store_true", + help="Only calculate coverage just before afl-cov exit.", + default=False, + ) + p.add_argument( + "--sleep", + type=int, + help="In --live mode, # of seconds to sleep between checking for new queue files", + default=60, + ) + p.add_argument( + "--gcov-check", + action="store_true", + help="Check to see if there is a binary in --coverage-cmd (or in --gcov-check-bin) has coverage support", + default=False, + ) + p.add_argument( + "--gcov-check-bin", + type=str, + help="Test a specific binary for code coverage support", + default=False, + ) + p.add_argument( + "--disable-gcov-check", + type=str, + help="Disable check for code coverage support", + default=False, + ) + p.add_argument( + "--background", + action="store_true", + help="Background mode - if also in --live mode, will exit when the alf-fuzz process is finished", + default=False, + ) + p.add_argument( + "--lcov-web-all", + action="store_true", + help="Generate lcov web reports for all id:NNNNNN* files instead of just the last one", + default=False, + ) + p.add_argument( + "--disable-lcov-exclude-pattern", + action="store_true", + help="Allow default /usr/include/* pattern to be included in lcov results", + default=False, + ) + p.add_argument( + "--lcov-exclude-pattern", + type=str, + help="Set exclude pattern for lcov results", + default="/usr/include/\*", + ) + p.add_argument( + "--func-search", type=str, help="Search for coverage of a specific function" + ) + p.add_argument( + "--line-search", + type=str, + help="Search for coverage of a specific line number (requires --src-file)", + ) + p.add_argument( + "--src-file", + type=str, + help="Restrict function or line search to a specific source file", + ) + p.add_argument( + "--afl-queue-id-limit", + type=int, + help="Limit the number of id:NNNNNN* files processed in the AFL queue/ directory", + default=0, + ) + p.add_argument( + "--ignore-core-pattern", + action="store_true", + help="Ignore the /proc/sys/kernel/core_pattern setting in --live mode", + default=False, + ) + p.add_argument( + "--lcov-path", type=str, help="Path to lcov command", default="/usr/bin/lcov" + ) + p.add_argument( + "--genhtml-path", + type=str, + help="Path to genhtml command", + default="/usr/bin/genhtml", + ) + p.add_argument( + "--readelf-path", + type=str, + help="Path to readelf command", + default="/usr/bin/readelf", + ) + p.add_argument( + "--stop-afl", + action="store_true", + help="Stop all running afl-fuzz instances associated with --afl-fuzzing-dir ", + default=False, + ) + p.add_argument( + "--validate-args", + action="store_true", + help="Validate args and exit", + default=False, + ) + p.add_argument( + "-v", "--verbose", action="store_true", help="Verbose mode", default=False + ) + p.add_argument( + "-V", + "--version", + action="store_true", + help="Print version and exit", + default=False, + ) + p.add_argument( + "-q", "--quiet", action="store_true", help="Quiet mode", default=False + ) + p.add_argument( + "-T", "--timeout", type=str, help="timeout (default 5 seconds)", default="5" + ) return p.parse_args() + if __name__ == "__main__": sys.exit(main()) From ff9013956d8a08cc982e200acd90dec88bf5e560 Mon Sep 17 00:00:00 2001 From: Dominik Maier Date: Wed, 3 Mar 2021 19:37:46 +0100 Subject: [PATCH 25/39] py3 fixes --- afl-cov | 547 +++++++++++++++++++++++++++-------------------------- afl-cov.sh | 2 +- 2 files changed, 283 insertions(+), 266 deletions(-) diff --git a/afl-cov b/afl-cov index 02bc7e1..a6d4153 100755 --- a/afl-cov +++ b/afl-cov @@ -2,7 +2,7 @@ # # File: afl-cov # -# Version: 0.6.6 +# Version: 0.7.0 # # Purpose: Perform lcov coverage diff's against each AFL queue file to see # new functions and line coverage evolve from an AFL fuzzing cycle. @@ -27,33 +27,36 @@ # Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02111-1301, # USA # - -from shutil import rmtree -from sys import argv -from tempfile import NamedTemporaryFile +import argparse import errno -import re import glob -import string -import argparse -import time +import os +import re import signal -import sys, os +import sys +import time +from shutil import rmtree +from sys import argv +from tempfile import NamedTemporaryFile +from typing import Any, Dict, Iterator, List, Union, Optional, Tuple try: import subprocess32 as subprocess except ImportError: import subprocess -__version__ = "0.6.6" +__version__ = "0.7.0" NO_OUTPUT = 0 WANT_OUTPUT = 1 LOG_ERRORS = 2 +# id_min id_max are int, dirs is Dict, the rest is str or dict. +CovPathsDictType = Dict[str, Union[Dict[str, str], str, int]] +CovDictType = Dict[bytes, Dict[bytes, Dict[bytes, Dict[bytes, bytes]]]] -def main(): +def main(): exit_success = 0 exit_failure = 1 @@ -96,39 +99,40 @@ def main(): return not process_afl_test_cases(cargs) -def run_in_background(): +# noinspection PyUnresolvedReferences +def run_in_background() -> None: # could use the python 'daemon' module, but it isn't always # installed, and we just need a basic backgrounding # capability anyway pid = os.fork() if pid < 0: print("[*] fork() error, exiting.") - os._exit() + os._exit(1) elif pid > 0: os._exit(0) else: os.setsid() - return -def process_afl_test_cases(cargs): - +def process_afl_test_cases(cargs: argparse.Namespace) -> bool: rv = True run_once = False tot_files = 0 - fuzz_dir = "" - curr_file = "" + curr_cycle = -1 - afl_files = [] - cov_paths = {} + afl_files = [] # type: List[str] + cov_paths = {} # type: CovPathsDictType # main coverage tracking dictionary - cov = {} - cov["zero"] = {} - cov["pos"] = {} + cov = { + b"zero": {}, + b"pos": {}, + } # type: CovDictType while True: + new_files = [] + if not import_fuzzing_dirs(cov_paths, cargs): rv = False break @@ -145,8 +149,7 @@ def process_afl_test_cases(cargs): do_break = False last_file = False num_files = 0 - new_files = [] - tmp_files = import_test_cases(fuzz_dir + "/queue") + tmp_files = import_test_cases(fuzz_dir + "/queue") # type: Iterator[str] dir_ctr += 1 f_ctr = 0 @@ -160,8 +163,8 @@ def process_afl_test_cases(cargs): if new_files: logr( - "\n*** Imported %d new test cases from: %s\n" - % (len(new_files), (fuzz_dir + "/queue")), + b"\n*** Imported %d new test cases from: %s\n" + % (len(new_files), (fuzz_dir + "/queue").encode()), cov_paths["log_file"], cargs, ) @@ -177,12 +180,17 @@ def process_afl_test_cases(cargs): # test cases have been processed do_coverage = True - out_lines = [] + out_lines = [] # type: List[bytes] curr_cycle = get_cycle_num(num_files, cargs) logr( - "[+] AFL test case: %s (%d / %d), cycle: %d" - % (os.path.basename(f), num_files, len(afl_files), curr_cycle), + b"[+] AFL test case: %s (%d / %d), cycle: %d" + % ( + os.path.basename(f).encode(), + num_files, + len(afl_files), + curr_cycle, + ), cov_paths["log_file"], cargs, ) @@ -222,7 +230,7 @@ def process_afl_test_cases(cargs): and num_files >= cargs.afl_queue_id_limit - 1 ): logr( - "[+] queue/ id limit of %d reached..." + b"[+] queue/ id limit of %d reached..." % cargs.afl_queue_id_limit, cov_paths["log_file"], cargs, @@ -238,31 +246,27 @@ def process_afl_test_cases(cargs): # diff to the previous code coverage, look for new # lines/functions, and write out results - coverage_diff(curr_cycle, fuzz_dir, cov_paths, f, cov, cargs) + coverage_diff(curr_cycle, cov_paths, f, cov, cargs) if cargs.cover_corpus: # reset the range values cov_paths["id_min"] = cov_paths["id_max"] = -1 if cargs.lcov_web_all: - gen_web_cov_report(fuzz_dir, cov_paths, cargs) + gen_web_cov_report(cov_paths, cargs) # log the output of the very first coverage command to # assist in troubleshooting - #if len(out_lines): - # logr( - # "\n\n++++++ BEGIN - first exec output for CMD: %s" - # % (cargs.coverage_cmd.replace("AFL_FILE", f)), - # cov_paths["log_file"], - # cargs, - # ) - # for line in out_lines: - # logr( - # " %s" % (line.decode("ascii")), - # cov_paths["log_file"], - # cargs, - # ) - # logr("++++++ END\n", cov_paths["log_file"], cargs) + if len(out_lines): + logr( + b"\n\n++++++ BEGIN - first exec output for CMD: %s" + % (cargs.coverage_cmd.replace("AFL_FILE", f).encode()), + cov_paths["log_file"], + cargs, + ) + for line in out_lines: + logr(b" %s" % line, cov_paths["log_file"], cargs) + logr(b"++++++ END\n", cov_paths["log_file"], cargs) cov_paths["id_file"] = "%s" % os.path.basename(f) @@ -276,7 +280,7 @@ def process_afl_test_cases(cargs): if is_afl_fuzz_running(cargs): if not len(new_files): logr( - "[-] No new AFL test cases, sleeping for %d seconds" + b"[-] No new AFL test cases, sleeping for %d seconds" % cargs.sleep, cov_paths["log_file"], cargs, @@ -285,7 +289,7 @@ def process_afl_test_cases(cargs): continue else: logr( - "[+] afl-fuzz appears to be stopped...", + b"[+] afl-fuzz appears to be stopped...", cov_paths["log_file"], cargs, ) @@ -296,7 +300,7 @@ def process_afl_test_cases(cargs): if tot_files > 0: logr( - "[+] Processed %d / %d test cases.\n" % (tot_files, len(afl_files)), + b"[+] Processed %d / %d test cases.\n" % (tot_files, len(afl_files)), cov_paths["log_file"], cargs, ) @@ -307,22 +311,20 @@ def process_afl_test_cases(cargs): # diff to the previous code coverage, look for new # lines/functions, and write out results - coverage_diff( - curr_cycle, fuzz_dir, cov_paths, cov_paths["id_file"], cov, cargs - ) + coverage_diff(curr_cycle, cov_paths, cov_paths["id_file"], cov, cargs) # write out the final zero coverage and positive coverage reports - write_zero_cov(cov["zero"], cov_paths, cargs) - write_pos_cov(cov["pos"], cov_paths, cargs) + write_zero_cov(cov[b"zero"], cov_paths, cargs) + write_pos_cov(cov[b"pos"], cov_paths, cargs) if not cargs.disable_lcov_web: lcov_gen_coverage(cov_paths, cargs) - gen_web_cov_report(fuzz_dir, cov_paths, cargs) + gen_web_cov_report(cov_paths, cargs) else: if rv: logr( - "[*] Did not find any AFL test cases, exiting.\n", + b"[*] Did not find any AFL test cases, exiting.\n", cov_paths["log_file"], cargs, ) @@ -331,8 +333,7 @@ def process_afl_test_cases(cargs): return rv -def id_range_update(afl_file, cov_paths): - +def id_range_update(afl_file: str, cov_paths: CovPathsDictType) -> None: id_val = int(os.path.basename(afl_file).split(",")[0].split(":")[1]) if cov_paths["id_min"] == -1: @@ -345,29 +346,32 @@ def id_range_update(afl_file, cov_paths): elif id_val > cov_paths["id_max"]: cov_paths["id_max"] = id_val - return - - -def coverage_diff(cycle_num, fuzz_dir, cov_paths, afl_file, cov, cargs): - log_lines = [] - delta_log_lines = [] +def coverage_diff( + cycle_num: int, + cov_paths: CovPathsDictType, + afl_file: str, + cov: CovDictType, + cargs: argparse.Namespace, +) -> None: + log_lines = [] # type: List[bytes] + delta_log_lines = [] # type: List[bytes] print_diff_header = True # defaults - a_file = "(init)" + a_file = b"(init)" # type: bytes if cov_paths["id_file"]: - a_file = cov_paths["id_file"] - delta_file = b_file = os.path.basename(afl_file) + a_file = cov_paths["id_file"].encode() + delta_file = b_file = os.path.basename(afl_file).encode() # type: bytes if cargs.cover_corpus or cargs.coverage_at_exit: - a_file = "id:%d..." % cov_paths["id_min"] - b_file = "id:%d..." % cov_paths["id_max"] - delta_file = "id:[%d-%d]..." % (cov_paths["id_min"], cov_paths["id_max"]) + a_file = b"id:%d..." % cov_paths["id_min"] + b_file = b"id:%d..." % cov_paths["id_max"] + delta_file = b"id:[%d-%d]..." % (cov_paths["id_min"], cov_paths["id_max"]) new_cov = extract_coverage( cov_paths["lcov_info_final"], cov_paths["log_file"], cargs - ) + ) # type: CovDictType if not new_cov: return @@ -377,157 +381,160 @@ def coverage_diff(cycle_num, fuzz_dir, cov_paths, afl_file, cov, cargs): # gcov stats aren't influenced by AFL directly) - what we want is # simply whether a new line or function has been executed at all by # this test case. So, we look for new positive coverage. - for f in new_cov["pos"]: + for f in new_cov[b"pos"]: print_filename = True - if f not in cov["zero"] and f not in cov["pos"]: # completely new file + if f not in cov[b"zero"] and f not in cov[b"pos"]: # completely new file cov_init(f, cov) if print_diff_header: - log_lines.append("diff %s -> %s" % (a_file, b_file)) + log_lines.append(b"diff %s -> %s" % (a_file, b_file)) print_diff_header = False - for ctype in new_cov["pos"][f]: - for val in sorted(new_cov["pos"][f][ctype]): - cov["pos"][f][ctype][val] = "" + for ctype in new_cov[b"pos"][f]: + for val in sorted(new_cov[b"pos"][f][ctype]): + cov[b"pos"][f][ctype][val] = b"" if print_filename: - log_lines.append("New src file: " + f) + log_lines.append(b"New src file: " + f) print_filename = False - log_lines.append(" New '" + ctype + "' coverage: " + val) - if ctype == "line": + log_lines.append(b" New '" + ctype + b"' coverage: " + val) + if ctype == b"line": if cargs.coverage_include_lines: delta_log_lines.append( - "%s, %s, %s, %s, %s\n" + b"%s, %d, %s, %s, %s\n" % (delta_file, cycle_num, f, ctype, val) ) else: delta_log_lines.append( - "%s, %s, %s, %s, %s\n" + b"%s, %d, %s, %s, %s\n" % (delta_file, cycle_num, f, ctype, val) ) - elif f in cov["zero"] and f in cov["pos"]: - for ctype in new_cov["pos"][f]: - for val in sorted(new_cov["pos"][f][ctype]): - if val not in cov["pos"][f][ctype]: - cov["pos"][f][ctype][val] = "" + elif f in cov[b"zero"] and f in cov[b"pos"]: + for ctype in new_cov[b"pos"][f]: + for val in sorted(new_cov[b"pos"][f][ctype]): + if val not in cov[b"pos"][f][ctype]: + cov[b"pos"][f][ctype][val] = b"" if print_diff_header: - log_lines.append("diff %s -> %s" % (a_file, b_file)) + log_lines.append(b"diff %s -> %s" % (a_file, b_file)) print_diff_header = False if print_filename: - log_lines.append("Src file: " + f) + log_lines.append(b"Src file: " + f) print_filename = False - log_lines.append(" New '" + ctype + "' coverage: " + val) - if ctype == "line": + log_lines.append(b" New '" + ctype + b"' coverage: " + val) + if ctype == b"line": if cargs.coverage_include_lines: delta_log_lines.append( - "%s, %s, %s, %s, %s\n" + b"%s, %d, %s, %s, %s\n" % (delta_file, cycle_num, f, ctype, val) ) else: delta_log_lines.append( - "%s, %s, %s, %s, %s\n" + b"%s, %d, %s, %s, %s\n" % (delta_file, cycle_num, f, ctype, val) ) # now that new positive coverage has been added, reset zero # coverage to the current new zero coverage - cov["zero"] = {} - cov["zero"] = new_cov["zero"].copy() + cov[b"zero"] = {} + cov[b"zero"] = new_cov[b"zero"].copy() if len(log_lines): logr( - "\n Coverage diff %s %s" % (a_file, b_file), cov_paths["log_file"], cargs + b"\n Coverage diff %s %s" % (a_file, b_file), + cov_paths["log_file"], + cargs, ) - for l in log_lines: - logr(l, cov_paths["log_file"], cargs) - append_file(l, cov_paths["diff"]) - logr("", cov_paths["log_file"], cargs) + for line in log_lines: + logr(line, cov_paths["log_file"], cargs) + append_file(line, cov_paths["diff"]) + logr(b"", cov_paths["log_file"], cargs) if len(delta_log_lines): - cfile = open(cov_paths["id_delta_cov"], "a") - for l in delta_log_lines: - cfile.write(l) + cfile = open(cov_paths["id_delta_cov"], "ab") + for line in delta_log_lines: + cfile.write(line) cfile.close() - return - - -def write_zero_cov(zero_cov, cov_paths, cargs): +def write_zero_cov( + zero_cov: CovDictType, cov_paths: CovPathsDictType, cargs: argparse.Namespace +) -> None: cpath = cov_paths["zero_cov"] - logr("[+] Final zero coverage report: %s" % cpath, cov_paths["log_file"], cargs) - cfile = open(cpath, "w") - cfile.write("# All functions / lines in this file were never executed by any\n") - cfile.write("# AFL test case.\n") + logr( + b"[+] Final zero coverage report: %s" % cpath.encode(), + cov_paths["log_file"], + cargs, + ) + cfile = open(cpath, "wb") + cfile.write(b"# All functions / lines in this file were never executed by any\n") + cfile.write(b"# AFL test case.\n") cfile.close() write_cov(cpath, zero_cov, cargs) - return -def write_pos_cov(pos_cov, cov_paths, cargs): - +def write_pos_cov( + pos_cov: CovDictType, cov_paths: CovPathsDictType, cargs: argparse.Namespace +) -> None: cpath = cov_paths["pos_cov"] - logr("[+] Final positive coverage report: %s" % cpath, cov_paths["log_file"], cargs) - cfile = open(cpath, "w") - cfile.write("# All functions / lines in this file were executed by at\n") - cfile.write("# least one AFL test case. See the cov/id-delta-cov file\n") - cfile.write("# for more information.\n") + logr( + b"[+] Final positive coverage report: %s" % cpath.encode(), + cov_paths["log_file"], + cargs, + ) + cfile = open(cpath, "wb") + cfile.write(b"# All functions / lines in this file were executed by at\n") + cfile.write(b"# least one AFL test case. See the cov/id-delta-cov file\n") + cfile.write(b"# for more information.\n") cfile.close() write_cov(cpath, pos_cov, cargs) - return -def write_cov(cpath, cov, cargs): - cfile = open(cpath, "a") +def write_cov(cpath: str, cov: CovDictType, cargs: argparse.Namespace) -> None: + cfile = open(cpath, "ab") for f in cov: - cfile.write("File: %s\n" % f) + cfile.write(b"File: %s\n" % f) for ctype in sorted(cov[f]): - if ctype == "function": + if ctype == b"function": for val in sorted(cov[f][ctype]): - cfile.write(" %s: %s\n" % (ctype, val)) - elif ctype == "line": + cfile.write(b" %s: %s\n" % (ctype, val)) + elif ctype == b"line": if cargs.coverage_include_lines: for val in sorted(cov[f][ctype], key=int): - cfile.write(" %s: %s\n" % (ctype, val)) + cfile.write(b" %s: %s\n" % (ctype, val)) cfile.close() - return - -def write_status(status_file): - f = open(status_file, "w") - f.write("afl_cov_pid : %d\n" % os.getpid()) - f.write("afl_cov_version : %s\n" % __version__) - f.write("command_line : %s\n" % " ".join(argv)) +def write_status(status_file: str) -> None: + f = open(status_file, "wb") + f.write(b"afl_cov_pid : %d\n" % os.getpid()) + f.write(b"afl_cov_version : %s\n" % __version__.encode()) + f.write(b"command_line : %s\n" % " ".join(argv).encode()) f.close() - return -def append_file(pstr, path): - f = open(path, "a") - f.write("%s\n" % pstr) +def append_file(pstr: bytes, path: str) -> None: + f = open(path, "ab") + f.write(b"%s\n" % pstr) f.close() - return -def cov_init(cfile, cov): - for k in ["zero", "pos"]: +def cov_init(cfile: bytes, cov: CovDictType) -> None: + for k in [b"zero", b"pos"]: if k not in cov: cov[k] = {} if cfile not in cov[k]: cov[k][cfile] = {} - cov[k][cfile]["function"] = {} - cov[k][cfile]["line"] = {} - return + cov[k][cfile][b"function"] = {} + cov[k][cfile][b"line"] = {} -def extract_coverage(lcov_file, log_file, cargs): - - search_rv = False - tmp_cov = {} +def extract_coverage( + lcov_file: str, log_file: str, cargs: argparse.Namespace +) -> CovDictType: + tmp_cov = {} # type: CovDictType if not os.path.exists(lcov_file): logr( - "[-] Coverage file '%s' does not exist, skipping." % lcov_file, + b"[-] Coverage file '%s' does not exist, skipping." % lcov_file.encode(), log_file, cargs, ) @@ -535,63 +542,62 @@ def extract_coverage(lcov_file, log_file, cargs): # populate old lcov output for functions/lines that were called # zero times - with open(lcov_file, "r") as f: - current_file = "" + with open(lcov_file, "rb") as f: + current_file = b"" # type: bytes for line in f: line = line.strip() - m = re.search("SF:(\S+)", line) + m = re.search(rb"SF:(\S+)", line) if m and m.group(1): current_file = m.group(1) cov_init(current_file, tmp_cov) continue if current_file: - m = re.search("^FNDA:(\d+),(\S+)", line) + m = re.search(rb"^FNDA:(\d+),(\S+)", line) if m and m.group(2): - fcn = m.group(2) + "()" - if m.group(1) == "0": + fcn = m.group(2) + b"()" + if m.group(1) == b"0": # the function was never called - tmp_cov["zero"][current_file]["function"][fcn] = "" + tmp_cov[b"zero"][current_file][b"function"][fcn] = b"" else: - tmp_cov["pos"][current_file]["function"][fcn] = "" + tmp_cov[b"pos"][current_file][b"function"][fcn] = b"" continue # look for lines that were never called - m = re.search("^DA:(\d+),(\d+)", line) + m = re.search(rb"^DA:(\d+),(\d+)", line) if m and m.group(1): lnum = m.group(1) - if m.group(2) == "0": + if m.group(2) == b"0": # the line was never executed - tmp_cov["zero"][current_file]["line"][lnum] = "" + tmp_cov[b"zero"][current_file][b"line"][lnum] = b"" else: - tmp_cov["pos"][current_file]["line"][lnum] = "" + tmp_cov[b"pos"][current_file][b"line"][lnum] = b"" return tmp_cov -def search_cov(cargs): - +def search_cov(cargs: argparse.Namespace) -> bool: search_rv = False id_delta_file = cargs.afl_fuzzing_dir + "/cov/id-delta-cov" log_file = cargs.afl_fuzzing_dir + "/cov/afl-cov.log" - with open(id_delta_file, "r") as f: + with open(id_delta_file, "rb") as f: for line in f: line = line.strip() # id:NNNNNN*_file, cycle, src_file, cov_type, fcn/line\n") - [id_file, cycle_num, src_file, cov_type, val] = line.split(", ") + [id_file, cycle_num, src_file, cov_type, val] = line.split(b", ") if ( cargs.func_search - and cov_type == "function" + and cov_type == b"function" and val == cargs.func_search ): if cargs.src_file: - if cargs.src_file == src_file: + if cargs.src_file.encode() == src_file: logr( - "[+] Function '%s' in file: '%s' executed by: '%s', cycle: %s" + b"[+] Function '%s' in file: '%s' executed by: '%s', cycle: %s" % (val, src_file, id_file, cycle_num), log_file, cargs, @@ -599,7 +605,7 @@ def search_cov(cargs): search_rv = True else: logr( - "[+] Function '%s' executed by: '%s', cycle: %s" + b"[+] Function '%s' executed by: '%s', cycle: %s" % (val, id_file, cycle_num), log_file, cargs, @@ -607,13 +613,14 @@ def search_cov(cargs): search_rv = True if ( - cargs.src_file == src_file + cargs.src_file + and cargs.src_file.encode() == src_file and cargs.line_search - and val == cargs.line_search + and val == cargs.line_search.encode() ): - if cargs.src_file == src_file: + if cargs.src_file.encode() == src_file: logr( - "[+] Line '%s' in file: '%s' executed by: '%s', cycle: %s" + b"[+] Line '%s' in file: '%s' executed by: '%s', cycle: %s" % (val, src_file, id_file, cycle_num), log_file, cargs, @@ -622,15 +629,22 @@ def search_cov(cargs): if not search_rv: if cargs.func_search: - logr("[-] Function '%s' not found..." % cargs.func_search, log_file, cargs) + logr( + b"[-] Function '%s' not found..." % cargs.func_search.encode(), + log_file, + cargs, + ) elif cargs.line_search: - logr("[-] Line %s not found..." % cargs.line_search, log_file, cargs) + logr( + b"[-] Line %s not found..." % cargs.line_search.encode(), + log_file, + cargs, + ) return search_rv -def get_cycle_num(id_num, cargs): - +def get_cycle_num(id_num: int, cargs: argparse.Namespace) -> int: # default cycle cycle_num = 0 @@ -650,9 +664,7 @@ def get_cycle_num(id_num, cargs): return cycle_num -def lcov_gen_coverage(cov_paths, cargs): - - out_lines = [] +def lcov_gen_coverage(cov_paths: CovPathsDictType, cargs: argparse.Namespace): lcov_opts = "" if cargs.enable_branch_coverage: @@ -681,9 +693,9 @@ def lcov_gen_coverage(cov_paths, cargs): cargs.lcov_path + lcov_opts + " --no-checksum -a " - + cov_paths["lcov_base"] + + str(cov_paths["lcov_base"]) + " -a " - + cov_paths["lcov_info"] + + str(cov_paths["lcov_info"]) + " --output-file " + cov_paths["lcov_info_final"], cov_paths["log_file"], @@ -698,9 +710,9 @@ def lcov_gen_coverage(cov_paths, cargs): cargs.lcov_path + lcov_opts + " --no-checksum -a " - + cov_paths["lcov_base"] + + str(cov_paths["lcov_base"]) + " -a " - + cov_paths["lcov_info"] + + str(cov_paths["lcov_info"]) + " --output-file " + tmp_file.name, cov_paths["log_file"], @@ -732,25 +744,28 @@ def lcov_gen_coverage(cov_paths, cargs): return -def log_coverage(out_lines, log_file, cargs): +def log_coverage(out_lines: List[bytes], log_file, cargs: argparse.Namespace): for line in out_lines: - m = re.search("^\s+(lines\.\..*\:\s.*)", line.decode("ascii")) + m = re.search(rb"^\s+(lines\.\..*:\s.*)", line) if m and m.group(1): - logr(" " + m.group(1), log_file, cargs) + logr(b" " + m.group(1), log_file, cargs) else: - m = re.search("^\s+(functions\.\..*\:\s.*)", line.decode("ascii")) + m = re.search(rb"^\s+(functions\.\..*:\s.*)", line) if m and m.group(1): - logr(" " + m.group(1), log_file, cargs) + logr(b" " + m.group(1), log_file, cargs) else: if cargs.enable_branch_coverage: - m = re.search("^\s+(branches\.\..*\:\s.*)", line.decode("ascii")) + m = re.search(rb"^\s+(branches\.\..*:\s.*)", line) if m and m.group(1): - logr(" " + m.group(1), log_file, cargs) + logr( + b" " + m.group(1), + log_file, + cargs, + ) return -def gen_web_cov_report(fuzz_dir, cov_paths, cargs): - +def gen_web_cov_report(cov_paths, cargs): genhtml_opts = "" if cargs.enable_branch_coverage: @@ -771,7 +786,8 @@ def gen_web_cov_report(fuzz_dir, cov_paths, cargs): ) logr( - "[+] Final lcov web report: %s/%s" % (cov_paths["web_dir"], "index.html"), + b"[+] Final lcov web report: %s/%s" + % (cov_paths["web_dir"].encode(), b"index.html"), cov_paths["log_file"], cargs, ) @@ -780,31 +796,33 @@ def gen_web_cov_report(fuzz_dir, cov_paths, cargs): def is_afl_fuzz_running(cargs): - pid = None stats_file = cargs.afl_fuzzing_dir + "/fuzzer_stats" if os.path.exists(stats_file): - pid = get_running_pid(stats_file, "fuzzer_pid\s+\:\s+(\d+)") + pid = get_running_pid(stats_file, rb"fuzzer_pid\s+\:\s+(\d+)") else: for p in os.listdir(cargs.afl_fuzzing_dir): - stats_file = "%s/%s/fuzzer_stats" % (cargs.afl_fuzzing_dir, p) + stats_file = "%s/%s/fuzzer_stats" % ( + cargs.afl_fuzzing_dir.encode(), + p.encode(), + ) if os.path.exists(stats_file): # allow a single running AFL instance in parallel mode # to mean that AFL is running (and may be generating # new code coverage) - pid = get_running_pid(stats_file, "fuzzer_pid\s+\:\s+(\d+)") + pid = get_running_pid(stats_file, rb"fuzzer_pid\s+\:\s+(\d+)") if pid: break return pid -def get_running_pid(stats_file, pid_re): +def get_running_pid(stats_file: str, pid_re: bytes) -> Optional[int]: pid = None if not os.path.exists(stats_file): return pid - with open(stats_file, "r") as f: + with open(stats_file, "rb") as f: for line in f: line = line.strip() # fuzzer_pid : 13238 @@ -822,29 +840,35 @@ def get_running_pid(stats_file, pid_re): return pid -def run_cmd(cmd, log_file, cargs, collect, aflrun, fn, timeout=None): - +def run_cmd( + cmd: str, + log_file: Optional[str], + cargs: argparse.Namespace, + collect: int, + aflrun: bool, + fn: str, + timeout: Optional[int] = None, +) -> Tuple[int, List[bytes]]: out = [] - fh = None if cargs.disable_cmd_redirection or collect == WANT_OUTPUT or collect == LOG_ERRORS: fh = NamedTemporaryFile(delete=False) else: - fh = open(os.devnull, "w") + fh = open(os.devnull, "wb") - if aflrun == True and len(fn) > 0: + if aflrun is True and len(fn) > 0: cmd = "cat " + fn + " | " + cmd if cargs.verbose: if log_file: - logr(" CMD: %s" % cmd, log_file, cargs) + logr(b" CMD: %s" % cmd.encode(), log_file, cargs) else: print(" CMD: %s" % cmd) if timeout: cmd = "timeout -s KILL %s %s" % (timeout, cmd) - es = subprocess.call( + exit_code = subprocess.call( cmd, stdin=None, stdout=fh, stderr=subprocess.STDOUT, shell=True ) @@ -856,21 +880,25 @@ def run_cmd(cmd, log_file, cargs, collect, aflrun, fn, timeout=None): out.append(line.rstrip(b"\n")) os.unlink(fh.name) - if (es != 0) and (collect == LOG_ERRORS or collect == WANT_OUTPUT): + if (exit_code != 0) and ( + collect == LOG_ERRORS or (collect == WANT_OUTPUT and cargs.verbose) + ): if log_file: logr( - " Non-zero exit status '%d' for CMD: %s" % (es, cmd), log_file, cargs + b" Non-zero exit status '%d' for CMD: %s" + % (exit_code, cmd.encode()), + log_file, + cargs, ) for line in out: - logr(line.decode("ascii"), log_file, cargs) + logr(b" " + line, log_file, cargs) else: - print(" Non-zero exit status '%d' for CMD: %s" % (es, cmd)) + print(" Non-zero exit status '%d' for CMD: %s" % (exit_code, cmd)) - return es, out + return exit_code, out -def import_fuzzing_dirs(cov_paths, cargs): - +def import_fuzzing_dirs(cov_paths: CovPathsDictType, cargs: argparse.Namespace) -> bool: if not cargs.afl_fuzzing_dir: print("[*] Must specify AFL fuzzing dir with --afl-fuzzing-dir or -d") return False @@ -897,12 +925,11 @@ def import_fuzzing_dirs(cov_paths, cargs): return True -def import_test_cases(qdir): +def import_test_cases(qdir: str) -> Iterator[str]: return sorted(glob.glob(qdir + "/id:*")) -def init_tracking(cov_paths, cargs): - +def init_tracking(cov_paths: CovPathsDictType, cargs: argparse.Namespace) -> bool: cov_paths["dirs"] = {} cov_paths["top_dir"] = "%s/cov" % cargs.afl_fuzzing_dir @@ -982,17 +1009,16 @@ def init_tracking(cov_paths, cargs): # credit: # http://stackoverflow.com/questions/377017/test-if-executable-exists-in-python -def is_exe(fpath): +def is_exe(fpath: str) -> bool: return os.path.isfile(fpath) and os.access(fpath, os.X_OK) -def is_bin_gcov_enabled(binary, cargs): - +def is_bin_gcov_enabled(binary: str, cargs: argparse.Namespace) -> bool: rv = False # run readelf against the binary to see if it contains gcov support for line in run_cmd( - "%s -a %s" % (cargs.readelf_path, binary), False, cargs, WANT_OUTPUT, False, "" + "%s -a %s" % (cargs.readelf_path, binary), None, cargs, WANT_OUTPUT, False, "" )[1]: if b" __gcov" in line: if cargs.validate_args or cargs.gcov_check or cargs.gcov_check_bin: @@ -1018,7 +1044,7 @@ def is_bin_gcov_enabled(binary, cargs): return rv -def which(prog): +def which(prog: str) -> Optional[str]: fpath, fname = os.path.split(prog) if fpath: if is_exe(prog): @@ -1032,36 +1058,35 @@ def which(prog): return None -def check_requirements(cargs): +def check_requirements(cargs: argparse.Namespace) -> bool: lcov = which("lcov") gcov = which("gcov") genhtml = which("genhtml") - if lcov == None: + if lcov is None: lcov = which(cargs.lcov_path) - if genhtml == None: + if genhtml is None: genhtml = which(cargs.genhtml_path) - if lcov == None or gcov == None: + if lcov is None or gcov is None: print("Required command not found :") else: - if genhtml == None and not cargs.disable_lcov_web: + if genhtml is None and not cargs.disable_lcov_web: print("Required command not found :") else: return True - if lcov == None: - print("[*] lcov command does not exist : %s" % (cargs.lcov_path)) - if genhtml == None and not cargs.disable_lcov_web: - print("[*] genhtml command does not exist : %s" % (cargs.genhtml_path)) - if gcov == None: - print("[*] gcov command does not exist : %s" % (cargs.gcov_path)) + if lcov is None: + print("[*] lcov command does not exist :", cargs.lcov_path) + if genhtml is None and not cargs.disable_lcov_web: + print("[*] genhtml command does not exist :", cargs.genhtml_path) + if gcov is None: + print("[*] gcov command does not exist :", cargs.gcov_path) return False -def is_gcov_enabled(cargs): - +def is_gcov_enabled(cargs: argparse.Namespace) -> bool: if not is_exe(cargs.readelf_path): print("[*] Need a valid path to readelf, use --readelf-path") return False @@ -1109,8 +1134,7 @@ def is_gcov_enabled(cargs): return True -def validate_cargs(cargs): - +def validate_cargs(cargs: argparse.Namespace) -> bool: if cargs.coverage_cmd: if not is_gcov_enabled(cargs): return False @@ -1163,8 +1187,7 @@ def validate_cargs(cargs): return True -def gcno_files_exist(cargs): - +def gcno_files_exist(cargs: argparse.Namespace) -> bool: # make sure the code has been compiled with code coverage support, # so *.gcno files should exist found_code_coverage_support = False @@ -1182,7 +1205,7 @@ def gcno_files_exist(cargs): return True -def is_afl_running(cargs): +def is_afl_running(cargs: argparse.Namespace) -> bool: while not is_dir(cargs.afl_fuzzing_dir): if not cargs.background: print( @@ -1202,13 +1225,11 @@ def is_afl_running(cargs): return -def add_dir(fdir, cov_paths): +def add_dir(fdir: str, cov_paths: Dict[str, Union[Dict[str, Any], bytes]]) -> None: cov_paths["dirs"][fdir] = {} - return - -def mkdirs(cov_paths, cargs): +def mkdirs(cov_paths: CovPathsDictType, cargs: argparse.Namespace) -> None: create_cov_dirs = False if is_dir(cov_paths["top_dir"]): if cargs.overwrite: @@ -1223,29 +1244,27 @@ def mkdirs(cov_paths, cargs): os.mkdir(cov_paths[k]) # write coverage results in the following format - cfile = open(cov_paths["id_delta_cov"], "w") + cfile = open(cov_paths["id_delta_cov"], "wb") if cargs.cover_corpus or cargs.coverage_at_exit: - cfile.write("# id:[range]..., cycle, src_file, coverage_type, fcn/line\n") + cfile.write(b"# id:[range]..., cycle, src_file, coverage_type, fcn/line\n") else: - cfile.write("# id:NNNNNN*_file, cycle, src_file, coverage_type, fcn/line\n") + cfile.write( + b"# id:NNNNNN*_file, cycle, src_file, coverage_type, fcn/line\n" + ) cfile.close() - return - -def is_dir(dpath): +def is_dir(dpath: str) -> bool: return os.path.exists(dpath) and os.path.isdir(dpath) -def logr(pstr, log_file, cargs): +def logr(pstr: bytes, log_file: str, cargs: argparse.Namespace) -> None: if not cargs.background and not cargs.quiet: - print(" " + pstr) + sys.stdout.buffer.write(b" %s\n" % pstr) append_file(pstr, log_file) - return - -def stop_afl(cargs): +def stop_afl(cargs: argparse.Namespace) -> bool: rv = True # note that this function only looks for afl-fuzz processes - it does not @@ -1265,7 +1284,7 @@ def stop_afl(cargs): if os.path.exists(cargs.afl_fuzzing_dir + "/fuzzer_stats"): afl_pid = get_running_pid( - cargs.afl_fuzzing_dir + "/fuzzer_stats", "fuzzer_pid\s+\:\s+(\d+)" + cargs.afl_fuzzing_dir + "/fuzzer_stats", rb"fuzzer_pid\s+\:\s+(\d+)" ) if afl_pid: print("[+] Stopping running afl-fuzz instance, PID: %d" % afl_pid) @@ -1278,7 +1297,7 @@ def stop_afl(cargs): for p in os.listdir(cargs.afl_fuzzing_dir): stats_file = cargs.afl_fuzzing_dir + "/" + p + "/fuzzer_stats" if os.path.exists(stats_file): - afl_pid = get_running_pid(stats_file, "fuzzer_pid\s+\:\s+(\d+)") + afl_pid = get_running_pid(stats_file, rb"fuzzer_pid\s+\:\s+(\d+)") if afl_pid: print("[+] Stopping running afl-fuzz instance, PID: %d" % afl_pid) os.kill(afl_pid, signal.SIGTERM) @@ -1290,8 +1309,7 @@ def stop_afl(cargs): return rv -def check_core_pattern(): - +def check_core_pattern() -> bool: rv = True core_pattern_file = "/proc/sys/kernel/core_pattern" @@ -1299,16 +1317,15 @@ def check_core_pattern(): # check /proc/sys/kernel/core_pattern to see if afl-fuzz will # accept it if os.path.exists(core_pattern_file): - with open(core_pattern_file, "r") as f: - if f.readline().rstrip()[0] == "|": + with open(core_pattern_file, "rb") as f: + if f.readline().rstrip()[0] == b"|": # same logic as implemented by afl-fuzz itself print("[*] afl-fuzz requires 'echo core >%s'" % core_pattern_file) rv = False return rv -def parse_cmdline(): - +def parse_cmdline() -> argparse.Namespace: p = argparse.ArgumentParser() p.add_argument( diff --git a/afl-cov.sh b/afl-cov.sh index cfc9854..c564de9 100755 --- a/afl-cov.sh +++ b/afl-cov.sh @@ -31,7 +31,7 @@ test -e "$DST"/queue || { } HOMEPATH=`dirname $0` -export PATH=$HOMEPATH:$PATH +export PATH="$HOMEPATH:$PATH" afl-cov $OPT1 $OPT2 -d "$DST" --cover-corpus --coverage-cmd "$2" --code-dir . --overwrite From 912a27822bb0dca0dd5715ffe7197aa5e2f89738 Mon Sep 17 00:00:00 2001 From: vanhauser-thc Date: Sat, 6 Mar 2021 08:57:37 +0100 Subject: [PATCH 26/39] update docs --- ChangeLog | 6 ++++++ README.md | 3 ++- afl-cov | 2 +- 3 files changed, 9 insertions(+), 2 deletions(-) diff --git a/ChangeLog b/ChangeLog index d1dee24..89ad89b 100644 --- a/ChangeLog +++ b/ChangeLog @@ -1,3 +1,9 @@ +afl-cov-0.7.0 + - python3 improvements by @domenukk - much faster now + +afl-cov-0.6.6 + - support clang for gathering coverage + afl-cov-0.6.5 (2020-05-28) - switched to python3 diff --git a/README.md b/README.md index e67bfa5..6bcbe8e 100644 --- a/README.md +++ b/README.md @@ -1,6 +1,6 @@ # afl-cov - AFL Fuzzing Code Coverage -Version: 0.6.6 +Version: 0.7.0 - [Preface](#preface) - [Introduction](#introduction) @@ -19,6 +19,7 @@ This is a modified afl-cov fork because the original author's account is inactive :-( It has several improvements: + * Much, much faster! * afl-cov now accepts "@@" like AFL++ in the target command parameters * afl-cov now can send to targets that read on stdin (just omit @@) * afl-cov has a timeout -T option to hangs are not an issue, default 5s diff --git a/afl-cov b/afl-cov index a6d4153..58900c9 100755 --- a/afl-cov +++ b/afl-cov @@ -8,7 +8,7 @@ # new functions and line coverage evolve from an AFL fuzzing cycle. # # Copyright (C) 2015-2016 Michael Rash (mbr@cipherdyne.org) -# Copyright (C) 2018-2020 Marc "vanHauser" Heuse (mh@mh-sec.de) +# Copyright (C) 2018-2021 Marc "vanHauser" Heuse (mh@mh-sec.de) # # License (GNU General Public License version 2 or any later version): # From 25910ebc605be6fbe4e027253f56e9c1ad2a73d0 Mon Sep 17 00:00:00 2001 From: vanhauser-thc Date: Thu, 1 Apr 2021 12:30:16 +0200 Subject: [PATCH 27/39] fix readme --- README.md | 25 ++++++++++++------------- 1 file changed, 12 insertions(+), 13 deletions(-) diff --git a/README.md b/README.md index 6bcbe8e..db42901 100644 --- a/README.md +++ b/README.md @@ -19,24 +19,24 @@ This is a modified afl-cov fork because the original author's account is inactive :-( It has several improvements: - * Much, much faster! + * Much, much faster (thanks to @domenukk)! * afl-cov now accepts "@@" like AFL++ in the target command parameters * afl-cov now can send to targets that read on stdin (just omit @@) - * afl-cov has a timeout -T option to hangs are not an issue, default 5s + * afl-cov has a timeout -T option, so hangs are not an issue. default: 5s * afl-cov.sh makes using afl-cov easier (just needs two parameters) - * afl-cov-build.sh makes builing a target for coverage easier, just type - `afl-cov-build.sh make` + * afl-cov-build.sh makes builing a target for coverage easier, just type e.g. + `afl-cov-build.sh ./configure ; make` * afl-cov/afl-cov.sh/afl-cov-build.sh now support clang coverage, just add - -c to afl-cov.sh/afl-cov-build.sh (--clang for afl-cov) + -c to afl-cov.sh/afl-cov-build.sh and --clang for afl-cov * afl-stat.sh shows the statistics of a run (in progress or completed) Enjoy! -Marc "vanHauser" Heuse +Marc "van Hauser" Heuse ## Introduction `afl-cov` uses test case files produced by the -[AFL fuzzer](http://lcamtuf.coredump.cx/afl/) `afl-fuzz` to generate gcov code +[AFL++ fuzzer](http://github.com/AFLplusplus/aflplusplus) `afl-fuzz` to generate gcov code coverage results for a targeted binary. Code coverage is interpreted from one case to the next by `afl-cov` in order to determine which new functions and lines are hit by AFL with each new test case. Further, `afl-cov` allows for @@ -116,22 +116,21 @@ Here is an example: ```bash $ cd /path/to/project-gcov/ $ afl-cov -d /path/to/afl-fuzz-output/ --live --coverage-cmd \ -"cat AFL_FILE | LD_LIBRARY_PATH=./lib/.libs ./bin/.libs/somebin -a -b -c" \ +"LD_LIBRARY_PATH=./lib/.libs ./bin/.libs/somebin -a -b -c" \ --code-dir . ``` `/path/to/afl-fuzz-output/` is the output directory of afl-fuzz. -The `AFL_FILE` string above refers to the test case file that AFL will +The `AFL_FILE` string refers to the test case file that AFL will build in the `queue/` directory under `/path/to/afl-fuzz-output`. Just leave this string as-is since `afl-cov` will automatically substitute it with each AFL `queue/id:NNNNNN*` in succession as it builds the code coverage reports. You can also use @@ instead of AFL_FILE, both notations work. Also, in the above command, this handles the case where the AFL fuzzing cycle -is fuzzing the targeted binary via stdin. This explains the -`cat AFL_FILE | ... ./bin/.lib/somebin ...` invocation. For the other style of -fuzzing with AFL where a file is read from the filesystem, here is an example: +is fuzzing the targeted binary via stdin. +For the other style of fuzzing with AFL where a file is read from the filesystem, here is an example: ```bash $ cd /path/to/project-gcov/ @@ -166,7 +165,7 @@ the `--enable-branch-coverage` argument as described above): ```bash $ afl-cov -d /path/to/afl-fuzz-output/ --live --coverage-cmd \ -"LD_LIBRARY_PATH=./lib/.libs ./bin/.libs/somebin -f AFL_FILE -a -b -c" \ +"LD_LIBRARY_PATH=./lib/.libs ./bin/.libs/somebin -f @@ -a -b -c" \ --code-dir . --enable-branch-coverage [+] Imported 184 files from: /path/to/afl-fuzz-output/queue [+] AFL file: id:000000,orig:somestr.start (1 / 184), cycle: 0 From 0befe13f1873f39c814f4e91b6c532478d36e5fb Mon Sep 17 00:00:00 2001 From: vanhauser-thc Date: Fri, 30 Jul 2021 16:30:24 +0200 Subject: [PATCH 28/39] add stuff from nirizr --- afl-cov | 44 ++++++++++++++++++++++++++------------------ 1 file changed, 26 insertions(+), 18 deletions(-) diff --git a/afl-cov b/afl-cov index 58900c9..17b56d9 100755 --- a/afl-cov +++ b/afl-cov @@ -285,7 +285,15 @@ def process_afl_test_cases(cargs: argparse.Namespace) -> bool: cov_paths["log_file"], cargs, ) - time.sleep(cargs.sleep) + try: + time.sleep(cargs.sleep) + except KeyboardInterrupt: + log( + b"[!] Stopping live collection due to user interrupt", + cov_paths["log_file"], + cargs, + ) + break continue else: logr( @@ -1170,9 +1178,9 @@ def validate_cargs(cargs: argparse.Namespace) -> bool: print("[*] Must set --src-file in --line-search mode") return False - if cargs.live and not cargs.ignore_core_pattern: - if not check_core_pattern(): - return False + #if cargs.live and not cargs.ignore_core_pattern: + # if not check_core_pattern(): + # return False if not cargs.live and not is_dir(cargs.afl_fuzzing_dir): print( @@ -1309,20 +1317,20 @@ def stop_afl(cargs: argparse.Namespace) -> bool: return rv -def check_core_pattern() -> bool: - rv = True - - core_pattern_file = "/proc/sys/kernel/core_pattern" - - # check /proc/sys/kernel/core_pattern to see if afl-fuzz will - # accept it - if os.path.exists(core_pattern_file): - with open(core_pattern_file, "rb") as f: - if f.readline().rstrip()[0] == b"|": - # same logic as implemented by afl-fuzz itself - print("[*] afl-fuzz requires 'echo core >%s'" % core_pattern_file) - rv = False - return rv +#def check_core_pattern() -> bool: +# rv = True +# +# core_pattern_file = "/proc/sys/kernel/core_pattern" +# +# # check /proc/sys/kernel/core_pattern to see if afl-fuzz will +# # accept it +# if os.path.exists(core_pattern_file): +# with open(core_pattern_file, "rb") as f: +# if f.readline().rstrip()[0] == b"|": +# # same logic as implemented by afl-fuzz itself +# print("[*] afl-fuzz requires 'echo core >%s'" % core_pattern_file) +# rv = False +# return rv def parse_cmdline() -> argparse.Namespace: From 47903f66b7b1a7604b9e1b30e501c02a05cf227d Mon Sep 17 00:00:00 2001 From: vanhauser-thc Date: Fri, 30 Jul 2021 16:31:58 +0200 Subject: [PATCH 29/39] add stuff from nirizr --- afl-cov | 9 +++++---- 1 file changed, 5 insertions(+), 4 deletions(-) diff --git a/afl-cov b/afl-cov index 17b56d9..3b07bf1 100755 --- a/afl-cov +++ b/afl-cov @@ -811,10 +811,11 @@ def is_afl_fuzz_running(cargs): pid = get_running_pid(stats_file, rb"fuzzer_pid\s+\:\s+(\d+)") else: for p in os.listdir(cargs.afl_fuzzing_dir): - stats_file = "%s/%s/fuzzer_stats" % ( - cargs.afl_fuzzing_dir.encode(), - p.encode(), - ) + #stats_file = "%s/%s/fuzzer_stats" % ( + # cargs.afl_fuzzing_dir.encode(), + # p.encode(), + #) + stats_file = "%s/%s/fuzzer_stats" % ( cargs.afl_fuzzing_dir, p) if os.path.exists(stats_file): # allow a single running AFL instance in parallel mode # to mean that AFL is running (and may be generating From f51eff6a15a330441c823974fe60817fb1642372 Mon Sep 17 00:00:00 2001 From: mmmds Date: Wed, 3 Nov 2021 19:12:16 +0100 Subject: [PATCH 30/39] support afl-fuzz -f --- afl-cov | 12 ++++++++++++ 1 file changed, 12 insertions(+) diff --git a/afl-cov b/afl-cov index 3b07bf1..36dc596 100755 --- a/afl-cov +++ b/afl-cov @@ -36,6 +36,7 @@ import signal import sys import time from shutil import rmtree +from shutil import copyfile from sys import argv from tempfile import NamedTemporaryFile from typing import Any, Dict, Iterator, List, Union, Optional, Tuple @@ -175,6 +176,14 @@ def process_afl_test_cases(cargs: argparse.Namespace) -> bool: if f_ctr == len(new_files): last_file = True + + if cargs.afl_file: + try: + copyfile(f, cargs.afl_file) + except Exception: + print("[-] Cannot copy file") + sys.exit(1) + if cargs.cover_corpus and last_dir and last_file: # in --cover-corpus mode, only run lcov after all AFL # test cases have been processed @@ -1536,6 +1545,9 @@ def parse_cmdline() -> argparse.Namespace: p.add_argument( "-T", "--timeout", type=str, help="timeout (default 5 seconds)", default="5" ) + p.add_argument( + "--afl-file", type=str, help="Filepath that is passed to AFL with -f argument", default="" + ) return p.parse_args() From bb51de02c06dca2bfa2dfb494fa3b095950ec879 Mon Sep 17 00:00:00 2001 From: vanhauser-thc Date: Fri, 14 Jan 2022 12:15:43 +0100 Subject: [PATCH 31/39] add fuzzer define --- afl-cov-build.sh | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/afl-cov-build.sh b/afl-cov-build.sh index 46214ce..286b045 100755 --- a/afl-cov-build.sh +++ b/afl-cov-build.sh @@ -17,7 +17,7 @@ test -z "$CXX" -a -z "$CLANG" && export CXX=g++ test -z "$CC" -a -n "$CLANG" && export CC=clang test -z "$CXX" -a -n "$CLANG" && export CXX=clang++ -export CFLAGS="-fprofile-arcs -ftest-coverage" +export CFLAGS="-fprofile-arcs -ftest-coverage -DFUZZING_BUILD_MODE_UNSAFE_FOR_PRODUCTION" export CXXFLAGS="$CFLAGS" export CPPFLAGS="$CFLAGS" test -z "$CLANG" && export LDFLAGS="-lgcov --coverage" From e90d5fe51a3bb9f8363edf4489481765b388eee7 Mon Sep 17 00:00:00 2001 From: Kuang-che Wu Date: Tue, 24 May 2022 12:37:42 +0800 Subject: [PATCH 32/39] avoid O(n^2) file scan this can save about 3 minutes for 100k files. --- afl-cov | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/afl-cov b/afl-cov index 3b07bf1..538519b 100755 --- a/afl-cov +++ b/afl-cov @@ -120,7 +120,7 @@ def process_afl_test_cases(cargs: argparse.Namespace) -> bool: tot_files = 0 curr_cycle = -1 - afl_files = [] # type: List[str] + afl_files = set() # type: Set[str] cov_paths = {} # type: CovPathsDictType # main coverage tracking dictionary @@ -158,7 +158,7 @@ def process_afl_test_cases(cargs: argparse.Namespace) -> bool: for f in tmp_files: if f not in afl_files: - afl_files.append(f) + afl_files.add(f) new_files.append(f) if new_files: From 971c222700ee3fed351a9fd919f884cb4940a931 Mon Sep 17 00:00:00 2001 From: Andrei Iakunin Date: Tue, 23 Aug 2022 13:30:29 +0300 Subject: [PATCH 33/39] Fixed "-T option don't work" bug Timeout was applied after "cat @@|" and works only for "cat". Now "--verbose" will print full command line, even with "timeout". --- afl-cov | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/afl-cov b/afl-cov index 3b07bf1..a7ba683 100755 --- a/afl-cov +++ b/afl-cov @@ -865,6 +865,9 @@ def run_cmd( else: fh = open(os.devnull, "wb") + if timeout: + cmd = "timeout -s KILL %s %s" % (timeout, cmd) + if aflrun is True and len(fn) > 0: cmd = "cat " + fn + " | " + cmd @@ -874,9 +877,6 @@ def run_cmd( else: print(" CMD: %s" % cmd) - if timeout: - cmd = "timeout -s KILL %s %s" % (timeout, cmd) - exit_code = subprocess.call( cmd, stdin=None, stdout=fh, stderr=subprocess.STDOUT, shell=True ) From 7355fc2e71a49e3ab7c9d16ca8459a4cfbd9511c Mon Sep 17 00:00:00 2001 From: Andrei Iakunin Date: Tue, 23 Aug 2022 14:00:42 +0300 Subject: [PATCH 34/39] Fixed fail during processing input file with no-utf characters. --- afl-cov | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/afl-cov b/afl-cov index a7ba683..39b6efc 100755 --- a/afl-cov +++ b/afl-cov @@ -186,7 +186,7 @@ def process_afl_test_cases(cargs: argparse.Namespace) -> bool: logr( b"[+] AFL test case: %s (%d / %d), cycle: %d" % ( - os.path.basename(f).encode(), + os.path.basename(f).encode(errors="namereplace"), num_files, len(afl_files), curr_cycle, @@ -873,7 +873,7 @@ def run_cmd( if cargs.verbose: if log_file: - logr(b" CMD: %s" % cmd.encode(), log_file, cargs) + logr(b" CMD: %s" % cmd.encode(errors="namereplace"), log_file, cargs) else: print(" CMD: %s" % cmd) From 722f235af653a99e43892e282c49b4d3c7c8b598 Mon Sep 17 00:00:00 2001 From: vanhauser-thc Date: Mon, 5 Sep 2022 13:56:57 +0200 Subject: [PATCH 35/39] egrep -> grep -e --- afl-cov.sh | 2 +- afl-stat.sh | 4 ++-- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/afl-cov.sh b/afl-cov.sh index c564de9..0afccd9 100755 --- a/afl-cov.sh +++ b/afl-cov.sh @@ -40,7 +40,7 @@ test -e "$1"/fuzzer_stats && { echo "runtime : $DIFF seconds" TIME=`date -u -d "@$SECONDS" +"%T"` echo "run_clock : $TIME" - egrep 'execs_done|paths_total|^unique_|stability' "$DST"/fuzzer_stats + grep -E 'execs_done|paths_total|^unique_|stability' "$DST"/fuzzer_stats LINES= test -e "$1"/cov/afl-cov.log && LINES=`grep -w lines "$1"/cov/afl-cov.log|tail -n 1|sed 's/.*(//'|sed 's/ .*//'` echo "coverage : $LINES lines" diff --git a/afl-stat.sh b/afl-stat.sh index 4c9b7f2..0801c4f 100755 --- a/afl-stat.sh +++ b/afl-stat.sh @@ -10,8 +10,8 @@ while [ -n "$1" ]; do test -e "$1"/fuzzer_stats || { echo Error: not an afl-fuzz -o out directory ; } echo File: `realpath "$1"` { - egrep 'run_time|execs_done|execs_per_sec|paths_total|^unique_|stability' "$1"/fuzzer_stats - SECONDS=`egrep run_time "$1"/fuzzer_stats | awk '{print$3}'` + grep -E 'run_time|execs_done|execs_per_sec|paths_total|^unique_|stability' "$1"/fuzzer_stats + SECONDS=`grep -E run_time "$1"/fuzzer_stats | awk '{print$3}'` test -n "$SECONDS" && { DAYS=`expr $SECONDS / 86400` SECONDS=`expr $SECONDS % 86400` From f97a5354160868202b242f9a2ff4c6d951f2baec Mon Sep 17 00:00:00 2001 From: vanhauser-thc Date: Thu, 23 Nov 2023 15:51:58 +0100 Subject: [PATCH 36/39] detect afl compiler --- README.md | 2 ++ afl-cov-build.sh | 2 ++ 2 files changed, 4 insertions(+) diff --git a/README.md b/README.md index db42901..bb7a04f 100644 --- a/README.md +++ b/README.md @@ -29,6 +29,8 @@ It has several improvements: * afl-cov/afl-cov.sh/afl-cov-build.sh now support clang coverage, just add -c to afl-cov.sh/afl-cov-build.sh and --clang for afl-cov * afl-stat.sh shows the statistics of a run (in progress or completed) + * For `LLVMFuzzerTestOneInput()` harnesses you can build + link with + `libfuzzer_driver.cpp` Enjoy! diff --git a/afl-cov-build.sh b/afl-cov-build.sh index 286b045..95212d9 100755 --- a/afl-cov-build.sh +++ b/afl-cov-build.sh @@ -9,6 +9,8 @@ test -z "$1" -o "$1" = "-h" && { exit 1 } +echo " $CC $CXX" | grep -q afl && { echo Error: AFL++ compiler is set.; exit 1; } + CLANG= test "$1" = "-c" && { CLANG=yes ; shift ; } From d48481156715ba591e5a0b9ac5a7d59928b6d3f2 Mon Sep 17 00:00:00 2001 From: James Lee-Jones Date: Mon, 17 Jun 2024 15:33:43 +0100 Subject: [PATCH 37/39] Prevent repeated coverage calculation for files --- afl-cov | 2 ++ 1 file changed, 2 insertions(+) diff --git a/afl-cov b/afl-cov index f021ffe..3521b9f 100755 --- a/afl-cov +++ b/afl-cov @@ -285,6 +285,8 @@ def process_afl_test_cases(cargs: argparse.Namespace) -> bool: if do_break: break + new_files = [] + if cargs.live: if is_afl_fuzz_running(cargs): if not len(new_files): From a1bf1a9e088741500e01c4897adfe167f0795bc4 Mon Sep 17 00:00:00 2001 From: James Lee-Jones Date: Mon, 17 Jun 2024 16:08:05 +0100 Subject: [PATCH 38/39] Fix progress counter --- afl-cov | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/afl-cov b/afl-cov index 3521b9f..ab7ccfa 100755 --- a/afl-cov +++ b/afl-cov @@ -197,7 +197,7 @@ def process_afl_test_cases(cargs: argparse.Namespace) -> bool: % ( os.path.basename(f).encode(errors="namereplace"), num_files, - len(afl_files), + len(new_files), curr_cycle, ), cov_paths["log_file"], From 17f92df7bac4d016a3519d9e89babcbe2a2b20ed Mon Sep 17 00:00:00 2001 From: kvalerio <24193167+kevin-valerio@users.noreply.github.com> Date: Thu, 26 Dec 2024 16:25:49 +0100 Subject: [PATCH 39/39] fixing * escape Fixing `afl-cov:1480: SyntaxWarning: invalid escape sequence '\*'` --- afl-cov | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/afl-cov b/afl-cov index ab7ccfa..9ea3368 100755 --- a/afl-cov +++ b/afl-cov @@ -1477,7 +1477,7 @@ def parse_cmdline() -> argparse.Namespace: "--lcov-exclude-pattern", type=str, help="Set exclude pattern for lcov results", - default="/usr/include/\*", + default=r"/usr/include/*", ) p.add_argument( "--func-search", type=str, help="Search for coverage of a specific function"