diff --git a/afl-fuzz.c b/afl-fuzz.c index 62a5086b..fce24082 100644 --- a/afl-fuzz.c +++ b/afl-fuzz.c @@ -92,7 +92,9 @@ EXP_ST u8 *in_dir, /* Input directory with test cases */ *orig_cmdline; /* Original command line */ EXP_ST u32 exec_tmout = EXEC_TIMEOUT; /* Configurable exec timeout (ms) */ -EXP_ST u64 mem_limit = MEM_LIMIT; /* Memory cap for child (MB) */ +static u32 hang_tmout = EXEC_TIMEOUT; /* Timeout used for hang det (ms) */ + +EXP_ST u64 mem_limit = MEM_LIMIT; /* Memory cap for child (MB) */ static u32 stats_update_freq = 1; /* Stats update frequency (execs) */ @@ -132,7 +134,7 @@ static s32 forksrv_pid, /* PID of the fork server */ EXP_ST u8* trace_bits; /* SHM with instrumentation bitmap */ EXP_ST u8 virgin_bits[MAP_SIZE], /* Regions yet untouched by fuzzing */ - virgin_hang[MAP_SIZE], /* Bits we haven't seen in hangs */ + virgin_tmout[MAP_SIZE], /* Bits we haven't seen in tmouts */ virgin_crash[MAP_SIZE]; /* Bits we haven't seen in crashes */ static u8 var_bytes[MAP_SIZE]; /* Bytes that appear to be variable */ @@ -162,7 +164,8 @@ EXP_ST u32 queued_paths, /* Total number of queued testcases */ EXP_ST u64 total_crashes, /* Total number of crashes */ unique_crashes, /* Crashes with unique signatures */ - total_hangs, /* Total number of hangs */ + total_tmouts, /* Total number of timeouts */ + unique_tmouts, /* Timeouts with unique signatures */ unique_hangs, /* Hangs with unique signatures */ total_execs, /* Total execve() calls */ start_time, /* Unix start time (ms) */ @@ -178,7 +181,7 @@ EXP_ST u64 total_crashes, /* Total number of crashes */ blocks_eff_total, /* Blocks subject to effector maps */ blocks_eff_select; /* Blocks selected as fuzzable */ -static u32 subseq_hangs; /* Number of hangs in a row */ +static u32 subseq_tmouts; /* Number of timeouts in a row */ static u8 *stage_name = "init", /* Name of the current fuzz stage */ *stage_short, /* Short stage name */ @@ -308,7 +311,7 @@ enum { enum { /* 00 */ FAULT_NONE, - /* 01 */ FAULT_HANG, + /* 01 */ FAULT_TMOUT, /* 02 */ FAULT_CRASH, /* 03 */ FAULT_ERROR, /* 04 */ FAULT_NOINST, @@ -1031,7 +1034,7 @@ static u32 count_non_255_bytes(u8* mem) { /* Destructively simplify trace by eliminating hit count information and replacing it with 0x80 or 0x01 depending on whether the tuple - is hit or not. Called on every new crash or hang, should be + is hit or not. Called on every new crash or timeout, should be reasonably fast. */ static const u8 simplify_lookup[256] = { @@ -1339,7 +1342,7 @@ EXP_ST void setup_shm(void) { if (!in_bitmap) memset(virgin_bits, 255, MAP_SIZE); - memset(virgin_hang, 255, MAP_SIZE); + memset(virgin_tmout, 255, MAP_SIZE); memset(virgin_crash, 255, MAP_SIZE); shm_id = shmget(IPC_PRIVATE, MAP_SIZE, IPC_CREAT | IPC_EXCL | 0600); @@ -2252,7 +2255,7 @@ EXP_ST void init_forkserver(char** argv) { /* Execute target application, monitoring for timeouts. Return status information. The called program will update trace_bits[]. */ -static u8 run_target(char** argv) { +static u8 run_target(char** argv, u32 timeout) { static struct itimerval it; static u32 prev_timed_out = 0; @@ -2378,8 +2381,8 @@ static u8 run_target(char** argv) { /* Configure timeout, as requested by user, then wait for child to terminate. */ - it.it_value.tv_sec = (exec_tmout / 1000); - it.it_value.tv_usec = (exec_tmout % 1000) * 1000; + it.it_value.tv_sec = (timeout / 1000); + it.it_value.tv_usec = (timeout % 1000) * 1000; setitimer(ITIMER_REAL, &it, NULL); @@ -2429,7 +2432,7 @@ static u8 run_target(char** argv) { /* Report outcome to caller. */ - if (child_timed_out) return FAULT_HANG; + if (child_timed_out) return FAULT_TMOUT; if (WIFSIGNALED(status) && !stop_soon) { kill_signal = WTERMSIG(status); @@ -2529,7 +2532,8 @@ static u8 calibrate_case(char** argv, struct queue_entry* q, u8* use_mem, u64 start_us, stop_us; - s32 old_sc = stage_cur, old_sm = stage_max, old_tmout = exec_tmout; + s32 old_sc = stage_cur, old_sm = stage_max; + u32 use_tmout = exec_tmout; u8* old_sn = stage_name; /* Be a bit more generous about timeouts when resuming sessions, or when @@ -2537,8 +2541,8 @@ static u8 calibrate_case(char** argv, struct queue_entry* q, u8* use_mem, to intermittent latency. */ if (!from_queue || resuming_fuzz) - exec_tmout = MAX(exec_tmout + CAL_TMOUT_ADD, - exec_tmout * CAL_TMOUT_PERC / 100); + use_tmout = MAX(exec_tmout + CAL_TMOUT_ADD, + exec_tmout * CAL_TMOUT_PERC / 100); q->cal_failed++; @@ -2563,7 +2567,7 @@ static u8 calibrate_case(char** argv, struct queue_entry* q, u8* use_mem, write_to_testcase(use_mem, q->len); - fault = run_target(argv); + fault = run_target(argv, use_tmout); /* stop_soon is set by the handler for Ctrl+C. When it's pressed, we want to bail out quickly. */ @@ -2657,7 +2661,6 @@ static u8 calibrate_case(char** argv, struct queue_entry* q, u8* use_mem, stage_name = old_sn; stage_cur = old_sc; stage_max = old_sm; - exec_tmout = old_tmout; if (!first_run) show_stats(); @@ -2730,7 +2733,7 @@ static void perform_dry_run(char** argv) { break; - case FAULT_HANG: + case FAULT_TMOUT: if (timeout_given) { @@ -2739,7 +2742,7 @@ static void perform_dry_run(char** argv) { out. */ if (timeout_given > 1) { - WARNF("Test case results in a hang (skipping)"); + WARNF("Test case results in a timeout (skipping)"); q->cal_failed = CAL_CHANCES; cal_failures++; break; @@ -2753,7 +2756,7 @@ static void perform_dry_run(char** argv) { " '+' at the end of the value passed to -t ('-t %u+').\n", exec_tmout, exec_tmout); - FATAL("Test case '%s' results in a hang", fn); + FATAL("Test case '%s' results in a timeout", fn); } else { @@ -2765,7 +2768,7 @@ static void perform_dry_run(char** argv) { " If this test case is just a fluke, the other option is to just avoid it\n" " altogether, and find one that is less of a CPU hog.\n", exec_tmout); - FATAL("Test case '%s' results in a hang", fn); + FATAL("Test case '%s' results in a timeout", fn); } @@ -3161,14 +3164,14 @@ static u8 save_if_interesting(char** argv, void* mem, u32 len, u8 fault) { switch (fault) { - case FAULT_HANG: + case FAULT_TMOUT: - /* Hangs are not very interesting, but we're still obliged to keep + /* Timeouts are not very interesting, but we're still obliged to keep a handful of samples. We use the presence of new bits in the hang-specific bitmap as a signal of uniqueness. In "dumb" mode, we just keep everything. */ - total_hangs++; + total_tmouts++; if (unique_hangs >= KEEP_UNIQUE_HANG) return keeping; @@ -3180,7 +3183,23 @@ static u8 save_if_interesting(char** argv, void* mem, u32 len, u8 fault) { simplify_trace((u32*)trace_bits); #endif /* ^__x86_64__ */ - if (!has_new_bits(virgin_hang)) return keeping; + if (!has_new_bits(virgin_tmout)) return keeping; + + } + + unique_tmouts++; + + /* Before saving, we make sure that it's a genuine hang by re-running + the target with a more generous timeout (unless the default timeout + is already generous). */ + + if (exec_tmout < hang_tmout) { + + u8 new_fault; + write_to_testcase(mem, len); + new_fault = run_target(argv, hang_tmout); + + if (stop_soon || new_fault != FAULT_TMOUT) return keeping; } @@ -3204,8 +3223,9 @@ static u8 save_if_interesting(char** argv, void* mem, u32 len, u8 fault) { case FAULT_CRASH: - /* This is handled in a manner roughly similar to hangs, - except for slightly different limits. */ + /* This is handled in a manner roughly similar to timeouts, + except for slightly different limits and no need to re-run test + cases. */ total_crashes++; @@ -4132,10 +4152,10 @@ static void show_stats(void) { } - sprintf(tmp, "%s (%s%s unique)", DI(total_hangs), DI(unique_hangs), + sprintf(tmp, "%s (%s%s unique)", DI(total_tmouts), DI(unique_tmouts), (unique_hangs >= KEEP_UNIQUE_HANG) ? "+" : ""); - SAYF (bSTG bV bSTOP " total hangs : " cRST "%-22s " bSTG bV "\n", tmp); + SAYF (bSTG bV bSTOP " total tmouts : " cRST "%-22s " bSTG bV "\n", tmp); /* Aaaalmost there... hold on! */ @@ -4388,6 +4408,12 @@ static void show_init_stats(void) { } + /* In dumb mode, re-running every timing out test case with a generous time + limit is very expensive, so let's select a more conservative default. */ + + if (dumb_mode && !getenv("AFL_HANG_TMOUT")) + hang_tmout = exec_tmout * 4; + OKF("All set and ready to roll!"); } @@ -4452,7 +4478,7 @@ static u8 trim_case(char** argv, struct queue_entry* q, u8* in_buf) { write_with_gap(in_buf, q->len, remove_pos, trim_avail); - fault = run_target(argv); + fault = run_target(argv, exec_tmout); trim_execs++; if (stop_soon || fault == FAULT_ERROR) goto abort_trimming; @@ -4547,18 +4573,18 @@ EXP_ST u8 common_fuzz_stuff(char** argv, u8* out_buf, u32 len) { write_to_testcase(out_buf, len); - fault = run_target(argv); + fault = run_target(argv, exec_tmout); if (stop_soon) return 1; - if (fault == FAULT_HANG) { + if (fault == FAULT_TMOUT) { - if (subseq_hangs++ > HANG_LIMIT) { + if (subseq_tmouts++ > TMOUT_LIMIT) { cur_skipped_paths++; return 1; } - } else subseq_hangs = 0; + } else subseq_tmouts = 0; /* Users can hit us with SIGUSR1 to request the current input to be abandoned. */ @@ -4963,7 +4989,7 @@ static u8 fuzz_one(char** argv) { out_buf = ck_alloc_nozero(len); - subseq_hangs = 0; + subseq_tmouts = 0; cur_depth = queue_cur->depth; @@ -4973,7 +4999,7 @@ static u8 fuzz_one(char** argv) { if (queue_cur->cal_failed) { - u8 res = FAULT_HANG; + u8 res = FAULT_TMOUT; if (queue_cur->cal_failed < CAL_CHANCES) { @@ -6674,7 +6700,7 @@ static void sync_fuzzers(char** argv) { write_to_testcase(mem, st.st_size); - fault = run_target(argv); + fault = run_target(argv, exec_tmout); if (stop_soon) return; @@ -7180,7 +7206,7 @@ static void check_crash_handling(void) { " external crash reporting utility. This will cause issues due to the\n" " extended delay between the fuzzed binary malfunctioning and this fact\n" " being relayed to the fuzzer via the standard waitpid() API.\n\n" - " To avoid having crashes misinterpreted as hangs, please run the\n" + " To avoid having crashes misinterpreted as timeouts, please run the\n" " following commands:\n\n" " SL=/System/Library; PL=com.apple.ReportCrash\n" @@ -7210,7 +7236,7 @@ static void check_crash_handling(void) { " between stumbling upon a crash and having this information relayed to the\n" " fuzzer via the standard waitpid() API.\n\n" - " To avoid having crashes misinterpreted as hangs, please log in as root\n" + " To avoid having crashes misinterpreted as timeouts, please log in as root\n" " and temporarily modify /proc/sys/kernel/core_pattern, like so:\n\n" " echo core >/proc/sys/kernel/core_pattern\n"); @@ -7858,6 +7884,11 @@ int main(int argc, char** argv) { if (getenv("AFL_NO_CPU_RED")) no_cpu_meter_red = 1; if (getenv("AFL_SHUFFLE_QUEUE")) shuffle_queue = 1; + if (getenv("AFL_HANG_TMOUT")) { + hang_tmout = atoi(getenv("AFL_HANG_TMOUT")); + if (!hang_tmout) FATAL("Invalid value of AFL_HANG_TMOUT"); + } + if (dumb_mode == 2 && no_forkserver) FATAL("AFL_DUMB_FORKSRV and AFL_NO_FORKSRV are mutually exclusive"); diff --git a/config.h b/config.h index 4e610d4c..58f56c80 100644 --- a/config.h +++ b/config.h @@ -21,7 +21,7 @@ /* Version string: */ -#define VERSION "2.40b" +#define VERSION "2.41b" /****************************************************** * * @@ -38,7 +38,8 @@ #define FANCY_BOXES -/* Default timeout for fuzzed code (milliseconds): */ +/* Default timeout for fuzzed code (milliseconds). This is the upper bound, + also used for detecting hangs; the actual value is auto-scaled: */ #define EXEC_TIMEOUT 1000 @@ -64,9 +65,9 @@ #define CAL_CYCLES 8 #define CAL_CYCLES_LONG 40 -/* Number of subsequent hangs before abandoning an input file: */ +/* Number of subsequent timeouts before abandoning an input file: */ -#define HANG_LIMIT 250 +#define TMOUT_LIMIT 250 /* Maximum number of unique hangs or crashes to record: */ diff --git a/docs/ChangeLog b/docs/ChangeLog index 6eca31d5..7fa987a6 100644 --- a/docs/ChangeLog +++ b/docs/ChangeLog @@ -16,6 +16,14 @@ Not sure if you should upgrade? The lowest currently recommended version is 2.31b. If you're stuck on an earlier release, it's strongly advisable to get on with the times. +-------------- +Version 2.41b: +-------------- + + - Addressed a major user complaint related to timeout detection. Timing out + inputs are now binned as "hangs" only if they exceed a far more generous + time limit than the one used to reject slow paths. + -------------- Version 2.40b: -------------- diff --git a/docs/README b/docs/README index d6a6096d..1f9f7699 100644 --- a/docs/README +++ b/docs/README @@ -220,10 +220,11 @@ in real time: fatal signal (e.g., SIGSEGV, SIGILL, SIGABRT). The entries are grouped by the received signal. - - hangs/ - unique test cases that cause the tested program to time out. Note - that when default (aggressive) timeout settings are in effect, - this can be slightly noisy due to latency spikes and other - natural phenomena. + - hangs/ - unique test cases that cause the tested program to time out. The + default time limit before something is classified as a hang is + the larger of 1 second and the value of the -t parameter. + The value can be fine-tuned by setting AFL_HANG_TMOUT, but this + is rarely necessary. Crashes and hangs are considered "unique" if the associated execution paths involve any state transitions not seen in previously-recorded faults. If a diff --git a/docs/env_variables.txt b/docs/env_variables.txt index a91b43ae..bae8fd8d 100644 --- a/docs/env_variables.txt +++ b/docs/env_variables.txt @@ -116,6 +116,13 @@ checks or alter some of the more exotic semantics of the tool: intermittently, but it's not really recommended under normal operating conditions. + - Setting AFL_HANG_TMOUT allows you to specify a different timeout for + deciding if a particular test case is a "hang". The default is 1 second + or the value of the -t parameter, whichever is larger. Dialing the value + down can be useful if you are very concerned about slow inputs, or if you + don't want AFL to spend too much time classifying that stuff and just + rapidly put all timeouts in that bin. + - AFL_SHUFFLE_QUEUE randomly reorders the input queue on startup. Requested by some users for unorthodox parallelized fuzzing setups, but not advisable otherwise. diff --git a/docs/status_screen.txt b/docs/status_screen.txt index ac09804f..4502a363 100644 --- a/docs/status_screen.txt +++ b/docs/status_screen.txt @@ -231,7 +231,7 @@ things up. | favored paths : 879 (41.96%) | | new edges on : 423 (20.19%) | | total crashes : 0 (0 unique) | - | total hangs : 24 (19 unique) | + | total tmouts : 24 (19 unique) | +--------------------------------------+ This gives you several metrics that are of interest mostly to complete nerds. @@ -239,7 +239,11 @@ The section includes the number of paths that the fuzzer likes the most based on a minimization algorithm baked into the code (these will get considerably more air time), and the number of test cases that actually resulted in better edge coverage (versus just pushing the branch hit counters up). There are also -additional, more detailed counters for crashes and hangs. +additional, more detailed counters for crashes and timeouts. + +Note that the timeout counter is somewhat different from the hang counter; this +one includes all test cases that exceeded the timeout, even if they did not +exceed it by a margin sufficient to be classified as hangs. 7) Fuzzing strategy yields --------------------------