/* * * honggfuzz - the main file * ----------------------------------------- * * Authors: Robert Swiecki * Felix Gröbert * * Copyright 2010-2019 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 #include #include #include #include #include #include #include #include #include #include "cmdline.h" #include "display.h" #include "fuzz.h" #include "input.h" #include "libhfcommon/common.h" #include "libhfcommon/files.h" #include "libhfcommon/log.h" #include "libhfcommon/util.h" #include "socketfuzzer.h" #include "subproc.h" static int sigReceived = 0; static bool clearWin = false; /* * CygWin/MinGW incorrectly copies stack during fork(), so we need to keep some * structures in the data section */ honggfuzz_t hfuzz; static void exitWithMsg(const char* msg, int exit_code) { HF_ATTR_UNUSED ssize_t sz = write(STDERR_FILENO, msg, strlen(msg)); for (;;) { exit(exit_code); _exit(exit_code); abort(); __builtin_trap(); } } static void sigHandler(int sig) { /* We should not terminate upon SIGALRM delivery */ if (sig == SIGALRM) { if (fuzz_shouldTerminate()) { exitWithMsg("Terminating forcefully\n", EXIT_FAILURE); } return; } if (sig == SIGWINCH) { ATOMIC_SET(clearWin, true); return; } /* It's handled in the signal thread */ if (sig == SIGCHLD) { return; } if (ATOMIC_GET(sigReceived) != 0) { exitWithMsg("Repeated termination signal caught\n", EXIT_FAILURE); } ATOMIC_SET(sigReceived, sig); } static void setupRLimits(void) { struct rlimit rlim; if (getrlimit(RLIMIT_NOFILE, &rlim) == -1) { PLOG_W("getrlimit(RLIMIT_NOFILE)"); return; } if (rlim.rlim_cur >= 1024) { return; } if (rlim.rlim_max < 1024) { LOG_E("RLIMIT_NOFILE max limit < 1024 (%zu). Expect troubles!", (size_t)rlim.rlim_max); return; } rlim.rlim_cur = HF_MIN(1024, rlim.rlim_max); // we don't need more if (setrlimit(RLIMIT_NOFILE, &rlim) == -1) { PLOG_E("Couldn't setrlimit(RLIMIT_NOFILE, cur=%zu/max=%zu)", (size_t)rlim.rlim_cur, (size_t)rlim.rlim_max); } } static void setupMainThreadTimer(void) { const struct itimerval it = { .it_value = { .tv_sec = 1, .tv_usec = 0, }, .it_interval = { .tv_sec = 0, .tv_usec = 1000ULL * 200ULL, }, }; if (setitimer(ITIMER_REAL, &it, NULL) == -1) { PLOG_F("setitimer(ITIMER_REAL)"); } } static void setupSignalsPreThreads(void) { /* Block signals which should be handled or blocked in the main thread */ sigset_t ss; sigemptyset(&ss); sigaddset(&ss, SIGTERM); sigaddset(&ss, SIGINT); sigaddset(&ss, SIGQUIT); sigaddset(&ss, SIGALRM); sigaddset(&ss, SIGPIPE); /* Linux/arch uses it to discover events from persistent fuzzing processes */ sigaddset(&ss, SIGIO); /* Let the signal thread catch SIGCHLD */ sigaddset(&ss, SIGCHLD); /* This is checked for via sigwaitinfo/sigtimedwait */ sigaddset(&ss, SIGWINCH); if (sigprocmask(SIG_SETMASK, &ss, NULL) != 0) { PLOG_F("sigprocmask(SIG_SETMASK)"); } struct sigaction sa = { .sa_handler = sigHandler, .sa_flags = 0, }; sigemptyset(&sa.sa_mask); if (sigaction(SIGTERM, &sa, NULL) == -1) { PLOG_F("sigaction(SIGTERM) failed"); } if (sigaction(SIGINT, &sa, NULL) == -1) { PLOG_F("sigaction(SIGINT) failed"); } if (sigaction(SIGQUIT, &sa, NULL) == -1) { PLOG_F("sigaction(SIGQUIT) failed"); } if (sigaction(SIGALRM, &sa, NULL) == -1) { PLOG_F("sigaction(SIGALRM) failed"); } if (sigaction(SIGCHLD, &sa, NULL) == -1) { PLOG_F("sigaction(SIGCHLD) failed"); } if (sigaction(SIGWINCH, &sa, NULL) == -1) { PLOG_F("sigaction(SIGWINCH) failed"); } } static void setupSignalsMainThread(void) { /* Unblock signals which should be handled by the main thread */ sigset_t ss; sigemptyset(&ss); sigaddset(&ss, SIGTERM); sigaddset(&ss, SIGINT); sigaddset(&ss, SIGQUIT); sigaddset(&ss, SIGALRM); sigaddset(&ss, SIGWINCH); if (pthread_sigmask(SIG_UNBLOCK, &ss, NULL) != 0) { PLOG_F("pthread_sigmask(SIG_UNBLOCK)"); } } static void printSummary(honggfuzz_t* hfuzz) { uint64_t exec_per_sec = 0; uint64_t elapsed_sec = time(NULL) - hfuzz->timing.timeStart; if (elapsed_sec) { exec_per_sec = hfuzz->cnts.mutationsCnt / elapsed_sec; } uint64_t guardNb = ATOMIC_GET(hfuzz->feedback.covFeedbackMap->guardNb); uint64_t branch_percent_cov = guardNb ? ((100 * ATOMIC_GET(hfuzz->feedback.hwCnts.softCntEdge)) / guardNb) : 0; struct rusage usage; if (getrusage(RUSAGE_CHILDREN, &usage)) { PLOG_W("getrusage failed"); usage.ru_maxrss = 0; // 0 means something went wrong with rusage } #ifdef _HF_ARCH_DARWIN usage.ru_maxrss >>= 20; #else usage.ru_maxrss >>= 10; #endif LOG_I("Summary iterations:%zu time:%" PRIu64 " speed:%" PRIu64 " " "crashes_count:%zu timeout_count:%zu new_units_added:%zu " "slowest_unit_ms:%" PRId64 " guard_nb:%" PRIu64 " branch_coverage_percent:%" PRIu64 " " "peak_rss_mb:%lu", hfuzz->cnts.mutationsCnt, elapsed_sec, exec_per_sec, hfuzz->cnts.crashesCnt, hfuzz->cnts.timeoutedCnt, hfuzz->io.newUnitsAdded, hfuzz->timing.timeOfLongestUnitUSecs / 1000U, hfuzz->feedback.covFeedbackMap->guardNb, branch_percent_cov, usage.ru_maxrss); } static void pingThreads(honggfuzz_t* hfuzz) { for (size_t i = 0; i < hfuzz->threads.threadsMax; i++) { if (pthread_kill(hfuzz->threads.threads[i], SIGCHLD) != 0 && errno != EINTR && errno != 0) { PLOG_W("pthread_kill(thread=%zu, SIGCHLD)", i); } } } static void* signalThread(void* arg) { honggfuzz_t* hfuzz = (honggfuzz_t*)arg; sigset_t ss; sigemptyset(&ss); sigaddset(&ss, SIGCHLD); if (pthread_sigmask(SIG_UNBLOCK, &ss, NULL) != 0) { PLOG_F("Couldn't unblock SIGCHLD in the signal thread"); } for (;;) { int sig = 0; errno = 0; int ret = sigwait(&ss, &sig); if (ret == EINTR) { continue; } if (ret != 0 && errno == EINTR) { continue; } if (ret != 0) { PLOG_F("sigwait(SIGCHLD)"); } if (fuzz_isTerminating()) { break; } if (sig == SIGCHLD) { pingThreads(hfuzz); } } return NULL; } static uint8_t mainThreadLoop(honggfuzz_t* hfuzz) { setupSignalsMainThread(); setupMainThreadTimer(); for (;;) { if (hfuzz->display.useScreen) { if (ATOMIC_XCHG(clearWin, false)) { display_clear(); } display_display(hfuzz); } if (ATOMIC_GET(sigReceived) > 0) { LOG_I("Signal %d (%s) received, terminating", ATOMIC_GET(sigReceived), strsignal(ATOMIC_GET(sigReceived))); break; } if (ATOMIC_GET(hfuzz->threads.threadsFinished) >= hfuzz->threads.threadsMax) { break; } if (hfuzz->timing.runEndTime > 0 && (time(NULL) > hfuzz->timing.runEndTime)) { LOG_I("Maximum run time reached, terminating"); break; } pingThreads(hfuzz); pause(); } fuzz_setTerminating(); for (;;) { if (ATOMIC_GET(hfuzz->threads.threadsFinished) >= hfuzz->threads.threadsMax) { break; } pingThreads(hfuzz); util_sleepForMSec(50); /* 50ms */ } if (hfuzz->cfg.exitUponCrash && ATOMIC_GET(hfuzz->cnts.crashesCnt) > 0) { return hfuzz->cfg.exitCodeUponCrash; } else { return hfuzz->cnts.crashesCnt > 0 ? EXIT_FAILURE : EXIT_SUCCESS; } } static const char* strYesNo(bool yes) { return (yes ? "true" : "false"); } static const char* getGitVersion() { static char version[] = "$Id: 380cf14962c64e3fa902d9442b6c6513869116ed $"; if (strlen(version) == 47) { version[45] = '\0'; return &version[5]; } return "UNKNOWN"; } int honggfuzz_main(int argc, char** argv) { /* * Work around CygWin/MinGW */ char** myargs = (char**)util_Calloc(sizeof(char*) * (argc + 1)); defer { free(myargs); }; int i; for (i = 0U; i < argc; i++) { myargs[i] = argv[i]; } myargs[i] = NULL; if (!cmdlineParse(argc, myargs, &hfuzz)) { LOG_F("Parsing of the cmd-line arguments failed"); } if (hfuzz.io.inputDir && access(hfuzz.io.inputDir, R_OK) == -1) { PLOG_F("Input directory '%s' is not readable", hfuzz.io.inputDir); } if (hfuzz.io.outputDir && access(hfuzz.io.outputDir, W_OK) == -1) { PLOG_F("Output directory '%s' is not writeable", hfuzz.io.outputDir); } if (hfuzz.cfg.minimize) { LOG_I("Minimization mode enabled. Setting number of threads to 1"); hfuzz.threads.threadsMax = 1; } char tmstr[64]; util_getLocalTime("%F.%H.%M.%S", tmstr, sizeof(tmstr), time(NULL)); LOG_I("Start time:'%s' bin:'%s', input:'%s', output:'%s', persistent:%s, stdin:%s, " "mutation_rate:%u, timeout:%ld, max_runs:%zu, threads:%zu, minimize:%s, git_commit:%s", tmstr, hfuzz.exe.cmdline[0], hfuzz.io.inputDir, hfuzz.io.outputDir ? hfuzz.io.outputDir : hfuzz.io.inputDir, strYesNo(hfuzz.exe.persistent), strYesNo(hfuzz.exe.fuzzStdin), hfuzz.mutate.mutationsPerRun, (long)hfuzz.timing.tmOut, hfuzz.mutate.mutationsMax, hfuzz.threads.threadsMax, strYesNo(hfuzz.cfg.minimize), getGitVersion()); sigemptyset(&hfuzz.exe.waitSigSet); sigaddset(&hfuzz.exe.waitSigSet, SIGIO); /* Persistent socket data */ sigaddset(&hfuzz.exe.waitSigSet, SIGCHLD); /* Ping from the signal thread */ if (hfuzz.display.useScreen) { display_init(); } if (hfuzz.socketFuzzer.enabled) { LOG_I("No input file corpus loaded, the external socket_fuzzer is responsible for " "creating the fuzz data"); setupSocketFuzzer(&hfuzz); } else if (!input_init(&hfuzz)) { LOG_F("Couldn't load input corpus"); exit(EXIT_FAILURE); } if (hfuzz.mutate.dictionaryFile && !input_parseDictionary(&hfuzz)) { LOG_F("Couldn't parse dictionary file ('%s')", hfuzz.mutate.dictionaryFile); } if (hfuzz.feedback.blocklistFile && !input_parseBlacklist(&hfuzz)) { LOG_F("Couldn't parse stackhash blocklist file ('%s')", hfuzz.feedback.blocklistFile); } #define hfuzzl hfuzz.arch_linux if (hfuzzl.symsBlFile && ((hfuzzl.symsBlCnt = files_parseSymbolFilter(hfuzzl.symsBlFile, &hfuzzl.symsBl)) == 0)) { LOG_F("Couldn't parse symbols blocklist file ('%s')", hfuzzl.symsBlFile); } if (hfuzzl.symsWlFile && ((hfuzzl.symsWlCnt = files_parseSymbolFilter(hfuzzl.symsWlFile, &hfuzzl.symsWl)) == 0)) { LOG_F("Couldn't parse symbols allowlist file ('%s')", hfuzzl.symsWlFile); } if (!(hfuzz.feedback.covFeedbackMap = files_mapSharedMem(sizeof(feedback_t), &hfuzz.feedback.covFeedbackFd, "hf-covfeedback", /* nocore= */ true, /* export= */ hfuzz.io.exportFeedback))) { LOG_F("files_mapSharedMem(name='hf-covfeddback', sz=%zu, dir='%s') failed", sizeof(feedback_t), hfuzz.io.workDir); } if (hfuzz.feedback.cmpFeedback) { if (!(hfuzz.feedback.cmpFeedbackMap = files_mapSharedMem(sizeof(cmpfeedback_t), &hfuzz.feedback.cmpFeedbackFd, "hf-cmpfeedback", /* nocore= */ true, /* export= */ hfuzz.io.exportFeedback))) { LOG_F("files_mapSharedMem(name='hf-cmpfeedback', sz=%zu, dir='%s') failed", sizeof(cmpfeedback_t), hfuzz.io.workDir); } } setupRLimits(); setupSignalsPreThreads(); fuzz_threadsStart(&hfuzz); pthread_t sigthread; if (!subproc_runThread(&hfuzz, &sigthread, signalThread, /* joinable= */ false)) { LOG_F("Couldn't start the signal thread"); } uint8_t exitcode = mainThreadLoop(&hfuzz); /* Clean-up global buffers */ if (hfuzz.feedback.blocklist) { free(hfuzz.feedback.blocklist); } #if defined(_HF_ARCH_LINUX) if (hfuzz.arch_linux.symsBl) { free(hfuzz.arch_linux.symsBl); } if (hfuzz.arch_linux.symsWl) { free(hfuzz.arch_linux.symsWl); } #elif defined(_HF_ARCH_NETBSD) if (hfuzz.arch_netbsd.symsBl) { free(hfuzz.arch_netbsd.symsBl); } if (hfuzz.arch_netbsd.symsWl) { free(hfuzz.arch_netbsd.symsWl); } #endif if (hfuzz.socketFuzzer.enabled) { cleanupSocketFuzzer(); } printSummary(&hfuzz); return exitcode; }