C++程序  |  737行  |  24.56 KB

/*
 *
 * honggfuzz - fuzzing routines
 * -----------------------------------------
 *
 * Author:
 * Robert Swiecki <swiecki@google.com>
 * Felix Gröbert <groebert@google.com>
 *
 * Copyright 2010-2015 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 "fuzz.h"

#include <errno.h>
#include <fcntl.h>
#include <inttypes.h>
#include <libgen.h>
#include <pthread.h>
#include <signal.h>
#include <stddef.h>
#include <stdint.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <sys/mman.h>
#include <sys/param.h>
#include <sys/stat.h>
#include <sys/time.h>
#include <sys/types.h>
#include <time.h>
#include <unistd.h>

#include "arch.h"
#include "honggfuzz.h"
#include "input.h"
#include "libcommon/common.h"
#include "libcommon/files.h"
#include "libcommon/log.h"
#include "libcommon/util.h"
#include "mangle.h"
#include "report.h"
#include "sancov.h"
#include "sanitizers.h"
#include "subproc.h"

static time_t termTimeStamp = 0;

bool fuzz_isTerminating(void) {
    if (ATOMIC_GET(termTimeStamp) != 0) {
        return true;
    }
    return false;
}

void fuzz_setTerminating(void) {
    if (ATOMIC_GET(termTimeStamp) != 0) {
        return;
    }
    ATOMIC_SET(termTimeStamp, time(NULL));
}

bool fuzz_shouldTerminate() {
    if (ATOMIC_GET(termTimeStamp) == 0) {
        return false;
    }
    if ((time(NULL) - ATOMIC_GET(termTimeStamp)) > 5) {
        return true;
    }
    return false;
}

static void fuzz_getFileName(run_t* run) {
    char bname[PATH_MAX];
    snprintf(bname, sizeof(bname), "%s", run->global->exe.cmdline[0]);
    snprintf(run->fileName, PATH_MAX, "%s/honggfuzz.input.%" PRIu32 ".%s.%s",
        run->global->io.workDir, run->fuzzNo, basename(bname), run->global->io.fileExtn);
}

static bool fuzz_prepareFileDynamically(run_t* run) {
    run->origFileName = "[DYNAMIC]";

    {
        MX_SCOPED_RWLOCK_READ(&run->global->dynfileq_mutex);

        if (run->global->dynfileqCnt == 0) {
            LOG_F(
                "The dynamic file corpus is empty. Apparently, the initial fuzzing of the "
                "provided file corpus (-f) has not produced any follow-up files with positive "
                "coverage and/or CPU counters");
        }

        if (run->dynfileqCurrent == NULL) {
            run->dynfileqCurrent = TAILQ_FIRST(&run->global->dynfileq);
        } else {
            if (run->dynfileqCurrent == TAILQ_LAST(&run->global->dynfileq, dyns_t)) {
                run->dynfileqCurrent = TAILQ_FIRST(&run->global->dynfileq);
            } else {
                run->dynfileqCurrent = TAILQ_NEXT(run->dynfileqCurrent, pointers);
            }
        }
    }

    memcpy(run->dynamicFile, run->dynfileqCurrent->data, run->dynfileqCurrent->size);
    run->dynamicFileSz = run->dynfileqCurrent->size;

    mangle_mangleContent(run);

    if (run->global->persistent == false &&
        files_writeBufToFile(run->fileName, run->dynamicFile, run->dynamicFileSz,
            O_WRONLY | O_CREAT | O_TRUNC | O_CLOEXEC) == false) {
        LOG_E("Couldn't write buffer to file '%s'", run->fileName);
        return false;
    }

    return true;
}

static bool fuzz_prepareFile(run_t* run, bool rewind) {
    char fname[PATH_MAX];
    if (input_getNext(run, fname, rewind) == false) {
        return false;
    }
    run->origFileName = files_basename(fname);

    ssize_t fileSz = files_readFileToBufMax(fname, run->dynamicFile, run->global->maxFileSz);
    if (fileSz < 0) {
        LOG_E("Couldn't read contents of '%s'", fname);
        return false;
    }
    run->dynamicFileSz = fileSz;

    mangle_mangleContent(run);

    if (run->global->persistent == false &&
        files_writeBufToFile(run->fileName, run->dynamicFile, run->dynamicFileSz,
            O_WRONLY | O_CREAT | O_TRUNC | O_CLOEXEC) == false) {
        LOG_E("Couldn't write buffer to file '%s'", run->fileName);
        return false;
    }

    return true;
}

static bool fuzz_prepareFileExternally(run_t* run) {
    char fname[PATH_MAX];
    if (input_getNext(run, fname, true /* rewind */)) {
        run->origFileName = files_basename(fname);
        if (files_copyFile(fname, run->fileName, NULL, false /* try_link */) == false) {
            LOG_E("files_copyFile('%s', '%s')", fname, run->fileName);
            return false;
        }
    } else {
        run->origFileName = "[EXTERNAL]";
        int dstfd = open(run->fileName, O_CREAT | O_TRUNC | O_RDWR | O_CLOEXEC, 0644);
        if (dstfd == -1) {
            PLOG_E("Couldn't create a temporary file '%s'", run->fileName);
            return false;
        }
        close(dstfd);
    }

    LOG_D("Created '%s' as an input file", run->fileName);

    const char* const argv[] = {run->global->exe.externalCommand, run->fileName, NULL};
    if (subproc_System(run, argv) != 0) {
        LOG_E("Subprocess '%s' returned abnormally", run->global->exe.externalCommand);
        return false;
    }
    LOG_D("Subporcess '%s' finished with success", run->global->exe.externalCommand);

    ssize_t rsz = files_readFileToBufMax(run->fileName, run->dynamicFile, run->global->maxFileSz);
    if (rsz < 0) {
        LOG_W("Couldn't read back '%s' to the buffer", run->fileName);
        return false;
    }
    run->dynamicFileSz = rsz;

    if (run->global->persistent) {
        unlink(run->fileName);
    }

    return true;
}

