text: /*
   *
   * honggfuzz - fuzzing routines
   * -----------------------------------------
   *
   * Authors: Robert Swiecki <swiecki@google.com>
   *          Felix Gröbert <groebert@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 "fuzz.h"
  
  #include <errno.h>
  #include <fcntl.h>
  #include <inttypes.h>
  #include <limits.h>
  #include <pthread.h>
  #include <signal.h>
  #include <stdbool.h>
  #include <stddef.h>
  #include <stdint.h>
  #include <stdio.h>
  #include <sys/types.h>
  #include <time.h>
  #include <unistd.h>
  
  #include "arch.h"
  #include "honggfuzz.h"
  #include "input.h"
  #include "libhfcommon/common.h"
  #include "libhfcommon/files.h"
  #include "libhfcommon/log.h"
  #include "libhfcommon/util.h"
  #include "report.h"
  #include "sanitizers.h"
  #include "socketfuzzer.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;
  }
  
  fuzzState_t fuzz_getState(honggfuzz_t* hfuzz) {
      return ATOMIC_GET(hfuzz->feedback.state);
  }
  
  static void fuzz_setDynamicMainState(run_t* run) {
      /* All threads need to indicate willingness to switch to the DYNAMIC_MAIN state. Count them! */
      static uint32_t cnt = 0;
      ATOMIC_PRE_INC(cnt);
  
      MX_SCOPED_LOCK(&run->global->mutex.state);
  
      if (fuzz_getState(run->global) != _HF_STATE_DYNAMIC_DRY_RUN) {
          /* Already switched out of the Dry Run */
          return;
      }
  
      LOG_I("Entering phase 2/3: Switching to the Feedback Driven Mode");
      ATOMIC_SET(run->global->cfg.switchingToFDM, true);
  
      for (;;) {
          /* Check if all threads have already reported in for changing state */
          if (ATOMIC_GET(cnt) == run->global->threads.threadsMax) {
              break;
          }
          if (fuzz_isTerminating()) {
              return;
          }
          util_sleepForMSec(10); /* Check every 10ms */
      }
  
      ATOMIC_SET(run->global->cfg.switchingToFDM, false);
  
      if (run->global->cfg.minimize) {
          LOG_I("Entering phase 3/3: Corpus Minimization");
          ATOMIC_SET(run->global->feedback.state, _HF_STATE_DYNAMIC_MINIMIZE);
          return;
      }
  
      /*
       * If the initial fuzzing yielded no useful coverage, just add a single empty file to the
       * dynamic corpus, so the dynamic phase doesn't fail because of lack of useful inputs
       */
      if (run->global->io.dynfileqCnt == 0) {
          dynfile_t dynfile = {
              .size          = 0,
              .cov           = {},
              .idx           = 0,
              .fd            = -1,
              .timeExecUSecs = 1,
              .path          = "[DYNAMIC-0-SIZE]",
              .data          = (uint8_t*)"",
          };
          dynfile_t* tmp_dynfile = run->dynfile;
          run->dynfile           = &dynfile;
          input_addDynamicInput(run);
          run->dynfile = tmp_dynfile;
      }
      snprintf(run->dynfile->path, sizeof(run->dynfile->path), "[DYNAMIC]");
  
      if (run->global->io.maxFileSz == 0 && run->global->mutate.maxInputSz > _HF_INPUT_DEFAULT_SIZE) {
          size_t newsz = (run->global->io.dynfileqMaxSz >= _HF_INPUT_DEFAULT_SIZE)
                             ? run->global->io.dynfileqMaxSz
                             : _HF_INPUT_DEFAULT_SIZE;
          newsz = (newsz + newsz / 4); /* Add 25% overhead for growth */
          if (newsz > run->global->mutate.maxInputSz) {
              newsz = run->global->mutate.maxInputSz;
          }
          LOG_I("Setting maximum input size to %zu bytes (previously %zu bytes)", newsz,
              run->global->mutate.maxInputSz);
          run->global->mutate.maxInputSz = newsz;
      }
  
      LOG_I("Entering phase 3/3: Dynamic Main (Feedback Driven Mode)");
      ATOMIC_SET(run->global->feedback.state, _HF_STATE_DYNAMIC_MAIN);
  }
  
  static void fuzz_minimizeRemoveFiles(run_t* run) {
      if (run->global->io.outputDir) {
          LOG_I("Minimized files were copied to '%s'", run->global->io.outputDir);
          return;
      }
      if (!input_getDirStatsAndRewind(run->global)) {
          return;
      }
      for (;;) {
          char fname[PATH_MAX];
          if (!input_getNext(run, fname, /* rewind= */ false)) {
              break;
          }
          if (!input_inDynamicCorpus(run, fname)) {
              if (input_removeStaticFile(run->global->io.inputDir, fname)) {
                  LOG_I("Removed unnecessary '%s'", fname);
              }
          }
      }
      LOG_I("Corpus minimization done");
  }
  
  static void fuzz_perfFeedback(run_t* run) {
      if (run->global->feedback.skipFeedbackOnTimeout && run->tmOutSignaled) {
          return;
      }
  
      MX_SCOPED_LOCK(&run->global->mutex.feedback);
      defer {
          wmb();
      };
  
      uint64_t softNewPC = ATOMIC_GET(run->global->feedback.covFeedbackMap->pidNewPC[run->fuzzNo]);
      ATOMIC_CLEAR(run->global->feedback.covFeedbackMap->pidNewPC[run->fuzzNo]);
      uint64_t softCurPC = ATOMIC_GET(run->global->feedback.covFeedbackMap->pidTotalPC[run->fuzzNo]);
      ATOMIC_CLEAR(run->global->feedback.covFeedbackMap->pidTotalPC[run->fuzzNo]);
  
      uint64_t softNewEdge =
          ATOMIC_GET(run->global->feedback.covFeedbackMap->pidNewEdge[run->fuzzNo]);
      ATOMIC_CLEAR(run->global->feedback.covFeedbackMap->pidNewEdge[run->fuzzNo]);
      uint64_t softCurEdge =
          ATOMIC_GET(run->global->feedback.covFeedbackMap->pidTotalEdge[run->fuzzNo]);
      ATOMIC_CLEAR(run->global->feedback.covFeedbackMap->pidTotalEdge[run->fuzzNo]);
  
      uint64_t softNewCmp = ATOMIC_GET(run->global->feedback.covFeedbackMap->pidNewCmp[run->fuzzNo]);
      ATOMIC_CLEAR(run->global->feedback.covFeedbackMap->pidNewCmp[run->fuzzNo]);
      uint64_t softCurCmp =
          ATOMIC_GET(run->global->feedback.covFeedbackMap->pidTotalCmp[run->fuzzNo]);
      ATOMIC_CLEAR(run->global->feedback.covFeedbackMap->pidTotalCmp[run->fuzzNo]);
  
      rmb();
  
      int64_t diff0 = (int64_t)run->global->feedback.hwCnts.cpuInstrCnt - run->hwCnts.cpuInstrCnt;
      int64_t diff1 = (int64_t)run->global->feedback.hwCnts.cpuBranchCnt - run->hwCnts.cpuBranchCnt;
  
      /* Any increase in coverage (edge, pc, cmp, hw) counters forces adding input to the corpus */
      if (run->hwCnts.newBBCnt > 0 || softNewPC > 0 || softNewEdge > 0 || softNewCmp > 0 ||
          diff0 < 0 || diff1 < 0) {
          if (diff0 < 0) {
              run->global->feedback.hwCnts.cpuInstrCnt = run->hwCnts.cpuInstrCnt;
          }
          if (diff1 < 0) {
              run->global->feedback.hwCnts.cpuBranchCnt = run->hwCnts.cpuBranchCnt;
          }
          run->global->feedback.hwCnts.bbCnt += run->hwCnts.newBBCnt;
          run->global->feedback.hwCnts.softCntPc += softNewPC;
          run->global->feedback.hwCnts.softCntEdge += softNewEdge;
          run->global->feedback.hwCnts.softCntCmp += softNewCmp;
  
          LOG_I("Sz:%zu Tm:%" _HF_NONMON_SEP PRIu64 "us (i/b/h/e/p/c) New:%" PRIu64 "/%" PRIu64
                "/%" PRIu64 "/%" PRIu64 "/%" PRIu64 "/%" PRIu64 ", Cur:%" PRIu64 "/%" PRIu64
                "/%" PRIu64 "/%" PRIu64 "/%" PRIu64 "/%" PRIu64,
              run->dynfile->size, util_timeNowUSecs() - run->timeStartedUSecs,
              run->hwCnts.cpuInstrCnt, run->hwCnts.cpuBranchCnt, run->hwCnts.newBBCnt, softNewEdge,
              softNewPC, softNewCmp, run->hwCnts.cpuInstrCnt, run->hwCnts.cpuBranchCnt,
              run->hwCnts.bbCnt, softCurEdge, softCurPC, softCurCmp);
  
          /* Update per-input coverage metrics */
          run->dynfile->cov[0] = softCurEdge + softCurPC + run->hwCnts.bbCnt;
          run->dynfile->cov[1] = softCurCmp;
          run->dynfile->cov[2] = run->hwCnts.cpuInstrCnt + run->hwCnts.cpuBranchCnt;
          run->dynfile->cov[3] = run->dynfile->size ? (64 - util_Log2(run->dynfile->size)) : 64;
          input_addDynamicInput(run);
  
          if (run->global->socketFuzzer.enabled) {
              LOG_D("SocketFuzzer: fuzz: new BB (perf)");
              fuzz_notifySocketFuzzerNewCov(run->global);
          }
      }
  }
  
  /* Return value indicates whether report file should be updated with the current verified crash */
  static bool fuzz_runVerifier(run_t* run) {
      if (!run->crashFileName[0] || !run->backtrace) {
          return false;
      }
  
      uint64_t backtrace = run->backtrace;
  
      char origCrashPath[PATH_MAX];
      snprintf(origCrashPath, sizeof(origCrashPath), "%s", run->crashFileName);
      /* Workspace is inherited, just append a extra suffix */
      char verFile[PATH_MAX];
      snprintf(verFile, sizeof(verFile), "%s.verified", origCrashPath);
  
      if (files_exists(verFile)) {
          LOG_D("Crash file to verify '%s' is already verified as '%s'", origCrashPath, verFile);
          return false;
      }
  
      for (int i = 0; i < _HF_VERIFIER_ITER; i++) {
          LOG_I("Launching verifier for HASH: %" PRIx64 " (iteration: %d out of %d)", run->backtrace,
              i + 1, _HF_VERIFIER_ITER);
          run->timeStartedUSecs = util_timeNowUSecs();
          run->backtrace        = 0;
          run->access           = 0;
          run->exception        = 0;
          run->mainWorker       = false;
  
          if (!subproc_Run(run)) {
              LOG_F("subproc_Run()");
          }
  
          /* If stack hash doesn't match skip name tag and exit */
          if (run->backtrace != backtrace) {
              LOG_E("Verifier stack mismatch: (original) %" PRIx64 " != (new) %" PRIx64, backtrace,
                  run->backtrace);
              run->backtrace = backtrace;
              return true;
          }
  
          LOG_I("Verifier for HASH: %" PRIx64 " (iteration: %d, left: %d). MATCH!", run->backtrace,
              i + 1, _HF_VERIFIER_ITER - i - 1);
      }
  
      /* Copy file with new suffix & remove original copy */
      int fd = TEMP_FAILURE_RETRY(open(verFile, O_CREAT | O_EXCL | O_WRONLY, 0600));
      if (fd == -1 && errno == EEXIST) {
          LOG_I("It seems that '%s' already exists, skipping", verFile);
          return false;
      }
      if (fd == -1) {
          PLOG_E("Couldn't create '%s'", verFile);
          return true;
      }
      defer {
          close(fd);
      };
      if (!files_writeToFd(fd, run->dynfile->data, run->dynfile->size)) {
          LOG_E("Couldn't save verified file as '%s'", verFile);
          unlink(verFile);
          return true;
      }
  
      LOG_I("Verified crash for HASH: %" PRIx64 " and saved it as '%s'", backtrace, verFile);
      ATOMIC_PRE_INC(run->global->cnts.verifiedCrashesCnt);
  
      return true;
  }
  
  static bool fuzz_fetchInput(run_t* run) {
      {
          fuzzState_t st = fuzz_getState(run->global);
          if (st == _HF_STATE_DYNAMIC_DRY_RUN) {
              run->mutationsPerRun = 0U;
              if (input_prepareStaticFile(run, /* rewind= */ false, true)) {
                  return true;
              }
              fuzz_setDynamicMainState(run);
              run->mutationsPerRun = run->global->mutate.mutationsPerRun;
          }
      }
  
      if (fuzz_getState(run->global) == _HF_STATE_DYNAMIC_MINIMIZE) {
          fuzz_minimizeRemoveFiles(run);
          return false;
      }
  
      if (fuzz_getState(run->global) == _HF_STATE_DYNAMIC_MAIN) {
          if (run->global->exe.externalCommand) {
              if (!input_prepareExternalFile(run)) {
                  LOG_E("input_prepareExternalFile() failed");
                  return false;
              }
          } else if (run->global->exe.feedbackMutateCommand) {
              if (!input_prepareDynamicInput(run, false)) {
                  LOG_E("input_prepareDynamicInput(() failed");
                  return false;
              }
          } else if (!input_prepareDynamicInput(run, true)) {
              LOG_E("input_prepareDynamicInput() failed");
              return false;
          }
      }
  
      if (fuzz_getState(run->global) == _HF_STATE_STATIC) {
          if (run->global->exe.externalCommand) {
              if (!input_prepareExternalFile(run)) {
                  LOG_E("input_prepareExternalFile() failed");
                  return false;
              }
          } else if (run->global->exe.feedbackMutateCommand) {
              if (!input_prepareStaticFile(run, true, false)) {
                  LOG_E("input_prepareStaticFile() failed");
                  return false;
              }
          } else if (!input_prepareStaticFile(run, true /* rewind */, true)) {
              LOG_E("input_prepareStaticFile() failed");
              return false;
          }
      }
  
      if (run->global->exe.postExternalCommand &&
          !input_postProcessFile(run, run->global->exe.postExternalCommand)) {
          LOG_E("input_postProcessFile('%s') failed", run->global->exe.postExternalCommand);
          return false;
      }
  
      if (run->global->exe.feedbackMutateCommand &&
          !input_postProcessFile(run, run->global->exe.feedbackMutateCommand)) {
          LOG_E("input_postProcessFile('%s') failed", run->global->exe.feedbackMutateCommand);
          return false;
      }
  
      return true;
  }
  
  static void fuzz_fuzzLoop(run_t* run) {
      run->timeStartedUSecs = util_timeNowUSecs();
      run->crashFileName[0] = '\0';
      run->pc               = 0;
      run->backtrace        = 0;
      run->access           = 0;
      run->exception        = 0;
      run->report[0]        = '\0';
      run->mainWorker       = true;
      run->mutationsPerRun  = run->global->mutate.mutationsPerRun;
      run->tmOutSignaled    = false;
  
      run->hwCnts.cpuInstrCnt  = 0;
      run->hwCnts.cpuBranchCnt = 0;
      run->hwCnts.bbCnt        = 0;
      run->hwCnts.newBBCnt     = 0;
  
      if (!fuzz_fetchInput(run)) {
          if (run->global->cfg.minimize && fuzz_getState(run->global) == _HF_STATE_DYNAMIC_MINIMIZE) {
              fuzz_setTerminating();
              return;
          }
          LOG_F("Cound't prepare input for fuzzing");
      }
      if (!subproc_Run(run)) {
          LOG_F("Couldn't run fuzzed command");
      }
  
      if (run->global->feedback.dynFileMethod != _HF_DYNFILE_NONE) {
          fuzz_perfFeedback(run);
      }
      if (run->global->cfg.useVerifier && !fuzz_runVerifier(run)) {
          return;
      }
      report_saveReport(run);
  }
  
  static void fuzz_fuzzLoopSocket(run_t* run) {
      run->timeStartedUSecs = util_timeNowUSecs();
      run->crashFileName[0] = '\0';
      run->pc               = 0;
      run->backtrace        = 0;
      run->access           = 0;
      run->exception        = 0;
      run->report[0]        = '\0';
      run->mainWorker       = true;
      run->mutationsPerRun  = run->global->mutate.mutationsPerRun;
      run->tmOutSignaled    = false;
  
      run->hwCnts.cpuInstrCnt  = 0;
      run->hwCnts.cpuBranchCnt = 0;
      run->hwCnts.bbCnt        = 0;
      run->hwCnts.newBBCnt     = 0;
  
      LOG_I("------------------------------------------------------");
  
      /* First iteration: Start target
         Other iterations: re-start target, if necessary
         subproc_Run() will decide by itself if a restart is necessary, via
         subproc_New()
      */
      LOG_D("------[ 1: subproc_run");
      if (!subproc_Run(run)) {
          LOG_W("Couldn't run server");
      }
  
      /* Tell the external fuzzer to send data to target
         The fuzzer will notify us when finished; block until then.
      */
      LOG_D("------[ 2: fetch input");
      if (!fuzz_waitForExternalInput(run)) {
          /* Fuzzer could not connect to target, and told us to
             restart it. Do it on the next iteration.
             or: it crashed by fuzzing. Restart it too.
             */
          LOG_D("------[ 2.1: Target down, will restart it");
          run->pid = 0;    // make subproc_Run() restart it on next iteration
          return;
      }
  
      LOG_D("------[ 3: feedback");
      if (run->global->feedback.dynFileMethod != _HF_DYNFILE_NONE) {
          fuzz_perfFeedback(run);
      }
      if (run->global->cfg.useVerifier && !fuzz_runVerifier(run)) {
          return;
      }
  
      report_saveReport(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. #%u", fuzzNo);
  
      run_t run = {
          .global         = hfuzz,
          .pid            = 0,
          .dynfile        = (dynfile_t*)util_Malloc(sizeof(dynfile_t) + hfuzz->io.maxFileSz),
          .fuzzNo         = fuzzNo,
          .persistentSock = -1,
          .tmOutSignaled  = false,
      };
  
      /* Do not try to handle input files with socketfuzzer */
      char mapname[32];
      snprintf(mapname, sizeof(mapname), "hf-%u-input", fuzzNo);
      if (!hfuzz->socketFuzzer.enabled) {
          if (!(run.dynfile->data = files_mapSharedMem(hfuzz->mutate.maxInputSz, &(run.dynfile->fd),
                    mapname, /* nocore= */ true, /* exportmap= */ false))) {
              LOG_F("Couldn't create an input file of size: %zu, name:'%s'", hfuzz->mutate.maxInputSz,
                  mapname);
          }
      }
      defer {
          if (run.dynfile->fd != -1) {
              close(run.dynfile->fd);
          }
      };
  
      snprintf(mapname, sizeof(mapname), "hf-%u-perthreadmap", fuzzNo);
      if ((run.perThreadCovFeedbackFd = files_createSharedMem(sizeof(feedback_t), mapname,
               /* exportmap= */ run.global->io.exportFeedback)) == -1) {
          LOG_F("files_createSharedMem(name='%s', sz=%zu, dir='%s') failed", mapname,
              sizeof(feedback_t), run.global->io.workDir);
      }
      defer {
          if (run.perThreadCovFeedbackFd != -1) {
              close(run.perThreadCovFeedbackFd);
          }
      };
  
      if (!arch_archThreadInit(&run)) {
          LOG_F("Could not initialize the thread");
      }
  
      for (;;) {
          /* Check if dry run mode with verifier enabled */
          if (run.global->mutate.mutationsPerRun == 0U && run.global->cfg.useVerifier &&
              !hfuzz->socketFuzzer.enabled) {
              if (ATOMIC_POST_INC(run.global->cnts.mutationsCnt) >= run.global->io.fileCnt) {
                  break;
              }
          }
          /* Check for max iterations limit if set */
          else if ((ATOMIC_POST_INC(run.global->cnts.mutationsCnt) >=
                       run.global->mutate.mutationsMax) &&
                   run.global->mutate.mutationsMax) {
              break;
          }
  
          if (hfuzz->socketFuzzer.enabled) {
              fuzz_fuzzLoopSocket(&run);
          } else {
              fuzz_fuzzLoop(&run);
          }
  
          if (fuzz_isTerminating()) {
              break;
          }
  
          if (run.global->cfg.exitUponCrash && ATOMIC_GET(run.global->cnts.crashesCnt) > 0) {
              LOG_I("Seen a crash. Terminating all fuzzing threads");
              fuzz_setTerminating();
              break;
          }
      }
  
      if (run.pid) {
          kill(run.pid, SIGKILL);
      }
  
      size_t j = ATOMIC_PRE_INC(run.global->threads.threadsFinished);
      LOG_I("Terminating thread no. #%" PRId32 ", left: %zu", fuzzNo, hfuzz->threads.threadsMax - j);
      return NULL;
  }
  
  void fuzz_threadsStart(honggfuzz_t* hfuzz) {
      if (!arch_archInit(hfuzz)) {
          LOG_F("Couldn't prepare arch for fuzzing");
      }
      if (!sanitizers_Init(hfuzz)) {
          LOG_F("Couldn't prepare sanitizer options");
      }
  
      if (hfuzz->socketFuzzer.enabled) {
          /* Don't do dry run with socketFuzzer */
          LOG_I("Entering phase - Feedback Driven Mode (SocketFuzzer)");
          hfuzz->feedback.state = _HF_STATE_DYNAMIC_MAIN;
      } else if (hfuzz->feedback.dynFileMethod != _HF_DYNFILE_NONE) {
          LOG_I("Entering phase 1/3: Dry Run");
          hfuzz->feedback.state = _HF_STATE_DYNAMIC_DRY_RUN;
      } else {
          LOG_I("Entering phase: Static");
          hfuzz->feedback.state = _HF_STATE_STATIC;
      }
  
      for (size_t i = 0; i < hfuzz->threads.threadsMax; i++) {
          if (!subproc_runThread(
                  hfuzz, &hfuzz->threads.threads[i], fuzz_threadNew, /* joinable= */ true)) {
              PLOG_F("Couldn't run a thread #%zu", i);
          }
      }
  }
  