/* ====== generate random file name for multi instance thanks to f*cking glibc we can not use tmpnam securely, it generates a security warning that cannot be suppressed so we do this worse workaround */ // 生成路径文件("一切皆文件!") snprintf(shm->g_shm_file_path, L_tmpnam, "/afl_%d_%ld", getpid(), random());
/* If somebody is asking us to fuzz instrumented binaries in non-instrumented 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? */
// 设置环境变量SHM_ENV_VAR,方便afl-qemu-trace读取共享内存 if (!non_instrumented_mode) setenv(SHM_ENV_VAR, shm->g_shm_file_path, 1);
if (shm->map == (void *)-1 || !shm->map) PFATAL("mmap() failed");
if (shm->cmplog_mode) { ### cmplog部分不关注 } ... return shm->map
fsrv->fsrv_pid = fork(); if (fsrv->fsrv_pid < 0) { PFATAL("fork() failed"); } if (!fsrv->fsrv_pid) { // 子进程作为fork server ... /* Set up control and status pipes, close the unneeded original fds. */ // 设置控制管道和状态管道。这部分用来作为fork server和afl之间的消息传递通道 if (dup2(ctl_pipe[0], FORKSRV_FD) < 0) { PFATAL("dup2() failed"); } // control if (dup2(st_pipe[1], FORKSRV_FD + 1) < 0) { PFATAL("dup2() failed"); } // status
pid_t child_pid; int t_fd[2]; u8 child_stopped = 0; u32 was_killed; int status = 0;
// 从afl_setup中获取一些状态信息 if (MAP_SIZE <= FS_OPT_MAX_MAPSIZE) status |= (FS_OPT_SET_MAPSIZE(MAP_SIZE) | FS_OPT_MAPSIZE); if (lkm_snapshot) status |= FS_OPT_SNAPSHOT; if (sharedmem_fuzzing != 0) status |= FS_OPT_SHDMEM_FUZZ; if (status) status |= (FS_OPT_ENABLED | FS_OPT_NEWCMPLOG); if (getenv("AFL_DEBUG")) fprintf(stderr, "Debug: Sending status %08x\n", status); memcpy(tmp, &status, 4);
/* Tell the parent that we're alive. If the parent doesn't want to talk, assume that we're not running in forkserver mode. */ // 还记得刚才的控制管道吗?由于execve和fork都会继承文件描述符,因此之前FORKSRV_FD + 1依然存在 if (write(FORKSRV_FD + 1, tmp, 4) != 4) return; afl_forksrv_pid = getpid(); int first_run = 1;
/* If we stopped the child in persistent mode, but there was a race condition and afl-fuzz already issued SIGKILL, write off the old process. */
if (child_stopped && was_killed) {
child_stopped = 0; if (waitpid(child_pid, &status, 0) < 0) exit(8);
}
if (!child_stopped) {
/* Establish a channel with child to grab translation commands. We'll read from t_fd[0], child will write to TSL_FD. */ // 产生t_fd管道,用于和子进程之间通信 if (pipe(t_fd) || dup2(t_fd[1], TSL_FD) < 0) exit(3); close(t_fd[1]);
// 这里看出:事实上当前的qemuafl担当了forkserver的角色,在while(1)循环中不断产生fork的子进程 child_pid = fork(); if (child_pid < 0) exit(4);
if (!child_pid) {
/* Child process. Close descriptors and run free. */ // 子进程,关闭不需要的描述符,并返回接着执行目标程序 afl_fork_child = 1; close(FORKSRV_FD); close(FORKSRV_FD + 1); close(t_fd[0]); return;
}
/* Parent. */
close(TSL_FD);
} else {
/* Special handling for persistent mode: if the child is alive but currently stopped, simply restart it with SIGCONT. */
/* Collect translation requests until child dies and closes the pipe. */ // 等待子进程翻译指令。 // 这是针对qemu模式下的一种优化。因为每一个新fork出来的进程都需要翻译很多指令,这里forkserver还担任一个cache的功能,也就是子进程未翻译的指令需要先从forkserver的翻译cache中寻找,如果找到了就直接使用。 afl_wait_tsl(cpu, t_fd[0]);
#ifdef __linux__ if (fsrv->nyx_mode) { // 不关注nyx_mode,跳过 return FSRV_RUN_OK; }
#endif /* After this memset, fsrv->trace_bits[] are effectively volatile, so we must prevent any earlier operations from venturing into that territory. */
/* we have the fork server (or faux server) up and running First, tell it if the previous run timed out. */ // 向fork server写入write_value(fsrv->last_run_timed_out) // 对应了之前代码中 read(FORKSRV_FD, &was_killed, 4)
if ((res = write(fsrv->fsrv_ctl_fd, &write_value, 4)) != 4) { if (*stop_soon_p) { return0; } RPFATAL(res, "Unable to request new process from fork server (OOM?)");
if (*stop_soon_p) { return0; } RPFATAL(res, "Unable to communicate with fork server");
}
// 检查目标进程是否终止,当子进程停止时返回true,否则false if (!WIFSTOPPED(fsrv->child_status)) { fsrv->child_pid = -1; }
fsrv->total_execs++;
/* Any subsequent operations on fsrv->trace_bits must not be moved by the compiler below this point. Past this location, fsrv->trace_bits[] behave very normally and do not have to be treated as volatile. */
MEM_BARRIER();
/* Report outcome to caller. */
/* Was the run unsuccessful? */ if (unlikely(*(u32 *)fsrv->trace_bits == EXEC_FAIL_SIG)) {
return FSRV_RUN_ERROR;
}
/* Did we timeout? */ if (unlikely(fsrv->last_run_timed_out)) {
/* Did we crash? In a normal case, (abort) WIFSIGNALED(child_status) will be set. MSAN in uses_asan mode uses a special exit code as it doesn't support abort_on_error. On top, a user may specify a custom AFL_CRASH_EXITCODE. Handle all three cases here. */ // 判断子进程是否crash // 是通过WIFSIGNALED()根据子进程pid获取子进程的退出状态做到的
if (unlikely( /* A normal crash/abort */ (WIFSIGNALED(fsrv->child_status)) || /* special handling for msan and lsan */ (fsrv->uses_asan && (WEXITSTATUS(fsrv->child_status) == MSAN_ERROR || WEXITSTATUS(fsrv->child_status) == LSAN_ERROR)) || /* the custom crash_exitcode was returned by the target */ (fsrv->uses_crash_exitcode && WEXITSTATUS(fsrv->child_status) == fsrv->crash_exitcode))) {
/* For a proper crash, set last_kill_signal to WTERMSIG, else set it to 0 */ fsrv->last_kill_signal = WIFSIGNALED(fsrv->child_status) ? WTERMSIG(fsrv->child_status) : 0; return FSRV_RUN_CRASH;
/* Looks like QEMU always maps to fixed locations, so ASLR is not a concern. Phew. But instruction addresses may be aligned. Let's mangle the value to get something quasi-uniform. */
/* Users can hit us with SIGUSR1 to request the current input to be abandoned. */ if (afl->skip_requested) { afl->skip_requested = 0; ++afl->cur_skipped_items; return1; }
/* This handles FAULT_ERROR for us: */ // 处理fault,这里的save_if_interesting下面详细说明 afl->queued_discovered += save_if_interesting(afl, out_buf, len, fault); if (!(afl->stage_cur % afl->stats_update_freq) || afl->stage_cur + 1 == afl->stage_max) { show_stats(afl); } return0; }
/* Calibrate a new test case. This is done when processing the input directory to warn about flaky or otherwise problematic test cases early on; and when new paths are discovered to detect variable behavior and so on. */