/*
 *
 * honggfuzz - architecture dependent code (LINUX/PTRACE)
 * -----------------------------------------
 *
 * Author: Robert Swiecki <swiecki@google.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 "linux/trace.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>
#if defined(__GLIBC__)
#include <sys/cdefs.h>
#endif
#include <sys/personality.h>
#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/user.h>
#include <sys/wait.h>
#include <time.h>
#include <unistd.h>

#include "libhfcommon/common.h"
#include "libhfcommon/files.h"
#include "libhfcommon/log.h"
#include "libhfcommon/util.h"
#include "linux/bfd.h"
#include "linux/unwind.h"
#include "report.h"
#include "sanitizers.h"
#include "socketfuzzer.h"
#include "subproc.h"

#if defined(__ANDROID__)
#include "capstone/capstone.h"
#endif

#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
#elif defined(__riscv)
#define MAX_INSTR_SZ 4
#endif

#if defined(__i386__) || defined(__x86_64__)
struct user_regs_32 {
    uint32_t ebx;
    uint32_t ecx;
    uint32_t edx;
    uint32_t esi;
    uint32_t edi;
    uint32_t ebp;
    uint32_t eax;
    uint16_t ds, __ds;
    uint16_t es, __es;
    uint16_t fs, __fs;
    uint16_t gs, __gs;
    uint32_t orig_eax;
    uint32_t eip;
    uint16_t cs, __cs;
    uint32_t eflags;
    uint32_t esp;
    uint16_t ss, __ss;
};

struct user_regs_64 {
    uint64_t r15;
    uint64_t r14;
    uint64_t r13;
    uint64_t r12;
    uint64_t bp;
    uint64_t bx;
    uint64_t r11;
    uint64_t r10;
    uint64_t r9;
    uint64_t r8;
    uint64_t ax;
    uint64_t cx;
    uint64_t dx;
    uint64_t si;
    uint64_t di;
    uint64_t orig_ax;
    uint64_t ip;
    uint64_t cs;
    uint64_t flags;
    uint64_t sp;
    uint64_t ss;
    uint64_t fs_base;
    uint64_t gs_base;
    uint64_t ds;
    uint64_t es;
    uint64_t fs;
    uint64_t gs;
};

union user_regs_t {
    struct user_regs_32 regs32;
    struct user_regs_64 regs64;
};
#endif /* defined(__i386__) || defined(__x86_64__) */

#if defined(__arm__) || defined(__aarch64__)
struct user_regs_32 {
    uint32_t r0;
    uint32_t r1;
    uint32_t r2;
    uint32_t r3;
    uint32_t r4;
    uint32_t r5;
    uint32_t r6;
    uint32_t r7;
    uint32_t r8;
    uint32_t r9;
    uint32_t r10;
    uint32_t fp;
    uint32_t ip;
    uint32_t sp;
    uint32_t lr;
    uint32_t pc;
    uint32_t cpsr;
    uint32_t ORIG_r0;
};

struct user_regs_64 {
    uint64_t regs[31];
    uint64_t sp;
    uint64_t pc;
    uint64_t pstate;
};

union user_regs_t {
    struct user_regs_32 regs32;
    struct user_regs_64 regs64;
};
#endif /* defined(__arm__) || defined(__aarch64__) */

#if defined(__powerpc64__) || defined(__powerpc__)
struct user_regs_32 {
    uint32_t gpr[32];
    uint32_t nip;
    uint32_t msr;
    uint32_t orig_gpr3;
    uint32_t ctr;
    uint32_t link;
    uint32_t xer;
    uint32_t ccr;
    uint32_t mq;
    uint32_t trap;
    uint32_t dar;
    uint32_t dsisr;
    uint32_t result;
    /*
     * elf.h's ELF_NGREG says it's 48 registers, so kernel fills it in
     * with some zeros
     */
    uint32_t zero0;
    uint32_t zero1;
    uint32_t zero2;
    uint32_t zero3;
};

struct user_regs_64 {
    uint64_t gpr[32];
    uint64_t nip;
    uint64_t msr;
    uint64_t orig_gpr3;
    uint64_t ctr;
    uint64_t link;
    uint64_t xer;
    uint64_t ccr;
    uint64_t softe;
    uint64_t trap;
    uint64_t dar;
    uint64_t dsisr;
    uint64_t result;
    /*
     * elf.h's ELF_NGREG says it's 48 registers, so kernel fills it in
     * with some zeros
     */
    uint64_t zero0;
    uint64_t zero1;
    uint64_t zero2;
    uint64_t zero3;
};

