/* * * honggfuzz - architecture dependent code (NETBSD/PTRACE) * ----------------------------------------- * * Author: Kamil Rytarowski * * 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 #include // clang-format on #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #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 /* * 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; }