diff options
Diffstat (limited to 'src/afl-fuzz-init.c')
-rw-r--r-- | src/afl-fuzz-init.c | 2064 |
1 files changed, 2064 insertions, 0 deletions
diff --git a/src/afl-fuzz-init.c b/src/afl-fuzz-init.c new file mode 100644 index 00000000..8a3ee6fa --- /dev/null +++ b/src/afl-fuzz-init.c @@ -0,0 +1,2064 @@ +/* + american fuzzy lop - fuzzer code + -------------------------------- + + Written and maintained by Michal Zalewski <lcamtuf@google.com> + + Forkserver design by Jann Horn <jannhorn@googlemail.com> + + Copyright 2013, 2014, 2015, 2016, 2017 Google Inc. All rights reserved. + + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at: + + http://www.apache.org/licenses/LICENSE-2.0 + + This is the real deal: the program takes an instrumented binary and + attempts a variety of basic fuzzing tricks, paying close attention to + how they affect the execution path. + + */ + +#include "afl-fuzz.h" + +#ifdef HAVE_AFFINITY + +/* Build a list of processes bound to specific cores. Returns -1 if nothing + can be found. Assumes an upper bound of 4k CPUs. */ + +void bind_to_free_cpu(void) { + + DIR* d; + struct dirent* de; + cpu_set_t c; + + u8 cpu_used[4096] = {0}; + u32 i; + + if (cpu_core_count < 2) return; + + if (getenv("AFL_NO_AFFINITY")) { + + WARNF("Not binding to a CPU core (AFL_NO_AFFINITY set)."); + return; + + } + + d = opendir("/proc"); + + if (!d) { + + WARNF("Unable to access /proc - can't scan for free CPU cores."); + return; + + } + + ACTF("Checking CPU core loadout..."); + + /* Introduce some jitter, in case multiple AFL tasks are doing the same + thing at the same time... */ + + usleep(R(1000) * 250); + + /* Scan all /proc/<pid>/status entries, checking for Cpus_allowed_list. + Flag all processes bound to a specific CPU using cpu_used[]. This will + fail for some exotic binding setups, but is likely good enough in almost + all real-world use cases. */ + + while ((de = readdir(d))) { + + u8* fn; + FILE* f; + u8 tmp[MAX_LINE]; + u8 has_vmsize = 0; + + if (!isdigit(de->d_name[0])) continue; + + fn = alloc_printf("/proc/%s/status", de->d_name); + + if (!(f = fopen(fn, "r"))) { + + ck_free(fn); + continue; + + } + + while (fgets(tmp, MAX_LINE, f)) { + + u32 hval; + + /* Processes without VmSize are probably kernel tasks. */ + + if (!strncmp(tmp, "VmSize:\t", 8)) has_vmsize = 1; + + if (!strncmp(tmp, "Cpus_allowed_list:\t", 19) && !strchr(tmp, '-') && + !strchr(tmp, ',') && sscanf(tmp + 19, "%u", &hval) == 1 && + hval < sizeof(cpu_used) && has_vmsize) { + + cpu_used[hval] = 1; + break; + + } + + } + + ck_free(fn); + fclose(f); + + } + + closedir(d); + + for (i = 0; i < cpu_core_count; ++i) + if (!cpu_used[i]) break; + + if (i == cpu_core_count) { + + SAYF("\n" cLRD "[-] " cRST + "Uh-oh, looks like all %d CPU cores on your system are allocated to\n" + " other instances of afl-fuzz (or similar CPU-locked tasks). " + "Starting\n" + " another fuzzer on this machine is probably a bad plan, but if " + "you are\n" + " absolutely sure, you can set AFL_NO_AFFINITY and try again.\n", + cpu_core_count); + + FATAL("No more free CPU cores"); + + } + + OKF("Found a free CPU core, binding to #%u.", i); + + cpu_aff = i; + + CPU_ZERO(&c); + CPU_SET(i, &c); + + if (sched_setaffinity(0, sizeof(c), &c)) PFATAL("sched_setaffinity failed"); + +} + +#endif /* HAVE_AFFINITY */ + +/* Load postprocessor, if available. */ + +void setup_post(void) { + + void* dh; + u8* fn = getenv("AFL_POST_LIBRARY"); + u32 tlen = 6; + + if (!fn) return; + + ACTF("Loading postprocessor from '%s'...", fn); + + dh = dlopen(fn, RTLD_NOW); + if (!dh) FATAL("%s", dlerror()); + + post_handler = dlsym(dh, "afl_postprocess"); + if (!post_handler) FATAL("Symbol 'afl_postprocess' not found."); + + /* Do a quick test. It's better to segfault now than later =) */ + + post_handler("hello", &tlen); + + OKF("Postprocessor installed successfully."); + +} + +void setup_custom_mutator(void) { + + void* dh; + u8* fn = getenv("AFL_CUSTOM_MUTATOR_LIBRARY"); + + if (!fn) return; + + ACTF("Loading custom mutator library from '%s'...", fn); + + dh = dlopen(fn, RTLD_NOW); + if (!dh) FATAL("%s", dlerror()); + + custom_mutator = dlsym(dh, "afl_custom_mutator"); + if (!custom_mutator) FATAL("Symbol 'afl_custom_mutator' not found."); + + pre_save_handler = dlsym(dh, "afl_pre_save_handler"); + // if (!pre_save_handler) WARNF("Symbol 'afl_pre_save_handler' not found."); + + OKF("Custom mutator installed successfully."); + +} + +/* Shuffle an array of pointers. Might be slightly biased. */ + +static void shuffle_ptrs(void** ptrs, u32 cnt) { + + u32 i; + + for (i = 0; i < cnt - 2; ++i) { + + u32 j = i + UR(cnt - i); + void* s = ptrs[i]; + ptrs[i] = ptrs[j]; + ptrs[j] = s; + + } + +} + +/* Read all testcases from the input directory, then queue them for testing. + Called at startup. */ + +void read_testcases(void) { + + struct dirent** nl; + s32 nl_cnt; + u32 i; + u8* fn1; + + /* Auto-detect non-in-place resumption attempts. */ + + fn1 = alloc_printf("%s/queue", in_dir); + if (!access(fn1, F_OK)) + in_dir = fn1; + else + ck_free(fn1); + + ACTF("Scanning '%s'...", in_dir); + + /* We use scandir() + alphasort() rather than readdir() because otherwise, + the ordering of test cases would vary somewhat randomly and would be + difficult to control. */ + + nl_cnt = scandir(in_dir, &nl, NULL, alphasort); + + if (nl_cnt < 0) { + + if (errno == ENOENT || errno == ENOTDIR) + + SAYF("\n" cLRD "[-] " cRST + "The input directory does not seem to be valid - try again. The " + "fuzzer needs\n" + " one or more test case to start with - ideally, a small file " + "under 1 kB\n" + " or so. The cases must be stored as regular files directly in " + "the input\n" + " directory.\n"); + + PFATAL("Unable to open '%s'", in_dir); + + } + + if (shuffle_queue && nl_cnt > 1) { + + ACTF("Shuffling queue..."); + shuffle_ptrs((void**)nl, nl_cnt); + + } + + for (i = 0; i < nl_cnt; ++i) { + + struct stat st; + + u8* fn2 = alloc_printf("%s/%s", in_dir, nl[i]->d_name); + u8* dfn = + alloc_printf("%s/.state/deterministic_done/%s", in_dir, nl[i]->d_name); + + u8 passed_det = 0; + + free(nl[i]); /* not tracked */ + + if (lstat(fn2, &st) || access(fn2, R_OK)) + PFATAL("Unable to access '%s'", fn2); + + /* This also takes care of . and .. */ + + if (!S_ISREG(st.st_mode) || !st.st_size || strstr(fn2, "/README.txt")) { + + ck_free(fn2); + ck_free(dfn); + continue; + + } + + if (st.st_size > MAX_FILE) + FATAL("Test case '%s' is too big (%s, limit is %s)", fn2, DMS(st.st_size), + DMS(MAX_FILE)); + + /* Check for metadata that indicates that deterministic fuzzing + is complete for this entry. We don't want to repeat deterministic + fuzzing when resuming aborted scans, because it would be pointless + and probably very time-consuming. */ + + if (!access(dfn, F_OK)) passed_det = 1; + ck_free(dfn); + + add_to_queue(fn2, st.st_size, passed_det); + + } + + free(nl); /* not tracked */ + + if (!queued_paths) { + + SAYF("\n" cLRD "[-] " cRST + "Looks like there are no valid test cases in the input directory! The " + "fuzzer\n" + " needs one or more test case to start with - ideally, a small " + "file under\n" + " 1 kB or so. The cases must be stored as regular files directly " + "in the\n" + " input directory.\n"); + + FATAL("No usable test cases in '%s'", in_dir); + + } + + last_path_time = 0; + queued_at_start = queued_paths; + +} + +/* Examine map coverage. Called once, for first test case. */ + +static void check_map_coverage(void) { + + u32 i; + + if (count_bytes(trace_bits) < 100) return; + + for (i = (1 << (MAP_SIZE_POW2 - 1)); i < MAP_SIZE; ++i) + if (trace_bits[i]) return; + + WARNF("Recompile binary with newer version of afl to improve coverage!"); + +} + +/* Perform dry run of all test cases to confirm that the app is working as + expected. This is done only for the initial inputs, and only once. */ + +void perform_dry_run(char** argv) { + + struct queue_entry* q = queue; + u32 cal_failures = 0; + u8* skip_crashes = getenv("AFL_SKIP_CRASHES"); + + while (q) { + + u8* use_mem; + u8 res; + s32 fd; + + u8* fn = strrchr(q->fname, '/') + 1; + + ACTF("Attempting dry run with '%s'...", fn); + + fd = open(q->fname, O_RDONLY); + if (fd < 0) PFATAL("Unable to open '%s'", q->fname); + + use_mem = ck_alloc_nozero(q->len); + + if (read(fd, use_mem, q->len) != q->len) + FATAL("Short read from '%s'", q->fname); + + close(fd); + + res = calibrate_case(argv, q, use_mem, 0, 1); + ck_free(use_mem); + + if (stop_soon) return; + + if (res == crash_mode || res == FAULT_NOBITS) + SAYF(cGRA " len = %u, map size = %u, exec speed = %llu us\n" cRST, + q->len, q->bitmap_size, q->exec_us); + + switch (res) { + + case FAULT_NONE: + + if (q == queue) check_map_coverage(); + + if (crash_mode) FATAL("Test case '%s' does *NOT* crash", fn); + + break; + + case FAULT_TMOUT: + + if (timeout_given) { + + /* The -t nn+ syntax in the command line sets timeout_given to '2' and + instructs afl-fuzz to tolerate but skip queue entries that time + out. */ + + if (timeout_given > 1) { + + WARNF("Test case results in a timeout (skipping)"); + q->cal_failed = CAL_CHANCES; + ++cal_failures; + break; + + } + + SAYF("\n" cLRD "[-] " cRST + "The program took more than %u ms to process one of the initial " + "test cases.\n" + " Usually, the right thing to do is to relax the -t option - " + "or to delete it\n" + " altogether and allow the fuzzer to auto-calibrate. That " + "said, if you know\n" + " what you are doing and want to simply skip the unruly test " + "cases, append\n" + " '+' at the end of the value passed to -t ('-t %u+').\n", + exec_tmout, exec_tmout); + + FATAL("Test case '%s' results in a timeout", fn); + + } else { + + SAYF("\n" cLRD "[-] " cRST + "The program took more than %u ms to process one of the initial " + "test cases.\n" + " This is bad news; raising the limit with the -t option is " + "possible, but\n" + " will probably make the fuzzing process extremely slow.\n\n" + + " 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 timeout", fn); + + } + + case FAULT_CRASH: + + if (crash_mode) break; + + if (skip_crashes) { + + WARNF("Test case results in a crash (skipping)"); + q->cal_failed = CAL_CHANCES; + ++cal_failures; + break; + + } + + if (mem_limit) { + + SAYF("\n" cLRD "[-] " cRST + "Oops, the program crashed with one of the test cases provided. " + "There are\n" + " several possible explanations:\n\n" + + " - The test case causes known crashes under normal working " + "conditions. If\n" + " so, please remove it. The fuzzer should be seeded with " + "interesting\n" + " inputs - but not ones that cause an outright crash.\n\n" + + " - The current memory limit (%s) is too low for this " + "program, causing\n" + " it to die due to OOM when parsing valid files. To fix " + "this, try\n" + " bumping it up with the -m setting in the command line. " + "If in doubt,\n" + " try something along the lines of:\n\n" + + MSG_ULIMIT_USAGE + " /path/to/binary [...] <testcase )\n\n" + + " Tip: you can use http://jwilk.net/software/recidivm to " + "quickly\n" + " estimate the required amount of virtual memory for the " + "binary. Also,\n" + " if you are using ASAN, see %s/notes_for_asan.txt.\n\n" + + MSG_FORK_ON_APPLE + + " - Least likely, there is a horrible bug in the fuzzer. If " + "other options\n" + " fail, poke <afl-users@googlegroups.com> for " + "troubleshooting tips.\n", + DMS(mem_limit << 20), mem_limit - 1, doc_path); + + } else { + + SAYF("\n" cLRD "[-] " cRST + "Oops, the program crashed with one of the test cases provided. " + "There are\n" + " several possible explanations:\n\n" + + " - The test case causes known crashes under normal working " + "conditions. If\n" + " so, please remove it. The fuzzer should be seeded with " + "interesting\n" + " inputs - but not ones that cause an outright crash.\n\n" + + MSG_FORK_ON_APPLE + + " - Least likely, there is a horrible bug in the fuzzer. If " + "other options\n" + " fail, poke <afl-users@googlegroups.com> for " + "troubleshooting tips.\n"); + + } + +#undef MSG_ULIMIT_USAGE +#undef MSG_FORK_ON_APPLE + + FATAL("Test case '%s' results in a crash", fn); + + case FAULT_ERROR: + + FATAL("Unable to execute target application ('%s')", argv[0]); + + case FAULT_NOINST: FATAL("No instrumentation detected"); + + case FAULT_NOBITS: + + ++useless_at_start; + + if (!in_bitmap && !shuffle_queue) + WARNF("No new instrumentation output, test case may be useless."); + + break; + + } + + if (q->var_behavior) WARNF("Instrumentation output varies across runs."); + + q = q->next; + + } + + if (cal_failures) { + + if (cal_failures == queued_paths) + FATAL("All test cases time out%s, giving up!", + skip_crashes ? " or crash" : ""); + + WARNF("Skipped %u test cases (%0.02f%%) due to timeouts%s.", cal_failures, + ((double)cal_failures) * 100 / queued_paths, + skip_crashes ? " or crashes" : ""); + + if (cal_failures * 5 > queued_paths) + WARNF(cLRD "High percentage of rejected test cases, check settings!"); + + } + + OKF("All test cases processed."); + +} + +/* Helper function: link() if possible, copy otherwise. */ + +static void link_or_copy(u8* old_path, u8* new_path) { + + s32 i = link(old_path, new_path); + s32 sfd, dfd; + u8* tmp; + + if (!i) return; + + sfd = open(old_path, O_RDONLY); + if (sfd < 0) PFATAL("Unable to open '%s'", old_path); + + dfd = open(new_path, O_WRONLY | O_CREAT | O_EXCL, 0600); + if (dfd < 0) PFATAL("Unable to create '%s'", new_path); + + tmp = ck_alloc(64 * 1024); + + while ((i = read(sfd, tmp, 64 * 1024)) > 0) + ck_write(dfd, tmp, i, new_path); + + if (i < 0) PFATAL("read() failed"); + + ck_free(tmp); + close(sfd); + close(dfd); + +} + +/* Create hard links for input test cases in the output directory, choosing + good names and pivoting accordingly. */ + +void pivot_inputs(void) { + + struct queue_entry* q = queue; + u32 id = 0; + + ACTF("Creating hard links for all input files..."); + + while (q) { + + u8 *nfn, *rsl = strrchr(q->fname, '/'); + u32 orig_id; + + if (!rsl) + rsl = q->fname; + else + ++rsl; + + /* If the original file name conforms to the syntax and the recorded + ID matches the one we'd assign, just use the original file name. + This is valuable for resuming fuzzing runs. */ + + if (!strncmp(rsl, CASE_PREFIX, 3) && + sscanf(rsl + 3, "%06u", &orig_id) == 1 && orig_id == id) { + + u8* src_str; + u32 src_id; + + resuming_fuzz = 1; + nfn = alloc_printf("%s/queue/%s", out_dir, rsl); + + /* Since we're at it, let's also try to find parent and figure out the + appropriate depth for this entry. */ + + src_str = strchr(rsl + 3, ':'); + + if (src_str && sscanf(src_str + 1, "%06u", &src_id) == 1) { + + struct queue_entry* s = queue; + while (src_id-- && s) + s = s->next; + if (s) q->depth = s->depth + 1; + + if (max_depth < q->depth) max_depth = q->depth; + + } + + } else { + + /* No dice - invent a new name, capturing the original one as a + substring. */ + +#ifndef SIMPLE_FILES + + u8* use_name = strstr(rsl, ",orig:"); + + if (use_name) + use_name += 6; + else + use_name = rsl; + nfn = alloc_printf("%s/queue/id:%06u,orig:%s", out_dir, id, use_name); + +#else + + nfn = alloc_printf("%s/queue/id_%06u", out_dir, id); + +#endif /* ^!SIMPLE_FILES */ + + } + + /* Pivot to the new queue entry. */ + + link_or_copy(q->fname, nfn); + ck_free(q->fname); + q->fname = nfn; + + /* Make sure that the passed_det value carries over, too. */ + + if (q->passed_det) mark_as_det_done(q); + + q = q->next; + ++id; + + } + + if (in_place_resume) nuke_resume_dir(); + +} + +/* When resuming, try to find the queue position to start from. This makes sense + only when resuming, and when we can find the original fuzzer_stats. */ + +u32 find_start_position(void) { + + static u8 tmp[4096]; /* Ought to be enough for anybody. */ + + u8 *fn, *off; + s32 fd, i; + u32 ret; + + if (!resuming_fuzz) return 0; + + if (in_place_resume) + fn = alloc_printf("%s/fuzzer_stats", out_dir); + else + fn = alloc_printf("%s/../fuzzer_stats", in_dir); + + fd = open(fn, O_RDONLY); + ck_free(fn); + + if (fd < 0) return 0; + + i = read(fd, tmp, sizeof(tmp) - 1); + (void)i; /* Ignore errors */ + close(fd); + + off = strstr(tmp, "cur_path : "); + if (!off) return 0; + + ret = atoi(off + 20); + if (ret >= queued_paths) ret = 0; + return ret; + +} + +/* The same, but for timeouts. The idea is that when resuming sessions without + -t given, we don't want to keep auto-scaling the timeout over and over + again to prevent it from growing due to random flukes. */ + +void find_timeout(void) { + + static u8 tmp[4096]; /* Ought to be enough for anybody. */ + + u8 *fn, *off; + s32 fd, i; + u32 ret; + + if (!resuming_fuzz) return; + + if (in_place_resume) + fn = alloc_printf("%s/fuzzer_stats", out_dir); + else + fn = alloc_printf("%s/../fuzzer_stats", in_dir); + + fd = open(fn, O_RDONLY); + ck_free(fn); + + if (fd < 0) return; + + i = read(fd, tmp, sizeof(tmp) - 1); + (void)i; /* Ignore errors */ + close(fd); + + off = strstr(tmp, "exec_timeout : "); + if (!off) return; + + ret = atoi(off + 17); + if (ret <= 4) return; + + exec_tmout = ret; + timeout_given = 3; + +} + +/* A helper function for maybe_delete_out_dir(), deleting all prefixed + files in a directory. */ + +static u8 delete_files(u8* path, u8* prefix) { + + DIR* d; + struct dirent* d_ent; + + d = opendir(path); + + if (!d) return 0; + + while ((d_ent = readdir(d))) { + + if (d_ent->d_name[0] != '.' && + (!prefix || !strncmp(d_ent->d_name, prefix, strlen(prefix)))) { + + u8* fname = alloc_printf("%s/%s", path, d_ent->d_name); + if (unlink(fname)) PFATAL("Unable to delete '%s'", fname); + ck_free(fname); + + } + + } + + closedir(d); + + return !!rmdir(path); + +} + +/* Get the number of runnable processes, with some simple smoothing. */ + +double get_runnable_processes(void) { + + static double res; + +#if defined(__APPLE__) || defined(__FreeBSD__) || defined(__OpenBSD__) + + /* I don't see any portable sysctl or so that would quickly give us the + number of runnable processes; the 1-minute load average can be a + semi-decent approximation, though. */ + + if (getloadavg(&res, 1) != 1) return 0; + +#else + + /* On Linux, /proc/stat is probably the best way; load averages are + computed in funny ways and sometimes don't reflect extremely short-lived + processes well. */ + + FILE* f = fopen("/proc/stat", "r"); + u8 tmp[1024]; + u32 val = 0; + + if (!f) return 0; + + while (fgets(tmp, sizeof(tmp), f)) { + + if (!strncmp(tmp, "procs_running ", 14) || + !strncmp(tmp, "procs_blocked ", 14)) + val += atoi(tmp + 14); + + } + + fclose(f); + + if (!res) { + + res = val; + + } else { + + res = res * (1.0 - 1.0 / AVG_SMOOTHING) + + ((double)val) * (1.0 / AVG_SMOOTHING); + + } + +#endif /* ^(__APPLE__ || __FreeBSD__ || __OpenBSD__) */ + + return res; + +} + +/* Delete the temporary directory used for in-place session resume. */ + +void nuke_resume_dir(void) { + + u8* fn; + + fn = alloc_printf("%s/_resume/.state/deterministic_done", out_dir); + if (delete_files(fn, CASE_PREFIX)) goto dir_cleanup_failed; + ck_free(fn); + + fn = alloc_printf("%s/_resume/.state/auto_extras", out_dir); + if (delete_files(fn, "auto_")) goto dir_cleanup_failed; + ck_free(fn); + + fn = alloc_printf("%s/_resume/.state/redundant_edges", out_dir); + if (delete_files(fn, CASE_PREFIX)) goto dir_cleanup_failed; + ck_free(fn); + + fn = alloc_printf("%s/_resume/.state/variable_behavior", out_dir); + if (delete_files(fn, CASE_PREFIX)) goto dir_cleanup_failed; + ck_free(fn); + + fn = alloc_printf("%s/_resume/.state", out_dir); + if (rmdir(fn) && errno != ENOENT) goto dir_cleanup_failed; + ck_free(fn); + + fn = alloc_printf("%s/_resume", out_dir); + if (delete_files(fn, CASE_PREFIX)) goto dir_cleanup_failed; + ck_free(fn); + + return; + +dir_cleanup_failed: + + FATAL("_resume directory cleanup failed"); + +} + +/* Delete fuzzer output directory if we recognize it as ours, if the fuzzer + is not currently running, and if the last run time isn't too great. */ + +void maybe_delete_out_dir(void) { + + FILE* f; + u8* fn = alloc_printf("%s/fuzzer_stats", out_dir); + + /* See if the output directory is locked. If yes, bail out. If not, + create a lock that will persist for the lifetime of the process + (this requires leaving the descriptor open).*/ + + out_dir_fd = open(out_dir, O_RDONLY); + if (out_dir_fd < 0) PFATAL("Unable to open '%s'", out_dir); + +#ifndef __sun + + if (flock(out_dir_fd, LOCK_EX | LOCK_NB) && errno == EWOULDBLOCK) { + + SAYF("\n" cLRD "[-] " cRST + "Looks like the job output directory is being actively used by " + "another\n" + " instance of afl-fuzz. You will need to choose a different %s\n" + " or stop the other process first.\n", + sync_id ? "fuzzer ID" : "output location"); + + FATAL("Directory '%s' is in use", out_dir); + + } + +#endif /* !__sun */ + + f = fopen(fn, "r"); + + if (f) { + + u64 start_time2, last_update; + + if (fscanf(f, + "start_time : %llu\n" + "last_update : %llu\n", + &start_time2, &last_update) != 2) + FATAL("Malformed data in '%s'", fn); + + fclose(f); + + /* Let's see how much work is at stake. */ + + if (!in_place_resume && last_update - start_time2 > OUTPUT_GRACE * 60) { + + SAYF("\n" cLRD "[-] " cRST + "The job output directory already exists and contains the results " + "of more\n" + " than %d minutes worth of fuzzing. To avoid data loss, afl-fuzz " + "will *NOT*\n" + " automatically delete this data for you.\n\n" + + " If you wish to start a new session, remove or rename the " + "directory manually,\n" + " or specify a different output location for this job. To resume " + "the old\n" + " session, put '-' as the input directory in the command line " + "('-i -') and\n" + " try again.\n", + OUTPUT_GRACE); + + FATAL("At-risk data found in '%s'", out_dir); + + } + + } + + ck_free(fn); + + /* The idea for in-place resume is pretty simple: we temporarily move the old + queue/ to a new location that gets deleted once import to the new queue/ + is finished. If _resume/ already exists, the current queue/ may be + incomplete due to an earlier abort, so we want to use the old _resume/ + dir instead, and we let rename() fail silently. */ + + if (in_place_resume) { + + u8* orig_q = alloc_printf("%s/queue", out_dir); + + in_dir = alloc_printf("%s/_resume", out_dir); + + rename(orig_q, in_dir); /* Ignore errors */ + + OKF("Output directory exists, will attempt session resume."); + + ck_free(orig_q); + + } else { + + OKF("Output directory exists but deemed OK to reuse."); + + } + + ACTF("Deleting old session data..."); + + /* Okay, let's get the ball rolling! First, we need to get rid of the entries + in <out_dir>/.synced/.../id:*, if any are present. */ + + if (!in_place_resume) { + + fn = alloc_printf("%s/.synced", out_dir); + if (delete_files(fn, NULL)) goto dir_cleanup_failed; + ck_free(fn); + + } + + /* Next, we need to clean up <out_dir>/queue/.state/ subdirectories: */ + + fn = alloc_printf("%s/queue/.state/deterministic_done", out_dir); + if (delete_files(fn, CASE_PREFIX)) goto dir_cleanup_failed; + ck_free(fn); + + fn = alloc_printf("%s/queue/.state/auto_extras", out_dir); + if (delete_files(fn, "auto_")) goto dir_cleanup_failed; + ck_free(fn); + + fn = alloc_printf("%s/queue/.state/redundant_edges", out_dir); + if (delete_files(fn, CASE_PREFIX)) goto dir_cleanup_failed; + ck_free(fn); + + fn = alloc_printf("%s/queue/.state/variable_behavior", out_dir); + if (delete_files(fn, CASE_PREFIX)) goto dir_cleanup_failed; + ck_free(fn); + + /* Then, get rid of the .state subdirectory itself (should be empty by now) + and everything matching <out_dir>/queue/id:*. */ + + fn = alloc_printf("%s/queue/.state", out_dir); + if (rmdir(fn) && errno != ENOENT) goto dir_cleanup_failed; + ck_free(fn); + + fn = alloc_printf("%s/queue", out_dir); + if (delete_files(fn, CASE_PREFIX)) goto dir_cleanup_failed; + ck_free(fn); + + /* All right, let's do <out_dir>/crashes/id:* and <out_dir>/hangs/id:*. */ + + if (!in_place_resume) { + + fn = alloc_printf("%s/crashes/README.txt", out_dir); + unlink(fn); /* Ignore errors */ + ck_free(fn); + + } + + fn = alloc_printf("%s/crashes", out_dir); + + /* Make backup of the crashes directory if it's not empty and if we're + doing in-place resume. */ + + if (in_place_resume && rmdir(fn)) { + + time_t cur_t = time(0); + struct tm* t = localtime(&cur_t); + +#ifndef SIMPLE_FILES + + u8* nfn = alloc_printf("%s.%04d-%02d-%02d-%02d:%02d:%02d", fn, + t->tm_year + 1900, t->tm_mon + 1, t->tm_mday, + t->tm_hour, t->tm_min, t->tm_sec); + +#else + + u8* nfn = alloc_printf("%s_%04d%02d%02d%02d%02d%02d", fn, t->tm_year + 1900, + t->tm_mon + 1, t->tm_mday, t->tm_hour, t->tm_min, + t->tm_sec); + +#endif /* ^!SIMPLE_FILES */ + + rename(fn, nfn); /* Ignore errors. */ + ck_free(nfn); + + } + + if (delete_files(fn, CASE_PREFIX)) goto dir_cleanup_failed; + ck_free(fn); + + fn = alloc_printf("%s/hangs", out_dir); + + /* Backup hangs, too. */ + + if (in_place_resume && rmdir(fn)) { + + time_t cur_t = time(0); + struct tm* t = localtime(&cur_t); + +#ifndef SIMPLE_FILES + + u8* nfn = alloc_printf("%s.%04d-%02d-%02d-%02d:%02d:%02d", fn, + t->tm_year + 1900, t->tm_mon + 1, t->tm_mday, + t->tm_hour, t->tm_min, t->tm_sec); + +#else + + u8* nfn = alloc_printf("%s_%04d%02d%02d%02d%02d%02d", fn, t->tm_year + 1900, + t->tm_mon + 1, t->tm_mday, t->tm_hour, t->tm_min, + t->tm_sec); + +#endif /* ^!SIMPLE_FILES */ + + rename(fn, nfn); /* Ignore errors. */ + ck_free(nfn); + + } + + if (delete_files(fn, CASE_PREFIX)) goto dir_cleanup_failed; + ck_free(fn); + + /* And now, for some finishing touches. */ + + if (file_extension) { + + fn = alloc_printf("%s/.cur_input.%s", out_dir, file_extension); + + } else { + + fn = alloc_printf("%s/.cur_input", out_dir); + + } + + if (unlink(fn) && errno != ENOENT) goto dir_cleanup_failed; + ck_free(fn); + + fn = alloc_printf("%s/fuzz_bitmap", out_dir); + if (unlink(fn) && errno != ENOENT) goto dir_cleanup_failed; + ck_free(fn); + + if (!in_place_resume) { + + fn = alloc_printf("%s/fuzzer_stats", out_dir); + if (unlink(fn) && errno != ENOENT) goto dir_cleanup_failed; + ck_free(fn); + + } + + fn = alloc_printf("%s/plot_data", out_dir); + if (unlink(fn) && errno != ENOENT) goto dir_cleanup_failed; + ck_free(fn); + + fn = alloc_printf("%s/cmdline", out_dir); + if (unlink(fn) && errno != ENOENT) goto dir_cleanup_failed; + ck_free(fn); + + OKF("Output dir cleanup successful."); + + /* Wow... is that all? If yes, celebrate! */ + + return; + +dir_cleanup_failed: + + SAYF("\n" cLRD "[-] " cRST + "Whoops, the fuzzer tried to reuse your output directory, but bumped " + "into\n" + " some files that shouldn't be there or that couldn't be removed - " + "so it\n" + " decided to abort! This happened while processing this path:\n\n" + + " %s\n\n" + " Please examine and manually delete the files, or specify a " + "different\n" + " output location for the tool.\n", + fn); + + FATAL("Output directory cleanup failed"); + +} + +/* Prepare output directories and fds. */ + +void setup_dirs_fds(void) { + + u8* tmp; + s32 fd; + + ACTF("Setting up output directories..."); + + if (sync_id && mkdir(sync_dir, 0700) && errno != EEXIST) + PFATAL("Unable to create '%s'", sync_dir); + + if (mkdir(out_dir, 0700)) { + + if (errno != EEXIST) PFATAL("Unable to create '%s'", out_dir); + + maybe_delete_out_dir(); + + } else { + + if (in_place_resume) + FATAL("Resume attempted but old output directory not found"); + + out_dir_fd = open(out_dir, O_RDONLY); + +#ifndef __sun + + if (out_dir_fd < 0 || flock(out_dir_fd, LOCK_EX | LOCK_NB)) + PFATAL("Unable to flock() output directory."); + +#endif /* !__sun */ + + } + + /* Queue directory for any starting & discovered paths. */ + + tmp = alloc_printf("%s/queue", out_dir); + if (mkdir(tmp, 0700)) PFATAL("Unable to create '%s'", tmp); + ck_free(tmp); + + /* Top-level directory for queue metadata used for session + resume and related tasks. */ + + tmp = alloc_printf("%s/queue/.state/", out_dir); + if (mkdir(tmp, 0700)) PFATAL("Unable to create '%s'", tmp); + ck_free(tmp); + + /* Directory for flagging queue entries that went through + deterministic fuzzing in the past. */ + + tmp = alloc_printf("%s/queue/.state/deterministic_done/", out_dir); + if (mkdir(tmp, 0700)) PFATAL("Unable to create '%s'", tmp); + ck_free(tmp); + + /* Directory with the auto-selected dictionary entries. */ + + tmp = alloc_printf("%s/queue/.state/auto_extras/", out_dir); + if (mkdir(tmp, 0700)) PFATAL("Unable to create '%s'", tmp); + ck_free(tmp); + + /* The set of paths currently deemed redundant. */ + + tmp = alloc_printf("%s/queue/.state/redundant_edges/", out_dir); + if (mkdir(tmp, 0700)) PFATAL("Unable to create '%s'", tmp); + ck_free(tmp); + + /* The set of paths showing variable behavior. */ + + tmp = alloc_printf("%s/queue/.state/variable_behavior/", out_dir); + if (mkdir(tmp, 0700)) PFATAL("Unable to create '%s'", tmp); + ck_free(tmp); + + /* Sync directory for keeping track of cooperating fuzzers. */ + + if (sync_id) { + + tmp = alloc_printf("%s/.synced/", out_dir); + + if (mkdir(tmp, 0700) && (!in_place_resume || errno != EEXIST)) + PFATAL("Unable to create '%s'", tmp); + + ck_free(tmp); + + } + + /* All recorded crashes. */ + + tmp = alloc_printf("%s/crashes", out_dir); + if (mkdir(tmp, 0700)) PFATAL("Unable to create '%s'", tmp); + ck_free(tmp); + + /* All recorded hangs. */ + + tmp = alloc_printf("%s/hangs", out_dir); + if (mkdir(tmp, 0700)) PFATAL("Unable to create '%s'", tmp); + ck_free(tmp); + + /* Generally useful file descriptors. */ + + dev_null_fd = open("/dev/null", O_RDWR); + if (dev_null_fd < 0) PFATAL("Unable to open /dev/null"); + +#ifndef HAVE_ARC4RANDOM + dev_urandom_fd = open("/dev/urandom", O_RDONLY); + if (dev_urandom_fd < 0) PFATAL("Unable to open /dev/urandom"); +#endif + + /* Gnuplot output file. */ + + tmp = alloc_printf("%s/plot_data", out_dir); + fd = open(tmp, O_WRONLY | O_CREAT | O_EXCL, 0600); + if (fd < 0) PFATAL("Unable to create '%s'", tmp); + ck_free(tmp); + + plot_file = fdopen(fd, "w"); + if (!plot_file) PFATAL("fdopen() failed"); + + fprintf(plot_file, + "# unix_time, cycles_done, cur_path, paths_total, " + "pending_total, pending_favs, map_size, unique_crashes, " + "unique_hangs, max_depth, execs_per_sec\n"); + /* ignore errors */ + +} + +void setup_cmdline_file(char** argv) { + + u8* tmp; + s32 fd; + u32 i = 0; + + FILE* cmdline_file = NULL; + + /* Store the command line to reproduce our findings */ + tmp = alloc_printf("%s/cmdline", out_dir); + fd = open(tmp, O_WRONLY | O_CREAT | O_EXCL, 0600); + if (fd < 0) PFATAL("Unable to create '%s'", tmp); + ck_free(tmp); + + cmdline_file = fdopen(fd, "w"); + if (!cmdline_file) PFATAL("fdopen() failed"); + + while (argv[i]) { + + fprintf(cmdline_file, "%s\n", argv[i]); + ++i; + + } + + fclose(cmdline_file); + +} + +/* Setup the output file for fuzzed data, if not using -f. */ + +void setup_stdio_file(void) { + + u8* fn; + if (file_extension) { + + fn = alloc_printf("%s/.cur_input.%s", out_dir, file_extension); + + } else { + + fn = alloc_printf("%s/.cur_input", out_dir); + + } + + unlink(fn); /* Ignore errors */ + + out_fd = open(fn, O_RDWR | O_CREAT | O_EXCL, 0600); + + if (out_fd < 0) PFATAL("Unable to create '%s'", fn); + + ck_free(fn); + +} + +/* Make sure that core dumps don't go to a program. */ + +void check_crash_handling(void) { + +#ifdef __APPLE__ + + /* Yuck! There appears to be no simple C API to query for the state of + loaded daemons on MacOS X, and I'm a bit hesitant to do something + more sophisticated, such as disabling crash reporting via Mach ports, + until I get a box to test the code. So, for now, we check for crash + reporting the awful way. */ + + if (system("launchctl list 2>/dev/null | grep -q '\\.ReportCrash$'")) return; + + SAYF( + "\n" cLRD "[-] " cRST + "Whoops, your system is configured to forward crash notifications to an\n" + " 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 timeouts, please run the\n" + " following commands:\n\n" + + " SL=/System/Library; PL=com.apple.ReportCrash\n" + " launchctl unload -w ${SL}/LaunchAgents/${PL}.plist\n" + " sudo launchctl unload -w ${SL}/LaunchDaemons/${PL}.Root.plist\n"); + + if (!getenv("AFL_I_DONT_CARE_ABOUT_MISSING_CRASHES")) + FATAL("Crash reporter detected"); + +#else + + /* This is Linux specific, but I don't think there's anything equivalent on + *BSD, so we can just let it slide for now. */ + + s32 fd = open("/proc/sys/kernel/core_pattern", O_RDONLY); + u8 fchar; + + if (fd < 0) return; + + ACTF("Checking core_pattern..."); + + if (read(fd, &fchar, 1) == 1 && fchar == '|') { + + SAYF( + "\n" cLRD "[-] " cRST + "Hmm, your system is configured to send core dump notifications to an\n" + " external utility. This will cause issues: there will be an " + "extended delay\n" + " 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 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"); + + if (!getenv("AFL_I_DONT_CARE_ABOUT_MISSING_CRASHES")) + FATAL("Pipe at the beginning of 'core_pattern'"); + + } + + close(fd); + +#endif /* ^__APPLE__ */ + +} + +/* Check CPU governor. */ + +void check_cpu_governor(void) { + +#ifdef __linux__ + FILE* f; + u8 tmp[128]; + u64 min = 0, max = 0; + + if (getenv("AFL_SKIP_CPUFREQ")) return; + + if (cpu_aff > 0) + snprintf(tmp, sizeof(tmp), "%s%d%s", "/sys/devices/system/cpu/cpu", cpu_aff, + "/cpufreq/scaling_governor"); + else + snprintf(tmp, sizeof(tmp), "%s", + "/sys/devices/system/cpu/cpu0/cpufreq/scaling_governor"); + f = fopen("/sys/devices/system/cpu/cpu0/cpufreq/scaling_governor", "r"); + if (!f) { + + if (cpu_aff > 0) + snprintf(tmp, sizeof(tmp), "%s%d%s", + "/sys/devices/system/cpu/cpufreq/policy", cpu_aff, + "/scaling_governor"); + else + snprintf(tmp, sizeof(tmp), "%s", + "/sys/devices/system/cpu/cpufreq/policy0/scaling_governor"); + f = fopen(tmp, "r"); + + } + + if (!f) { + + WARNF("Could not check CPU scaling governor"); + return; + + } + + ACTF("Checking CPU scaling governor..."); + + if (!fgets(tmp, 128, f)) PFATAL("fgets() failed"); + + fclose(f); + + if (!strncmp(tmp, "perf", 4)) return; + + f = fopen("/sys/devices/system/cpu/cpu0/cpufreq/scaling_min_freq", "r"); + + if (f) { + + if (fscanf(f, "%llu", &min) != 1) min = 0; + fclose(f); + + } + + f = fopen("/sys/devices/system/cpu/cpu0/cpufreq/scaling_max_freq", "r"); + + if (f) { + + if (fscanf(f, "%llu", &max) != 1) max = 0; + fclose(f); + + } + + if (min == max) return; + + SAYF("\n" cLRD "[-] " cRST + "Whoops, your system uses on-demand CPU frequency scaling, adjusted\n" + " between %llu and %llu MHz. Unfortunately, the scaling algorithm in " + "the\n" + " kernel is imperfect and can miss the short-lived processes spawned " + "by\n" + " afl-fuzz. To keep things moving, run these commands as root:\n\n" + + " cd /sys/devices/system/cpu\n" + " echo performance | tee cpu*/cpufreq/scaling_governor\n\n" + + " You can later go back to the original state by replacing " + "'performance' with\n" + " 'ondemand'. If you don't want to change the settings, set " + "AFL_SKIP_CPUFREQ\n" + " to make afl-fuzz skip this check - but expect some performance " + "drop.\n", + min / 1024, max / 1024); + + FATAL("Suboptimal CPU scaling governor"); +#endif + +} + +/* Count the number of logical CPU cores. */ + +void get_core_count(void) { + +#if defined(__APPLE__) || defined(__FreeBSD__) || defined(__OpenBSD__) + + size_t s = sizeof(cpu_core_count); + + /* On *BSD systems, we can just use a sysctl to get the number of CPUs. */ + +# ifdef __APPLE__ + + if (sysctlbyname("hw.logicalcpu", &cpu_core_count, &s, NULL, 0) < 0) return; + +# else + + int s_name[2] = {CTL_HW, HW_NCPU}; + + if (sysctl(s_name, 2, &cpu_core_count, &s, NULL, 0) < 0) return; + +# endif /* ^__APPLE__ */ + +#else + +# ifdef HAVE_AFFINITY + + cpu_core_count = sysconf(_SC_NPROCESSORS_ONLN); + +# else + + FILE* f = fopen("/proc/stat", "r"); + u8 tmp[1024]; + + if (!f) return; + + while (fgets(tmp, sizeof(tmp), f)) + if (!strncmp(tmp, "cpu", 3) && isdigit(tmp[3])) ++cpu_core_count; + + fclose(f); + +# endif /* ^HAVE_AFFINITY */ + +#endif /* ^(__APPLE__ || __FreeBSD__ || __OpenBSD__) */ + + if (cpu_core_count > 0) { + + u32 cur_runnable = 0; + + cur_runnable = (u32)get_runnable_processes(); + +#if defined(__APPLE__) || defined(__FreeBSD__) || defined(__OpenBSD__) + + /* Add ourselves, since the 1-minute average doesn't include that yet. */ + + ++cur_runnable; + +#endif /* __APPLE__ || __FreeBSD__ || __OpenBSD__ */ + + OKF("You have %d CPU core%s and %u runnable tasks (utilization: %0.0f%%).", + cpu_core_count, cpu_core_count > 1 ? "s" : "", cur_runnable, + cur_runnable * 100.0 / cpu_core_count); + + if (cpu_core_count > 1) { + + if (cur_runnable > cpu_core_count * 1.5) { + + WARNF("System under apparent load, performance may be spotty."); + + } else if (cur_runnable + 1 <= cpu_core_count) { + + OKF("Try parallel jobs - see %s/parallel_fuzzing.txt.", doc_path); + + } + + } + + } else { + + cpu_core_count = 0; + WARNF("Unable to figure out the number of CPU cores."); + + } + +} + +/* Validate and fix up out_dir and sync_dir when using -S. */ + +void fix_up_sync(void) { + + u8* x = sync_id; + + if (dumb_mode) FATAL("-S / -M and -n are mutually exclusive"); + + if (skip_deterministic) { + + if (force_deterministic) FATAL("use -S instead of -M -d"); + // else + // FATAL("-S already implies -d"); + + } + + while (*x) { + + if (!isalnum(*x) && *x != '_' && *x != '-') + FATAL("Non-alphanumeric fuzzer ID specified via -S or -M"); + + ++x; + + } + + if (strlen(sync_id) > 32) FATAL("Fuzzer ID too long"); + + x = alloc_printf("%s/%s", out_dir, sync_id); + + sync_dir = out_dir; + out_dir = x; + + if (!force_deterministic) { + + skip_deterministic = 1; + use_splicing = 1; + + } + +} + +/* Handle screen resize (SIGWINCH). */ + +static void handle_resize(int sig) { + + clear_screen = 1; + +} + +/* Check ASAN options. */ + +void check_asan_opts(void) { + + u8* x = getenv("ASAN_OPTIONS"); + + if (x) { + + if (!strstr(x, "abort_on_error=1")) + FATAL("Custom ASAN_OPTIONS set without abort_on_error=1 - please fix!"); + + if (!strstr(x, "symbolize=0")) + FATAL("Custom ASAN_OPTIONS set without symbolize=0 - please fix!"); + + } + + x = getenv("MSAN_OPTIONS"); + + if (x) { + + if (!strstr(x, "exit_code=" STRINGIFY(MSAN_ERROR))) + FATAL("Custom MSAN_OPTIONS set without exit_code=" STRINGIFY( + MSAN_ERROR) " - please fix!"); + + if (!strstr(x, "symbolize=0")) + FATAL("Custom MSAN_OPTIONS set without symbolize=0 - please fix!"); + + } + +} + +/* Handle stop signal (Ctrl-C, etc). */ + +static void handle_stop_sig(int sig) { + + stop_soon = 1; + + if (child_pid > 0) kill(child_pid, SIGKILL); + if (forksrv_pid > 0) kill(forksrv_pid, SIGKILL); + +} + +/* Handle skip request (SIGUSR1). */ + +static void handle_skipreq(int sig) { + + skip_requested = 1; + +} + +/* Do a PATH search and find target binary to see that it exists and + isn't a shell script - a common and painful mistake. We also check for + a valid ELF header and for evidence of AFL instrumentation. */ + +void check_binary(u8* fname) { + + u8* env_path = 0; + struct stat st; + + s32 fd; + u8* f_data; + u32 f_len = 0; + + ACTF("Validating target binary..."); + + if (strchr(fname, '/') || !(env_path = getenv("PATH"))) { + + target_path = ck_strdup(fname); + if (stat(target_path, &st) || !S_ISREG(st.st_mode) || + !(st.st_mode & 0111) || (f_len = st.st_size) < 4) + FATAL("Program '%s' not found or not executable", fname); + + } else { + + while (env_path) { + + u8 *cur_elem, *delim = strchr(env_path, ':'); + + if (delim) { + + cur_elem = ck_alloc(delim - env_path + 1); + memcpy(cur_elem, env_path, delim - env_path); + ++delim; + + } else + + cur_elem = ck_strdup(env_path); + + env_path = delim; + + if (cur_elem[0]) + target_path = alloc_printf("%s/%s", cur_elem, fname); + else + target_path = ck_strdup(fname); + + ck_free(cur_elem); + + if (!stat(target_path, &st) && S_ISREG(st.st_mode) && + (st.st_mode & 0111) && (f_len = st.st_size) >= 4) + break; + + ck_free(target_path); + target_path = 0; + + } + + if (!target_path) FATAL("Program '%s' not found or not executable", fname); + + } + + if (getenv("AFL_SKIP_BIN_CHECK")) return; + + /* Check for blatant user errors. */ + + if ((!strncmp(target_path, "/tmp/", 5) && !strchr(target_path + 5, '/')) || + (!strncmp(target_path, "/var/tmp/", 9) && !strchr(target_path + 9, '/'))) + FATAL("Please don't keep binaries in /tmp or /var/tmp"); + + fd = open(target_path, O_RDONLY); + + if (fd < 0) PFATAL("Unable to open '%s'", target_path); + + f_data = mmap(0, f_len, PROT_READ, MAP_PRIVATE, fd, 0); + + if (f_data == MAP_FAILED) PFATAL("Unable to mmap file '%s'", target_path); + + close(fd); + + if (f_data[0] == '#' && f_data[1] == '!') { + + SAYF("\n" cLRD "[-] " cRST + "Oops, the target binary looks like a shell script. Some build " + "systems will\n" + " sometimes generate shell stubs for dynamically linked programs; " + "try static\n" + " library mode (./configure --disable-shared) if that's the " + "case.\n\n" + + " Another possible cause is that you are actually trying to use a " + "shell\n" + " wrapper around the fuzzed component. Invoking shell can slow " + "down the\n" + " fuzzing process by a factor of 20x or more; it's best to write " + "the wrapper\n" + " in a compiled language instead.\n"); + + FATAL("Program '%s' is a shell script", target_path); + + } + +#ifndef __APPLE__ + + if (f_data[0] != 0x7f || memcmp(f_data + 1, "ELF", 3)) + FATAL("Program '%s' is not an ELF binary", target_path); + +#else + +# if !defined(__arm__) && !defined(__arm64__) + if (f_data[0] != 0xCF || f_data[1] != 0xFA || f_data[2] != 0xED) + FATAL("Program '%s' is not a 64-bit Mach-O binary", target_path); +# endif + +#endif /* ^!__APPLE__ */ + + if (!qemu_mode && !unicorn_mode && !dumb_mode && + !memmem(f_data, f_len, SHM_ENV_VAR, strlen(SHM_ENV_VAR) + 1)) { + + SAYF( + "\n" cLRD "[-] " cRST + "Looks like the target binary is not instrumented! The fuzzer depends " + "on\n" + " compile-time instrumentation to isolate interesting test cases " + "while\n" + " mutating the input data. For more information, and for tips on " + "how to\n" + " instrument binaries, please see %s/README.\n\n" + + " When source code is not available, you may be able to leverage " + "QEMU\n" + " mode support. Consult the README for tips on how to enable this.\n" + + " (It is also possible to use afl-fuzz as a traditional, \"dumb\" " + "fuzzer.\n" + " For that, you can use the -n option - but expect much worse " + "results.)\n", + doc_path); + + FATAL("No instrumentation detected"); + + } + + if ((qemu_mode || unicorn_mode) && + memmem(f_data, f_len, SHM_ENV_VAR, strlen(SHM_ENV_VAR) + 1)) { + + SAYF("\n" cLRD "[-] " cRST + "This program appears to be instrumented with afl-gcc, but is being " + "run in\n" + " QEMU or Unicorn mode (-Q or -U). This is probably not what you " + "want -\n" + " this setup will be slow and offer no practical benefits.\n"); + + FATAL("Instrumentation found in -Q or -U mode"); + + } + + if (memmem(f_data, f_len, "libasan.so", 10) || + memmem(f_data, f_len, "__msan_init", 11)) + uses_asan = 1; + + /* Detect persistent & deferred init signatures in the binary. */ + + if (memmem(f_data, f_len, PERSIST_SIG, strlen(PERSIST_SIG) + 1)) { + + OKF(cPIN "Persistent mode binary detected."); + setenv(PERSIST_ENV_VAR, "1", 1); + persistent_mode = 1; + + } else if (getenv("AFL_PERSISTENT")) { + + WARNF("AFL_PERSISTENT is no longer supported and may misbehave!"); + + } + + if (memmem(f_data, f_len, DEFER_SIG, strlen(DEFER_SIG) + 1)) { + + OKF(cPIN "Deferred forkserver binary detected."); + setenv(DEFER_ENV_VAR, "1", 1); + deferred_mode = 1; + + } else if (getenv("AFL_DEFER_FORKSRV")) { + + WARNF("AFL_DEFER_FORKSRV is no longer supported and may misbehave!"); + + } + + if (munmap(f_data, f_len)) PFATAL("unmap() failed"); + +} + +/* Trim and possibly create a banner for the run. */ + +void fix_up_banner(u8* name) { + + if (!use_banner) { + + if (sync_id) { + + use_banner = sync_id; + + } else { + + u8* trim = strrchr(name, '/'); + if (!trim) + use_banner = name; + else + use_banner = trim + 1; + + } + + } + + if (strlen(use_banner) > 32) { + + u8* tmp = ck_alloc(36); + sprintf(tmp, "%.32s...", use_banner); + use_banner = tmp; + + } + +} + +/* Check if we're on TTY. */ + +void check_if_tty(void) { + + struct winsize ws; + + if (getenv("AFL_NO_UI")) { + + OKF("Disabling the UI because AFL_NO_UI is set."); + not_on_tty = 1; + return; + + } + + if (ioctl(1, TIOCGWINSZ, &ws)) { + + if (errno == ENOTTY) { + + OKF("Looks like we're not running on a tty, so I'll be a bit less " + "verbose."); + not_on_tty = 1; + + } + + return; + + } + +} + +/* Set up signal handlers. More complicated that needs to be, because libc on + Solaris doesn't resume interrupted reads(), sets SA_RESETHAND when you call + siginterrupt(), and does other stupid things. */ + +void setup_signal_handlers(void) { + + struct sigaction sa; + + sa.sa_handler = NULL; + sa.sa_flags = SA_RESTART; + sa.sa_sigaction = NULL; + + sigemptyset(&sa.sa_mask); + + /* Various ways of saying "stop". */ + + sa.sa_handler = handle_stop_sig; + sigaction(SIGHUP, &sa, NULL); + sigaction(SIGINT, &sa, NULL); + sigaction(SIGTERM, &sa, NULL); + + /* Exec timeout notifications. */ + + sa.sa_handler = handle_timeout; + sigaction(SIGALRM, &sa, NULL); + + /* Window resize */ + + sa.sa_handler = handle_resize; + sigaction(SIGWINCH, &sa, NULL); + + /* SIGUSR1: skip entry */ + + sa.sa_handler = handle_skipreq; + sigaction(SIGUSR1, &sa, NULL); + + /* Things we don't care about. */ + + sa.sa_handler = SIG_IGN; + sigaction(SIGTSTP, &sa, NULL); + sigaction(SIGPIPE, &sa, NULL); + +} + +/* Rewrite argv for QEMU. */ + +char** get_qemu_argv(u8* own_loc, char** argv, int argc) { + + char** new_argv = ck_alloc(sizeof(char*) * (argc + 4)); + u8 * tmp, *cp, *rsl, *own_copy; + + memcpy(new_argv + 3, argv + 1, sizeof(char*) * argc); + + new_argv[2] = target_path; + new_argv[1] = "--"; + + /* Now we need to actually find the QEMU binary to put in argv[0]. */ + + tmp = getenv("AFL_PATH"); + + if (tmp) { + + cp = alloc_printf("%s/afl-qemu-trace", tmp); + + if (access(cp, X_OK)) FATAL("Unable to find '%s'", tmp); + + target_path = new_argv[0] = cp; + return new_argv; + + } + + own_copy = ck_strdup(own_loc); + rsl = strrchr(own_copy, '/'); + + if (rsl) { + + *rsl = 0; + + cp = alloc_printf("%s/afl-qemu-trace", own_copy); + ck_free(own_copy); + + if (!access(cp, X_OK)) { + + target_path = new_argv[0] = cp; + return new_argv; + + } + + } else + + ck_free(own_copy); + + if (!access(BIN_PATH "/afl-qemu-trace", X_OK)) { + + target_path = new_argv[0] = ck_strdup(BIN_PATH "/afl-qemu-trace"); + return new_argv; + + } + + SAYF("\n" cLRD "[-] " cRST + "Oops, unable to find the 'afl-qemu-trace' binary. The binary must be " + "built\n" + " separately by following the instructions in qemu_mode/README.qemu. " + "If you\n" + " already have the binary installed, you may need to specify " + "AFL_PATH in the\n" + " environment.\n\n" + + " Of course, even without QEMU, afl-fuzz can still work with " + "binaries that are\n" + " instrumented at compile time with afl-gcc. It is also possible to " + "use it as a\n" + " traditional \"dumb\" fuzzer by specifying '-n' in the command " + "line.\n"); + + FATAL("Failed to locate 'afl-qemu-trace'."); + +} + +/* Make a copy of the current command line. */ + +void save_cmdline(u32 argc, char** argv) { + + u32 len = 1, i; + u8* buf; + + for (i = 0; i < argc; ++i) + len += strlen(argv[i]) + 1; + + buf = orig_cmdline = ck_alloc(len); + + for (i = 0; i < argc; ++i) { + + u32 l = strlen(argv[i]); + + memcpy(buf, argv[i], l); + buf += l; + + if (i != argc - 1) *(buf++) = ' '; + + } + + *buf = 0; + +} + |