union user_regs_t {
    struct user_regs_32 regs32;
    struct user_regs_64 regs64;
};

#endif /* defined(__powerpc64__) || defined(__powerpc__) */

#if defined(__mips__) || defined(__mips64__)
struct user_regs_64 {
    uint64_t regs[32];

    uint64_t lo;
    uint64_t hi;
    uint64_t cp0_epc;
    uint64_t cp0_badvaddr;
    uint64_t cp0_status;
    uint64_t cp0_cause;
};

/*
 * Despite what mips linux kernel headers/source code says, mips32 uses the same register laytout as
 * mips64 only with GETREGS. With GETREGSET(NT_PRSTATUS) the structure size is 180 bytes
 * (ELF_NGREG=45 * sizeof(uint32_t)=4 = 180). It also uses 24-byte padding for some,
 * not-entierely-clear, reasons. The structure itself is not defined in the kernel, but only through
 * how mips_dump_regs32() saves those registers via via MIPS32_EF_* defines.
 */
struct user_regs_32 {
    uint32_t pad0[6];

    uint32_t regs[32];

    uint32_t lo;
    uint32_t hi;
    uint32_t cp0_epc;
    uint32_t cp0_badvaddr;
    uint32_t cp0_status;
    uint32_t cp0_cause;
    uint32_t unused0;
};

union user_regs_t {
    struct user_regs_32 regs32;
    struct user_regs_64 regs64;
};
#endif /* defined(__mips__) || defined(__mips64__) */

#if defined(__riscv)
struct user_regs_64 {
    uint64_t epc;
    uint64_t ra;
    uint64_t sp;
    uint64_t gp;
    uint64_t tp;
    uint64_t t0;
    uint64_t t1;
    uint64_t t2;
    uint64_t s0;
    uint64_t s1;
    uint64_t a0;
    uint64_t a1;
    uint64_t a2;
    uint64_t a3;
    uint64_t a4;
    uint64_t a5;
    uint64_t a6;
    uint64_t a7;
    uint64_t s2;
    uint64_t s3;
    uint64_t s4;
    uint64_t s5;
    uint64_t s6;
    uint64_t s7;
    uint64_t s8;
    uint64_t s9;
    uint64_t s10;
    uint64_t s11;
    uint64_t t3;
    uint64_t t4;
    uint64_t t5;
    uint64_t t6;
    uint64_t status;
    uint64_t badaddr;
    uint64_t cause;
    uint64_t orig_a0;
};

struct user_regs_32 {
    uint32_t epc;
    uint32_t ra;
    uint32_t sp;
    uint32_t gp;
    uint32_t tp;
    uint32_t t0;
    uint32_t t1;
    uint32_t t2;
    uint32_t s0;
    uint32_t s1;
    uint32_t a0;
    uint32_t a1;
    uint32_t a2;
    uint32_t a3;
    uint32_t a4;
    uint32_t a5;
    uint32_t a6;
    uint32_t a7;
    uint32_t s2;
    uint32_t s3;
    uint32_t s4;
    uint32_t s5;
    uint32_t s6;
    uint32_t s7;
    uint32_t s8;
    uint32_t s9;
    uint32_t s10;
    uint32_t s11;
    uint32_t t3;
    uint32_t t4;
    uint32_t t5;
    uint32_t t6;
    uint32_t status;
    uint32_t badaddr;
    uint32_t cause;
    uint32_t orig_a0;
};

union user_regs_t {
    struct user_regs_32 regs32;
    struct user_regs_64 regs64;
};
#endif /* defined(__riscv) */

#if defined(__clang__)
_Pragma("clang diagnostic push");
_Pragma("clang diagnostic ignored \"-Woverride-init\"");
#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",

    [SIGABRT].important = true,
    [SIGABRT].descr     = "SIGABRT",

    /* Is affected from tmoutVTALRM flag */
    [SIGVTALRM].important = false,
    [SIGVTALRM].descr     = "SIGVTALRM-TMOUT",

    /* seccomp-bpf kill */
    [SIGSYS].important = true,
    [SIGSYS].descr     = "SIGSYS",
};

