bool arch_prepareParent(honggfuzz_t * hfuzz) { if (!hfuzz->pid) { return true; } #define MAX_THREAD_IN_TASK 4096 int tasks[MAX_THREAD_IN_TASK + 1]; tasks[MAX_THREAD_IN_TASK] = 0; if (!arch_listThreads(tasks, MAX_THREAD_IN_TASK, hfuzz->pid)) { LOGMSG(l_ERROR, "Couldn't read thread list for pid '%d'", hfuzz->pid); return false; } for (int i = 0; i < MAX_THREAD_IN_TASK && tasks[i]; i++) { if (ptrace(PT_ATTACH, tasks[i], NULL, NULL) == -1) { LOGMSG_P(l_ERROR, "Couldn't ptrace() ATTACH to pid: %d", tasks[i]); return false; } int status; while (waitpid(tasks[i], &status, WUNTRACED | __WALL) != tasks[i]) ; if (ptrace(PT_CONTINUE, tasks[i], NULL, NULL) == -1) { LOGMSG_P(l_ERROR, "Couldn't ptrace() CONTINUE pid: %d", tasks[i]); ptrace(PT_DETACH, tasks[i], 0, SIGCONT); return false; } LOGMSG(l_INFO, "Successfully attached to pid/tid: %d", tasks[i]); } return true; }
bool arch_ptraceAttach(pid_t pid) { #define MAX_THREAD_IN_TASK 4096 int tasks[MAX_THREAD_IN_TASK + 1]; tasks[MAX_THREAD_IN_TASK] = 0; if (!arch_listThreads(tasks, MAX_THREAD_IN_TASK, pid)) { LOGMSG(l_ERROR, "Couldn't read thread list for pid '%d'", pid); return false; } for (int i = 0; i < MAX_THREAD_IN_TASK && tasks[i]; i++) { if (ptrace (PTRACE_SEIZE, tasks[i], NULL, PTRACE_O_TRACECLONE | PTRACE_O_TRACEFORK | PTRACE_O_TRACEVFORK | PTRACE_O_TRACEEXIT) == -1) { LOGMSG_P(l_ERROR, "Couldn't ptrace(PTRACE_SEIZE) to pid: %d", tasks[i]); return false; } LOGMSG(l_DEBUG, "Successfully attached to pid/tid: %d", tasks[i]); } for (int i = 0; i < MAX_THREAD_IN_TASK && tasks[i]; i++) { ptrace(PT_CONTINUE, tasks[i], NULL, NULL); } return true; }
static bool arch_bfdInit(pid_t pid, bfd_t * bfdParams) { char fname[PATH_MAX]; snprintf(fname, sizeof(fname), "/proc/%d/exe", pid); if ((bfdParams->bfdh = bfd_openr(fname, 0)) == NULL) { LOGMSG(l_ERROR, "bfd_openr(%s) failed", fname); return false; } if (!bfd_check_format(bfdParams->bfdh, bfd_object)) { LOGMSG(l_ERROR, "bfd_check_format() failed"); return false; } int storage_needed = bfd_get_symtab_upper_bound(bfdParams->bfdh); if (storage_needed <= 0) { LOGMSG(l_ERROR, "bfd_get_symtab_upper_bound() returned '%d'", storage_needed); return false; } if ((bfdParams->syms = (asymbol **) malloc(storage_needed)) == NULL) { LOGMSG_P(l_ERROR, "malloc(%d) failed", storage_needed); return false; } bfd_canonicalize_symtab(bfdParams->bfdh, bfdParams->syms); if ((bfdParams->section = bfd_get_section_by_name(bfdParams->bfdh, ".text")) == NULL) { LOGMSG(l_ERROR, "bfd_get_section_by_name('.text') failed"); return false; } return true; }
static bool arch_listThreads(int tasks[], size_t thrSz, int pid) { size_t count = 0; char path[512]; snprintf(path, sizeof(path), "/proc/%d/task", pid); DIR *dir = opendir(path); if (!dir) { LOGMSG_P(l_ERROR, "Couldn't open dir '%s'", path); return false; } for (;;) { struct dirent de, *res; if (readdir_r(dir, &de, &res) > 0) { LOGMSG_P(l_ERROR, "Couldn't read contents of '%s'", path); closedir(dir); return false; } if (res == NULL) { break; } pid_t pid = (pid_t) strtol(res->d_name, (char **)NULL, 10); if (pid == 0) { LOGMSG(l_DEBUG, "The following dir entry couldn't be converted to pid_t '%s'", res->d_name); continue; } tasks[count++] = pid; LOGMSG(l_DEBUG, "Added pid '%d' from '%s/%s'", pid, path, res->d_name); if (count >= thrSz) { break; } } closedir(dir); LOGMSG_P(l_DEBUG, "Total number of threads in pid '%d': '%d'", pid, count); tasks[count + 1] = 0; if (count < 1) { return false; } return true; }
void arch_reapChild(honggfuzz_t * hfuzz, fuzzer_t * fuzzer) { timer_t timerid; if (arch_setTimer(&timerid) == false) { LOGMSG(l_FATAL, "Couldn't set timer"); } int perfFd[3]; if (hfuzz->pid == 0) { int status; pid_t pid = wait4(fuzzer->pid, &status, __WNOTHREAD | __WALL | WUNTRACED, NULL); if (pid != pid) { LOGMSG(l_FATAL, "wait4() =! pid (%d)", fuzzer->pid); } if (!WIFSTOPPED(status)) { LOGMSG(l_FATAL, "PID '%d' is not in a stopped state", fuzzer->pid); } if (arch_ptraceAttach(fuzzer->pid) == false) { LOGMSG(l_FATAL, "Couldn't attach to pid %d", fuzzer->pid); } if (arch_perfEnable(fuzzer->pid, hfuzz, perfFd) == false) { LOGMSG(l_FATAL, "Couldn't enable perf counters for pid %d", fuzzer->pid); } arch_ptraceAnalyze(hfuzz, status, fuzzer->pid, fuzzer); } for (;;) { int status; pid_t pid = wait3(&status, __WNOTHREAD | __WALL | WUNTRACED, NULL); LOGMSG(l_DEBUG, "PID '%d' returned with status '%d'", pid, status); if (pid == -1 && errno == EINTR) { arch_checkTimeLimit(hfuzz, fuzzer); continue; } if (pid == -1 && errno == ECHILD) { if (hfuzz->pid == 0) { arch_perfAnalyze(hfuzz, fuzzer, perfFd); } LOGMSG(l_DEBUG, "No more processes to track"); arch_removeTimer(&timerid); return; } if (pid == -1) { LOGMSG_P(l_FATAL, "wait3() failed"); } if (hfuzz->pid == 0) { uint64_t tmp = arch_ptraceGetCustomPerf(hfuzz, pid); if (tmp != 0ULL) { fuzzer->branchCnt[3] = tmp; } arch_ptraceAnalyze(hfuzz, status, pid, fuzzer); } } }
/* * Returns true if a process exited (so, presumably, we can delete an input * file) */ static bool arch_analyzePtrace(honggfuzz_t * hfuzz, pid_t pid, int status) { /* * It's our child which fuzzess our process (that we had attached to) finished */ int idx = HF_SLOT(hfuzz, pid); if (hfuzz->pid && idx != -1) { if (WIFEXITED(status) || WIFSIGNALED(status)) { LOGMSG_P(l_DEBUG, "Process pid: %d finished"); return true; } else { return false; } } /* * If it's an uninteresting signal (even SIGTRAP), let it run and relay the * signal (if not SIGTRAP) */ if (WIFSTOPPED(status) && !arch_sigs[WSTOPSIG(status)].important) { int sig = WSTOPSIG(status) == SIGTRAP ? 0 : WSTOPSIG(status); ptrace(PT_CONTINUE, pid, 0, sig); return false; } /* * If it's an interesting signal, save the testcase, and detach * the tracer (relay the signal as well) */ if (WIFSTOPPED(status) && arch_sigs[WSTOPSIG(status)].important) { arch_savePtraceData(hfuzz, pid, status); ptrace(PT_CONTINUE, pid, 0, WSTOPSIG(status)); return false; } /* * Resumed by delivery of SIGCONT */ if (WIFCONTINUED(status)) { return false; } /* * Process exited */ if (WIFEXITED(status) || WIFSIGNALED(status)) { if (hfuzz->pid && pid == hfuzz->pid) { LOGMSG(l_WARN, "Monitored process PID: %d finished", pid); exit(EXIT_SUCCESS); } return true; } abort(); /* NOTREACHED */ return true; }
static bool arch_enablePtrace(honggfuzz_t * hfuzz) { // We're fuzzing an external process, so just return true if (hfuzz->pid) { return true; } if (ptrace(PT_TRACE_ME, 0, 0, 0) == -1) { LOGMSG_P(l_FATAL, "Couldn't attach ptrace to pid %d", getpid()); return false; } return true; }
static void fuzz_runNext(honggfuzz_t * hfuzz) { int i = HF_SLOT(hfuzz, 0); fuzz_getFileName(hfuzz, hfuzz->fuzzers[i].fileName); pid_t pid = fork(); if (pid == -1) { LOGMSG_P(l_FATAL, "Couldn't fork"); exit(EXIT_FAILURE); } if (!pid) { /* * We've forked, other pid's might have the same rnd seeds now, * reinitialize it */ util_rndInit(); hfuzz->fuzzers[i].pid = getpid(); if (hfuzz->externalCommand != NULL) { if (!fuzz_prepareFileExternally(hfuzz, hfuzz->fuzzers[i].fileName)) { exit(EXIT_FAILURE); } } else { if (!fuzz_prepareFile(hfuzz, hfuzz->fuzzers[i].fileName)) { exit(EXIT_FAILURE); } } /* * Ok, kill the parent */ if (!arch_launchChild(hfuzz, hfuzz->fuzzers[i].fileName)) { kill(getppid(), SIGTERM); exit(EXIT_FAILURE); } } hfuzz->threadsCnt++; hfuzz->fuzzers[i].pid = pid; LOGMSG(l_INFO, "Launched new process, pid: %d, (%d/%d)", pid, hfuzz->threadsCnt, hfuzz->threadsMax); return; }
bool util_redirectStdin(char *inputFile) { int fd = open(inputFile, O_RDONLY); if (fd == -1) { LOGMSG_P(l_ERROR, "Couldn't open '%s'", inputFile); return false; } dup2(fd, 0); if (fd != 0) { close(fd); } return true; }
void util_nullifyStdio(void) { int fd = open("/dev/null", O_RDWR); if (fd == -1) { LOGMSG_P(l_ERROR, "Couldn't open '/dev/null'"); return; } dup2(fd, 0); dup2(fd, 1); dup2(fd, 2); if (fd > 2) { close(fd); } return; }
static size_t arch_getProcMem(pid_t pid, uint8_t * buf, size_t len, void *pc) { /* * len must be aligned to the sizeof(long) */ int cnt = len / sizeof(long); size_t memsz = 0; for (int x = 0; x < cnt; x++) { uint8_t *addr = (uint8_t *) pc + (int)(x * sizeof(long)); long ret = ptrace(PT_READ_D, pid, addr, NULL); if (errno != 0) { LOGMSG_P(l_WARN, "Couldn't PT_READ_D on pid %d, addr: %p", pid, addr); break; } memsz += sizeof(long); memcpy(&buf[x * sizeof(long)], &ret, sizeof(long)); } return memsz; }
static bool fuzz_prepareFile(honggfuzz_t * hfuzz, char *fileName) { int rnd_index = util_rndGet(0, hfuzz->fileCnt - 1); off_t fileSz; int srcfd; uint8_t *buf = files_mapFileToRead(hfuzz->files[rnd_index], &fileSz, &srcfd); if (buf == NULL) { LOGMSG(l_ERROR, "Couldn't open and map '%s' in R/O mode", hfuzz->files[rnd_index]); return false; } LOGMSG(l_DEBUG, "Mmaped '%s' in R/O mode, size: %d", hfuzz->files[rnd_index], fileSz); int dstfd = open(fileName, O_CREAT | O_EXCL | O_RDWR, 0644); if (dstfd == -1) { LOGMSG_P(l_ERROR, "Couldn't create a temporary file '%s' in the current directory", fileName); munmap(buf, fileSz); close(srcfd); return false; } fuzz_mangleContent(hfuzz, buf, fileSz); if (!files_writeToFd(dstfd, buf, fileSz)) { munmap(buf, fileSz); close(srcfd); close(dstfd); return false; } munmap(buf, fileSz); close(srcfd); close(dstfd); return true; }
static size_t arch_getProcMem(pid_t pid, uint8_t * buf, size_t len, REG_TYPE pc) { /* * Let's try process_vm_readv first */ const struct iovec local_iov = { .iov_base = buf, .iov_len = len, }; const struct iovec remote_iov = { .iov_base = (void *)(uintptr_t) pc, .iov_len = len, }; if (process_vm_readv(pid, &local_iov, 1, &remote_iov, 1, 0) == (ssize_t) len) { return len; } // Debug if failed since it shouldn't happen very often LOGMSG_P(l_DEBUG, "process_vm_readv() failed"); /* * Ok, let's do it via ptrace() then. * len must be aligned to the sizeof(long) */ int cnt = len / sizeof(long); size_t memsz = 0; for (int x = 0; x < cnt; x++) { uint8_t *addr = (uint8_t *) (uintptr_t) pc + (int)(x * sizeof(long)); long ret = ptrace(PT_READ_D, pid, addr, NULL); if (errno != 0) { LOGMSG_P(l_WARN, "Couldn't PT_READ_D on pid %d, addr: %p", pid, addr); break; } memsz += sizeof(long); memcpy(&buf[x * sizeof(long)], &ret, sizeof(long)); } return memsz; } // Non i386 / x86_64 ISA fail build due to unused pid argument #pragma GCC diagnostic push #pragma GCC diagnostic ignored "-Wunused-parameter" uint64_t arch_ptraceGetCustomPerf(honggfuzz_t * hfuzz, pid_t pid) { if ((hfuzz->dynFileMethod & _HF_DYNFILE_CUSTOM) == 0) { return 0ULL; } #if defined(__i386__) || defined(__x86_64__) HEADERS_STRUCT regs; struct iovec pt_iov = { .iov_base = ®s, .iov_len = sizeof(regs), }; if (ptrace(PTRACE_GETREGSET, pid, NT_PRSTATUS, &pt_iov) == -1L) { LOGMSG_P(l_DEBUG, "ptrace(PTRACE_GETREGSET) failed"); // If PTRACE_GETREGSET fails, try PTRACE_GETREGS if available #if PTRACE_GETREGS_AVAILABLE if (ptrace(PTRACE_GETREGS, pid, 0, ®s)) { LOGMSG_P(l_DEBUG, "ptrace(PTRACE_GETREGS) failed"); LOGMSG(l_WARN, "ptrace PTRACE_GETREGSET & PTRACE_GETREGS failed to" " extract target registers"); return 0ULL; } #else return 0ULL; #endif } /* * 32-bit */ if (pt_iov.iov_len == sizeof(struct user_regs_struct_32)) { struct user_regs_struct_32 *r32 = (struct user_regs_struct_32 *)®s; return (uint64_t) r32->gs; } /* * 64-bit */ if (pt_iov.iov_len == sizeof(struct user_regs_struct_64)) { struct user_regs_struct_64 *r64 = (struct user_regs_struct_64 *)®s; return (uint64_t) r64->gs_base; } LOGMSG(l_WARN, "Unknown registers structure size: '%d'", pt_iov.iov_len); #endif /* defined(__i386__) || defined(__x86_64__) */ return 0ULL; } #pragma GCC diagnostic pop /* ignored "-Wunused-parameter" */ static size_t arch_getPC(pid_t pid, REG_TYPE * pc, REG_TYPE * status_reg) { HEADERS_STRUCT regs; struct iovec pt_iov = { .iov_base = ®s, .iov_len = sizeof(regs), }; if (ptrace(PTRACE_GETREGSET, pid, NT_PRSTATUS, &pt_iov) == -1L) { LOGMSG_P(l_DEBUG, "ptrace(PTRACE_GETREGSET) failed"); // If PTRACE_GETREGSET fails, try PTRACE_GETREGS if available #if PTRACE_GETREGS_AVAILABLE if (ptrace(PTRACE_GETREGS, pid, 0, ®s)) { LOGMSG_P(l_DEBUG, "ptrace(PTRACE_GETREGS) failed"); LOGMSG(l_WARN, "ptrace PTRACE_GETREGSET & PTRACE_GETREGS failed to" " extract target registers"); return 0; } #else return 0; #endif } #if defined(__i386__) || defined(__x86_64__) /* * 32-bit */ if (pt_iov.iov_len == sizeof(struct user_regs_struct_32)) { struct user_regs_struct_32 *r32 = (struct user_regs_struct_32 *)®s; *pc = r32->eip; *status_reg = r32->eflags; return pt_iov.iov_len; } /* * 64-bit */ if (pt_iov.iov_len == sizeof(struct user_regs_struct_64)) { struct user_regs_struct_64 *r64 = (struct user_regs_struct_64 *)®s; *pc = r64->ip; *status_reg = r64->flags; return pt_iov.iov_len; } LOGMSG(l_WARN, "Unknown registers structure size: '%d'", pt_iov.iov_len); return 0; #endif /* defined(__i386__) || defined(__x86_64__) */ #if defined(__arm__) || defined(__aarch64__) /* * 32-bit */ if (pt_iov.iov_len == sizeof(struct user_regs_struct_32)) { struct user_regs_struct_32 *r32 = (struct user_regs_struct_32 *)®s; #ifdef __ANDROID__ *pc = r32->ARM_pc; *status_reg = r32->ARM_cpsr; #else *pc = r32->uregs[ARM_pc]; *status_reg = r32->uregs[ARM_cpsr]; #endif return pt_iov.iov_len; } /* * 64-bit */ if (pt_iov.iov_len == sizeof(struct user_regs_struct_64)) { struct user_regs_struct_64 *r64 = (struct user_regs_struct_64 *)®s; *pc = r64->pc; *status_reg = r64->pstate; return pt_iov.iov_len; } LOGMSG(l_WARN, "Unknown registers structure size: '%d'", pt_iov.iov_len); return 0; #endif /* defined(__arm__) || defined(__aarch64__) */ #if defined(__powerpc64__) || defined(__powerpc__) /* * 32-bit */ if (pt_iov.iov_len == sizeof(struct user_regs_struct_32)) { struct user_regs_struct_32 *r32 = (struct user_regs_struct_32 *)®s; *pc = r32->nip; return pt_iov.iov_len; } /* * 64-bit */ if (pt_iov.iov_len == sizeof(struct user_regs_struct_64)) { struct user_regs_struct_64 *r64 = (struct user_regs_struct_64 *)®s; *pc = r64->nip; return pt_iov.iov_len; } LOGMSG(l_WARN, "Unknown registers structure size: '%d'", pt_iov.iov_len); return 0; #endif /* defined(__powerpc64__) || defined(__powerpc__) */ LOGMSG(l_DEBUG, "Unknown/unsupported CPU architecture"); return 0; } static void arch_getInstrStr(pid_t pid, REG_TYPE * pc, char *instr) { /* * We need a value aligned to 8 * which is sizeof(long) on 64bit CPU archs (on most of them, I hope;) */ uint8_t buf[MAX_INSTR_SZ]; size_t memsz; REG_TYPE status_reg = 0; snprintf(instr, _HF_INSTR_SZ, "%s", "[UNKNOWN]"); size_t pcRegSz = arch_getPC(pid, pc, &status_reg); if (!pcRegSz) { LOGMSG(l_WARN, "Current architecture not supported for disassembly"); return; } if ((memsz = arch_getProcMem(pid, buf, sizeof(buf), *pc)) == 0) { snprintf(instr, _HF_INSTR_SZ, "%s", "[NOT_MMAPED]"); return; } #if !defined(__ANDROID__) arch_bfdDisasm(pid, buf, memsz, instr); #else cs_arch arch; cs_mode mode; #if defined(__arm__) || defined(__aarch64__) arch = (pcRegSz == sizeof(struct user_regs_struct_64)) ? CS_ARCH_ARM64 : CS_ARCH_ARM; if (arch == CS_ARCH_ARM) { mode = (status_reg & 0x20) ? CS_MODE_THUMB : CS_MODE_ARM; } else { mode = CS_MODE_ARM; } #elif defined(__i386__) || defined(__x86_64__) arch = CS_ARCH_X86; mode = (pcRegSz == sizeof(struct user_regs_struct_64)) ? CS_MODE_64 : CS_MODE_32; #else LOGMSG(l_ERROR, "Unknown/unsupported Android CPU architecture"); #endif csh handle; cs_err err = cs_open(arch, mode, &handle); if (err != CS_ERR_OK) { LOGMSG(l_WARN, "Capstone initialization failed: '%s'", cs_strerror(err)); return; } cs_insn *insn; size_t count = cs_disasm(handle, buf, sizeof(buf), *pc, 0, &insn); if (count < 1) { LOGMSG(l_WARN, "Couldn't disassemble the assembler instructions' stream: '%s'", cs_strerror(cs_errno(handle))); cs_close(&handle); return; } snprintf(instr, _HF_INSTR_SZ, "%s %s", insn[0].mnemonic, insn[0].op_str); cs_free(insn, count); cs_close(&handle); #endif for (int x = 0; instr[x] && x < _HF_INSTR_SZ; x++) { if (instr[x] == '/' || instr[x] == '\\' || isspace(instr[x]) || !isprint(instr[x])) { instr[x] = '_'; } } return; } static void arch_ptraceGenerateReport(pid_t pid, fuzzer_t * fuzzer, funcs_t * funcs, size_t funcCnt, siginfo_t * si, const char *instr) { fuzzer->report[0] = '\0'; util_ssnprintf(fuzzer->report, sizeof(fuzzer->report), "ORIG_FNAME: %s\n", fuzzer->origFileName); util_ssnprintf(fuzzer->report, sizeof(fuzzer->report), "FUZZ_FNAME: %s\n", fuzzer->fileName); util_ssnprintf(fuzzer->report, sizeof(fuzzer->report), "PID: %d\n", pid); util_ssnprintf(fuzzer->report, sizeof(fuzzer->report), "SIGNAL: %s (%d)\n", arch_sigs[si->si_signo].descr, si->si_signo); util_ssnprintf(fuzzer->report, sizeof(fuzzer->report), "FAULT ADDRESS: %p\n", si->si_addr); util_ssnprintf(fuzzer->report, sizeof(fuzzer->report), "INSTRUCTION: %s\n", instr); util_ssnprintf(fuzzer->report, sizeof(fuzzer->report), "STACK:\n"); for (size_t i = 0; i < funcCnt; i++) { #ifdef __HF_USE_CAPSTONE__ util_ssnprintf(fuzzer->report, sizeof(fuzzer->report), " <" REG_PD REG_PM "> ", (REG_TYPE) (long)funcs[i].pc, funcs[i].func, funcs[i].line); if (funcs[i].func[0] != '\0') util_ssnprintf(fuzzer->report, sizeof(fuzzer->report), "[%s + 0x%x]\n", funcs[i].func, funcs[i].line); else util_ssnprintf(fuzzer->report, sizeof(fuzzer->report), "[]\n"); #else util_ssnprintf(fuzzer->report, sizeof(fuzzer->report), " <" REG_PD REG_PM "> [%s():%u]\n", (REG_TYPE) (long)funcs[i].pc, funcs[i].func, funcs[i].line); #endif } // libunwind is not working for 32bit targets in 64bit systems #if defined(__aarch64__) if (funcCnt == 0) { util_ssnprintf(fuzzer->report, sizeof(fuzzer->report), " !ERROR: If 32bit fuzz target" " in aarch64 system, try ARM 32bit build\n"); } #endif return; }
static bool fuzz_prepareFileExternally(honggfuzz_t * hfuzz, char *fileName) { int rnd_index = util_rndGet(0, hfuzz->fileCnt - 1); off_t fileSz; int srcfd; int dstfd = open(fileName, O_CREAT | O_EXCL | O_RDWR, 0644); if (dstfd == -1) { LOGMSG_P(l_ERROR, "Couldn't create a temporary file '%s' in the current directory", fileName); return false; } LOGMSG(l_DEBUG, "Created '%f' as an input file", fileName); if (hfuzz->inputFile) { uint8_t *buf = files_mapFileToRead(hfuzz->files[rnd_index], &fileSz, &srcfd); if (buf == NULL) { LOGMSG(l_ERROR, "Couldn't open and map '%s' in R/O mode", hfuzz->files[rnd_index]); close(dstfd); return false; } LOGMSG(l_DEBUG, "Mmaped '%s' in R/O mode, size: %d", hfuzz->files[rnd_index], fileSz); bool ret = files_writeToFd(dstfd, buf, fileSz); munmap(buf, fileSz); close(srcfd); if (!ret) { close(dstfd); return false; } } close(dstfd); pid_t pid = fork(); if (pid == -1) { LOGMSG_P(l_ERROR, "Couldn't fork"); return false; } if (!pid) { /* * child does the external file modifications */ execl(hfuzz->externalCommand, hfuzz->externalCommand, fileName, NULL); LOGMSG_P(l_FATAL, "Couldn't execute '%s %s'", hfuzz->externalCommand, fileName); return false; } else { /* * parent waits until child is done fuzzing the input file */ int childStatus; pid_t terminatedPid; do { terminatedPid = wait(&childStatus); } while (terminatedPid != pid); if (WIFEXITED(childStatus)) { LOGMSG(l_DEBUG, "External command exited with status %d", WEXITSTATUS(childStatus)); return true; } else if (WIFSIGNALED(childStatus)) { LOGMSG(l_ERROR, "External command terminated with signal %d", WTERMSIG(childStatus)); return false; } LOGMSG(l_FATAL, "External command terminated abnormally, status: %d", childStatus); return false; } abort(); /* NOTREACHED */ }
int main(int argc, char **argv) { int c; int ll = l_INFO; honggfuzz_t hfuzz; hfuzz.inputFile = NULL; hfuzz.nullifyStdio = false; hfuzz.fuzzStdin = false; hfuzz.saveUnique = false; hfuzz.fileExtn = "fuzz"; hfuzz.flipRate = 0.001f; hfuzz.flipMode = 'B'; hfuzz.fuzzStart = 0; hfuzz.fuzzEnd = UINT_MAX; hfuzz.externalCommand = NULL; hfuzz.tmOut = 3; hfuzz.ignoreAddr = (void *)0UL; hfuzz.threadsMax = 5; hfuzz.asLimit = 0UL; hfuzz.cmdline = NULL; hfuzz.pid = 0; hfuzz.files = NULL; hfuzz.threadsCnt = 0; printf(AB PROG_NAME " version " PROG_VERSION "\n" PROG_AUTHORS AC "\n"); if (argc < 2) { usage(); exit(EXIT_SUCCESS); } for (;;) { c = getopt(argc, argv, "hqsuf:d:e:r:m:c:t:a:n:l:p:b:w:"); if (c < 0) break; switch (c) { case 'f': hfuzz.inputFile = optarg; break; case 'h': usage(); break; case 'q': hfuzz.nullifyStdio = true; break; case 's': hfuzz.fuzzStdin = true; break; case 'u': hfuzz.saveUnique = true; break; case 'd': ll = atoi(optarg); break; case 'e': hfuzz.fileExtn = optarg; break; case 'r': hfuzz.flipRate = atof(optarg); break; case 'm': hfuzz.flipMode = optarg[0]; break; case 'c': hfuzz.externalCommand = optarg; break; case 't': hfuzz.tmOut = atol(optarg); break; case 'a': hfuzz.ignoreAddr = (void *)atol(optarg); break; case 'n': hfuzz.threadsMax = atol(optarg); break; case 'l': hfuzz.asLimit = strtoul(optarg, NULL, 10); break; case 'p': hfuzz.pid = atoi(optarg); break; case 'b': hfuzz.fuzzStart = strtoul(optarg, NULL, 10); break; case 'w': hfuzz.fuzzEnd = strtoul(optarg, NULL, 10); break; default: break; } } hfuzz.cmdline = &argv[optind]; util_rndInit(); log_setMinLevel(ll); if (!hfuzz.cmdline[0]) { LOGMSG(l_FATAL, "Please specify binary to fuzz"); usage(); } if (!hfuzz.fuzzStdin && !checkFor_FILE_PLACEHOLDER(hfuzz.cmdline)) { LOGMSG(l_FATAL, "You must specify '" FILE_PLACEHOLDER "' when the -s (stdin fuzzing) option is not set"); usage(); } if (hfuzz.pid) { LOGMSG(l_INFO, "External PID specified, concurrency disabled"); hfuzz.threadsMax = 1; } if (strchr(hfuzz.fileExtn, '/')) { LOGMSG(l_FATAL, "The file extension contains the '/' character: '%s'", hfuzz.fileExtn); usage(); } if (hfuzz.fuzzStart > hfuzz.fuzzEnd || hfuzz.fuzzStart == hfuzz.fuzzEnd) { LOGMSG(l_FATAL, "Invalid mangle fuzz area file offsets"); usage(); } LOGMSG(l_INFO, "debugLevel: %d, inputFile '%s', nullifyStdio: %d, fuzzStdin: %d, saveUnique: %d, flipRate: %lf, " "flipMode: '%c', externalCommand: '%s', tmOut: %ld, threadsMax: %ld, fileExtn '%s', ignoreAddr: %p, " "memoryLimit: %lu (MiB), fuzzExe: '%s', fuzzedPid: %d", ll, hfuzz.inputFile, hfuzz.nullifyStdio ? 1 : 0, hfuzz.fuzzStdin ? 1 : 0, hfuzz.saveUnique ? 1 : 0, hfuzz.flipRate, hfuzz.flipMode, hfuzz.externalCommand == NULL ? "NULL" : hfuzz.externalCommand, hfuzz.tmOut, hfuzz.threadsMax, hfuzz.fileExtn, hfuzz.ignoreAddr, hfuzz.asLimit, hfuzz.cmdline[0], hfuzz.pid); if (!(hfuzz.fuzzers = malloc(sizeof(hfuzz.fuzzers[0]) * hfuzz.threadsMax))) { LOGMSG_P(l_FATAL, "Couldn't allocate memory"); exit(EXIT_FAILURE); } memset(hfuzz.fuzzers, '\0', sizeof(hfuzz.fuzzers[0]) * hfuzz.threadsMax); if (!files_init(&hfuzz)) { LOGMSG(l_FATAL, "Couldn't load input files"); exit(EXIT_FAILURE); } /* * So far so good */ fuzz_main(&hfuzz); abort(); /* NOTREACHED */ return EXIT_SUCCESS; }
static void arch_savePtraceData(honggfuzz_t * hfuzz, pid_t pid, int status) { void *pc = NULL; char instr[MAX_OP_STRING] = "[UNKNOWN]"; siginfo_t si; if (ptrace(PT_GETSIGINFO, pid, 0, &si) == -1) { LOGMSG_P(l_WARN, "Couldn't get siginfo for pid %d", pid); return; } struct user_regs_struct regs; if (ptrace(PT_GETREGS, pid, NULL, ®s) == -1) { LOGMSG(l_ERROR, "Couldn't get CPU registers"); } #ifdef __i386__ pc = (void *)regs.eip; arch_getX86InstrStr(pid, instr, pc); #endif /* __i386__ */ #ifdef __x86_64__ pc = (void *)regs.rip; arch_getX86InstrStr(pid, instr, pc); #endif /* __x86_64__ */ LOGMSG(l_DEBUG, "Pid: %d, signo: %d, errno: %d, code: %d, addr: %p, pc: %p, instr: '%s'", pid, si.si_signo, si.si_errno, si.si_code, si.si_addr, pc, instr); int idx = HF_SLOT(hfuzz, pid); // If we're checkign state of an external process, then the idx is 0 (cause // there's no concurrency) if (hfuzz->pid) { idx = 0; } if (si.si_addr < hfuzz->ignoreAddr) { LOGMSG(l_INFO, "'%s' is interesting (%s), but the si.si_addr is %p (below %p), skipping", hfuzz->fuzzers[idx].fileName, arch_sigs[si.si_signo].descr, si.si_addr, hfuzz->ignoreAddr); return; } char newname[PATH_MAX]; if (hfuzz->saveUnique) { snprintf(newname, sizeof(newname), "%s.PC.%p.CODE.%d.ADDR.%p.INSTR.%s.%s.%s", arch_sigs[si.si_signo].descr, pc, si.si_code, si.si_addr, instr, hfuzz->fuzzers[idx].origFileName, hfuzz->fileExtn); } else { char localtmstr[PATH_MAX]; util_getLocalTime("%F.%H.%M.%S", localtmstr, sizeof(localtmstr)); snprintf(newname, sizeof(newname), "%s.PC.%p.CODE.%d.ADDR.%p.INSTR.%s.%s.%d.%s.%s", arch_sigs[si.si_signo].descr, pc, si.si_code, si.si_addr, instr, localtmstr, pid, hfuzz->fuzzers[idx].origFileName, hfuzz->fileExtn); } if (link(hfuzz->fuzzers[idx].fileName, newname) == 0) { LOGMSG(l_INFO, "Ok, that's interesting, saved '%s' as '%s'", hfuzz->fuzzers[idx].fileName, newname); } else { if (errno == EEXIST) { LOGMSG(l_INFO, "It seems that '%s' already exists, skipping", newname); } else { LOGMSG_P(l_ERROR, "Couldn't link '%s' to '%s'", hfuzz->fuzzers[idx].fileName, newname); } } }
bool arch_launchChild(honggfuzz_t * hfuzz, char *fileName) { if (!arch_enablePtrace(hfuzz)) { return false; } #ifdef __linux__ #include <sys/prctl.h> #include <sys/personality.h> /* * Kill a process (with ABRT) which corrupts its own heap */ if (setenv("MALLOC_CHECK_", "3", 1) == -1) { LOGMSG_P(l_ERROR, "setenv(MALLOC_CHECK_=3) failed"); return false; } /* * Kill the children when fuzzer dies (e.g. due to Ctrl+C) */ if (prctl(PR_SET_PDEATHSIG, (long)SIGKILL, 0L, 0L, 0L) == -1) { LOGMSG_P(l_ERROR, "prctl(PR_SET_PDEATHSIG, SIGKILL) failed"); return false; } /* * Disable ASLR */ if (personality(ADDR_NO_RANDOMIZE) == -1) { LOGMSG_P(l_ERROR, "personality(ADDR_NO_RANDOMIZE) failed"); return false; } #endif /* __linux__ */ #define ARGS_MAX 512 char *args[ARGS_MAX + 2]; int x; char *pCurArg = NULL; char argData[PATH_MAX] = { 0 }; for (x = 0; x < ARGS_MAX && hfuzz->cmdline[x]; x++) { pCurArg = hfuzz->cmdline[x]; if (!hfuzz->fuzzStdin && strcmp(pCurArg, FILE_PLACEHOLDER) == 0) { args[x] = fileName; } else if (!hfuzz->fuzzStdin && strstr(pCurArg, CON_FILE_PLACEHOLDER) != NULL) { const char *off = strstr(hfuzz->cmdline[x], CON_FILE_PLACEHOLDER); snprintf(argData, PATH_MAX, "%.*s=%s", (off - pCurArg), pCurArg, fileName); args[x] = argData; } else { args[x] = hfuzz->cmdline[x]; } } args[x++] = NULL; LOGMSG(l_DEBUG, "Launching '%s' on file '%s'", args[0], fileName); /* * Set timeout (prof), real timeout (2*prof), and rlimit_cpu (2*prof) */ if (hfuzz->tmOut) { struct itimerval it; /* * The hfuzz->tmOut is real CPU usage time... */ it.it_value.tv_sec = hfuzz->tmOut; it.it_value.tv_usec = 0; it.it_interval.tv_sec = 0; it.it_interval.tv_usec = 0; if (setitimer(ITIMER_PROF, &it, NULL) == -1) { LOGMSG_P(l_ERROR, "Couldn't set the ITIMER_PROF timer"); return false; } /* * ...so, if a process sleeps, this one should * trigger a signal... */ it.it_value.tv_sec = hfuzz->tmOut * 2UL; it.it_value.tv_usec = 0; it.it_interval.tv_sec = 0; it.it_interval.tv_usec = 0; if (setitimer(ITIMER_REAL, &it, NULL) == -1) { LOGMSG_P(l_ERROR, "Couldn't set the ITIMER_REAL timer"); return false; } /* * ..if a process sleeps and catches SIGPROF/SIGALRM * rlimits won't help either */ struct rlimit rl; rl.rlim_cur = hfuzz->tmOut * 2; rl.rlim_max = hfuzz->tmOut * 2; if (setrlimit(RLIMIT_CPU, &rl) == -1) { LOGMSG_P(l_ERROR, "Couldn't enforce the RLIMIT_CPU resource limit"); return false; } } /* * The address space limit. If big enough - roughly the size of RAM used */ if (hfuzz->asLimit) { struct rlimit rl; rl.rlim_cur = hfuzz->asLimit * 1024UL * 1024UL; rl.rlim_max = hfuzz->asLimit * 1024UL * 1024UL; if (setrlimit(RLIMIT_AS, &rl) == -1) { LOGMSG_P(l_DEBUG, "Couldn't encforce the RLIMIT_AS resource limit, ignoring"); } } if (hfuzz->nullifyStdio) { util_nullifyStdio(); } if (hfuzz->fuzzStdin) { /* Uglyyyyyy ;) */ if (!util_redirectStdin(fileName)) { return false; } } execvp(args[0], args); util_recoverStdio(); LOGMSG(l_FATAL, "Failed to create new '%s' process", args[0]); return false; }
bool arch_launchChild(honggfuzz_t * hfuzz, char *fileName) { /* * Kill a process which corrupts its own heap (with ABRT) */ if (setenv("MALLOC_CHECK_", "3", 1) == -1) { LOGMSG_P(l_ERROR, "setenv(MALLOC_CHECK_=3) failed"); return false; } /* * Tell asan to ignore SEGVs */ if (setenv ("ASAN_OPTIONS", "allow_user_segv_handler=1:handle_segv=0:abort_on_error=1:allocator_may_return_null=1", 1) == -1) { LOGMSG_P(l_ERROR, "setenv(ASAN_OPTIONS) failed"); return false; } /* * Kill the children when fuzzer dies (e.g. due to Ctrl+C) */ if (prctl(PR_SET_PDEATHSIG, (long)SIGKILL, 0L, 0L, 0L) == -1) { LOGMSG_P(l_ERROR, "prctl(PR_SET_PDEATHSIG, SIGKILL) failed"); return false; } /* * Disable ASLR */ if (personality(ADDR_NO_RANDOMIZE) == -1) { LOGMSG_P(l_ERROR, "personality(ADDR_NO_RANDOMIZE) failed"); return false; } #define ARGS_MAX 512 char *args[ARGS_MAX + 2]; int x; for (x = 0; x < ARGS_MAX && hfuzz->cmdline[x]; x++) { if (!hfuzz->fuzzStdin && strcmp(hfuzz->cmdline[x], _HF_FILE_PLACEHOLDER) == 0) { args[x] = fileName; } else { args[x] = hfuzz->cmdline[x]; } } args[x++] = NULL; LOGMSG(l_DEBUG, "Launching '%s' on file '%s'", args[0], fileName); /* * Set timeout (prof), real timeout (2*prof), and rlimit_cpu (2*prof) */ if (hfuzz->tmOut) { /* * Set the CPU rlimit to twice the value of the time-out */ struct rlimit rl = { .rlim_cur = hfuzz->tmOut * 2, .rlim_max = hfuzz->tmOut * 2, }; if (setrlimit(RLIMIT_CPU, &rl) == -1) { LOGMSG_P(l_ERROR, "Couldn't enforce the RLIMIT_CPU resource limit"); return false; } } /* * The address space limit. If big enough - roughly the size of RAM used */ if (hfuzz->asLimit) { struct rlimit rl = { .rlim_cur = hfuzz->asLimit * 1024UL * 1024UL, .rlim_max = hfuzz->asLimit * 1024UL * 1024UL, }; if (setrlimit(RLIMIT_AS, &rl) == -1) { LOGMSG_P(l_DEBUG, "Couldn't encforce the RLIMIT_AS resource limit, ignoring"); } } for (size_t i = 0; i < ARRAYSIZE(hfuzz->envs) && hfuzz->envs[i]; i++) { putenv(hfuzz->envs[i]); } if (hfuzz->nullifyStdio) { util_nullifyStdio(); } if (hfuzz->fuzzStdin) { /* * Uglyyyyyy ;) */ if (!util_redirectStdin(fileName)) { return false; } } /* * Wait for the ptrace to attach */ syscall(__NR_tkill, syscall(__NR_gettid), SIGSTOP); execvp(args[0], args); util_recoverStdio(); LOGMSG(l_FATAL, "Failed to create new '%s' process", args[0]); return false; } static void arch_sigFunc(int signo, siginfo_t * si, void *dummy) { if (signo != SIGALRM) { LOGMSG(l_ERROR, "Signal != SIGALRM (%d)", signo); } return; if (si == NULL) { return; } if (dummy == NULL) { return; } } static void arch_removeTimer(timer_t * timerid) { timer_delete(*timerid); } static bool arch_setTimer(timer_t * timerid) { struct sigevent sevp = { .sigev_value.sival_ptr = timerid, .sigev_signo = SIGALRM, .sigev_notify = SIGEV_THREAD_ID | SIGEV_SIGNAL, ._sigev_un._tid = syscall(__NR_gettid), }; if (timer_create(CLOCK_REALTIME, &sevp, timerid) == -1) { LOGMSG_P(l_ERROR, "timer_create(CLOCK_REALTIME) failed"); return false; } /* * Kick in every 200ms, starting with the next second */ const struct itimerspec ts = { .it_value = {.tv_sec = 1,.tv_nsec = 0}, .it_interval = {.tv_sec = 0,.tv_nsec = 200000000,}, }; if (timer_settime(*timerid, 0, &ts, NULL) == -1) { LOGMSG_P(l_ERROR, "timer_settime() failed"); timer_delete(*timerid); return false; } sigset_t smask; sigemptyset(&smask); struct sigaction sa = { .sa_handler = NULL, .sa_sigaction = arch_sigFunc, .sa_mask = smask, .sa_flags = SA_SIGINFO, .sa_restorer = NULL, }; if (sigaction(SIGALRM, &sa, NULL) == -1) { LOGMSG_P(l_ERROR, "sigaciton(SIGALRM) failed"); return false; } return true; } static void arch_checkTimeLimit(honggfuzz_t * hfuzz, fuzzer_t * fuzzer) { int64_t curMillis = util_timeNowMillis(); int64_t diffMillis = curMillis - fuzzer->timeStartedMillis; if (diffMillis > (hfuzz->tmOut * 1000)) { LOGMSG(l_WARN, "PID %d took too much time (limit %ld s). Sending SIGKILL", fuzzer->pid, hfuzz->tmOut); kill(fuzzer->pid, SIGKILL); } }
static bool fuzz_prepareFileExternally(honggfuzz_t * hfuzz, fuzzer_t * fuzzer, int rnd_index) { int dstfd = open(fuzzer->fileName, O_CREAT | O_EXCL | O_RDWR, 0644); if (dstfd == -1) { LOGMSG_P(l_ERROR, "Couldn't create a temporary file '%s' in the current directory", fuzzer->fileName); return false; } LOGMSG(l_DEBUG, "Created '%f' as an input file", fuzzer->fileName); if (hfuzz->inputFile) { size_t fileSz = files_readFileToBufMax(hfuzz->files[rnd_index], fuzzer->dynamicFile, hfuzz->maxFileSz); if (fileSz == 0UL) { LOGMSG(l_ERROR, "Couldn't read '%s'", hfuzz->files[rnd_index]); unlink(fuzzer->fileName); return false; } if (files_writeToFd(dstfd, fuzzer->dynamicFile, fileSz) == false) { close(dstfd); unlink(fuzzer->fileName); return false; } } close(dstfd); pid_t pid = fork(); if (pid == -1) { LOGMSG_P(l_ERROR, "Couldn't vfork"); return false; } if (!pid) { /* * child performs the external file modifications */ execl(hfuzz->externalCommand, hfuzz->externalCommand, fuzzer->fileName, NULL); LOGMSG_P(l_FATAL, "Couldn't execute '%s %s'", hfuzz->externalCommand, fuzzer->fileName); return false; } /* * parent waits until child is done fuzzing the input file */ int childStatus; int flags = 0; #if defined(__WNOTHREAD) flags |= __WNOTHREAD; #endif /* defined(__WNOTHREAD) */ while (wait4(pid, &childStatus, flags, NULL) != pid) ; if (WIFEXITED(childStatus)) { LOGMSG(l_DEBUG, "External command exited with status %d", WEXITSTATUS(childStatus)); return true; } if (WIFSIGNALED(childStatus)) { LOGMSG(l_ERROR, "External command terminated with signal %d", WTERMSIG(childStatus)); return false; } LOGMSG(l_FATAL, "External command terminated abnormally, status: %d", childStatus); return false; abort(); /* NOTREACHED */ }
static void arch_ptraceSaveData(honggfuzz_t * hfuzz, pid_t pid, fuzzer_t * fuzzer) { __sync_fetch_and_add(&hfuzz->crashesCnt, 1UL); REG_TYPE pc = 0; char instr[_HF_INSTR_SZ] = "\x00"; siginfo_t si; bzero(&si, sizeof(si)); if (ptrace(PT_GETSIGINFO, pid, 0, &si) == -1) { LOGMSG_P(l_WARN, "Couldn't get siginfo for pid %d", pid); } arch_getInstrStr(pid, &pc, instr); LOGMSG(l_DEBUG, "Pid: %d, signo: %d, errno: %d, code: %d, addr: %p, pc: %" REG_PM ", instr: '%s'", pid, si.si_signo, si.si_errno, si.si_code, si.si_addr, pc, instr); if (si.si_addr < hfuzz->ignoreAddr) { LOGMSG(l_INFO, "'%s' is interesting (%s), but the si.si_addr is %p (below %p), skipping", fuzzer->fileName, arch_sigs[si.si_signo].descr, si.si_addr, hfuzz->ignoreAddr); return; } char newname[PATH_MAX]; if (hfuzz->saveUnique) { snprintf(newname, sizeof(newname), "%s.PC.%" REG_PM ".CODE.%d.ADDR.%p.INSTR.%s.%s", arch_sigs[si.si_signo].descr, pc, si.si_code, si.si_addr, instr, hfuzz->fileExtn); } else { char localtmstr[PATH_MAX]; util_getLocalTime("%F.%H:%M:%S", localtmstr, sizeof(localtmstr), time(NULL)); snprintf(newname, sizeof(newname), "%s.PC.%" REG_PM ".CODE.%d.ADDR.%p.INSTR.%s.%s.%d.%s", arch_sigs[si.si_signo].descr, pc, si.si_code, si.si_addr, instr, localtmstr, pid, hfuzz->fileExtn); } if (link(fuzzer->fileName, newname) == 0) { LOGMSG(l_INFO, "Ok, that's interesting, saved '%s' as '%s'", fuzzer->fileName, newname); } else { if (errno == EEXIST) { LOGMSG(l_INFO, "It seems that '%s' already exists, skipping", newname); // Don't bother unwinding & generating reports for duplicate crashes return; } else { LOGMSG_P(l_ERROR, "Couldn't link '%s' to '%s'", fuzzer->fileName, newname); } } funcs_t funcs[_HF_MAX_FUNCS] = { [0 ... (_HF_MAX_FUNCS - 1)].pc = NULL, [0 ... (_HF_MAX_FUNCS - 1)].line = 0, [0 ... (_HF_MAX_FUNCS - 1)].func = {'\0'} , }; #if !defined(__ANDROID__) size_t funcCnt = arch_unwindStack(pid, funcs); arch_bfdResolveSyms(pid, funcs, funcCnt); #else size_t funcCnt = arch_unwindStack(pid, funcs); #endif arch_ptraceGenerateReport(pid, fuzzer, funcs, funcCnt, &si, instr); }