if (skip_deterministic) FATAL("Multiple -d options not supported"); skip_deterministic = 1; use_splicing = 1; break;
// 只关心shm某些位置? 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 to mutate it without rediscovering any of the test cases already found during an earlier run. To use this mode, you need to point -B to the fuzz_bitmap produced by an earlier run for the exact same binary... and that's it. I only used this once or twice to get variants of a particular file, so I'm not making this an official setting. */
if (in_bitmap) FATAL("Multiple -B options not supported");
/* 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 unnecessary things. */
if (crash_mode) FATAL("-C and -n are mutually exclusive"); if (qemu_mode) FATAL("-Q and -n are mutually exclusive");
}
if (getenv("AFL_NO_FORKSRV")) no_forkserver = 1; if (getenv("AFL_NO_CPU_RED")) no_cpu_meter_red = 1; if (getenv("AFL_NO_ARITH")) no_arith = 1; if (getenv("AFL_SHUFFLE_QUEUE")) shuffle_queue = 1; if (getenv("AFL_FAST_CAL")) fast_cal = 1;
// hang 超时时间 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");
if (getenv("AFL_PRELOAD")) { setenv("LD_PRELOAD", getenv("AFL_PRELOAD"), 1); setenv("DYLD_INSERT_LIBRARIES", getenv("AFL_PRELOAD"), 1); }
if (getenv("AFL_LD_PRELOAD")) FATAL("Use AFL_PRELOAD instead of AFL_LD_PRELOAD");
/* If somebody is asking us to fuzz instrumented binaries in dumb mode, we don't want them to detect instrumentation, since we won't be sending fork server commands. This should be replaced with better auto-detection later on, perhaps? */ // 环境变量 if (!dumb_mode) setenv(SHM_ENV_VAR, shm_str, 1);
/* We use scandir() + alphasort() rather than readdir() because otherwise, the ordering of test cases would vary somewhat randomly and would be difficult to control. */ // 不使用readdir是因为测试用例的顺序将随机变化,难以控制。读取后的文件将按字母排序。 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");
free(nl[i]); /* not tracked */ if (lstat(fn, &st) || access(fn, R_OK)) PFATAL("Unable to access '%s'", fn);
/* This also takes care of . and .. */ if (!S_ISREG(st.st_mode) || !st.st_size || strstr(fn, "/README.testcases")) {
ck_free(fn); ck_free(dfn); continue;
}
if (st.st_size > MAX_FILE) FATAL("Test case '%s' is too big (%s, limit is %s)", fn, 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. */ // 如果存在则判定该测试用例已完成确定性变异,过滤该input if (!access(dfn, F_OK)) passed_det = 1; ck_free(dfn); // 如果存在则判定该测试用例已完成确定性变异,过滤该input // 如果不存在这加入队列 add_to_queue(fn, 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");
/* Spin up fork server (instrumented mode only). The idea is explained here: http://lcamtuf.blogspot.com/2014/10/fuzzing-binaries-without-execve.html In essence, the instrumentation allows us to skip execve(), and just keep cloning a stopped child. So, we just execute once, and then send commands through a pipe. The other part of this logic is in afl-as.h. */
EXP_ST voidinit_forkserver(char** argv){
staticstructitimerval it; // status pipe // control pipe int st_pipe[2], ctl_pipe[2]; int status; s32 rlen;
ACTF("Spinning up the fork server...");
if (pipe(st_pipe) || pipe(ctl_pipe)) PFATAL("pipe() failed");
forksrv_pid = fork();
if (forksrv_pid < 0) PFATAL("fork() failed");
// 子进程 if (!forksrv_pid) {
structrlimit r;
/* Umpf. On OpenBSD, the default fd limit for root users is set to soft 128. Let's try to fix that... */
if (!getrlimit(RLIMIT_NOFILE, &r) && r.rlim_cur < FORKSRV_FD + 2) {
/* This takes care of OpenBSD, which doesn't have RLIMIT_AS, but according to reliable sources, RLIMIT_DATA covers anonymous maps - so we should be getting good protection against OOM bugs. */
setrlimit(RLIMIT_DATA, &r); /* Ignore errors */
#endif/* ^RLIMIT_AS */
}
/* Dumping cores is slow and can lead to anomalies if SIGKILL is delivered before the dump is complete. */
r.rlim_max = r.rlim_cur = 0;
setrlimit(RLIMIT_CORE, &r); /* Ignore errors */
/* Isolate the process and configure standard descriptors. If out_file is specified, stdin is /dev/null; otherwise, out_fd is cloned instead. */
setsid();
dup2(dev_null_fd, 1); dup2(dev_null_fd, 2);
if (out_file) {
dup2(dev_null_fd, 0);
} else {
dup2(out_fd, 0); close(out_fd);
}
/* Set up control and status pipes, close the unneeded original fds. */
if (dup2(ctl_pipe[0], FORKSRV_FD) < 0) PFATAL("dup2() failed"); if (dup2(st_pipe[1], FORKSRV_FD + 1) < 0) PFATAL("dup2() failed");
/* If we have a four-byte "hello" message from the server, we're all set. Otherwise, try to figure out what went wrong. */
if (rlen == 4) { OKF("All right - fork server is up."); return; }
if (child_timed_out) FATAL("Timeout while initializing fork server (adjusting -t may help)"); // 等待子进程 if (waitpid(forksrv_pid, &status, 0) <= 0) PFATAL("waitpid() failed"); // 很多的SAYF... }
/* Take the current entry from the queue, fuzz it for a while. This function is a tad too long... returns 0 if fuzzed successfully, 1 if skipped or bailed out. */
/* In IGNORE_FINDS mode, skip any entries that weren't in the initial data set. */
if (queue_cur->depth > 1) return1;
#else
/* 判断pending_favored的值:(这里用到的常量可以在config.h找到) 1.如果为0,对于queue_cur被fuzz过或者不是favored的,有99%的概率不执行,直接返回1 2.如果不为0,并且不是dumb_mode、不是favored的、queued_paths>10: a.如果queue_cycle大于1,且没有被fuzz过,那么有95%的概率不执行,直接返回1 b.否则,有75%的概率不执行,直接返回1 */ if (pending_favored) {
/* If we have any favored, non-fuzzed new arrivals in the queue, possibly skip to them at the expense of already-fuzzed or non-favored cases. */ if ((queue_cur->was_fuzzed || !queue_cur->favored) && UR(100) < SKIP_TO_NEW_PROB) return1;
/* Otherwise, still possibly skip non-favored cases, albeit less often. The odds of skipping stuff are higher for already-fuzzed inputs and lower for never-fuzzed entries. */
if (queue_cycle > 1 && !queue_cur->was_fuzzed) { if (UR(100) < SKIP_NFAV_NEW_PROB) return1;
} else { if (UR(100) < SKIP_NFAV_OLD_PROB) return1; } }
#endif/* ^IGNORE_FINDS */
/* 如果不是tty模式,输出提示信息并刷新stdout缓冲区 */ if (not_on_tty) { ACTF("Fuzzing test case #%u (%u total, %llu uniq crashes found)...", current_entry, queued_paths, unique_crashes); fflush(stdout); }
/* Map the test case into memory. */ /* 这部分主要将case映射到内存的处理: 1.设置len为queue_cur->len 2.打开case对应的文件,并通过mmap映射到内存里,将地址赋值给in_buf和orig_in 3.分配len大小的内存,并初始化为全0,然后将地址赋值给out_buf 4.将连续超时计数器subseq_tmout清零 5.设置cur_depth为queue_cur->depth */ fd = open(queue_cur->fname, O_RDONLY); if (fd < 0) PFATAL("Unable to open '%s'", queue_cur->fname);
if (orig_in == MAP_FAILED) PFATAL("Unable to mmap '%s'", queue_cur->fname); close(fd);
/* We could mmap() out_buf as MAP_PRIVATE, but we end up clobbering every single byte anyway, so it wouldn't give us any performance or memory usage benefits. */
/******************************************* * CALIBRATION (only if failed earlier on) * *******************************************/
/* 这里开始进入CALIBRATION阶段: 1.假如当前项有校准错误,并且校准错误次数小于3次,那么就调用calibrate_case再次校准 2.如果设置了stop_soon,或者res不等于crash_mode: a.计数器cur_skipped_paths加1 b.进入abandon_entry作后续处理 */ if (queue_cur->cal_failed) {
u8 res = FAULT_TMOUT;
if (queue_cur->cal_failed < CAL_CHANCES) {
/* Reset exec_cksum to tell calibrate_case to re-execute the testcase avoiding the usage of an invalid trace_bits. For more info: https://github.com/AFLplusplus/AFLplusplus/pull/425 */
queue_cur->exec_cksum = 0;
res = calibrate_case(argv, queue_cur, in_buf, queue_cycle - 1, 0);
if (res == FAULT_ERROR) FATAL("Unable to execute target application");
}
if (stop_soon || res != crash_mode) { cur_skipped_paths++; goto abandon_entry; }
/* Skip right away if -d is given, if we have done deterministic fuzzing on this entry ourselves (was_fuzzed), or if it has gone through deterministic testing in earlier, resumed runs (passed_det). */
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. */