#if defined(__clang__)
_Pragma("clang diagnostic pop");
#endif

#ifndef SI_FROMUSER
#define SI_FROMUSER(siptr) ((siptr)->si_code <= 0)
#endif /* SI_FROMUSER */

static size_t arch_getProcMem(pid_t pid, uint8_t* buf, size_t len, uint64_t 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(PTRACE_PEEKDATA, 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;
}

static size_t arch_getPC(pid_t pid, uint64_t* pc, uint64_t* status_reg HF_ATTR_UNUSED) {
    union user_regs_t  regs;
    const struct iovec pt_iov = {
        .iov_base = &regs,
        .iov_len  = sizeof(regs),
    };

    if (ptrace(PTRACE_GETREGSET, pid, NT_PRSTATUS, &pt_iov) == -1L) {
        PLOG_D("ptrace(PTRACE_GETREGSET) failed");
        return 0;
    }

#if defined(__i386__) || defined(__x86_64__)
    /* 32-bit */
    if (pt_iov.iov_len == sizeof(struct user_regs_32)) {
        *pc         = regs.regs32.eip;
        *status_reg = regs.regs32.eflags;
        return pt_iov.iov_len;
    }
    /* 64-bit */
    if (pt_iov.iov_len == sizeof(struct user_regs_64)) {
        *pc         = regs.regs64.ip;
        *status_reg = regs.regs64.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_32)) {
        *pc         = regs.regs32.pc;
        *status_reg = regs.regs32.cpsr;
        return pt_iov.iov_len;
    }
    /* 64-bit */
    if (pt_iov.iov_len == sizeof(struct user_regs_64)) {
        *pc         = regs.regs64.pc;
        *status_reg = regs.regs64.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_32)) {
        *pc = regs.regs32.nip;
        return pt_iov.iov_len;
    }
    /* 64-bit */
    if (pt_iov.iov_len == sizeof(struct user_regs_64)) {
        *pc = regs.regs64.nip;
        return pt_iov.iov_len;
    }

    LOG_W("Unknown registers structure size: '%zd'", pt_iov.iov_len);
    return 0;
#endif /* defined(__powerpc64__) || defined(__powerpc__) */

#if defined(__mips__) || defined(__mips64__)
    if (pt_iov.iov_len == sizeof(struct user_regs_64)) {
        *pc = regs.regs64.cp0_epc;
        return pt_iov.iov_len;
    }
    if (pt_iov.iov_len == sizeof(struct user_regs_32)) {
        *pc = regs.regs32.cp0_epc;
        return pt_iov.iov_len;
    }

    LOG_W("Unknown registers structure size: '%zd'", pt_iov.iov_len);
    return 0;
#endif /* defined(__mips__) || defined(__mips64__) */

#if defined(__riscv)
    if (pt_iov.iov_len == sizeof(struct user_regs_64)) {
        *pc = regs.regs64.epc;
        return pt_iov.iov_len;
    }
    if (pt_iov.iov_len == sizeof(struct user_regs_32)) {
        *pc = regs.regs32.epc;
        return pt_iov.iov_len;
    }

    LOG_W("Unknown registers structure size: '%zd'", pt_iov.iov_len);
    return 0;
#endif /* defined(__riscv) */

    LOG_D("Unknown/unsupported CPU architecture");
    return 0;
}