static bool fuzz_postProcessFile(run_t* run) {
    if (run->global->persistent) {
        if (files_writeBufToFile(run->fileName, run->dynamicFile, run->dynamicFileSz,
                O_CREAT | O_TRUNC | O_WRONLY | O_CLOEXEC) == false) {
            LOG_E("Couldn't write file to '%s'", run->fileName);
            return false;
        }
    }

    const char* const argv[] = {run->global->exe.postExternalCommand, run->fileName, NULL};
    if (subproc_System(run, argv) != 0) {
        LOG_E("Subprocess '%s' returned abnormally", run->global->exe.postExternalCommand);
        return false;
    }
    LOG_D("Subporcess '%s' finished with success", run->global->exe.externalCommand);

    ssize_t rsz = files_readFileToBufMax(run->fileName, run->dynamicFile, run->global->maxFileSz);
    if (rsz < 0) {
        LOG_W("Couldn't read back '%s' to the buffer", run->fileName);
        return false;
    }
    run->dynamicFileSz = rsz;

    return true;
}

static fuzzState_t fuzz_getState(honggfuzz_t* hfuzz) { return ATOMIC_GET(hfuzz->state); }

static void fuzz_setState(honggfuzz_t* hfuzz, fuzzState_t state) {
    /* All threads must indicate willingness to switch to _HF_STATE_DYNAMIC_MAIN */
    if (state == _HF_STATE_DYNAMIC_MAIN) {
        static size_t cnt = 0;
        ATOMIC_PRE_INC(cnt);
        while (ATOMIC_GET(cnt) < hfuzz->threads.threadsMax) {
            if (fuzz_isTerminating()) {
                return;
            }
            sleep(1);
        }
    }

    static pthread_mutex_t state_mutex = PTHREAD_MUTEX_INITIALIZER;
    MX_SCOPED_LOCK(&state_mutex);

    if (hfuzz->state == state) {
        return;
    }

    switch (state) {
        case _HF_STATE_DYNAMIC_PRE:
            LOG_I("Entering phase 1/2: Dry Run");
            break;
        case _HF_STATE_DYNAMIC_MAIN:
            LOG_I("Entering phase 2/2: Main");
            break;
        case _HF_STATE_STATIC:
            LOG_I("Entering phase: Static");
            break;
        default:
            LOG_I("Entering unknown phase: %d", state);
            break;
    }

    ATOMIC_SET(hfuzz->state, state);
}

