static void run_target(char** argv) { static struct itimerval it; int status = 0; if (!quiet_mode) SAYF("-- Program output begins --\n" cRST); MEM_BARRIER(); child_pid = fork(); if (child_pid < 0) PFATAL("fork() failed"); if (!child_pid) { struct rlimit r; if (quiet_mode) { s32 fd = open("/dev/null", O_RDWR); if (fd < 0 || dup2(fd, 1) < 0 || dup2(fd, 2) < 0) { *(u32*)trace_bits = EXEC_FAIL_SIG; PFATAL("Descriptor initialization failed"); } close(fd); } if (mem_limit) { r.rlim_max = r.rlim_cur = ((rlim_t)mem_limit) << 20; #ifdef RLIMIT_AS setrlimit(RLIMIT_AS, &r); /* Ignore errors */ #else setrlimit(RLIMIT_DATA, &r); /* Ignore errors */ #endif /* ^RLIMIT_AS */ } if (!keep_cores) r.rlim_max = r.rlim_cur = 0; else r.rlim_max = r.rlim_cur = RLIM_INFINITY; setrlimit(RLIMIT_CORE, &r); /* Ignore errors */ if (!getenv("LD_BIND_LAZY")) setenv("LD_BIND_NOW", "1", 0); setsid(); execv(target_path, argv); *(u32*)trace_bits = EXEC_FAIL_SIG; exit(0); } /* Configure timeout, wait for child, cancel timeout. */ if (exec_tmout) { child_timed_out = 0; it.it_value.tv_sec = (exec_tmout / 1000); it.it_value.tv_usec = (exec_tmout % 1000) * 1000; } setitimer(ITIMER_REAL, &it, NULL); if (waitpid(child_pid, &status, 0) <= 0) FATAL("waitpid() failed"); child_pid = 0; it.it_value.tv_sec = 0; it.it_value.tv_usec = 0; setitimer(ITIMER_REAL, &it, NULL); MEM_BARRIER(); /* Clean up bitmap, analyze exit condition, etc. */ if (*(u32*)trace_bits == EXEC_FAIL_SIG) FATAL("Unable to execute '%s'", argv[0]); classify_counts(trace_bits, binary_mode ? count_class_binary : count_class_human); if (!quiet_mode) SAYF(cRST "-- Program output ends --\n"); if (!child_timed_out && !stop_soon && WIFSIGNALED(status)) child_crashed = 1; if (!quiet_mode) { if (child_timed_out) SAYF(cLRD "\n+++ Program timed off +++\n" cRST); else if (stop_soon) SAYF(cLRD "\n+++ Program aborted by user +++\n" cRST); else if (child_crashed) SAYF(cLRD "\n+++ Program killed by signal %u +++\n" cRST, WTERMSIG(status)); } }
static void add_instrumentation(void) { static u8 line[MAX_AS_LINE]; FILE* inf; FILE* outf; s32 outfd; u32 ins_lines = 0; u8 instr_ok = 0, skip_csect = 0, skip_next_label = 0; #ifdef __APPLE__ u8* colon_pos; #endif /* __APPLE__ */ if (input_file) { inf = fopen(input_file, "r"); if (!inf) PFATAL("Unable to read '%s'", input_file); } else inf = stdin; outfd = open(modified_file, O_WRONLY | O_EXCL | O_CREAT, 0600); if (outfd < 0) PFATAL("Unable to write to '%s'", modified_file); outf = fdopen(outfd, "w"); if (!outf) PFATAL("fdopen() failed"); while (fgets(line, MAX_AS_LINE, inf)) { fputs(line, outf); if (pass_thru) continue; /* We only want to instrument the .text section. So, let's keep track of that in processed files. */ if (line[0] == '\t' && line[1] == '.') { /* OpenBSD puts jump tables directly inline with the code, which is a bit annoying. They use a specific format of p2align directives around them, so we use that as a signal. */ if (!clang_mode && instr_ok && !strncmp(line + 2, "p2align ", 8) && isdigit(line[10]) && line[11] == '\n') skip_next_label = 1; if (!strncmp(line + 2, "text\n", 5) || !strncmp(line + 2, "section\t.text", 13) || !strncmp(line + 2, "section\t__TEXT,__text", 21) || !strncmp(line + 2, "section __TEXT,__text", 21)) { instr_ok = 1; continue; } if (!strncmp(line + 2, "section\t", 8) || !strncmp(line + 2, "section ", 8) || !strncmp(line + 2, "bss\n", 4) || !strncmp(line + 2, "data\n", 5)) { instr_ok = 0; continue; } } if (strstr(line, ".code")) { if (strstr(line, ".code32")) skip_csect = use_64bit; if (strstr(line, ".code64")) skip_csect = !use_64bit; } /* If we're in the right mood for instrumenting, check for function names or conditional labels. This is a bit messy, but in essence, we want to catch: ^main: - function entry point (always instrumented) ^.L0: - GCC branch label ^.LBB0_0: - clang branch label (but only in clang mode) ^\tjnz foo - conditional branches ...but not: ^# BB#0: - clang comments ^ # BB#0: - ditto ^.Ltmp0: - clang non-branch labels ^.LC0 - GCC non-branch labels ^\tjmp foo - non-conditional jumps Additionally, for clang on MacOS X follows a different convention with no leading dots on labels, hence the weird maze of #ifdefs later on. */ if (skip_csect || !instr_ok || line[0] == '#' || line[0] == ' ') continue; /* Conditional branch instruction (jnz, etc). */ if (line[0] == '\t') { if (line[1] == 'j' && line[2] != 'm' && R(100) < inst_ratio) { fprintf(outf, use_64bit ? trampoline_fmt_64 : trampoline_fmt_32, R(MAP_SIZE)); ins_lines++; } continue; } /* Label of some sort. */ #ifdef __APPLE__ /* Apple: L<whatever><digit>: */ if ((colon_pos = strstr(line, ":"))) { if (line[0] == 'L' && isdigit(*(colon_pos - 1))) { #else /* Everybody else: .L<whatever>: */ if (strstr(line, ":")) { if (line[0] == '.') { #endif /* __APPPLE__ */ /* .L0: or LBB0_0: style jump destination */ #ifdef __APPLE__ /* Apple: L<num> / LBB<num> */ if ((isdigit(line[1]) || (clang_mode && !strncmp(line, "LBB", 3))) && R(100) < inst_ratio) { #else /* Apple: .L<num> / .LBB<num> */ if ((isdigit(line[2]) || (clang_mode && !strncmp(line + 1, "LBB", 3))) && R(100) < inst_ratio) { #endif /* __APPLE__ */ if (!skip_next_label) { fprintf(outf, use_64bit ? trampoline_fmt_64 : trampoline_fmt_32, R(MAP_SIZE)); ins_lines++; } else skip_next_label = 0; } } else { /* Function label (always instrumented) */ fprintf(outf, use_64bit ? trampoline_fmt_64 : trampoline_fmt_32, R(MAP_SIZE)); ins_lines++; } } } if (ins_lines) fputs(use_64bit ? main_payload_64 : main_payload_32, outf); if (input_file) fclose(inf); fclose(outf); if (!be_quiet) { if (!ins_lines) WARNF("No instrumentation targets found."); else OKF("Instrumented %u locations (%s-bit, %s mode, ratio %u%%).", ins_lines, use_64bit ? "64" : "32", getenv("AFL_HARDEN") ? "hardened" : "non-hardened", inst_ratio); } } /* Main entry point */ int main(int argc, char** argv) { s32 pid; u32 rand_seed; int status; u8* inst_ratio_str = getenv("AFL_INST_RATIO"); struct timeval tv; struct timezone tz; clang_mode = !!getenv("__AFL_CLANG_MODE"); if (isatty(2) && !getenv("AFL_QUIET")) { SAYF(cCYA "afl-as " cBRI VERSION cRST " (" __DATE__ " " __TIME__ ") by <*****@*****.**>\n"); } else be_quiet = 1; if (argc < 2) { SAYF("\n" "This is a helper application for afl-fuzz. It is a wrapper around GNU 'as',\n" "executed by the toolchain whenever using afl-gcc or afl-clang. You probably\n" "don't want to run this program directly.\n\n" "Rarely, when dealing with extremely complex projects, it may be advisable to\n" "set AFL_INST_RATIO to a value less than 100 in order to reduce the odds of\n" "instrumenting every discovered branch.\n\n"); exit(1); } gettimeofday(&tv, &tz); rand_seed = tv.tv_sec ^ tv.tv_usec ^ getpid(); srandom(rand_seed); edit_params(argc, argv); if (inst_ratio_str) { if (sscanf(inst_ratio_str, "%u", &inst_ratio) != 1 || inst_ratio > 100) FATAL("Bad value of AFL_INST_RATIO (must be between 0 and 100)"); } if (getenv("__AFL_AS_BEENHERE")) FATAL("Endless loop when calling 'as' (remove '.' from your PATH)"); setenv("__AFL_AS_BEENHERE", "1", 1); /* When compiling with ASAN, we don't have a particularly elegant way to skip ASAN-specific branches. But we can probabilistically compensate for that... */ if (getenv("AFL_USE_ASAN")) inst_ratio /= 3; add_instrumentation(); if (!(pid = fork())) { execvp(as_params[0], (char**)as_params); FATAL("Oops, failed to execute '%s' - check your PATH", as_params[0]); } if (pid < 0) PFATAL("fork() failed"); if (waitpid(pid, &status, 0) <= 0) PFATAL("waitpid() failed"); if (!getenv("AFL_KEEP_ASSEMBLY")) unlink(modified_file); exit(WEXITSTATUS(status)); }