diff --git a/afl-fuzz.c b/afl-fuzz.c index f915f002..ea3029f0 100644 --- a/afl-fuzz.c +++ b/afl-fuzz.c @@ -112,12 +112,12 @@ EXP_ST u8 skip_deterministic, /* Skip deterministic stages? */ in_place_resume, /* Attempt in-place resume? */ auto_changed, /* Auto-generated tokens changed? */ no_cpu_meter_red, /* Feng shui on the status screen */ - no_var_check, /* Don't detect variable behavior */ shuffle_queue, /* Shuffle input queue? */ bitmap_changed = 1, /* Time to update bitmap? */ qemu_mode, /* Running in QEMU mode? */ skip_requested, /* Skip request, via SIGUSR1 */ - run_over10m; /* Run time over 10 minutes? */ + run_over10m, /* Run time over 10 minutes? */ + persistent_mode; /* Running in persistent mode? */ static s32 out_fd, /* Persistent fd for out_file */ dev_urandom_fd = -1, /* Persistent fd for /dev/urandom */ @@ -135,6 +135,8 @@ EXP_ST u8 virgin_bits[MAP_SIZE], /* Regions yet untouched by fuzzing */ virgin_hang[MAP_SIZE], /* Bits we haven't seen in hangs */ virgin_crash[MAP_SIZE]; /* Bits we haven't seen in crashes */ +static u8 var_bytes[MAP_SIZE]; /* Bytes that appear to be variable */ + static s32 shm_id; /* ID of the SHM region */ static volatile u8 stop_soon, /* Ctrl-C pressed? */ @@ -154,6 +156,7 @@ EXP_ST u32 queued_paths, /* Total number of queued testcases */ cur_depth, /* Current path depth */ max_depth, /* Max path depth */ useless_at_start, /* Number of useless starting paths */ + var_byte_count, /* Bitmap bytes with var behavior */ current_entry, /* Current queue entry ID */ havoc_div = 1; /* Cycle count divisor for havoc */ @@ -166,6 +169,7 @@ EXP_ST u64 total_crashes, /* Total number of crashes */ last_path_time, /* Time for most recent path (ms) */ last_crash_time, /* Time for most recent crash (ms) */ last_hang_time, /* Time for most recent hang (ms) */ + last_crash_execs, /* Exec counter at last crash */ queue_cycle, /* Queue round counter */ cycles_wo_finds, /* Cycles without any new paths */ trim_execs, /* Execs done to trim input files */ @@ -183,6 +187,8 @@ static u8 *stage_name = "init", /* Name of the current fuzz stage */ static s32 stage_cur, stage_max; /* Stage progression */ static s32 splicing_with = -1; /* Splicing with which test case? */ +static u32 master_id, master_max; /* Master instance job splitting */ + static u32 syncing_case; /* Syncing with case #... */ static s32 stage_cur_byte, /* Byte offset of current stage op */ @@ -2515,7 +2521,11 @@ static void show_stats(void); static u8 calibrate_case(char** argv, struct queue_entry* q, u8* use_mem, u32 handicap, u8 from_queue) { - u8 fault = 0, new_bits = 0, var_detected = 0, first_run = (q->exec_cksum == 0); + static u8 first_trace[MAP_SIZE]; + + u8 fault = 0, new_bits = 0, var_detected = 0, + first_run = (q->exec_cksum == 0); + u64 start_us, stop_us; s32 old_sc = stage_cur, old_sm = stage_max, old_tmout = exec_tmout; @@ -2532,7 +2542,7 @@ static u8 calibrate_case(char** argv, struct queue_entry* q, u8* use_mem, q->cal_failed++; stage_name = "calibration"; - stage_max = no_var_check ? CAL_CYCLES_NO_VAR : CAL_CYCLES; + stage_max = CAL_CYCLES; /* Make sure the forkserver is up before we do anything, and let's not count its spin-up time toward binary calibration. */ @@ -2540,6 +2550,8 @@ static u8 calibrate_case(char** argv, struct queue_entry* q, u8* use_mem, if (dumb_mode != 1 && !no_forkserver && !forksrv_pid) init_forkserver(argv); + if (q->exec_cksum) memcpy(first_trace, trace_bits, MAP_SIZE); + start_us = get_cur_time_us(); for (stage_cur = 0; stage_cur < stage_max; stage_cur++) { @@ -2569,12 +2581,24 @@ static u8 calibrate_case(char** argv, struct queue_entry* q, u8* use_mem, u8 hnb = has_new_bits(virgin_bits); if (hnb > new_bits) new_bits = hnb; - if (!no_var_check && q->exec_cksum) { + if (q->exec_cksum) { + + u32 i; + + for (i = 0; i < MAP_SIZE; i++) + if (!var_bytes[i] && first_trace[i] != trace_bits[i]) { + var_bytes[i] = 1; + stage_max = CAL_CYCLES_LONG; + } var_detected = 1; - stage_max = CAL_CYCLES_LONG; - } else q->exec_cksum = cksum; + } else { + + q->exec_cksum = cksum; + memcpy(first_trace, trace_bits, MAP_SIZE); + + } } @@ -2613,9 +2637,15 @@ static u8 calibrate_case(char** argv, struct queue_entry* q, u8* use_mem, /* Mark variable paths. */ - if (var_detected && !q->var_behavior) { - mark_as_variable(q); - queued_variable++; + if (var_detected) { + + var_byte_count = count_bytes(var_bytes); + + if (!q->var_behavior) { + mark_as_variable(q); + queued_variable++; + } + } stage_name = old_sn; @@ -3204,6 +3234,7 @@ static u8 save_if_interesting(char** argv, void* mem, u32 len, u8 fault) { unique_crashes++; last_crash_time = get_cur_time(); + last_crash_execs = total_execs; break; @@ -3301,9 +3332,9 @@ static void find_timeout(void) { /* Update stats file for unattended monitoring. */ -static void write_stats_file(double bitmap_cvg, double eps) { +static void write_stats_file(double bitmap_cvg, double stability, double eps) { - static double last_bcvg, last_eps; + static double last_bcvg, last_stab, last_eps; u8* fn = alloc_printf("%s/fuzzer_stats", out_dir); s32 fd; @@ -3322,46 +3353,51 @@ static void write_stats_file(double bitmap_cvg, double eps) { /* Keep last values in case we're called from another context where exec/sec stats and such are not readily available. */ - if (!bitmap_cvg && !eps) { + if (!bitmap_cvg && !stability && !eps) { bitmap_cvg = last_bcvg; + stability = last_stab; eps = last_eps; } else { last_bcvg = bitmap_cvg; + last_stab = stability; last_eps = eps; } - fprintf(f, "start_time : %llu\n" - "last_update : %llu\n" - "fuzzer_pid : %u\n" - "cycles_done : %llu\n" - "execs_done : %llu\n" - "execs_per_sec : %0.02f\n" - "paths_total : %u\n" - "paths_favored : %u\n" - "paths_found : %u\n" - "paths_imported : %u\n" - "max_depth : %u\n" - "cur_path : %u\n" - "pending_favs : %u\n" - "pending_total : %u\n" - "variable_paths : %u\n" - "bitmap_cvg : %0.02f%%\n" - "unique_crashes : %llu\n" - "unique_hangs : %llu\n" - "last_path : %llu\n" - "last_crash : %llu\n" - "last_hang : %llu\n" - "exec_timeout : %u\n" - "afl_banner : %s\n" - "afl_version : " VERSION "\n" - "command_line : %s\n", + fprintf(f, "start_time : %llu\n" + "last_update : %llu\n" + "fuzzer_pid : %u\n" + "cycles_done : %llu\n" + "execs_done : %llu\n" + "execs_per_sec : %0.02f\n" + "paths_total : %u\n" + "paths_favored : %u\n" + "paths_found : %u\n" + "paths_imported : %u\n" + "max_depth : %u\n" + "cur_path : %u\n" + "pending_favs : %u\n" + "pending_total : %u\n" + "variable_paths : %u\n" + "stability : %0.02f%%\n" + "bitmap_cvg : %0.02f%%\n" + "unique_crashes : %llu\n" + "unique_hangs : %llu\n" + "last_path : %llu\n" + "last_crash : %llu\n" + "last_hang : %llu\n" + "execs_since_crash : %llu\n" + "exec_timeout : %u\n" + "afl_banner : %s\n" + "afl_version : " VERSION "\n" + "command_line : %s\n", start_time / 1000, get_cur_time() / 1000, getpid(), queue_cycle ? (queue_cycle - 1) : 0, total_execs, eps, queued_paths, queued_favored, queued_discovered, queued_imported, max_depth, current_entry, pending_favored, pending_not_fuzzed, - queued_variable, bitmap_cvg, unique_crashes, unique_hangs, - last_path_time / 1000, last_crash_time / 1000, - last_hang_time / 1000, exec_tmout, use_banner, orig_cmdline); + queued_variable, stability, bitmap_cvg, unique_crashes, + unique_hangs, last_path_time / 1000, last_crash_time / 1000, + last_hang_time / 1000, total_execs - last_crash_execs, + exec_tmout, use_banner, orig_cmdline); /* ignore errors */ fclose(f); @@ -3784,7 +3820,7 @@ static void show_stats(void) { static u64 last_stats_ms, last_plot_ms, last_ms, last_execs; static double avg_exec; - double t_byte_ratio; + double t_byte_ratio, stab_ratio; u64 cur_ms; u32 t_bytes, t_bits; @@ -3837,12 +3873,17 @@ static void show_stats(void) { t_bytes = count_non_255_bytes(virgin_bits); t_byte_ratio = ((double)t_bytes * 100) / MAP_SIZE; + if (t_bytes) + stab_ratio = 100 - ((double)var_byte_count) * 100 / t_bytes; + else + stab_ratio = 100; + /* Roughly every minute, update fuzzer stats and save auto tokens. */ if (cur_ms - last_stats_ms > STATS_UPDATE_SEC * 1000) { last_stats_ms = cur_ms; - write_stats_file(t_byte_ratio, avg_exec); + write_stats_file(t_byte_ratio, stab_ratio, avg_exec); save_auto(); write_bitmap(); @@ -4004,8 +4045,8 @@ static void show_stats(void) { SAYF(bV bSTOP " now processing : " cRST "%-17s " bSTG bV bSTOP, tmp); - - sprintf(tmp, "%s (%0.02f%%)", DI(t_bytes), t_byte_ratio); + sprintf(tmp, "%0.02f%% / %0.02f%%", ((double)queue_cur->bitmap_size) * + 100 / MAP_SIZE, t_byte_ratio); SAYF(" map density : %s%-21s " bSTG bV "\n", t_byte_ratio > 70 ? cLRD : ((t_bytes < 200 && !dumb_mode) ? cPIN : cRST), tmp); @@ -4149,9 +4190,15 @@ static void show_stats(void) { DI(stage_finds[STAGE_HAVOC]), DI(stage_cycles[STAGE_HAVOC]), DI(stage_finds[STAGE_SPLICE]), DI(stage_cycles[STAGE_SPLICE])); - SAYF(bV bSTOP " havoc : " cRST "%-37s " bSTG bV bSTOP - " variable : %s%-10s " bSTG bV "\n", tmp, queued_variable ? cLRD : cRST, - no_var_check ? (u8*)"n/a" : DI(queued_variable)); + sprintf(tmp, "%s (%0.02f%%)", DI(t_bytes), t_byte_ratio); + + SAYF(bV bSTOP " havoc : " cRST "%-37s " bSTG bV bSTOP, tmp); + + if (t_bytes) sprintf(tmp, "%0.02f%%", stab_ratio); + else strcpy(tmp, "n/a"); + + SAYF(" stability : %s%-10s " bSTG bV "\n", stab_ratio < 90 ? cLRD : + ((queued_variable && !persistent_mode) ? cMGN : cRST), tmp); if (!bytes_trim_out) { @@ -4962,6 +5009,12 @@ static u8 fuzz_one(char** argv) { if (skip_deterministic || queue_cur->was_fuzzed || queue_cur->passed_det) goto havoc_stage; + /* Skip deterministic fuzzing if exec path checksum puts this out of scope + for this master instance. */ + + if (master_max && (queue_cur->exec_cksum % master_max) != master_id - 1) + goto havoc_stage; + /********************************************* * SIMPLE BITFLIP (+dictionary construction) * *********************************************/ @@ -5131,7 +5184,7 @@ static u8 fuzz_one(char** argv) { /* Effector map setup. These macros calculate: EFF_APOS - position of a particular file offset in the map. - EFF_ALEN - length of n map with a particular number of bytes. + EFF_ALEN - length of a map with a particular number of bytes. EFF_SPAN_ALEN - map span for a sequence of bytes. */ @@ -6562,8 +6615,14 @@ static void sync_fuzzers(char** argv) { path = alloc_printf("%s/%s", qd_path, qd_ent->d_name); + /* Allow this to fail in case the other fuzzer is resuming or so... */ + fd = open(path, O_RDONLY); - if (fd < 0) PFATAL("Unable to open '%s'", path); + + if (fd < 0) { + ck_free(path); + continue; + } if (fstat(fd, &st)) PFATAL("fstat() failed"); @@ -6797,7 +6856,6 @@ EXP_ST void check_binary(u8* fname) { OKF(cPIN "Persistent mode binary detected."); setenv(PERSIST_ENV_VAR, "1", 1); - no_var_check = 1; } else if (getenv("AFL_PERSISTENT")) { @@ -6809,6 +6867,7 @@ EXP_ST void check_binary(u8* fname) { OKF(cPIN "Deferred forkserver binary detected."); setenv(DEFER_ENV_VAR, "1", 1); + persistent_mode = 1; } else if (getenv("AFL_DEFER_FORKSRV")) { @@ -7567,7 +7626,7 @@ int main(int argc, char** argv) { switch (opt) { - case 'i': + case 'i': /* input dir */ if (in_dir) FATAL("Multiple -i options not supported"); in_dir = optarg; @@ -7582,15 +7641,30 @@ int main(int argc, char** argv) { out_dir = optarg; break; - case 'M': + case 'M': /* master sync ID */ force_deterministic = 1; /* Fall through */ - case 'S': /* sync ID */ + case 'S': { /* secondary sync ID */ + + u8* c; + + if (sync_id) FATAL("Multiple -S or -M options not supported"); + sync_id = optarg; + + if ((c = strchr(sync_id, ':'))) { + + *c = 0; + + if (sscanf(c + 1, "%u/%u", &master_id, &master_max) != 2 || + !master_id || !master_max || master_id > master_max || + master_max > 1000000) FATAL("Bogus master ID passed to -M"); + + } + + } - if (sync_id) FATAL("Multiple -S or -M options not supported"); - sync_id = optarg; break; case 'f': /* target file */ @@ -7599,13 +7673,13 @@ int main(int argc, char** argv) { out_file = optarg; break; - case 'x': + case 'x': /* dictionary */ if (extras_dir) FATAL("Multiple -x options not supported"); extras_dir = optarg; break; - case 't': { + case 't': { /* timeout */ u8 suffix = 0; @@ -7622,7 +7696,7 @@ int main(int argc, char** argv) { } - case 'm': { + case 'm': { /* mem limit */ u8 suffix = 'M'; @@ -7659,14 +7733,14 @@ int main(int argc, char** argv) { break; - case 'd': + case 'd': /* skip deterministic */ if (skip_deterministic) FATAL("Multiple -d options not supported"); skip_deterministic = 1; use_splicing = 1; break; - case 'B': + case 'B': /* load bitmap */ /* This is a secret undocumented option! It is useful if you find an interesting test case during a normal fuzzing process, and want @@ -7685,26 +7759,26 @@ int main(int argc, char** argv) { read_bitmap(in_bitmap); break; - case 'C': + case 'C': /* crash mode */ if (crash_mode) FATAL("Multiple -C options not supported"); crash_mode = FAULT_CRASH; break; - case 'n': + case 'n': /* dumb mode */ if (dumb_mode) FATAL("Multiple -n options not supported"); if (getenv("AFL_DUMB_FORKSRV")) dumb_mode = 2; else dumb_mode = 1; break; - case 'T': + case 'T': /* banner */ if (use_banner) FATAL("Multiple -T options not supported"); use_banner = optarg; break; - case 'Q': + case 'Q': /* QEMU mode */ if (qemu_mode) FATAL("Multiple -Q options not supported"); qemu_mode = 1; @@ -7738,7 +7812,6 @@ int main(int argc, char** argv) { if (getenv("AFL_NO_FORKSRV")) no_forkserver = 1; if (getenv("AFL_NO_CPU_RED")) no_cpu_meter_red = 1; - if (getenv("AFL_NO_VAR_CHECK")) no_var_check = 1; if (getenv("AFL_SHUFFLE_QUEUE")) shuffle_queue = 1; if (dumb_mode == 2 && no_forkserver) @@ -7797,7 +7870,7 @@ int main(int argc, char** argv) { seek_to = find_start_position(); - write_stats_file(0, 0); + write_stats_file(0, 0, 0); save_auto(); if (stop_soon) goto stop_fuzzing; @@ -7873,7 +7946,7 @@ int main(int argc, char** argv) { if (queue_cur) show_stats(); write_bitmap(); - write_stats_file(0, 0); + write_stats_file(0, 0, 0); save_auto(); stop_fuzzing: diff --git a/config.h b/config.h index ce91b5ba..73dac535 100644 --- a/config.h +++ b/config.h @@ -21,7 +21,7 @@ /* Version string: */ -#define VERSION "2.19b" +#define VERSION "2.20b" /****************************************************** * * @@ -61,13 +61,9 @@ /* Number of calibration cycles per every new test case (and for test cases that show variable behavior): */ -#define CAL_CYCLES 10 +#define CAL_CYCLES 8 #define CAL_CYCLES_LONG 40 -/* The same, but when AFL_NO_VAR_CHECK is set in the environment: */ - -#define CAL_CYCLES_NO_VAR 4 - /* Number of subsequent hangs before abandoning an input file: */ #define HANG_LIMIT 250 diff --git a/docs/ChangeLog b/docs/ChangeLog index 2ac12144..5ee67e5e 100644 --- a/docs/ChangeLog +++ b/docs/ChangeLog @@ -16,6 +16,22 @@ Not sure if you should upgrade? The lowest currently recommended version is 2.18b. If you're stuck on an earlier release, it's strongly advisable to get on with the times. +-------------- +Version 2.20b: +-------------- + + - Revamped the handling of variable paths, replacing path count with a + "stability" score to give users a much better signal. Based on the + feedback from Vegard Nossum. + + - Made a stability improvement to the syncing behavior with resuming + fuzzers. Based on the feedback from Vegard. + + - Changed the UI to include current input bitmap density along with + total density. Ditto. + + - Added experimental support for parallelizing -M. + -------------- Version 2.19b: -------------- diff --git a/docs/README b/docs/README index 27068208..dc1f425f 100644 --- a/docs/README +++ b/docs/README @@ -464,6 +464,7 @@ bug reports, or patches from: Daniel Godas-Lopez Franjo Ivancic Austin Seipp Daniel Komaromy Daniel Binderman Jonathan Metzman + Vegard Nossum Thank you! diff --git a/docs/env_variables.txt b/docs/env_variables.txt index 8bd2362a..18debadb 100644 --- a/docs/env_variables.txt +++ b/docs/env_variables.txt @@ -99,11 +99,6 @@ checks or alter some of the more exotic semantics of the tool: normally done when starting up the forkserver and causes a pretty significant performance drop. - - Setting AFL_NO_VAR_CHECK skips the detection of variable test cases, - greatly speeding up session resumption and path discovery for complex - multi-threaded apps (but depriving you of a potentially useful signal - in more orderly programs). - - AFL_EXIT_WHEN_DONE causes afl-fuzz to terminate when all existing paths have been fuzzed and there were no new finds for a while. This would be normally indicated by the cycle counter in the UI turning green. May be diff --git a/docs/parallel_fuzzing.txt b/docs/parallel_fuzzing.txt index 69c1a9ae..b077fa0e 100644 --- a/docs/parallel_fuzzing.txt +++ b/docs/parallel_fuzzing.txt @@ -51,13 +51,25 @@ Each instance will also periodically rescan the top-level sync directory for any test cases found by other fuzzers - and will incorporate them into its own fuzzing when they are deemed interesting enough. -The only difference between the -M and -S modes is that the master instance -will still perform deterministic checks; while the secondary instances will +The difference between the -M and -S modes is that the master instance will +still perform deterministic checks; while the secondary instances will proceed straight to random tweaks. If you don't want to do deterministic fuzzing at all, it's OK to run all instances with -S. With very slow or complex targets, or when running heavily parallelized jobs, this is usually a good plan. -You can monitor the progress of your jobs from the command line with the +Note that running multiple -M instances is wasteful, although there is an +experimental support for parallelizing the deterministic checks. To leverage +that, you need to create -M instances like so: + +$ ./afl-fuzz -i testcase_dir -o sync_dir -M masterA:1/3 [...] +$ ./afl-fuzz -i testcase_dir -o sync_dir -M masterB:2/3 [...] +$ ./afl-fuzz -i testcase_dir -o sync_dir -M masterC:3/3 [...] + +...where the first value after ':' is the sequential ID of a particular master +instance (starting at 1), and the second value is the total number of fuzzers to +distribute the deterministic fuzzing across. + +You can also monitor the progress of your jobs from the command line with the provided afl-whatsup tool. When the instances are no longer finding new paths, it's probably time to stop. diff --git a/docs/sister_projects.txt b/docs/sister_projects.txt index f8fe7312..9c706045 100644 --- a/docs/sister_projects.txt +++ b/docs/sister_projects.txt @@ -78,6 +78,13 @@ TriforceAFL (Tim Newsham and Jesse Hertz) https://www.nccgroup.trust/us/about-us/newsroom-and-events/blog/2016/june/project-triforce-run-afl-on-everything/ +WinAFL (Ivan Fratric) +--------------------- + + As the name implies, allows you to fuzz Windows binaries (using DynamoRio). + + https://github.com/ivanfratric/winafl + ---------------- Network fuzzing: ---------------- diff --git a/docs/status_screen.txt b/docs/status_screen.txt index b1dd1dea..00fb0f19 100644 --- a/docs/status_screen.txt +++ b/docs/status_screen.txt @@ -119,7 +119,7 @@ If you feel that the fuzzer is progressing too slowly, see the note about the --------------- +--------------------------------------+ - | map density : 4763 (29.07%) | + | map density : 10.15% / 29.07% | | count coverage : 4.03 bits/tuple | +--------------------------------------+ @@ -127,7 +127,11 @@ The section provides some trivia about the coverage observed by the instrumentation embedded in the target binary. The first line in the box tells you how many branch tuples we have already -hit, in proportion to how much the bitmap can hold. Be wary of extremes: +hit, in proportion to how much the bitmap can hold. The number on the left +describes the current input; the one on the right is the value for the entire +input corpus. + +Be wary of extremes: - Absolute numbers below 200 or so suggest one of three things: that the program is extremely simple; that it is not instrumented properly (e.g., @@ -271,7 +275,7 @@ some of the more expensive deterministic fuzzing steps. | pend fav : 583 | | own finds : 0 | | imported : 0 | - | variable : 0 | + | stability : 100.00% | +---------------------+ The first field in this section tracks the path depth reached through the @@ -291,27 +295,33 @@ Next, we have the number of new paths found during this fuzzing section and imported from other fuzzer instances when doing parallelized fuzzing; and the number of inputs that produce seemingly variable behavior in the tested binary. -That last bit is actually fairly interesting. There are four quasi-common -explanations for variable behavior of the tested program: +That last bit is actually fairly interesting: it measures the consistency of +observed traces. If a program always behaves the same for the same input data, +it will earn a score of 100%. When the value is over 90%, the fuzzing process +is still unlikely to be negatively affected. If it gets much lower, you may +be in trouble, since AFL will have difficulty discerning between meaningful +and "phantom" effects of tweaking the input file. - - Use of uninitialized memory in conjunction with some intrinsic sources of - entropy in the tested binary. This can be indicative of a security bug. +Now, most targets will just get a 100% score, but when you see lower figures, +there are several things to look at: - - Attempts to create files that were already created during previous runs, or - otherwise interact with some form of persistent state. This is harmless, - but you may want to instruct the targeted program to write to stdout or to - /dev/null to avoid surprises (and disable the creation of temporary files - and similar artifacts, if applicable). + - The use of uninitialized memory in conjunction with some intrinsic sources + of entropy in the tested binary. Harmless to AFL, but could be indicative + of a security bug. - - Hitting functionality that is actually designed to behave randomly. For - example, when fuzzing sqlite, the fuzzer will dutifully detect variable - behavior once the mutation engine generates something like: + - Attempts to manipulate persistent resources, such as left over temporary + files or shared memory objects. This is usually harmless, but you may want + to double-check to make sure the program isn't bailing out prematurely. + Running out of disk space, SHM handles, or other global resources can + trigger this, too. - select random(); + - Hitting some functionality that is actually designed to behave randomly. + Generally harmless. For example, when fuzzing sqlite, an input like + 'select random();' will trigger a variable execution path. - - Multiple threads executing at once in semi-random order. This is usually - just a nuisance, but if the number of variable paths is very high, try the - following options: + - Multiple threads executing at once in semi-random order. This is harmless + when the 'stability' metric stays over 90% or so, but can become an issue + if not. Here's what to try: - Use afl-clang-fast from llvm_mode/ - it uses a thread-local tracking model that is less prone to concurrency issues, @@ -323,17 +333,10 @@ explanations for variable behavior of the tested program: - Replace pthreads with GNU Pth (https://www.gnu.org/software/pth/), which allows you to use a deterministic scheduler. -Less likely causes may include running out of disk space, SHM handles, or other -globally limited resources. - The paths where variable behavior is detected are marked with a matching entry in the /queue/.state/variable_behavior/ directory, so you can look them up easily. -If you can't suppress variable behavior and don't want to see these warnings, -simply set AFL_NO_VAR_CHECK=1 in the environment before running afl-fuzz. This -will also dramatically speed up session resumption. - 9) CPU load ----------- @@ -378,6 +381,7 @@ directory. This includes: - cur_path - currently processed entry number - pending_favs - number of favored entries still waiting to be fuzzed - pending_total - number of all entries waiting to be fuzzed + - stability - percentage of bitmap bytes that behave consistently - variable_paths - number of test cases showing variable behavior - unique_crashes - number of unique crashes recorded - unique_hangs - number of unique hangs encountered diff --git a/llvm_mode/README.llvm b/llvm_mode/README.llvm index f3789e2b..602d0e6c 100644 --- a/llvm_mode/README.llvm +++ b/llvm_mode/README.llvm @@ -163,8 +163,8 @@ wary of memory leaks and of the state of file descriptors. When running in this mode, the execution paths will inherently vary a bit depending on whether the input loop is being entered for the first time or -executed again. To avoid spurious warnings, the feature implies -AFL_NO_VAR_CHECK and hides the "variable path" warnings in the UI. +executed again. This can cause the "stability" metric in the UI to dip +slightly under 100%. PS. Because there are task switches still involved, the mode isn't as fast as "pure" in-process fuzzing offered, say, by LLVM's LibFuzzer; but it is a lot