static bool fuzz_runVerifier(run_t* crashedFuzzer) {
    int crashFd = -1;
    uint8_t* crashBuf = NULL;
    off_t crashFileSz = 0;

    crashBuf = files_mapFile(crashedFuzzer->crashFileName, &crashFileSz, &crashFd, false);
    if (crashBuf == NULL) {
        LOG_E("Couldn't open and map '%s' in R/O mode", crashedFuzzer->crashFileName);
        return false;
    }
    defer {
        munmap(crashBuf, crashFileSz);
        close(crashFd);
    };

    LOG_I("Launching verifier for %" PRIx64 " hash", crashedFuzzer->backtrace);
    for (int i = 0; i < _HF_VERIFIER_ITER; i++) {
        run_t vFuzzer = {
            .global = crashedFuzzer->global,
            .pid = 0,
            .persistentPid = 0,
            .state = fuzz_getState(crashedFuzzer->global),
            .timeStartedMillis = util_timeNowMillis(),
            .crashFileName = {0},
            .pc = 0ULL,
            .backtrace = 0ULL,
            .access = 0ULL,
            .exception = 0,
            .dynfileqCurrent = NULL,
            .dynamicFileSz = 0,
            .dynamicFile = NULL,
            .sanCovCnts =
                {
                    .hitBBCnt = 0ULL,
                    .totalBBCnt = 0ULL,
                    .dsoCnt = 0ULL,
                    .iDsoCnt = 0ULL,
                    .newBBCnt = 0ULL,
                    .crashesCnt = 0ULL,
                },
            .report = {'\0'},
            .mainWorker = false,
            .fuzzNo = crashedFuzzer->fuzzNo,
            .persistentSock = -1,
            .tmOutSignaled = false,

            .linux =
                {
                    .hwCnts =
                        {
                            .cpuInstrCnt = 0ULL,
                            .cpuBranchCnt = 0ULL,
                            .bbCnt = 0ULL,
                            .newBBCnt = 0ULL,
                            .softCntPc = 0ULL,
                            .softCntEdge = 0ULL,
                            .softCntCmp = 0ULL,
                        },
                    .attachedPid = 0,
                },
        };

        if (arch_archThreadInit(&vFuzzer) == false) {
            LOG_F("Could not initialize the thread");
        }

        fuzz_getFileName(&vFuzzer);
        if (files_writeBufToFile(vFuzzer.fileName, crashBuf, crashFileSz,
                O_WRONLY | O_CREAT | O_TRUNC | O_CLOEXEC) == false) {
            LOG_E("Couldn't write buffer to file '%s'", vFuzzer.fileName);
            return false;
        }

        if (subproc_Run(&vFuzzer) == false) {
            LOG_F("subproc_Run()");
        }

        /* Delete intermediate files generated from verifier */
        unlink(vFuzzer.fileName);

        /* If stack hash doesn't match skip name tag and exit */
        if (crashedFuzzer->backtrace != vFuzzer.backtrace) {
            LOG_D("Verifier stack hash mismatch");
            return false;
        }
    }

    /* Workspace is inherited, just append a extra suffix */
    char verFile[PATH_MAX] = {0};
    snprintf(verFile, sizeof(verFile), "%s.verified", crashedFuzzer->crashFileName);

    /* Copy file with new suffix & remove original copy */
    bool dstFileExists = false;
    if (files_copyFile(
            crashedFuzzer->crashFileName, verFile, &dstFileExists, true /* try_link */)) {
        LOG_I("Successfully verified, saving as (%s)", verFile);
        ATOMIC_POST_INC(crashedFuzzer->global->cnts.verifiedCrashesCnt);
        unlink(crashedFuzzer->crashFileName);
    } else {
        if (dstFileExists) {
            LOG_I("It seems that '%s' already exists, skipping", verFile);
        } else {
            LOG_E("Couldn't copy '%s' to '%s'", crashedFuzzer->crashFileName, verFile);
            return false;
        }
    }

    return true;
}