static void arch_getInstrStr(pid_t pid, uint64_t pc, uint64_t status_reg HF_ATTR_UNUSED,
    size_t pcRegSz HF_ATTR_UNUSED, 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;

    snprintf(instr, _HF_INSTR_SZ, "%s", "[UNKNOWN]");

    if ((memsz = arch_getProcMem(pid, buf, sizeof(buf), pc)) == 0) {
        snprintf(instr, _HF_INSTR_SZ, "%s", "[NOT_MMAPED]");
        return;
    }
#if !defined(__ANDROID__)
#if !defined(_HF_LINUX_NO_BFD)
    arch_bfdDisasm(pid, buf, memsz, instr);
#endif /* !defined(_HF_LINUX_NO_BFD) */
#else  /* !defined(__ANDROID__) */
    cs_arch arch;
    cs_mode mode;
#if defined(__arm__) || defined(__aarch64__)
    arch = (pcRegSz == sizeof(struct user_regs_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_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 /* defined(__ANDROID__) */

    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_traceAnalyzeData(run_t* run, pid_t pid) {
    funcs_t* funcs = util_Calloc(_HF_MAX_FUNCS * sizeof(funcs_t));
    defer {
        free(funcs);
    };

    uint64_t pc         = 0;
    uint64_t status_reg = 0;
    size_t   pcRegSz    = arch_getPC(pid, &pc, &status_reg);
    if (!pcRegSz) {
        LOG_W("ptrace arch_getPC failed");
        return;
    }

    uint64_t crashAddr               = 0;
    char     description[HF_STR_LEN] = {};
    size_t   funcCnt = sanitizers_parseReport(run, pid, funcs, &pc, &crashAddr, description);
    if (funcCnt <= 0) {
        funcCnt = arch_unwindStack(pid, funcs);
#if !defined(__ANDROID__)
#if !defined(_HF_LINUX_NO_BFD)
        arch_bfdResolveSyms(pid, funcs, funcCnt);
#endif /* !defined(_HF_LINUX_NO_BFD) */
#endif /* !defined(__ANDROID__) */
    }

#if !defined(__ANDROID__)
#if !defined(_HF_LINUX_NO_BFD)
    arch_bfdDemangle(funcs, funcCnt);
#endif /* !defined(_HF_LINUX_NO_BFD) */
#endif /* !defined(__ANDROID__) */

    /*
     * Calculate backtrace callstack hash signature
     */
    run->backtrace = sanitizers_hashCallstack(run, funcs, funcCnt, false);
}

static void arch_traceSaveData(run_t* run, pid_t pid) {
    char      instr[_HF_INSTR_SZ] = "\x00";
    siginfo_t si;
    memset(&si, '\0', sizeof(si));

    if (ptrace(PTRACE_GETSIGINFO, pid, 0, &si) == -1) {
        PLOG_W("Couldn't get siginfo for pid %d", pid);
    }

    uint64_t crashAddr = (uint64_t)(uintptr_t)si.si_addr;
    /* User-induced signals don't set si.si_addr */
    if (SI_FROMUSER(&si)) {
        crashAddr = 0UL;
    }

    int      open_flags = O_CREAT | O_EXCL | O_WRONLY | O_CLOEXEC;
    uint64_t pc         = 0;
    uint64_t status_reg = 0;
    size_t   pcRegSz    = arch_getPC(pid, &pc, &status_reg);
    if (!pcRegSz) {
        LOG_W("ptrace arch_getPC failed");
        return;
    }

    /*
     * Unwind and resolve symbols
     */
    funcs_t* funcs = util_Calloc(_HF_MAX_FUNCS * sizeof(funcs_t));
    defer {
        free(funcs);
    };

    char   description[HF_STR_LEN] = {};
    size_t funcCnt = sanitizers_parseReport(run, pid, funcs, &pc, &crashAddr, description);
    if (funcCnt == 0) {
        funcCnt = arch_unwindStack(pid, funcs);
#if !defined(__ANDROID__)
#if !defined(_HF_LINUX_NO_BFD)
        arch_bfdResolveSyms(pid, funcs, funcCnt);
#endif /* !defined(_HF_LINUX_NO_BFD) */
#endif /* !defined(__ANDROID__) */
    }

#if !defined(__ANDROID__)
#if !defined(_HF_LINUX_NO_BFD)
    arch_bfdDemangle(funcs, funcCnt);
#endif /* !defined(_HF_LINUX_NO_BFD) */
#endif /* !defined(__ANDROID__) */
    arch_getInstrStr(pid, pc, status_reg, pcRegSz, instr);

    LOG_D("Pid: %d, signo: %d, errno: %d, code: %d, addr: %p, pc: %" PRIx64 ", crashAddr: %" PRIx64
          " instr: '%s'",
        pid, si.si_signo, si.si_errno, si.si_code, si.si_addr, pc, crashAddr, instr);

    if (!SI_FROMUSER(&si) && pc &&
        crashAddr < (uint64_t)(uintptr_t)run->global->arch_linux.ignoreAddr) {
        LOG_I("Input is interesting (%s), but the si.si_addr is %p (below %p), skipping",
            util_sigName(si.si_signo), si.si_addr, run->global->arch_linux.ignoreAddr);
        return;
    }

    /*
     * Temp local copy of previous backtrace value in case worker hit crashes into multiple
     * tids for same target main thread. Will be 0 for first crash against target.
     */
    uint64_t oldBacktrace = run->backtrace;

    /* Local copy since flag is overridden for some crashes */
    bool saveUnique = run->global->io.saveUnique;

    /*
     * Calculate backtrace callstack hash signature
     */
    run->backtrace = sanitizers_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 == 0)) {
        saveUnique = false;
    }

    /*
     * If worker crashFileName member is set, it means that a tid has already crashed
     * from target main 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 allowlisted symbol. Whitelist overrides
     * both stackhash and symbol blocklist. Crash is always kept regardless
     * of the status of uniqueness flag.
     */
    if (run->global->arch_linux.symsWl) {
        char* wlSymbol = arch_btContainsSymbol(
            run->global->arch_linux.symsWlCnt, run->global->arch_linux.symsWl, funcCnt, funcs);
        if (wlSymbol != NULL) {
            saveUnique = false;
            LOG_D("Whitelisted symbol '%s' found, skipping blocklist checks", wlSymbol);
        }
    } else {
        /*
         * Check if stackhash is blocklisted
         */
        if (run->global->feedback.blocklist &&
            (fastArray64Search(run->global->feedback.blocklist, run->global->feedback.blocklistCnt,
                 run->backtrace) != -1)) {
            LOG_I("Blacklisted stack hash '%" PRIx64 "', skipping", run->backtrace);
            ATOMIC_POST_INC(run->global->cnts.blCrashesCnt);
            return;
        }

        /*
         * Check if backtrace contains blocklisted symbol
         */
        char* blSymbol = arch_btContainsSymbol(
            run->global->arch_linux.symsBlCnt, run->global->arch_linux.symsBl, funcCnt, funcs);
        if (blSymbol != NULL) {
            LOG_I("Blacklisted symbol '%s' found, skipping", blSymbol);
            ATOMIC_POST_INC(run->global->cnts.blCrashesCnt);
            return;
        }
    }

    /* If non-blocklisted crash detected, zero set two MSB */
    ATOMIC_POST_ADD(run->global->cfg.dynFileIterExpire, _HF_DYNFILE_SUB_MASK);

    /* Those addresses will be random, so depend on stack-traces for uniqueness */
    if (!run->global->arch_linux.disableRandomization) {
        pc        = 0UL;
        crashAddr = 0UL;
    }
    /* crashAddr (si.si_addr) never makes sense for SIGABRT */
    if (si.si_signo == SIGABRT) {
        crashAddr = 0UL;
    }

    /* 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->dynfile->path);
    } else if (saveUnique) {
        snprintf(run->crashFileName, sizeof(run->crashFileName),
            "%s/%s.PC.%" PRIx64 ".STACK.%" PRIx64 ".CODE.%d.ADDR.%" PRIx64 ".INSTR.%s.%s",
            run->global->io.crashDir, util_sigName(si.si_signo), pc, run->backtrace, si.si_code,
            crashAddr, instr, run->global->io.fileExtn);
    } else {
        char localtmstr[HF_STR_LEN];
        util_getLocalTime("%F.%H:%M:%S", localtmstr, sizeof(localtmstr), time(NULL));
        snprintf(run->crashFileName, sizeof(run->crashFileName),
            "%s/%s.PC.%" PRIx64 ".STACK.%" PRIx64 ".CODE.%d.ADDR.%" PRIx64 ".INSTR.%s.%s.%d.%s",
            run->global->io.crashDir, util_sigName(si.si_signo), pc, run->backtrace, si.si_code,
            crashAddr, 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)) {
        if (run->global->io.saveSmaller) {
            /*
             * If the new run produces a smaller file than exists already, we
             * will replace it.
             *
             * If this is the second test case, we save the first with .orig
             * suffix before overwriting.
             */
            struct stat st;
            char        origFile[PATH_MAX];
            if (stat(run->crashFileName, &st) == -1) {
                LOG_W("Couldn't stat() the '%s' file", run->crashFileName);
            } else if (st.st_size <= (off_t)run->dynfile->size) {
                LOG_I("Crash (dup): '%s' exists and is smaller, skipping", run->crashFileName);
                /* Clear filename so that verifier can understand we hit a duplicate */
                memset(run->crashFileName, 0, sizeof(run->crashFileName));
                return;
            } else {
                /* we have a new champion */
                LOG_I("Crash: overwriting '%s' (old %zu bytes, new %zu bytes)", run->crashFileName,
                    (size_t)st.st_size, (size_t)run->dynfile->size);
            }

            snprintf(origFile, sizeof(origFile), "%s.orig", run->crashFileName);
            if (!files_exists(origFile)) {
                rename(run->crashFileName, origFile);
            } else {
                /* allow overwrite */
                open_flags = O_CREAT | O_WRONLY | O_CLOEXEC;
            }
        } else {
            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->dynfile->data, run->dynfile->size, open_flags)) {
        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);

    report_appendReport(pid, run, funcs, funcCnt, pc, crashAddr, si.si_signo, instr, description);
}

