| /* |
| * |
| * honggfuzz - architecture dependent code (NETBSD/PTRACE) |
| * ----------------------------------------- |
| * |
| * Author: Kamil Rytarowski <n54@gmx.com> |
| * |
| * Copyright 2010-2018 by Google Inc. All Rights Reserved. |
| * |
| * Licensed under the Apache License, Version 2.0 (the "License"); you may |
| * not use this file except in compliance with the License. You may obtain |
| * a copy of the License at |
| * |
| * http://www.apache.org/licenses/LICENSE-2.0 |
| * |
| * Unless required by applicable law or agreed to in writing, software |
| * distributed under the License is distributed on an "AS IS" BASIS, |
| * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or |
| * implied. See the License for the specific language governing |
| * permissions and limitations under the License. |
| * |
| */ |
| |
| #include "netbsd/trace.h" |
| |
| // clang-format off |
| #include <sys/param.h> |
| #include <sys/types.h> |
| // clang-format on |
| |
| #include <sys/ptrace.h> |
| #include <sys/resource.h> |
| #include <sys/stat.h> |
| #include <sys/syscall.h> |
| #include <sys/time.h> |
| #include <sys/types.h> |
| #include <sys/uio.h> |
| #include <sys/wait.h> |
| |
| #include <ctype.h> |
| #include <dirent.h> |
| #include <elf.h> |
| #include <endian.h> |
| #include <errno.h> |
| #include <fcntl.h> |
| #include <inttypes.h> |
| #include <signal.h> |
| #include <stdio.h> |
| #include <stdlib.h> |
| #include <string.h> |
| #include <time.h> |
| #include <unistd.h> |
| |
| #include "libhfcommon/common.h" |
| #include "libhfcommon/files.h" |
| #include "libhfcommon/log.h" |
| #include "libhfcommon/util.h" |
| #include "netbsd/unwind.h" |
| #include "socketfuzzer.h" |
| #include "subproc.h" |
| |
| #include <capstone/capstone.h> |
| |
| /* |
| * Size in characters required to store a string representation of a |
| * register value (0xdeadbeef style)) |
| */ |
| #define REGSIZEINCHAR (2 * sizeof(register_t) + 3) |
| |
| #define _HF_INSTR_SZ 64 |
| |
| #if defined(__i386__) || defined(__x86_64__) |
| #define MAX_INSTR_SZ 16 |
| #elif defined(__arm__) || defined(__powerpc__) || defined(__powerpc64__) |
| #define MAX_INSTR_SZ 4 |
| #elif defined(__aarch64__) |
| #define MAX_INSTR_SZ 8 |
| #elif defined(__mips__) || defined(__mips64__) |
| #define MAX_INSTR_SZ 8 |
| #endif |
| |
| static struct { |
| const char* descr; |
| bool important; |
| } arch_sigs[_NSIG + 1] = { |
| [0 ...(_NSIG)].important = false, |
| [0 ...(_NSIG)].descr = "UNKNOWN", |
| |
| [SIGTRAP].important = false, |
| [SIGTRAP].descr = "SIGTRAP", |
| |
| [SIGILL].important = true, |
| [SIGILL].descr = "SIGILL", |
| |
| [SIGFPE].important = true, |
| [SIGFPE].descr = "SIGFPE", |
| |
| [SIGSEGV].important = true, |
| [SIGSEGV].descr = "SIGSEGV", |
| |
| [SIGBUS].important = true, |
| [SIGBUS].descr = "SIGBUS", |
| |
| /* Is affected from monitorSIGABRT flag */ |
| [SIGABRT].important = false, |
| [SIGABRT].descr = "SIGABRT", |
| |
| /* Is affected from tmoutVTALRM flag */ |
| [SIGVTALRM].important = false, |
| [SIGVTALRM].descr = "SIGVTALRM-TMOUT", |
| |
| /* seccomp-bpf kill */ |
| [SIGSYS].important = true, |
| [SIGSYS].descr = "SIGSYS", |
| }; |
| |
| #ifndef SI_FROMUSER |
| #define SI_FROMUSER(siptr) ((siptr)->si_code == SI_USER) |
| #endif /* SI_FROMUSER */ |
| |
| static __thread char arch_signame[32]; |
| static const char* arch_sigName(int signo) { |
| snprintf(arch_signame, sizeof(arch_signame), "SIG%s", signalname(signo)); |
| return arch_signame; |
| } |
| |
| static size_t arch_getProcMem(pid_t pid, uint8_t* buf, size_t len, register_t pc) { |
| struct ptrace_io_desc io; |
| size_t bytes_read; |
| |
| bytes_read = 0; |
| io.piod_op = PIOD_READ_D; |
| io.piod_len = len; |
| |
| do { |
| io.piod_offs = (void*)(pc + bytes_read); |
| io.piod_addr = buf + bytes_read; |
| |
| if (ptrace(PT_IO, pid, &io, 0) == -1) { |
| PLOG_W("Couldn't read process memory on pid %d, " |
| "piod_op: %d offs: %p addr: %p piod_len: %zu", |
| pid, io.piod_op, io.piod_offs, io.piod_addr, io.piod_len); |
| break; |
| } |
| |
| bytes_read = io.piod_len; |
| io.piod_len = len - bytes_read; |
| } while (bytes_read < len); |
| |
| return bytes_read; |
| } |
| |
| static size_t arch_getPC( |
| pid_t pid, lwpid_t lwp, register_t* pc, register_t* status_reg HF_ATTR_UNUSED) { |
| struct reg r; |
| |
| if (ptrace(PT_GETREGS, pid, &r, lwp) != 0) { |
| PLOG_D("ptrace(PT_GETREGS) failed"); |
| return 0; |
| } |
| *pc = PTRACE_REG_PC(&r); |
| #if defined(__i386__) |
| *status_reg = r.regs[_REG_EFLAGS]; |
| #elif defined(__x86_64__) |
| *status_reg = r.regs[_REG_RFLAGS]; |
| #else |
| #error unsupported CPU architecture |
| #endif |
| |
| return sizeof(r); |
| } |
| |
| static void arch_getInstrStr(pid_t pid, lwpid_t lwp, register_t* 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; |
| register_t status_reg = 0; |
| |
| snprintf(instr, _HF_INSTR_SZ, "%s", "[UNKNOWN]"); |
| |
| size_t pcRegSz = arch_getPC(pid, lwp, 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; |
| } |
| |
| cs_arch arch; |
| cs_mode mode; |
| |
| #if defined(__i386__) |
| arch = CS_ARCH_X86; |
| mode = CS_MODE_32; |
| #elif defined(__x86_64__) |
| arch = CS_ARCH_X86; |
| mode = CS_MODE_64; |
| #else |
| #error Unsupported 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); |
| |
| for (int x = 0; instr[x] && x < _HF_INSTR_SZ; x++) { |
| if (instr[x] == '/' || instr[x] == '\\' || isspace((unsigned char)instr[x]) || |
| !isprint((unsigned char)instr[x])) { |
| instr[x] = '_'; |
| } |
| } |
| |
| return; |
| } |
| |
| static void arch_hashCallstack( |
| run_t* run, funcs_t* funcs HF_ATTR_UNUSED, size_t funcCnt, bool enableMasking) { |
| uint64_t hash = 0; |
| for (size_t i = 0; i < funcCnt && i < run->global->netbsd.numMajorFrames; i++) { |
| /* |
| * Convert PC to char array to be compatible with hash function |
| */ |
| char pcStr[REGSIZEINCHAR] = {0}; |
| snprintf(pcStr, REGSIZEINCHAR, "%" PRIxREGISTER, (register_t)(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; |
| } |
| run->backtrace = hash; |
| } |
| |
| static void arch_traceGenerateReport( |
| pid_t pid, run_t* run, funcs_t* funcs, size_t funcCnt, siginfo_t* si, const char* instr) { |
| run->report[0] = '\0'; |
| util_ssnprintf(run->report, sizeof(run->report), "ORIG_FNAME: %s\n", run->origFileName); |
| util_ssnprintf(run->report, sizeof(run->report), "FUZZ_FNAME: %s\n", run->crashFileName); |
| util_ssnprintf(run->report, sizeof(run->report), "PID: %d\n", pid); |
| util_ssnprintf(run->report, sizeof(run->report), "SIGNAL: %s (%d)\n", |
| arch_sigName(si->si_signo), si->si_signo); |
| util_ssnprintf(run->report, sizeof(run->report), "FAULT ADDRESS: %p\n", |
| SI_FROMUSER(si) ? NULL : si->si_addr); |
| util_ssnprintf(run->report, sizeof(run->report), "INSTRUCTION: %s\n", instr); |
| util_ssnprintf( |
| run->report, sizeof(run->report), "STACK HASH: %016" PRIx64 "\n", run->backtrace); |
| util_ssnprintf(run->report, sizeof(run->report), "STACK:\n"); |
| for (size_t i = 0; i < funcCnt; i++) { |
| util_ssnprintf(run->report, sizeof(run->report), " <%" PRIxREGISTER "> [%s():%zu at %s]\n", |
| (register_t)(long)funcs[i].pc, funcs[i].func, funcs[i].line, funcs[i].mapName); |
| } |
| |
| return; |
| } |
| |
| static void arch_traceAnalyzeData(run_t* run, pid_t pid) { |
| ptrace_siginfo_t info; |
| register_t pc = 0, status_reg = 0; |
| |
| if (ptrace(PT_GET_SIGINFO, pid, &info, sizeof(info)) == -1) { |
| PLOG_W("Couldn't get siginfo for pid %d", pid); |
| } |
| |
| size_t pcRegSz = arch_getPC(pid, info.psi_lwpid, &pc, &status_reg); |
| if (!pcRegSz) { |
| LOG_W("ptrace arch_getPC failed"); |
| return; |
| } |
| |
| /* |
| * Unwind and resolve symbols |
| */ |
| funcs_t* funcs = util_Malloc(_HF_MAX_FUNCS * sizeof(funcs_t)); |
| defer { |
| free(funcs); |
| }; |
| memset(funcs, 0, _HF_MAX_FUNCS * sizeof(funcs_t)); |
| |
| size_t funcCnt = 0; |
| |
| /* |
| * Use PC from ptrace GETREGS if not zero. |
| * If PC reg zero return and callers should handle zero hash case. |
| */ |
| if (pc) { |
| /* Manually update major frame PC & frames counter */ |
| funcs[0].pc = (void*)(uintptr_t)pc; |
| funcCnt = 1; |
| } else { |
| return; |
| } |
| |
| /* |
| * Calculate backtrace callstack hash signature |
| */ |
| arch_hashCallstack(run, funcs, funcCnt, false); |
| } |
| |
| static void arch_traceSaveData(run_t* run, pid_t pid) { |
| register_t pc = 0; |
| |
| /* Local copy since flag is overridden for some crashes */ |
| bool saveUnique = run->global->io.saveUnique; |
| |
| char instr[_HF_INSTR_SZ] = "\x00"; |
| struct ptrace_siginfo info; |
| memset(&info, 0, sizeof(info)); |
| |
| if (ptrace(PT_GET_SIGINFO, pid, &info, sizeof(info)) == -1) { |
| PLOG_W("Couldn't get siginfo for pid %d", pid); |
| } |
| |
| arch_getInstrStr(pid, info.psi_lwpid, &pc, instr); |
| |
| LOG_D("Pid: %d, signo: %d, errno: %d, code: %d, addr: %p, pc: %" PRIxREGISTER ", instr: '%s'", |
| pid, info.psi_siginfo.si_signo, info.psi_siginfo.si_errno, info.psi_siginfo.si_code, |
| info.psi_siginfo.si_addr, pc, instr); |
| |
| if (!SI_FROMUSER(&info.psi_siginfo) && pc && |
| info.psi_siginfo.si_addr < run->global->netbsd.ignoreAddr) { |
| LOG_I("Input is interesting (%s), but the si.si_addr is %p (below %p), skipping", |
| arch_sigName(info.psi_siginfo.si_signo), info.psi_siginfo.si_addr, |
| run->global->netbsd.ignoreAddr); |
| return; |
| } |
| |
| /* |
| * Unwind and resolve symbols |
| */ |
| funcs_t* funcs = util_Malloc(_HF_MAX_FUNCS * sizeof(funcs_t)); |
| defer { |
| free(funcs); |
| }; |
| memset(funcs, 0, _HF_MAX_FUNCS * sizeof(funcs_t)); |
| |
| size_t funcCnt = 0; |
| |
| /* |
| * Use PC from ptrace GETREGS if not zero. |
| * If PC reg zero, temporarily disable uniqueness flag since callstack |
| * hash will be also zero, thus not safe for unique decisions. |
| */ |
| if (pc) { |
| /* Manually update major frame PC & frames counter */ |
| funcs[0].pc = (void*)(uintptr_t)pc; |
| funcCnt = 1; |
| } else { |
| saveUnique = false; |
| } |
| |
| /* |
| * Temp local copy of previous backtrace value in case worker hit crashes into multiple |
| * tids for same target master thread. Will be 0 for first crash against target. |
| */ |
| uint64_t oldBacktrace = run->backtrace; |
| |
| /* |
| * Calculate backtrace callstack hash signature |
| */ |
| arch_hashCallstack(run, funcs, funcCnt, saveUnique); |
| |
| /* |
| * If unique flag is set and single frame crash, disable uniqueness for this crash |
| * to always save (timestamp will be added to the filename) |
| */ |
| if (saveUnique && (funcCnt == 1)) { |
| saveUnique = false; |
| } |
| |
| /* |
| * If worker crashFileName member is set, it means that a tid has already crashed |
| * from target master thread. |
| */ |
| if (run->crashFileName[0] != '\0') { |
| LOG_D("Multiple crashes detected from worker against attached tids group"); |
| |
| /* |
| * If stackhashes match, don't re-analyze. This will avoid duplicates |
| * and prevent verifier from running multiple passes. Depth of check is |
| * always 1 (last backtrace saved only per target iteration). |
| */ |
| if (oldBacktrace == run->backtrace) { |
| return; |
| } |
| } |
| |
| /* Increase global crashes counter */ |
| ATOMIC_POST_INC(run->global->cnts.crashesCnt); |
| |
| /* |
| * Check if backtrace contains whitelisted symbol. Whitelist overrides |
| * both stackhash and symbol blacklist. Crash is always kept regardless |
| * of the status of uniqueness flag. |
| */ |
| if (run->global->netbsd.symsWl) { |
| char* wlSymbol = arch_btContainsSymbol( |
| run->global->netbsd.symsWlCnt, run->global->netbsd.symsWl, funcCnt, funcs); |
| if (wlSymbol != NULL) { |
| saveUnique = false; |
| LOG_D("Whitelisted symbol '%s' found, skipping blacklist checks", wlSymbol); |
| } |
| } else { |
| /* |
| * Check if stackhash is blacklisted |
| */ |
| if (run->global->feedback.blacklist && |
| (fastArray64Search(run->global->feedback.blacklist, run->global->feedback.blacklistCnt, |
| run->backtrace) != -1)) { |
| LOG_I("Blacklisted stack hash '%" PRIx64 "', skipping", run->backtrace); |
| ATOMIC_POST_INC(run->global->cnts.blCrashesCnt); |
| return; |
| } |
| |
| /* |
| * Check if backtrace contains blacklisted symbol |
| */ |
| char* blSymbol = arch_btContainsSymbol( |
| run->global->netbsd.symsBlCnt, run->global->netbsd.symsBl, funcCnt, funcs); |
| if (blSymbol != NULL) { |
| LOG_I("Blacklisted symbol '%s' found, skipping", blSymbol); |
| ATOMIC_POST_INC(run->global->cnts.blCrashesCnt); |
| return; |
| } |
| } |
| |
| /* If non-blacklisted crash detected, zero set two MSB */ |
| ATOMIC_POST_ADD(run->global->cfg.dynFileIterExpire, _HF_DYNFILE_SUB_MASK); |
| |
| void* sig_addr = info.psi_siginfo.si_addr; |
| pc = 0UL; |
| sig_addr = NULL; |
| |
| /* User-induced signals don't set si.si_addr */ |
| if (SI_FROMUSER(&info.psi_siginfo)) { |
| sig_addr = NULL; |
| } |
| |
| /* If dry run mode, copy file with same name into workspace */ |
| if (run->global->mutate.mutationsPerRun == 0U && run->global->cfg.useVerifier) { |
| snprintf(run->crashFileName, sizeof(run->crashFileName), "%s/%s", run->global->io.crashDir, |
| run->origFileName); |
| } else if (saveUnique) { |
| snprintf(run->crashFileName, sizeof(run->crashFileName), |
| "%s/%s.PC.%" PRIxREGISTER ".STACK.%" PRIx64 ".CODE.%d.ADDR.%p.INSTR.%s.%s", |
| run->global->io.crashDir, arch_sigName(info.psi_siginfo.si_signo), pc, run->backtrace, |
| info.psi_siginfo.si_code, sig_addr, instr, run->global->io.fileExtn); |
| } else { |
| char localtmstr[PATH_MAX]; |
| util_getLocalTime("%F.%H:%M:%S", localtmstr, sizeof(localtmstr), time(NULL)); |
| snprintf(run->crashFileName, sizeof(run->crashFileName), |
| "%s/%s.PC.%" PRIxREGISTER ".STACK.%" PRIx64 ".CODE.%d.ADDR.%p.INSTR.%s.%s.%d.%s", |
| run->global->io.crashDir, arch_sigName(info.psi_siginfo.si_signo), pc, run->backtrace, |
| info.psi_siginfo.si_code, sig_addr, instr, localtmstr, pid, run->global->io.fileExtn); |
| } |
| |
| /* Target crashed (no duplicate detection yet) */ |
| if (run->global->socketFuzzer.enabled) { |
| LOG_D("SocketFuzzer: trace: Crash Identified"); |
| } |
| |
| if (files_exists(run->crashFileName)) { |
| LOG_I("Crash (dup): '%s' already exists, skipping", run->crashFileName); |
| // Clear filename so that verifier can understand we hit a duplicate |
| memset(run->crashFileName, 0, sizeof(run->crashFileName)); |
| return; |
| } |
| |
| if (!files_writeBufToFile(run->crashFileName, run->dynamicFile, run->dynamicFileSz, |
| O_CREAT | O_EXCL | O_WRONLY | O_CLOEXEC)) { |
| LOG_E("Couldn't write to '%s'", run->crashFileName); |
| return; |
| } |
| |
| /* Unique new crash, notify fuzzer */ |
| if (run->global->socketFuzzer.enabled) { |
| LOG_D("SocketFuzzer: trace: New Uniqu Crash"); |
| fuzz_notifySocketFuzzerCrash(run); |
| } |
| LOG_I("Crash: saved as '%s'", run->crashFileName); |
| |
| ATOMIC_POST_INC(run->global->cnts.uniqueCrashesCnt); |
| /* If unique crash found, reset dynFile counter */ |
| ATOMIC_CLEAR(run->global->cfg.dynFileIterExpire); |
| |
| arch_traceGenerateReport(pid, run, funcs, funcCnt, &info.psi_siginfo, instr); |
| } |
| |
| static void arch_traceEvent(run_t* run HF_ATTR_UNUSED, pid_t pid) { |
| ptrace_state_t state; |
| ptrace_siginfo_t info; |
| int sig = 0; |
| |
| if (ptrace(PT_GET_SIGINFO, pid, &info, sizeof(info)) == -1) { |
| PLOG_E("ptrace(PT_GET_SIGINFO, pid=%d)", (int)pid); |
| } else { |
| switch (info.psi_siginfo.si_code) { |
| case TRAP_BRKPT: |
| /* Software breakpoint trap, pass it over to tracee */ |
| sig = SIGTRAP; |
| LOG_D("PID: %d breakpoint software trap (TRAP_BRKPT)", pid); |
| break; |
| case TRAP_TRACE: |
| /* Single step unused */ |
| LOG_E("PID: %d unexpected single step trace trap (TRAP_TRACE)", pid); |
| break; |
| case TRAP_EXEC: |
| /* exec(3) trap, ignore */ |
| LOG_D("PID: %d breakpoint software trap (TRAP_EXEC)", pid); |
| break; |
| case TRAP_CHLD: |
| case TRAP_LWP: |
| /* Child/LWP trap, ignore */ |
| if (ptrace(PT_GET_PROCESS_STATE, pid, &state, sizeof(state)) != -1) { |
| switch (state.pe_report_event) { |
| case PTRACE_FORK: |
| LOG_D("PID: %d child trap (TRAP_CHLD) : fork", (int)pid); |
| break; |
| case PTRACE_VFORK: |
| LOG_D("PID: %d child trap (TRAP_CHLD) : vfork", (int)pid); |
| break; |
| case PTRACE_VFORK_DONE: |
| LOG_D("PID: %d child trap (TRAP_CHLD) : vfork (PTRACE_VFORK_DONE)", |
| (int)pid); |
| break; |
| case PTRACE_LWP_CREATE: |
| LOG_E("PID: %d unexpected lwp trap (TRAP_LWP) : create " |
| "(PTRACE_LWP_CREATE)", |
| (int)pid); |
| break; |
| case PTRACE_LWP_EXIT: |
| LOG_E("PID: %d unexpected lwp trap (TRAP_LWP) : exit (PTRACE_LWP_EXIT)", |
| (int)pid); |
| break; |
| default: |
| LOG_D("PID: %d unknown child/lwp trap (TRAP_LWP/TRAP_CHLD) : unknown " |
| "pe_report_event=%d", |
| (int)pid, state.pe_report_event); |
| break; |
| } |
| } |
| break; |
| case TRAP_DBREG: |
| /* Debug Register trap unused */ |
| LOG_E("PID: %d unexpected debug register trap (TRAP_DBREG)", pid); |
| break; |
| case TRAP_SCE: |
| /* Syscall Enter trap unused */ |
| LOG_E("PID: %d unexpected syscall enter trap (TRAP_SCE)", pid); |
| break; |
| case TRAP_SCX: |
| /* Syscall Exit trap unused */ |
| LOG_E("PID: %d unexpected syscall exit trap (TRAP_SCX)", pid); |
| break; |
| default: |
| /* Other trap, pass it over to tracee */ |
| sig = SIGTRAP; |
| LOG_D("PID: %d other trap si_code=%d", pid, info.psi_siginfo.si_code); |
| break; |
| } |
| } |
| |
| ptrace(PT_CONTINUE, pid, (void*)1, sig); |
| } |
| |
| void arch_traceAnalyze(run_t* run, int status, pid_t pid) { |
| /* |
| * It's a ptrace event, deal with it elsewhere |
| */ |
| if (WIFSTOPPED(status) && WSTOPSIG(status) == SIGTRAP) { |
| return arch_traceEvent(run, pid); |
| } |
| |
| if (WIFSTOPPED(status)) { |
| /* |
| * If it's an interesting signal, save the testcase |
| */ |
| if (arch_sigs[WSTOPSIG(status)].important) { |
| /* |
| * If fuzzer worker is from core fuzzing process run full |
| * analysis. Otherwise just unwind and get stack hash signature. |
| */ |
| if (run->mainWorker) { |
| arch_traceSaveData(run, pid); |
| } else { |
| arch_traceAnalyzeData(run, pid); |
| } |
| } |
| /* Do not deliver SIGSTOP */ |
| int sig = (WSTOPSIG(status) != SIGSTOP) ? WSTOPSIG(status) : 0; |
| ptrace(PT_CONTINUE, pid, (void*)1, sig); |
| return; |
| } |
| |
| /* |
| * Resumed by delivery of SIGCONT |
| */ |
| if (WIFCONTINUED(status)) { |
| return; |
| } |
| |
| /* |
| * Process exited |
| */ |
| if (WIFEXITED(status)) { |
| return; |
| } |
| |
| if (WIFSIGNALED(status)) { |
| return; |
| } |
| |
| abort(); /* NOTREACHED */ |
| } |
| |
| bool arch_traceWaitForPidStop(pid_t pid) { |
| LOG_D("Waiting for pid=%d to stop", (int)pid); |
| |
| for (;;) { |
| int status; |
| pid_t ret = wait4(pid, &status, __WALL | WUNTRACED | WTRAPPED, NULL); |
| if (ret == -1 && errno == EINTR) { |
| continue; |
| } |
| if (ret == -1) { |
| PLOG_W("wait4(pid=%d) failed", pid); |
| return false; |
| } |
| if (!WIFSTOPPED(status)) { |
| LOG_W("PID %d not in a stopped state - status:%d", pid, status); |
| return false; |
| } |
| |
| LOG_D("pid=%d stopped", (int)pid); |
| return true; |
| } |
| } |
| |
| bool arch_traceAttach(run_t* run) { |
| if (!arch_traceWaitForPidStop(run->pid)) { |
| return false; |
| } |
| if (ptrace(PT_ATTACH, run->pid, NULL, 0) == -1) { |
| PLOG_W("Couldn't ptrace(PT_ATTACH) to pid: %d", (int)run->pid); |
| return false; |
| } |
| if (!arch_traceWaitForPidStop(run->pid)) { |
| return false; |
| } |
| |
| ptrace_event_t event = { |
| /* |
| * NetBSD 8.0 seems to support PTRACE_FORK only: |
| * .pe_set_event = PTRACE_FORK | PTRACE_VFORK | PTRACE_VFORK_DONE, |
| */ |
| .pe_set_event = PTRACE_FORK, |
| }; |
| if (ptrace(PT_SET_EVENT_MASK, run->pid, &event, sizeof(event)) == -1) { |
| PLOG_W("Couldn't ptrace(PT_SET_EVENT_MASK) to pid: %d", (int)run->pid); |
| return false; |
| } |
| |
| LOG_D("Attached to PID: %d", run->pid); |
| |
| if (ptrace(PT_CONTINUE, run->pid, (void*)1, 0) == -1) { |
| PLOG_W("Couldn't ptrace(PT_CONTINUE) to pid: (int)%d", run->pid); |
| return false; |
| } |
| |
| return true; |
| } |
| |
| void arch_traceDetach(pid_t pid) { |
| if (ptrace(PT_DETACH, pid, NULL, 0) == -1) { |
| PLOG_E("PID: %d ptrace(PT_DETACH) failed", pid); |
| } |
| } |
| |
| void arch_traceSignalsInit(honggfuzz_t* hfuzz) { |
| /* Default is true for all platforms except Android */ |
| arch_sigs[SIGABRT].important = hfuzz->cfg.monitorSIGABRT; |
| |
| /* Default is false */ |
| arch_sigs[SIGVTALRM].important = hfuzz->timing.tmoutVTALRM; |
| } |