static bool fuzz_writeCovFile(const char* dir, const uint8_t* data, size_t len) {
    char fname[PATH_MAX];

    uint64_t crc64f = util_CRC64(data, len);
    uint64_t crc64r = util_CRC64Rev(data, len);
    snprintf(fname, sizeof(fname), "%s/%016" PRIx64 "%016" PRIx64 ".%08" PRIx32 ".honggfuzz.cov",
        dir, crc64f, crc64r, (uint32_t)len);

    if (access(fname, R_OK) == 0) {
        LOG_D("File '%s' already exists in the output corpus directory '%s'", fname, dir);
        return true;
    }

    LOG_D("Adding file '%s' to the corpus directory '%s'", fname, dir);

    if (files_writeBufToFile(fname, data, len, O_WRONLY | O_CREAT | O_EXCL | O_TRUNC | O_CLOEXEC) ==
        false) {
        LOG_W("Couldn't write buffer to file '%s'", fname);
        return false;
    }

    return true;
}

static void fuzz_addFileToFileQ(run_t* run) {
    struct dynfile_t* dynfile = (struct dynfile_t*)util_Malloc(sizeof(struct dynfile_t));
    dynfile->size = run->dynamicFileSz;
    dynfile->data = (uint8_t*)util_Malloc(run->dynamicFileSz);
    memcpy(dynfile->data, run->dynamicFile, run->dynamicFileSz);

    MX_SCOPED_RWLOCK_WRITE(&run->global->dynfileq_mutex);
    TAILQ_INSERT_TAIL(&run->global->dynfileq, dynfile, pointers);
    run->global->dynfileqCnt++;

    if (!fuzz_writeCovFile(run->global->io.covDirAll, run->dynamicFile, run->dynamicFileSz)) {
        LOG_E("Couldn't save the coverage data to '%s'", run->global->io.covDirAll);
    }

    /* No need to add files to the new coverage dir, if this is just the dry-run phase */
    if (run->state == _HF_STATE_DYNAMIC_PRE || run->global->io.covDirNew == NULL) {
        return;
    }

    if (!fuzz_writeCovFile(run->global->io.covDirNew, run->dynamicFile, run->dynamicFileSz)) {
        LOG_E("Couldn't save the new coverage data to '%s'", run->global->io.covDirNew);
    }
}

static void fuzz_perfFeedback(run_t* run) {
    if (run->global->skipFeedbackOnTimeout && run->tmOutSignaled) {
        return;
    }

    LOG_D("New file size: %zu, Perf feedback new/cur (instr,branch): %" PRIu64 "/%" PRIu64
          "/%" PRIu64 "/%" PRIu64 ", BBcnt new/total: %" PRIu64 "/%" PRIu64,
        run->dynamicFileSz, run->linux.hwCnts.cpuInstrCnt, run->global->linux.hwCnts.cpuInstrCnt,
        run->linux.hwCnts.cpuBranchCnt, run->global->linux.hwCnts.cpuBranchCnt,
        run->linux.hwCnts.newBBCnt, run->global->linux.hwCnts.bbCnt);

    MX_SCOPED_LOCK(&run->global->feedback_mutex);

    uint64_t softCntPc = 0UL;
    uint64_t softCntEdge = 0UL;
    uint64_t softCntCmp = 0UL;
    if (run->global->bbFd != -1) {
        softCntPc = ATOMIC_GET(run->global->feedback->pidFeedbackPc[run->fuzzNo]);
        ATOMIC_CLEAR(run->global->feedback->pidFeedbackPc[run->fuzzNo]);
        softCntEdge = ATOMIC_GET(run->global->feedback->pidFeedbackEdge[run->fuzzNo]);
        ATOMIC_CLEAR(run->global->feedback->pidFeedbackEdge[run->fuzzNo]);
        softCntCmp = ATOMIC_GET(run->global->feedback->pidFeedbackCmp[run->fuzzNo]);
        ATOMIC_CLEAR(run->global->feedback->pidFeedbackCmp[run->fuzzNo]);
    }

    int64_t diff0 = run->global->linux.hwCnts.cpuInstrCnt - run->linux.hwCnts.cpuInstrCnt;
    int64_t diff1 = run->global->linux.hwCnts.cpuBranchCnt - run->linux.hwCnts.cpuBranchCnt;

    /*
     * Coverage is the primary counter, the rest is secondary, and taken into consideration only
     * if the coverage counter has not been changed
     */
    if (run->linux.hwCnts.newBBCnt > 0 || softCntPc > 0 || softCntEdge > 0 || softCntCmp > 0 ||
        diff0 < 0 || diff1 < 0) {
        if (diff0 < 0) {
            run->global->linux.hwCnts.cpuInstrCnt = run->linux.hwCnts.cpuInstrCnt;
        }
        if (diff1 < 0) {
            run->global->linux.hwCnts.cpuBranchCnt = run->linux.hwCnts.cpuBranchCnt;
        }
        run->global->linux.hwCnts.bbCnt += run->linux.hwCnts.newBBCnt;
        run->global->linux.hwCnts.softCntPc += softCntPc;
        run->global->linux.hwCnts.softCntEdge += softCntEdge;
        run->global->linux.hwCnts.softCntCmp += softCntCmp;

        LOG_I("Size:%zu (i,b,hw,edge,ip,cmp): %" PRIu64 "/%" PRIu64 "/%" PRIu64 "/%" PRIu64
              "/%" PRIu64 "/%" PRIu64 ", Tot:%" PRIu64 "/%" PRIu64 "/%" PRIu64 "/%" PRIu64
              "/%" PRIu64 "/%" PRIu64,
            run->dynamicFileSz, run->linux.hwCnts.cpuInstrCnt, run->linux.hwCnts.cpuBranchCnt,
            run->linux.hwCnts.newBBCnt, softCntEdge, softCntPc, softCntCmp,
            run->global->linux.hwCnts.cpuInstrCnt, run->global->linux.hwCnts.cpuBranchCnt,
            run->global->linux.hwCnts.bbCnt, run->global->linux.hwCnts.softCntEdge,
            run->global->linux.hwCnts.softCntPc, run->global->linux.hwCnts.softCntCmp);

        fuzz_addFileToFileQ(run);
    }
}