#define __WEVENT(status) ((status & 0xFF0000) >> 16)
static void arch_traceEvent(int status, pid_t pid) {
    LOG_D("PID: %d, Ptrace event: %d", pid, __WEVENT(status));
    switch (__WEVENT(status)) {
    case PTRACE_EVENT_EXIT: {
        unsigned long event_msg;
        if (ptrace(PTRACE_GETEVENTMSG, pid, NULL, &event_msg) == -1) {
            PLOG_E("ptrace(PTRACE_GETEVENTMSG,%d) failed", pid);
            return;
        }

        if (WIFEXITED(event_msg)) {
            LOG_D("PID: %d exited with exit_code: %lu", pid, (unsigned long)WEXITSTATUS(event_msg));
        } else if (WIFSIGNALED(event_msg)) {
            LOG_D("PID: %d terminated with signal: %lu", pid, (unsigned long)WTERMSIG(event_msg));
        } else {
            LOG_D("PID: %d exited with unknown status: %lu (%s)", pid, event_msg,
                subproc_StatusToStr(event_msg));
        }
    } break;
    default:
        break;
    }

    ptrace(PTRACE_CONT, pid, 0, 0);
}

void arch_traceAnalyze(run_t* run, int status, pid_t pid) {
    /*
     * It's a ptrace event, deal with it elsewhere
     */
    if (WIFSTOPPED(status) && __WEVENT(status)) {
        return arch_traceEvent(status, 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, as we don't support PTRACE_LISTEN anyway */
        int sig = (WSTOPSIG(status) != SIGSTOP) ? WSTOPSIG(status) : 0;
        ptrace(PTRACE_CONT, pid, 0, sig);
        return;
    }

    /*
     * Resumed by delivery of SIGCONT
     */
    if (WIFCONTINUED(status)) {
        return;
    }

    /*
     * Process exited
     */
    if (WIFEXITED(status)) {
        return;
    }

    if (WIFSIGNALED(status)) {
        return;
    }

    abort(); /* NOTREACHED */
}

static bool arch_listThreads(int tasks[], size_t thrSz, int pid) {
    char path[512];
    snprintf(path, sizeof(path), "/proc/%d/task", pid);

    /* An optimization, the number of threads is st.st_nlink - 2 (. and ..) */
    struct stat st;
    if (stat(path, &st) != -1) {
        if (st.st_nlink == 3) {
            tasks[0] = pid;
            tasks[1] = 0;
            return true;
        }
    }

    size_t count = 0;
    DIR*   dir   = opendir(path);
    if (!dir) {
        PLOG_E("Couldn't open dir '%s'", path);
        return false;
    }
    defer {
        closedir(dir);
    };

    for (;;) {
        errno                    = 0;
        const struct dirent* res = readdir(dir);
        if (res == NULL && errno != 0) {
            PLOG_E("Couldn't read contents of '%s'", path);
            return false;
        }

        if (res == NULL) {
            break;
        }

        pid_t pid = (pid_t)strtol(res->d_name, (char**)NULL, 10);
        if (pid == 0) {
            LOG_D("The following dir entry couldn't be converted to pid_t '%s'", res->d_name);
            continue;
        }

        tasks[count++] = pid;
        LOG_D("Added pid '%d' from '%s/%s'", pid, path, res->d_name);

        if (count >= thrSz) {
            break;
        }
    }
    PLOG_D("Total number of threads in pid '%d': '%zd'", pid, count);
    tasks[count + 1] = 0;
    if (count < 1) {
        return false;
    }
    return true;
}

bool arch_traceWaitForPidStop(pid_t pid) {
    for (;;) {
        int   status;
        pid_t ret = wait4(pid, &status, __WALL | WUNTRACED, 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 (%s)", pid, status,
                subproc_StatusToStr(status));
            return false;
        }
        return true;
    }
}

