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->crashFileName); 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_FROMUSER(si) ? NULL : si->si_addr); util_ssnprintf(fuzzer->report, sizeof(fuzzer->report), "INSTRUCTION: %s\n", instr); util_ssnprintf(fuzzer->report, sizeof(fuzzer->report), "STACK HASH: %016llx\n", fuzzer->backtrace); 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; }
/* * Special book keeping for cases where crashes are detected based on exitcode and not * a raised signal. Such case is the ASan fuzzing for Android. Crash file name maintains * the same format for compatibility with post campaign tools. */ static void arch_ptraceExitSaveData(honggfuzz_t * hfuzz, pid_t pid, fuzzer_t * fuzzer, int exitCode) { REG_TYPE pc = 0; void *crashAddr = 0; char *op = "UNKNOWN"; pid_t targetPid = (hfuzz->linux.pid > 0) ? hfuzz->linux.pid : fuzzer->pid; /* Save only the first hit for each worker */ if (fuzzer->crashFileName[0] != '\0') { return; } /* Increase global crashes counter */ ATOMIC_POST_INC(hfuzz->crashesCnt); ATOMIC_POST_AND(hfuzz->dynFileIterExpire, _HF_DYNFILE_SUB_MASK); /* * If fuzzing with sanitizer coverage feedback increase crashes counter used * as metric for dynFile evolution */ if (hfuzz->useSanCov) { fuzzer->sanCovCnts.crashesCnt++; } /* Get sanitizer string tag based on exitcode */ const char *sanStr = arch_sanCodeToStr(exitCode); /* If sanitizer produces reports with stack traces (e.g. ASan), they're parsed manually */ int funcCnt = 0; /* *INDENT-OFF* */ 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'} , }; /* *INDENT-ON* */ /* If ASan crash, parse report */ if (exitCode == HF_ASAN_EXIT_CODE) { /* ASan is saving reports against parent PID */ if (targetPid != pid) { return; } funcCnt = arch_parseAsanReport(hfuzz, pid, funcs, &crashAddr, &op); /* * -1 error indicates a file not found for report. This is expected to happen often since * ASan report is generated once for crashing TID. Ptrace arch is not guaranteed to parse * that TID first. Not setting the 'crashFileName' variable will ensure that this branch * is executed again for all TIDs until the matching report is found */ if (funcCnt == -1) { return; } /* Since crash address is available, apply ignoreAddr filters */ if (crashAddr < hfuzz->linux.ignoreAddr) { LOG_I("'%s' is interesting, but the crash addr is %p (below %p), skipping", fuzzer->fileName, crashAddr, hfuzz->linux.ignoreAddr); return; } /* If frames successfully recovered, calculate stack hash & populate crash PC */ arch_hashCallstack(hfuzz, fuzzer, funcs, funcCnt, false); pc = (uintptr_t) funcs[0].pc; /* Since stack hash is available apply blacklist filters */ if (hfuzz->blacklist && (fastArray64Search(hfuzz->blacklist, hfuzz->blacklistCnt, fuzzer->backtrace) != -1)) { LOG_I("Blacklisted stack hash '%" PRIx64 "', skipping", fuzzer->backtrace); ATOMIC_POST_INC(hfuzz->blCrashesCnt); return; } } /* If dry run mode, copy file with same name into workspace */ if (hfuzz->origFlipRate == 0.0L && hfuzz->useVerifier) { snprintf(fuzzer->crashFileName, sizeof(fuzzer->crashFileName), "%s/%s", hfuzz->workDir, fuzzer->origFileName); } else { /* Keep the crashes file name format identical */ if (fuzzer->backtrace != 0ULL && hfuzz->saveUnique) { snprintf(fuzzer->crashFileName, sizeof(fuzzer->crashFileName), "%s/%s.PC.%" REG_PM ".STACK.%" PRIx64 ".CODE.%s.ADDR.%p.INSTR.%s.%s", hfuzz->workDir, sanStr, pc, fuzzer->backtrace, op, crashAddr, "[UNKNOWN]", hfuzz->fileExtn); } else { /* If no stack hash available, all crashes treated as unique */ char localtmstr[PATH_MAX]; util_getLocalTime("%F.%H:%M:%S", localtmstr, sizeof(localtmstr), time(NULL)); snprintf(fuzzer->crashFileName, sizeof(fuzzer->crashFileName), "%s/%s.PC.%" REG_PM ".STACK.%" PRIx64 ".CODE.%s.ADDR.%p.INSTR.%s.%s.%s", hfuzz->workDir, sanStr, pc, fuzzer->backtrace, op, crashAddr, "[UNKNOWN]", localtmstr, hfuzz->fileExtn); } } bool dstFileExists = false; if (files_copyFile(fuzzer->fileName, fuzzer->crashFileName, &dstFileExists)) { LOG_I("Ok, that's interesting, saved '%s' as '%s'", fuzzer->fileName, fuzzer->crashFileName); /* Increase unique crashes counters */ ATOMIC_POST_INC(hfuzz->uniqueCrashesCnt); ATOMIC_CLEAR(hfuzz->dynFileIterExpire); } else { if (dstFileExists) { LOG_I("It seems that '%s' already exists, skipping", fuzzer->crashFileName); /* Clear stack hash so that verifier can understand we hit a duplicate */ fuzzer->backtrace = 0ULL; } else { LOG_E("Couldn't copy '%s' to '%s'", fuzzer->fileName, fuzzer->crashFileName); /* In case of write error, clear crashFileName to so that other monitored TIDs can retry */ memset(fuzzer->crashFileName, 0, sizeof(fuzzer->crashFileName)); } /* Don't bother generating reports for duplicate or non-saved crashes */ return; } /* Generate report */ 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->crashFileName); util_ssnprintf(fuzzer->report, sizeof(fuzzer->report), "PID: %d\n", pid); util_ssnprintf(fuzzer->report, sizeof(fuzzer->report), "EXIT CODE: %d (%s)\n", exitCode, sanStr); util_ssnprintf(fuzzer->report, sizeof(fuzzer->report), "OPERATION: %s\n", op); util_ssnprintf(fuzzer->report, sizeof(fuzzer->report), "FAULT ADDRESS: %p\n", crashAddr); if (funcCnt > 0) { util_ssnprintf(fuzzer->report, sizeof(fuzzer->report), "STACK HASH: %016llx\n", fuzzer->backtrace); util_ssnprintf(fuzzer->report, sizeof(fuzzer->report), "STACK:\n"); for (int i = 0; i < funcCnt; i++) { 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"); } } } }
void log_msg(log_level_t dl, bool perr, const char *file, const char *func, int line, const char *fmt, ...) { char msg[8192] = { "\0" }; if (dl > log_minLevel) { if (dl == l_FATAL) { exit(EXIT_FAILURE); } return; } char strerr[512]; if (perr) { snprintf(strerr, sizeof(strerr), "%s", strerror(errno)); } struct tm tm; struct timeval tv; gettimeofday(&tv, NULL); localtime_r((const time_t *)&tv.tv_sec, &tm); if (log_isStdioTTY == true) { util_ssnprintf(msg, sizeof(msg), "%s", logLevels[dl].prefix); } #if defined(_HF_ARCH_LINUX) #include <unistd.h> #include <sys/syscall.h> pid_t pid = (pid_t) syscall(__NR_gettid); #else /* defined(_HF_ARCH_LINUX) */ pid_t pid = getpid(); #endif /* defined(_HF_ARCH_LINUX) */ if (log_minLevel != l_INFO || !log_isStdioTTY) { util_ssnprintf(msg, sizeof(msg), "%s [%d] %d/%02d/%02d %02d:%02d:%02d (%s:%s %d) ", logLevels[dl].descr, pid, tm.tm_year + 1900, tm.tm_mon + 1, tm.tm_mday, tm.tm_hour, tm.tm_min, tm.tm_sec, file, func, line); } else { util_ssnprintf(msg, sizeof(msg), "%s ", logLevels[dl].descr); } va_list args; va_start(args, fmt); util_vssnprintf(msg, sizeof(msg), fmt, args); va_end(args); if (perr) { util_ssnprintf(msg, sizeof(msg), ": %s", strerr); } if (log_isStdioTTY == true) { util_ssnprintf(msg, sizeof(msg), "\033[0m"); } util_ssnprintf(msg, sizeof(msg), "\n"); log_mutexLock(); if (write(STDOUT_FILENO, msg, strlen(msg)) == -1) { } log_mutexUnLock(); if (dl == l_FATAL) { exit(EXIT_FAILURE); } }
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 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 PLOG_D("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) { PLOG_W("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; } void arch_ptraceGetCustomPerf(honggfuzz_t * hfuzz, pid_t pid UNUSED, uint64_t * cnt UNUSED) { if ((hfuzz->dynFileMethod & _HF_DYNFILE_CUSTOM) == 0) { return; } #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) { PLOG_D("ptrace(PTRACE_GETREGSET) failed"); // If PTRACE_GETREGSET fails, try PTRACE_GETREGS if available #if PTRACE_GETREGS_AVAILABLE if (ptrace(PTRACE_GETREGS, pid, 0, ®s)) { PLOG_D("ptrace(PTRACE_GETREGS) failed"); LOG_W("ptrace PTRACE_GETREGSET & PTRACE_GETREGS failed to extract target registers"); return; } #else return; #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; *cnt = (uint64_t) r32->gs; return; } /* * 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; *cnt = (uint64_t) r64->gs_base; return; } LOG_W("Unknown registers structure size: '%zd'", pt_iov.iov_len); #endif /* defined(__i386__) || defined(__x86_64__) */ } static size_t arch_getPC(pid_t pid, REG_TYPE * pc, REG_TYPE * status_reg) { /* * Some old ARM android kernels are failing with PTRACE_GETREGS to extract * the correct register values if struct size is bigger than expected. As such the * 32/64-bit multiplexing trick is not working for them in case PTRACE_GETREGSET * fails or is not implemented. To cover such cases we explicitly define * the struct size to 32bit version for arm CPU. */ #if defined(__arm__) struct user_regs_struct_32 regs; #else HEADERS_STRUCT regs; #endif struct iovec pt_iov = { .iov_base = ®s, .iov_len = sizeof(regs), }; if (ptrace(PTRACE_GETREGSET, pid, NT_PRSTATUS, &pt_iov) == -1L) { PLOG_D("ptrace(PTRACE_GETREGSET) failed"); // If PTRACE_GETREGSET fails, try PTRACE_GETREGS if available #if PTRACE_GETREGS_AVAILABLE if (ptrace(PTRACE_GETREGS, pid, 0, ®s)) { PLOG_D("ptrace(PTRACE_GETREGS) failed"); LOG_W("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; } LOG_W("Unknown registers structure size: '%zd'", 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; } LOG_W("Unknown registers structure size: '%zd'", 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; } LOG_W("Unknown registers structure size: '%zd'", pt_iov.iov_len); return 0; #endif /* defined(__powerpc64__) || defined(__powerpc__) */ LOG_D("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) { LOG_W("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 LOG_E("Unknown/unsupported Android CPU architecture"); #endif csh handle; cs_err err = cs_open(arch, mode, &handle); if (err != CS_ERR_OK) { LOG_W("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) { LOG_W("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_hashCallstack(fuzzer_t * fuzzer, funcs_t * funcs, size_t funcCnt, bool enableMasking) { uint64_t hash = 0; for (size_t i = 0; i < funcCnt && i < NMAJORFRAMES; i++) { /* * Convert PC to char array to be compatible with hash function */ char pcStr[REGSIZEINCHAR] = { 0 }; snprintf(pcStr, REGSIZEINCHAR, REG_PD REG_PM, (REG_TYPE) (long)funcs[i].pc); /* * Hash the last three nibbles */ hash ^= util_hash(&pcStr[strlen(pcStr) - 3], 3); } /* * If only one frame, hash is not safe to be used for uniqueness. We mask it * here with a constant prefix, so analyzers can pick it up and create filenames * accordingly. 'enableMasking' is controlling masking for cases where it should * not be enabled (e.g. fuzzer worker is from verifier). */ if (enableMasking && funcCnt == 1) { hash |= _HF_SINGLE_FRAME_MASK; } fuzzer->backtrace = hash; } 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->crashFileName); 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_FROMUSER(si) ? NULL : si->si_addr); util_ssnprintf(fuzzer->report, sizeof(fuzzer->report), "INSTRUCTION: %s\n", instr); util_ssnprintf(fuzzer->report, sizeof(fuzzer->report), "STACK HASH: %016llx\n", fuzzer->backtrace); 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; }