static void fuzz_sanCovFeedback(run_t* run) {
    if (run->global->skipFeedbackOnTimeout && run->tmOutSignaled) {
        return;
    }

    LOG_D("File size (Best/New): %zu, SanCov feedback (bb,dso): Best: [%" PRIu64 ",%" PRIu64
          "] / New: [%" PRIu64 ",%" PRIu64 "], newBBs:%" PRIu64,
        run->dynamicFileSz, run->global->sanCovCnts.hitBBCnt, run->global->sanCovCnts.iDsoCnt,
        run->sanCovCnts.hitBBCnt, run->sanCovCnts.iDsoCnt, run->sanCovCnts.newBBCnt);

    MX_SCOPED_LOCK(&run->global->feedback_mutex);

    int64_t diff0 = run->global->linux.hwCnts.cpuInstrCnt - run->linux.hwCnts.cpuInstrCnt;
    int64_t diff1 = run->global->linux.hwCnts.cpuBranchCnt - run->linux.hwCnts.cpuBranchCnt;

    /*
     * Keep mutated seed if:
     *  a) Newly discovered (not met before) BBs
     *  b) More instrumented DSOs loaded
     *
     * TODO: (a) method can significantly assist to further improvements in interesting areas
     * discovery if combined with seeds pool/queue support. If a runtime queue is maintained
     * more interesting seeds can be saved between runs instead of instantly discarded
     * based on current absolute elitism (only one mutated seed is promoted).
     */

    bool newCov =
        (run->sanCovCnts.newBBCnt > 0 || run->global->sanCovCnts.iDsoCnt < run->sanCovCnts.iDsoCnt);

    if (newCov || (diff0 < 0 || diff1 < 0)) {
        LOG_I("SanCov Update: fsize:%zu, newBBs:%" PRIu64 ", (Cur,New): %" PRIu64 "/%" PRIu64
              ",%" PRIu64 "/%" PRIu64,
            run->dynamicFileSz, run->sanCovCnts.newBBCnt, run->global->sanCovCnts.hitBBCnt,
            run->global->sanCovCnts.iDsoCnt, run->sanCovCnts.hitBBCnt, run->sanCovCnts.iDsoCnt);

        run->global->sanCovCnts.hitBBCnt += run->sanCovCnts.newBBCnt;
        run->global->sanCovCnts.dsoCnt = run->sanCovCnts.dsoCnt;
        run->global->sanCovCnts.iDsoCnt = run->sanCovCnts.iDsoCnt;
        run->global->sanCovCnts.crashesCnt += run->sanCovCnts.crashesCnt;
        run->global->sanCovCnts.newBBCnt = run->sanCovCnts.newBBCnt;

        if (run->global->sanCovCnts.totalBBCnt < run->sanCovCnts.totalBBCnt) {
            /* Keep only the max value (for dlopen cases) to measure total target coverage */
            run->global->sanCovCnts.totalBBCnt = run->sanCovCnts.totalBBCnt;
        }

        run->global->linux.hwCnts.cpuInstrCnt = run->linux.hwCnts.cpuInstrCnt;
        run->global->linux.hwCnts.cpuBranchCnt = run->linux.hwCnts.cpuBranchCnt;

        fuzz_addFileToFileQ(run);
    }
}