#define MAX_THREAD_IN_TASK 4096
bool arch_traceAttach(run_t* run) {
/*
 * It should be present since, at least, Linux kernel 3.8, but
 * not always defined in kernel-headers
 */
#if !defined(PTRACE_O_EXITKILL)
#define PTRACE_O_EXITKILL (1 << 20)
#endif /* !defined(PTRACE_O_EXITKILL) */
    long seize_options =
        PTRACE_O_TRACECLONE | PTRACE_O_TRACEFORK | PTRACE_O_TRACEVFORK | PTRACE_O_EXITKILL;
    /* The event is only used with sanitizers */
    if (run->global->sanitizer.enable) {
        seize_options |= PTRACE_O_TRACEEXIT;
    }

    if (!arch_traceWaitForPidStop(run->pid)) {
        return false;
    }

    if (ptrace(PTRACE_SEIZE, run->pid, NULL, seize_options) == -1) {
        PLOG_W("Couldn't ptrace(PTRACE_SEIZE) to pid: %d", (int)run->pid);
        return false;
    }

    LOG_D("Attached to PID: %d", (int)run->pid);

    int tasks[MAX_THREAD_IN_TASK + 1] = {0};
    if (!arch_listThreads(tasks, MAX_THREAD_IN_TASK, run->pid)) {
        LOG_E("Couldn't read thread list for pid '%d'", run->pid);
        return false;
    }

    for (int i = 0; i < MAX_THREAD_IN_TASK && tasks[i]; i++) {
        if (tasks[i] == run->pid) {
            continue;
        }
        if (ptrace(PTRACE_SEIZE, tasks[i], NULL, seize_options) == -1) {
            PLOG_W("Couldn't ptrace(PTRACE_SEIZE) to pid: %d", tasks[i]);
            continue;
        }
        LOG_D("Attached to PID: %d (thread_group:%d)", tasks[i], run->pid);
    }

    if (ptrace(PTRACE_CONT, run->pid, NULL, NULL) == -1) {
        PLOG_W("ptrace(PTRACE_CONT) to pid: %d", (int)run->pid);
    }

    return true;
}