static void fuzz_fuzzLoop(run_t* run) {
    run->pid = 0;
    run->timeStartedMillis = util_timeNowMillis();
    run->state = fuzz_getState(run->global);
    run->crashFileName[0] = '\0';
    run->pc = 0ULL;
    run->backtrace = 0ULL;
    run->access = 0ULL;
    run->exception = 0;
    run->report[0] = '\0';
    run->mainWorker = true;
    run->origFileName = "DYNAMIC";
    run->mutationsPerRun = run->global->mutationsPerRun;
    run->dynamicFileSz = 0;

    run->sanCovCnts.hitBBCnt = 0ULL;
    run->sanCovCnts.totalBBCnt = 0ULL;
    run->sanCovCnts.dsoCnt = 0ULL;
    run->sanCovCnts.newBBCnt = 0ULL;
    run->sanCovCnts.crashesCnt = 0ULL;

    run->linux.hwCnts.cpuInstrCnt = 0ULL;
    run->linux.hwCnts.cpuBranchCnt = 0ULL;
    run->linux.hwCnts.bbCnt = 0ULL;
    run->linux.hwCnts.newBBCnt = 0ULL;

    if (run->state == _HF_STATE_DYNAMIC_PRE) {
        run->mutationsPerRun = 0U;
        if (fuzz_prepareFile(run, false /* rewind */) == false) {
            fuzz_setState(run->global, _HF_STATE_DYNAMIC_MAIN);
            run->state = fuzz_getState(run->global);
        }
    }

    if (fuzz_isTerminating()) {
        return;
    }

    if (run->state == _HF_STATE_DYNAMIC_MAIN) {
        if (run->global->exe.externalCommand) {
            if (!fuzz_prepareFileExternally(run)) {
                LOG_F("fuzz_prepareFileExternally() failed");
            }
        } else if (!fuzz_prepareFileDynamically(run)) {
            LOG_F("fuzz_prepareFileDynamically() failed");
        }

        if (run->global->exe.postExternalCommand) {
            if (!fuzz_postProcessFile(run)) {
                LOG_F("fuzz_postProcessFile() failed");
            }
        }
    }

    if (run->state == _HF_STATE_STATIC) {
        if (run->global->exe.externalCommand) {
            if (!fuzz_prepareFileExternally(run)) {
                LOG_F("fuzz_prepareFileExternally() failed");
            }
        } else {
            if (!fuzz_prepareFile(run, true /* rewind */)) {
                LOG_F("fuzz_prepareFile() failed");
            }
        }

        if (run->global->exe.postExternalCommand != NULL) {
            if (!fuzz_postProcessFile(run)) {
                LOG_F("fuzz_postProcessFile() failed");
            }
        }
    }

    if (subproc_Run(run) == false) {
        LOG_F("subproc_Run()");
    }

    if (run->global->persistent == false) {
        unlink(run->fileName);
    }

    if (run->global->dynFileMethod != _HF_DYNFILE_NONE) {
        fuzz_perfFeedback(run);
    }
    if (run->global->useSanCov) {
        fuzz_sanCovFeedback(run);
    }

    if (run->global->useVerifier && (run->crashFileName[0] != 0) && run->backtrace) {
        if (!fuzz_runVerifier(run)) {
            LOG_I("Failed to verify %s", run->crashFileName);
        }
    }

    report_Report(run);
}

static void* fuzz_threadNew(void* arg) {
    honggfuzz_t* hfuzz = (honggfuzz_t*)arg;
    unsigned int fuzzNo = ATOMIC_POST_INC(hfuzz->threads.threadsActiveCnt);
    LOG_I("Launched new fuzzing thread, no. #%" PRId32, fuzzNo);

    run_t run = {
        .global = hfuzz,
        .pid = 0,
        .persistentPid = 0,
        .dynfileqCurrent = NULL,
        .dynamicFile = util_Calloc(hfuzz->maxFileSz),
        .fuzzNo = fuzzNo,
        .persistentSock = -1,
        .tmOutSignaled = false,
        .fileName = "[UNSET]",

        .linux.attachedPid = 0,
    };
    defer { free(run.dynamicFile); };
    fuzz_getFileName(&run);

    if (arch_archThreadInit(&run) == false) {
        LOG_F("Could not initialize the thread");
    }

    for (;;) {
        /* Check if dry run mode with verifier enabled */
        if (run.global->mutationsPerRun == 0U && run.global->useVerifier) {
            if (ATOMIC_POST_INC(run.global->cnts.mutationsCnt) >= run.global->io.fileCnt) {
                ATOMIC_POST_INC(run.global->threads.threadsFinished);
                break;
            }
        }
        /* Check for max iterations limit if set */
        else if ((ATOMIC_POST_INC(run.global->cnts.mutationsCnt) >= run.global->mutationsMax) &&
                 run.global->mutationsMax) {
            ATOMIC_POST_INC(run.global->threads.threadsFinished);
            break;
        }

        fuzz_fuzzLoop(&run);

        if (fuzz_isTerminating()) {
            break;
        }

        if (run.global->exitUponCrash && ATOMIC_GET(run.global->cnts.crashesCnt) > 0) {
            LOG_I("Seen a crash. Terminating all fuzzing threads");
            fuzz_setTerminating();
            break;
        }
    }

    LOG_I("Terminating thread no. #%" PRId32, fuzzNo);
    ATOMIC_POST_INC(run.global->threads.threadsFinished);
    pthread_kill(run.global->threads.mainThread, SIGALRM);
    return NULL;
}

static void fuzz_runThread(honggfuzz_t* hfuzz, pthread_t* thread, void* (*thread_func)(void*)) {
    pthread_attr_t attr;

    pthread_attr_init(&attr);
    pthread_attr_setdetachstate(&attr, PTHREAD_CREATE_JOINABLE);
    pthread_attr_setstacksize(&attr, _HF_PTHREAD_STACKSIZE);
    pthread_attr_setguardsize(&attr, (size_t)sysconf(_SC_PAGESIZE));

    if (pthread_create(thread, &attr, thread_func, (void*)hfuzz) < 0) {
        PLOG_F("Couldn't create a new thread");
    }

    pthread_attr_destroy(&attr);

    return;
}

void fuzz_threadsStart(honggfuzz_t* hfuzz, pthread_t* threads) {
    if (!arch_archInit(hfuzz)) {
        LOG_F("Couldn't prepare arch for fuzzing");
    }
    if (!sanitizers_Init(hfuzz)) {
        LOG_F("Couldn't prepare sanitizer options");
    }
    if (!sancov_Init(hfuzz)) {
        LOG_F("Couldn't prepare sancov options");
    }

    if (hfuzz->useSanCov || hfuzz->dynFileMethod != _HF_DYNFILE_NONE) {
        fuzz_setState(hfuzz, _HF_STATE_DYNAMIC_PRE);
    } else {
        fuzz_setState(hfuzz, _HF_STATE_STATIC);
    }

    for (size_t i = 0; i < hfuzz->threads.threadsMax; i++) {
        fuzz_runThread(hfuzz, &threads[i], fuzz_threadNew);
    }
}

void fuzz_threadsStop(honggfuzz_t* hfuzz, pthread_t* threads) {
    for (size_t i = 0; i < hfuzz->threads.threadsMax; i++) {
        void* retval;
        if (pthread_join(threads[i], &retval) != 0) {
            PLOG_F("Couldn't pthread_join() thread: %zu", i);
        }
    }
    LOG_I("All threads done");
}