void arch_traceDetach(pid_t pid) {
    if (syscall(__NR_kill, pid, 0) == -1 && errno == ESRCH) {
        LOG_D("PID: %d no longer exists", pid);
        return;
    }

    int tasks[MAX_THREAD_IN_TASK + 1] = {0};
    if (!arch_listThreads(tasks, MAX_THREAD_IN_TASK, pid)) {
        LOG_E("Couldn't read thread list for pid '%d'", pid);
        return;
    }

    for (int i = 0; i < MAX_THREAD_IN_TASK && tasks[i]; i++) {
        ptrace(PTRACE_INTERRUPT, tasks[i], NULL, NULL);
        arch_traceWaitForPidStop(tasks[i]);
        ptrace(PTRACE_DETACH, tasks[i], NULL, NULL);
    }
}

void arch_traceSignalsInit(honggfuzz_t* hfuzz) {
    /* Default is false */
    arch_sigs[SIGVTALRM].important = hfuzz->timing.tmoutVTALRM;

    /* Let *SAN handle it, if it's enabled */
    if (hfuzz->sanitizer.enable) {
        LOG_I("Sanitizer support enabled. SIGSEGV/SIGBUS/SIGILL/SIGFPE will not be reported, and "
              "should be handled by *SAN code internally");
        arch_sigs[SIGSEGV].important = false;
        arch_sigs[SIGBUS].important  = false;
        arch_sigs[SIGILL].important  = false;
        arch_sigs[SIGFPE].important  = false;
    }
}
