/* * Log to stderr by default. Use a dup()d fd, because in the future we'll associate the * connection socket with fd (0, 1, 2). */ bool logInitLogFile(struct nsjconf_t *nsjconf, const char *logfile, bool is_verbose) { log_verbose = is_verbose; if (logfile == NULL && nsjconf->daemonize == true) { logfile = _LOG_DEFAULT_FILE; } if (logfile == NULL) { logfile = "/proc/self/fd/2"; } log_fd = open(logfile, O_CREAT | O_RDWR | O_APPEND, 0640); if (log_fd == -1) { log_fd = STDERR_FILENO; PLOG_E("Couldn't open logfile open('%s')", logfile); return false; } log_fd_isatty = (isatty(log_fd) == 1 ? true : false); return true; }
static procMap_t *arch_parsePidMaps(pid_t pid, size_t * mapsCount) { FILE *f = NULL; char fProcMaps[PATH_MAX] = { 0 }; snprintf(fProcMaps, PATH_MAX, "/proc/%d/maps", pid); if ((f = fopen(fProcMaps, "rb")) == NULL) { PLOG_E("Couldn't open '%s' - R/O mode", fProcMaps); return 0; } defer { fclose(f); }; *mapsCount = 0; procMap_t *mapsList = malloc(sizeof(procMap_t)); if (mapsList == NULL) { PLOG_W("malloc(size='%zu')", sizeof(procMap_t)); return NULL; } while (!feof(f)) { char buf[sizeof(procMap_t) + 1]; if (fgets(buf, sizeof(buf), f) == 0) { break; } mapsList[*mapsCount].name[0] = '\0'; sscanf(buf, "%lx-%lx %5s %lx %7s %ld %s", &mapsList[*mapsCount].start, &mapsList[*mapsCount].end, mapsList[*mapsCount].perms, &mapsList[*mapsCount].offset, mapsList[*mapsCount].dev, &mapsList[*mapsCount].inode, mapsList[*mapsCount].name); *mapsCount += 1; if ((mapsList = realloc(mapsList, (*mapsCount + 1) * sizeof(procMap_t))) == NULL) { PLOG_W("realloc failed (sz=%zu)", (*mapsCount + 1) * sizeof(procMap_t)); free(mapsList); return NULL; } } return mapsList; }
void util_recoverStdio(void) { int fd = open("/dev/tty", O_RDWR); if (fd == -1) { PLOG_E("Couldn't open '/dev/tty'"); return; } dup2(fd, 0); dup2(fd, 1); dup2(fd, 2); if (tcsetpgrp(fd, getpid()) == -1) { PLOG_W("tcsetpgrp(%d) failed", getpid()); } if (fd > 2) { close(fd); } }
bool arch_launchChild(honggfuzz_t * hfuzz, char *fileName) { #define ARGS_MAX 512 char *args[ARGS_MAX + 2]; char argData[PATH_MAX] = { 0 }; int x; for (x = 0; x < ARGS_MAX && hfuzz->cmdline[x]; x++) { if (!hfuzz->fuzzStdin && strcmp(hfuzz->cmdline[x], _HF_FILE_PLACEHOLDER) == 0) { args[x] = fileName; } else if (!hfuzz->fuzzStdin && strstr(hfuzz->cmdline[x], _HF_FILE_PLACEHOLDER)) { const char *off = strstr(hfuzz->cmdline[x], _HF_FILE_PLACEHOLDER); snprintf(argData, PATH_MAX, "%.*s%s", (int)(off - hfuzz->cmdline[x]), hfuzz->cmdline[x], fileName); args[x] = argData; } else { args[x] = hfuzz->cmdline[x]; } } args[x++] = NULL; LOG_D("Launching '%s' on file '%s'", args[0], fileName); /* * Set timeout (prof), real timeout (2*prof), and rlimit_cpu (2*prof) */ if (hfuzz->tmOut) { struct itimerval it; /* * The hfuzz->tmOut is real CPU usage time... */ it.it_value.tv_sec = hfuzz->tmOut; it.it_value.tv_usec = 0; it.it_interval.tv_sec = 0; it.it_interval.tv_usec = 0; if (setitimer(ITIMER_PROF, &it, NULL) == -1) { PLOG_E("Couldn't set the ITIMER_PROF timer"); return false; } /* * ...so, if a process sleeps, this one should * trigger a signal... */ it.it_value.tv_sec = hfuzz->tmOut * 2UL; it.it_value.tv_usec = 0; it.it_interval.tv_sec = 0; it.it_interval.tv_usec = 0; if (setitimer(ITIMER_REAL, &it, NULL) == -1) { PLOG_E("Couldn't set the ITIMER_REAL timer"); return false; } /* * ..if a process sleeps and catches SIGPROF/SIGALRM * rlimits won't help either */ struct rlimit rl; rl.rlim_cur = hfuzz->tmOut * 2; rl.rlim_max = hfuzz->tmOut * 2; if (setrlimit(RLIMIT_CPU, &rl) == -1) { PLOG_E("Couldn't enforce the RLIMIT_CPU resource limit"); return false; } } /* * The address space limit. If big enough - roughly the size of RAM used */ if (hfuzz->asLimit) { struct rlimit rl; rl.rlim_cur = hfuzz->asLimit * 1024UL * 1024UL; rl.rlim_max = hfuzz->asLimit * 1024UL * 1024UL; if (setrlimit(RLIMIT_AS, &rl) == -1) { PLOG_D("Couldn't enforce the RLIMIT_AS resource limit, ignoring"); } } if (hfuzz->nullifyStdio) { util_nullifyStdio(); } if (hfuzz->fuzzStdin) { /* * Uglyyyyyy ;) */ if (!util_redirectStdin(fileName)) { return false; } } for (size_t i = 0; i < ARRAYSIZE(hfuzz->envs) && hfuzz->envs[i]; i++) { putenv(hfuzz->envs[i]); } execvp(args[0], args); util_recoverStdio(); LOG_F("Failed to create new '%s' process", args[0]); return false; }
void arch_perfAnalyze(honggfuzz_t * hfuzz, fuzzer_t * fuzzer, perfFd_t * perfFds) { if (hfuzz->dynFileMethod == _HF_DYNFILE_NONE) { return; } uint64_t instrCount = 0; if (hfuzz->dynFileMethod & _HF_DYNFILE_INSTR_COUNT) { ioctl(perfFds->cpuInstrFd, PERF_EVENT_IOC_DISABLE, 0); if (read(perfFds->cpuInstrFd, &instrCount, sizeof(instrCount)) != sizeof(instrCount)) { PLOG_E("read(perfFd='%d') failed", perfFds->cpuInstrFd); } close(perfFds->cpuInstrFd); } uint64_t branchCount = 0; if (hfuzz->dynFileMethod & _HF_DYNFILE_BRANCH_COUNT) { ioctl(perfFds->cpuBranchFd, PERF_EVENT_IOC_DISABLE, 0); if (read(perfFds->cpuBranchFd, &branchCount, sizeof(branchCount)) != sizeof(branchCount)) { PLOG_E("read(perfFd='%d') failed", perfFds->cpuBranchFd); } close(perfFds->cpuBranchFd); } uint64_t pathCount = 0; if (hfuzz->dynFileMethod & _HF_DYNFILE_UNIQUE_BLOCK_COUNT) { ioctl(perfFds->uniquePcFd, PERF_EVENT_IOC_DISABLE, 0); close(perfFds->uniquePcFd); arch_perfMmapParse(); pathCount = arch_perfCountBranches(); if (perfRecordsLost > 0UL) { LOG_W("%" PRIu64 " PERF_RECORD_LOST events received, possibly too many concurrent fuzzing threads in progress", perfRecordsLost); } } uint64_t edgeCount = 0; if (hfuzz->dynFileMethod & _HF_DYNFILE_UNIQUE_EDGE_COUNT) { ioctl(perfFds->uniqueEdgeFd, PERF_EVENT_IOC_DISABLE, 0); close(perfFds->uniqueEdgeFd); arch_perfMmapParse(); edgeCount = arch_perfCountBranches(); if (perfRecordsLost > 0UL) { LOG_W("%" PRIu64 " PERF_RECORD_LOST events received, possibly too many concurrent fuzzing threads in progress", perfRecordsLost); } } if (perfMmapBuf != NULL) { munmap(perfMmapBuf, perfMmapSz + getpagesize()); } if (perfBloom != NULL) { munmap(perfBloom, _HF_PERF_BLOOM_SZ); } fuzzer->hwCnts.cpuInstrCnt = instrCount; fuzzer->hwCnts.cpuBranchCnt = branchCount; fuzzer->hwCnts.pcCnt = pathCount; fuzzer->hwCnts.pathCnt = edgeCount; }
static bool arch_perfOpen(pid_t pid, dynFileMethod_t method, int *perfFd) { LOG_D("Enabling PERF for PID=%d (mmapBufSz=%zu), method=%x", pid, perfMmapSz, method); perfDynamicMethod = method; perfBranchesCnt = 0; perfRecordsLost = 0; struct perf_event_attr pe; memset(&pe, 0, sizeof(struct perf_event_attr)); pe.size = sizeof(struct perf_event_attr); pe.disabled = 0; pe.exclude_kernel = 1; pe.exclude_hv = 1; pe.exclude_callchain_kernel = 1; pe.enable_on_exec = 1; pe.type = PERF_TYPE_HARDWARE; switch (method) { case _HF_DYNFILE_INSTR_COUNT: LOG_D("Using: PERF_COUNT_HW_INSTRUCTIONS for PID: %d", pid); pe.config = PERF_COUNT_HW_INSTRUCTIONS; pe.inherit = 1; break; case _HF_DYNFILE_BRANCH_COUNT: LOG_D("Using: PERF_COUNT_HW_BRANCH_INSTRUCTIONS for PID: %d", pid); pe.config = PERF_COUNT_HW_BRANCH_INSTRUCTIONS; pe.inherit = 1; break; case _HF_DYNFILE_UNIQUE_BLOCK_COUNT: LOG_D("Using: PERF_SAMPLE_BRANCH_STACK/PERF_SAMPLE_IP for PID: %d", pid); pe.config = PERF_COUNT_HW_BRANCH_INSTRUCTIONS; pe.sample_type = PERF_SAMPLE_IP; pe.sample_period = 1; /* It's BTS based, so must be equal to 1 */ pe.watermark = 1; pe.wakeup_events = perfMmapSz / 2; break; case _HF_DYNFILE_UNIQUE_EDGE_COUNT: LOG_D("Using: PERF_SAMPLE_BRANCH_STACK/PERF_SAMPLE_IP|PERF_SAMPLE_ADDR for PID: %d", pid); pe.config = PERF_COUNT_HW_BRANCH_INSTRUCTIONS; pe.sample_type = PERF_SAMPLE_IP | PERF_SAMPLE_ADDR; pe.sample_period = 1; /* It's BTS based, so must be equal to 1 */ pe.watermark = 1; pe.wakeup_events = perfMmapSz / 2; break; default: LOG_E("Unknown perf mode: '%d' for PID: %d", method, pid); return false; break; } *perfFd = perf_event_open(&pe, pid, -1, -1, 0); if (*perfFd == -1) { if (method == _HF_DYNFILE_UNIQUE_BLOCK_COUNT || method == _HF_DYNFILE_UNIQUE_EDGE_COUNT) { LOG_E ("'-LDp'/'-LDe' mode (sample IP/jump) requires LBR/BTS, which present in Intel Haswell " "and newer CPUs (i.e. not in AMD CPUs)"); } PLOG_F("perf_event_open() failed"); return false; } if (method != _HF_DYNFILE_UNIQUE_BLOCK_COUNT && method != _HF_DYNFILE_UNIQUE_EDGE_COUNT) { return true; } perfBloom = mmap(NULL, _HF_PERF_BLOOM_SZ, PROT_READ | PROT_WRITE, MAP_PRIVATE | MAP_ANONYMOUS | MAP_NORESERVE, -1, 0); if (perfBloom == MAP_FAILED) { perfBloom = NULL; PLOG_E("mmap(size=%zu) failed", (size_t) _HF_PERF_BLOOM_SZ); } perfMmapBuf = mmap(NULL, perfMmapSz + getpagesize(), PROT_READ | PROT_WRITE, MAP_SHARED, *perfFd, 0); if (perfMmapBuf == MAP_FAILED) { perfMmapBuf = NULL; PLOG_E("mmap() failed"); close(*perfFd); return false; } struct sigaction sa = { .sa_handler = arch_perfSigHandler, .sa_flags = SA_RESTART, }; sigemptyset(&sa.sa_mask); if (sigaction(_HF_RT_SIG, &sa, NULL) == -1) { PLOG_E("sigaction() failed"); return false; } if (fcntl(*perfFd, F_SETFL, O_RDWR | O_NONBLOCK | O_ASYNC) == -1) { PLOG_E("fnctl(F_SETFL)"); close(*perfFd); return false; } if (fcntl(*perfFd, F_SETSIG, _HF_RT_SIG) == -1) { PLOG_E("fnctl(F_SETSIG)"); close(*perfFd); return false; } struct f_owner_ex foe = { .type = F_OWNER_TID, .pid = syscall(__NR_gettid) }; if (fcntl(*perfFd, F_SETOWN_EX, &foe) == -1) { PLOG_E("fnctl(F_SETOWN_EX)"); close(*perfFd); return false; } return true; }
static bool containDropPrivs(struct nsjconf_t *nsjconf) { /* * Best effort because of /proc/self/setgroups */ gid_t *group_list = NULL; if (setgroups(0, group_list) == -1) { PLOG_D("setgroups(NULL) failed"); } if (setresgid(nsjconf->inside_gid, nsjconf->inside_gid, nsjconf->inside_gid) == -1) { PLOG_E("setresgid(%u)", nsjconf->inside_gid); return false; } if (setresuid(nsjconf->inside_uid, nsjconf->inside_uid, nsjconf->inside_uid) == -1) { PLOG_E("setresuid(%u)", nsjconf->inside_uid); return false; } #ifndef PR_SET_NO_NEW_PRIVS #define PR_SET_NO_NEW_PRIVS 38 #endif if (prctl(PR_SET_NO_NEW_PRIVS, 1, 0, 0, 0) == -1) { /* Only new kernels support it */ PLOG_W("prctl(PR_SET_NO_NEW_PRIVS, 1)"); } if (nsjconf->keep_caps == false) { for (unsigned long i = 0; i < 128UL; i++) { /* * Number of capabilities differs between kernels, so * wait for the first one which returns EINVAL */ if (prctl(PR_CAPBSET_DROP, i, 0UL, 0UL, 0UL) == -1 && errno == EINVAL) { break; } } if (prctl(PR_SET_KEEPCAPS, 0, 0, 0, 0) == -1) { PLOG_E("prctl(PR_SET_KEEPCAPS, 0)"); return false; } struct __user_cap_header_struct cap_hdr = { .version = _LINUX_CAPABILITY_VERSION_3, .pid = 0, }; const struct __user_cap_data_struct cap_data[_LINUX_CAPABILITY_U32S_3] = { [0 ... (_LINUX_CAPABILITY_U32S_3 - 1)].inheritable = 0U, [0 ... (_LINUX_CAPABILITY_U32S_3 - 1)].effective = 0U, [0 ... (_LINUX_CAPABILITY_U32S_3 - 1)].permitted = 0U, }; if (syscall(__NR_capset, &cap_hdr, &cap_data) == -1) { PLOG_E("capset()"); return false; } } return true; } static bool containPrepareEnv(struct nsjconf_t *nsjconf) { if (prctl(PR_SET_PDEATHSIG, SIGKILL, 0, 0, 0) == -1) { PLOG_E("prctl(PR_SET_PDEATHSIG, SIGKILL)"); return false; } if (nsjconf->personality && personality(nsjconf->personality) == -1) { PLOG_E("personality(%lx)", nsjconf->personality); return false; } errno = 0; if (setpriority(PRIO_PROCESS, 0, 19) == -1 && errno != 0) { PLOG_W("setpriority(19)"); } if (nsjconf->skip_setsid == false) { setsid(); } return true; } static bool containInitMountNs(struct nsjconf_t *nsjconf) { return mountInitNs(nsjconf); } static bool containSetLimits(struct nsjconf_t *nsjconf) { struct rlimit64 rl; rl.rlim_cur = rl.rlim_max = nsjconf->rl_as; if (prlimit64(0, RLIMIT_AS, &rl, NULL) == -1) { PLOG_E("prlimit64(0, RLIMIT_AS, %" PRIu64 ")", nsjconf->rl_as); return false; } rl.rlim_cur = rl.rlim_max = nsjconf->rl_core; if (prlimit64(0, RLIMIT_CORE, &rl, NULL) == -1) { PLOG_E("prlimit64(0, RLIMIT_CORE, %" PRIu64 ")", nsjconf->rl_core); return false; } rl.rlim_cur = rl.rlim_max = nsjconf->rl_cpu; if (prlimit64(0, RLIMIT_CPU, &rl, NULL) == -1) { PLOG_E("prlimit64(0, RLIMIT_CPU, %" PRIu64 ")", nsjconf->rl_cpu); return false; } rl.rlim_cur = rl.rlim_max = nsjconf->rl_fsize; if (prlimit64(0, RLIMIT_FSIZE, &rl, NULL) == -1) { PLOG_E("prlimit64(0, RLIMIT_FSIZE, %" PRIu64 ")", nsjconf->rl_fsize); return false; } rl.rlim_cur = rl.rlim_max = nsjconf->rl_nofile; if (prlimit64(0, RLIMIT_NOFILE, &rl, NULL) == -1) { PLOG_E("prlimit64(0, RLIMIT_NOFILE, %" PRIu64 ")", nsjconf->rl_nofile); return false; } rl.rlim_cur = rl.rlim_max = nsjconf->rl_nproc; if (prlimit64(0, RLIMIT_NPROC, &rl, NULL) == -1) { PLOG_E("prlimit64(0, RLIMIT_NPROC, %" PRIu64 ")", nsjconf->rl_nproc); return false; } rl.rlim_cur = rl.rlim_max = nsjconf->rl_stack; if (prlimit64(0, RLIMIT_STACK, &rl, NULL) == -1) { PLOG_E("prlimit64(0, RLIMIT_STACK, %" PRIu64 ")", nsjconf->rl_stack); return false; } return true; } static bool containMakeFdsCOENaive(void) { // Don't use getrlimit(RLIMIT_NOFILE) here, as it can return an artifically small value // (e.g. 32), which could be smaller than a maximum assigned number to file-descriptors // in this process. Just use some reasonably sane value (e.g. 1024) for (unsigned fd = (STDERR_FILENO + 1); fd < 1024; fd++) { int flags = fcntl(fd, F_GETFD, 0); if (flags == -1) { continue; } fcntl(fd, F_SETFD, flags | FD_CLOEXEC); LOG_D("Set fd '%d' flag to FD_CLOEXEC", fd); } return true; } static bool containMakeFdsCOEProc(void) { /* Make all fds above stderr close-on-exec */ DIR *dir = opendir("/proc/self/fd"); if (dir == NULL) { PLOG_D("opendir('/proc/self/fd')"); return false; } defer { closedir(dir); }; for (;;) { errno = 0; struct dirent *entry = readdir(dir); if (entry == NULL && errno != 0) { PLOG_D("readdir('/proc/self/fd')"); return false; } if (entry == NULL) { break; } if (strcmp(".", entry->d_name) == 0) { continue; } if (strcmp("..", entry->d_name) == 0) { continue; } int fd = strtoul(entry->d_name, NULL, 10); if (errno == EINVAL) { LOG_W("Cannot convert /proc/self/fd/%s to a number", entry->d_name); continue; } if (fd > STDERR_FILENO) { int flags = fcntl(fd, F_GETFD, 0); if (flags == -1) { PLOG_D("fcntl(fd, F_GETFD, 0)"); return false; } fcntl(fd, F_SETFD, flags | FD_CLOEXEC); LOG_D("Set fd '%d' flag to FD_CLOEXEC", fd); } } return true; }
static bool arch_sanCovParseRaw(honggfuzz_t * hfuzz, fuzzer_t * fuzzer) { int dataFd = -1; uint8_t *dataBuf = NULL; off_t dataFileSz = 0, pos = 0; bool is32bit = true, ret = false, isSeedFirstRun = false; char covFile[PATH_MAX] = { 0 }; /* Fuzzer local runtime data structs - need free() before exit */ uint64_t *startMapsIndex = NULL; memMap_t *mapsBuf = NULL; /* Local counters */ uint64_t nBBs = 0; /* Total BB hits found in raw file */ uint64_t nZeroBBs = 0; /* Number of non-hit instrumented BBs */ uint64_t mapsNum = 0; /* Total number of entries in map file */ uint64_t noCovMapsNum = 0; /* Loaded DSOs not compiled with coverage */ /* File line-by-line read help buffers */ char *pLine = NULL; size_t lineSz = 0; /* Coverage data analysis starts by parsing map file listing */ snprintf(covFile, sizeof(covFile), "%s/%s/%d.sancov.map", hfuzz->workDir, _HF_SANCOV_DIR, fuzzer->pid); if (!files_exists(covFile)) { LOG_D("sancov map file not found"); return false; } FILE *fCovMap = fopen(covFile, "rb"); if (fCovMap == NULL) { PLOG_E("Couldn't open '%s' - R/O mode", covFile); goto bail; } /* First line contains PC length (32/64-bit) */ if (getline(&pLine, &lineSz, fCovMap) == -1) { LOG_E("Invalid map file '%s'", covFile); fclose(fCovMap); goto bail; } int pcLen = atoi(pLine); if (pcLen == 32) { is32bit = true; } else if (pcLen == 64) { is32bit = false; } else { LOG_E("Invalid PC length (%d) in map file '%s'", pcLen, covFile); } /* Interaction with global Trie should mutex wrap to avoid threads races */ MX_LOCK(&hfuzz->sanCov_mutex); { /* If runtime data destroy flag, new seed has been picked so destroy old & create new Trie */ if (hfuzz->clearCovMetadata == true) { /* Since this path is invoked on first run too, destroy old Trie only if exists */ if (hfuzz->covMetadata != NULL) { arch_trieDestroy(hfuzz->covMetadata); } arch_trieCreate(&hfuzz->covMetadata); hfuzz->clearCovMetadata = false; isSeedFirstRun = true; } } MX_UNLOCK(&hfuzz->sanCov_mutex); /* See if #maps is available from previous run to avoid realloc inside loop */ uint64_t prevMapsNum = __sync_fetch_and_add(&hfuzz->sanCovCnts.dsoCnt, 0UL); if (prevMapsNum > 0) { if ((mapsBuf = malloc(prevMapsNum * sizeof(memMap_t))) == NULL) { PLOG_E("malloc failed (sz=%" PRIu64 ")", prevMapsNum * sizeof(memMap_t)); /* This will be picked-up later from realloc branch */ prevMapsNum = 0; } } /* Iterate map entries */ for (;;) { if (getline(&pLine, &lineSz, fCovMap) == -1) { break; } /* Trim trailing whitespaces, not sure if needed copied from upstream sancov.py */ char *lineEnd = pLine + strlen(pLine) - 1; while (lineEnd > pLine && isspace(*lineEnd)) { lineEnd--; } *(lineEnd + 1) = 0; /* * Each line has following format: * Start End Base bin/DSO name * b5843000 b584e6ac b5843000 liblog.so */ memMap_t mapData = {.start = 0 }; char *savePtr = NULL; mapData.start = strtoull(strtok_r(pLine, " ", &savePtr), NULL, 16); mapData.end = strtoull(strtok_r(NULL, " ", &savePtr), NULL, 16); mapData.base = strtoull(strtok_r(NULL, " ", &savePtr), NULL, 16); char *mapName = strtok_r(NULL, " ", &savePtr); memcpy(mapData.mapName, mapName, strlen(mapName)); /* Interaction with global Trie should mutex wrap to avoid threads races */ MX_LOCK(&hfuzz->sanCov_mutex); { /* Add entry to Trie with zero data if not already */ if (!arch_trieSearch(hfuzz->covMetadata->children, mapData.mapName)) { arch_trieAdd(&hfuzz->covMetadata, mapData.mapName); } } MX_UNLOCK(&hfuzz->sanCov_mutex); /* If not DSO number history (first run) or new DSO loaded, realloc local maps metadata buf */ if (prevMapsNum == 0 || prevMapsNum < mapsNum) { if ((mapsBuf = realloc(mapsBuf, (size_t) (mapsNum + 1) * sizeof(memMap_t))) == NULL) { PLOG_E("realloc failed (sz=%" PRIu64 ")", (mapsNum + 1) * sizeof(memMap_t)); goto bail; } } /* Add entry to local maps metadata array */ memcpy(&mapsBuf[mapsNum], &mapData, sizeof(memMap_t)); /* Increase loaded maps counter (includes non-instrumented DSOs too) */ mapsNum++; } /* Delete .sancov.map file */ fclose(fCovMap); unlink(covFile); /* Create a quick index array with maps start addresses */ startMapsIndex = malloc(mapsNum * sizeof(uint64_t)); if (startMapsIndex == NULL) { PLOG_E("malloc failed (sz=%" PRIu64 ")", mapsNum * sizeof(uint64_t)); goto bail; } /* Sort quick maps index */ qsort(mapsBuf, mapsNum, sizeof(memMap_t), arch_qsortCmp); for (size_t i = 0; i < mapsNum; i++) { startMapsIndex[i] = mapsBuf[i].start; } /* mmap() .sancov.raw file */ snprintf(covFile, sizeof(covFile), "%s/%s/%d.sancov.raw", hfuzz->workDir, _HF_SANCOV_DIR, fuzzer->pid); dataBuf = files_mapFile(covFile, &dataFileSz, &dataFd, false); if (dataBuf == NULL) { LOG_E("Couldn't open and map '%s' in R/O mode", covFile); goto bail; } /* * Avoid cost of size checks inside raw data read loop by defining the read function * & pivot size based on PC length. */ uint64_t(*pReadRawBBAddrFunc) (const uint8_t *) = NULL; uint8_t pivot = 0; if (is32bit) { pReadRawBBAddrFunc = &util_getUINT32; pivot = 4; } else { pReadRawBBAddrFunc = &util_getUINT64; pivot = 8; } /* * Take advantage of data locality (next processed addr is very likely to belong * to same map) to avoid Trie node search for each read entry. */ node_t *curMap = NULL; uint64_t prevIndex = 0; /* Iterate over data buffer containing list of hit BB addresses */ while (pos < dataFileSz) { uint64_t bbAddr = pReadRawBBAddrFunc(dataBuf + pos); pos += pivot; /* Don't bother for zero BB addr (inserted checks without hit) */ if (bbAddr == 0x0) { nZeroBBs++; continue; } else { /* Find best hit based on start addr & verify range for errors */ uint64_t bestFit = arch_interpSearch(startMapsIndex, mapsNum, bbAddr); if (bbAddr >= mapsBuf[bestFit].start && bbAddr < mapsBuf[bestFit].end) { /* Increase exe/DSO total BB counter */ mapsBuf[bestFit].bbCnt++; /* Update current Trie node if map changed */ if (curMap == NULL || (prevIndex != bestFit)) { prevIndex = bestFit; /* Interaction with global Trie should mutex wrap to avoid threads races */ MX_LOCK(&hfuzz->sanCov_mutex); { curMap = arch_trieSearch(hfuzz->covMetadata->children, mapsBuf[bestFit].mapName); if (curMap == NULL) { LOG_E("Corrupted Trie - '%s' not found", mapsBuf[bestFit].mapName); MX_UNLOCK(&hfuzz->sanCov_mutex); continue; } /* Maintain bitmaps only for exec/DSOs with coverage enabled - allocate on first use */ if (curMap->data.pBM == NULL) { LOG_D("Allocating bitmap for map '%s'", mapsBuf[bestFit].mapName); curMap->data.pBM = arch_newBitmap(_HF_BITMAP_SIZE); /* * If bitmap allocation failed, unset cached Trie node ptr * to execute this selection branch again. */ if (curMap->data.pBM == NULL) { curMap = NULL; MX_UNLOCK(&hfuzz->sanCov_mutex); continue; } } } MX_UNLOCK(&hfuzz->sanCov_mutex); } /* If new relative BB addr update DSO's bitmap */ uint32_t relAddr = (uint32_t) (bbAddr - mapsBuf[bestFit].base); if (!arch_queryBitmap(curMap->data.pBM, relAddr)) { /* Interaction with global Trie should mutex wrap to avoid threads races */ MX_LOCK(&hfuzz->sanCov_mutex); { arch_setBitmap(curMap->data.pBM, relAddr); } MX_UNLOCK(&hfuzz->sanCov_mutex); /* Also increase new BBs counter at worker's thread runtime data */ mapsBuf[bestFit].newBBCnt++; } } else { /* * Normally this should never get executed. If hit, sanitizer * coverage data collection come across some kind of bug. */ LOG_E("Invalid BB addr (%" PRIx64 ") at offset %ld", bbAddr, pos); } } nBBs++; } /* Finally iterate over all instrumented maps to sum-up the number of newly met BB addresses */ for (uint64_t i = 0; i < mapsNum; i++) { if (mapsBuf[i].bbCnt > 0 && !isSeedFirstRun) { fuzzer->sanCovCnts.newBBCnt += mapsBuf[i].newBBCnt; } else { noCovMapsNum++; } } /* Successful parsing - update fuzzer worker's counters */ fuzzer->sanCovCnts.hitBBCnt = nBBs; fuzzer->sanCovCnts.totalBBCnt = nBBs + nZeroBBs; fuzzer->sanCovCnts.dsoCnt = mapsNum; fuzzer->sanCovCnts.iDsoCnt = mapsNum - noCovMapsNum; /* Instrumented DSOs */ ret = true; bail: unlink(covFile); if (dataBuf) { munmap(dataBuf, dataFileSz); } if (dataFd != -1) { close(dataFd); } if (mapsBuf) { free(mapsBuf); } if (startMapsIndex) { free(startMapsIndex); } if (pLine) { free(pLine); } return ret; } static bool arch_sanCovParse(honggfuzz_t * hfuzz, fuzzer_t * fuzzer) { int dataFd = -1; uint8_t *dataBuf = NULL; off_t dataFileSz = 0, pos = 0; bool is32bit = true; char covFile[PATH_MAX] = { 0 }; DIR *pSanCovDir = NULL; bool ret = false; snprintf(covFile, sizeof(covFile), "%s/%s/%s.%d.sancov", hfuzz->workDir, _HF_SANCOV_DIR, files_basename(hfuzz->cmdline[0]), fuzzer->pid); if (!files_exists(covFile)) { LOG_D("Target sancov file not found"); return false; } /* Local cache file suffix to use for file search of worker pid data */ char pidFSuffix[13] = { 0 }; snprintf(pidFSuffix, sizeof(pidFSuffix), "%d.sancov", fuzzer->pid); /* Total BBs counter summarizes all DSOs */ uint64_t nBBs = 0; /* Iterate sancov dir for files generated against fuzzer pid */ snprintf(covFile, sizeof(covFile), "%s/%s", hfuzz->workDir, _HF_SANCOV_DIR); pSanCovDir = opendir(covFile); struct dirent *pDir = NULL; while ((pDir = readdir(pSanCovDir)) != NULL) { /* Parse files with worker's PID */ if (strstr(pDir->d_name, pidFSuffix)) { snprintf(covFile, sizeof(covFile), "%s/%s/%s", hfuzz->workDir, _HF_SANCOV_DIR, pDir->d_name); dataBuf = files_mapFile(covFile, &dataFileSz, &dataFd, false); if (dataBuf == NULL) { LOG_E("Couldn't open and map '%s' in R/O mode", covFile); goto bail; } if (dataFileSz < 8) { LOG_E("Coverage data file too short"); goto bail; } /* Check magic values & derive PC length */ uint64_t magic = util_getUINT64(dataBuf); if (magic == kMagic32) { is32bit = true; } else if (magic == kMagic64) { is32bit = false; } else { LOG_E("Invalid coverage data file"); goto bail; } pos += 8; /* * Avoid cost of size checks inside raw data read loop by defining the read function * & pivot size based on PC length. */ uint64_t(*pReadRawBBAddrFunc) (const uint8_t *) = NULL; uint8_t pivot = 0; if (is32bit) { pReadRawBBAddrFunc = &util_getUINT32; pivot = 4; } else { pReadRawBBAddrFunc = &util_getUINT64; pivot = 8; } while (pos < dataFileSz) { uint32_t bbAddr = pReadRawBBAddrFunc(dataBuf + pos); pos += pivot; if (bbAddr == 0x0) { continue; } nBBs++; } } } /* Successful parsing - update fuzzer worker counters */ fuzzer->sanCovCnts.hitBBCnt = nBBs; ret = true; bail: unlink(covFile); if (dataBuf) { munmap(dataBuf, dataFileSz); } if (dataFd != -1) { close(dataFd); } if (pSanCovDir) { closedir(pSanCovDir); } return ret; }
bool arch_launchChild(honggfuzz_t * hfuzz, char *fileName) { /* * Kill the children when fuzzer dies (e.g. due to Ctrl+C) */ if (prctl(PR_SET_PDEATHSIG, (long)SIGKILL, 0L, 0L, 0L) == -1) { PLOG_E("prctl(PR_SET_PDEATHSIG, SIGKILL) failed"); return false; } /* * Kill a process which corrupts its own heap (with ABRT) */ if (setenv("MALLOC_CHECK_", "3", 1) == -1) { PLOG_E("setenv(MALLOC_CHECK_=3) failed"); return false; } /* * Disable ASLR */ if (hfuzz->disableRandomization && personality(ADDR_NO_RANDOMIZE) == -1) { PLOG_E("personality(ADDR_NO_RANDOMIZE) failed"); return false; } #define ARGS_MAX 512 char *args[ARGS_MAX + 2]; char argData[PATH_MAX] = { 0 }; int x; for (x = 0; x < ARGS_MAX && hfuzz->cmdline[x]; x++) { if (!hfuzz->fuzzStdin && strcmp(hfuzz->cmdline[x], _HF_FILE_PLACEHOLDER) == 0) { args[x] = fileName; } else if (!hfuzz->fuzzStdin && strstr(hfuzz->cmdline[x], _HF_FILE_PLACEHOLDER)) { const char *off = strstr(hfuzz->cmdline[x], _HF_FILE_PLACEHOLDER); snprintf(argData, PATH_MAX, "%.*s%s", (int)(off - hfuzz->cmdline[x]), hfuzz->cmdline[x], fileName); args[x] = argData; } else { args[x] = hfuzz->cmdline[x]; } } args[x++] = NULL; LOG_D("Launching '%s' on file '%s'", args[0], fileName); /* * Wait for the ptrace to attach */ syscall(__NR_tkill, syscall(__NR_gettid), (uintptr_t) SIGSTOP); #ifdef __NR_execveat syscall(__NR_execveat, hfuzz->exeFd, "", args, environ, AT_EMPTY_PATH); #endif execvp(args[0], args); PLOG_E("execvp('%s')", args[0]); return false; }
bool arch_launchChild(honggfuzz_t * hfuzz, char *fileName) { /* * Kill the children when fuzzer dies (e.g. due to Ctrl+C) */ if (prctl(PR_SET_PDEATHSIG, (long)SIGKILL, 0L, 0L, 0L) == -1) { PLOG_E("prctl(PR_SET_PDEATHSIG, SIGKILL) failed"); return false; } /* * Kill a process which corrupts its own heap (with ABRT) */ if (setenv("MALLOC_CHECK_", "3", 1) == -1) { PLOG_E("setenv(MALLOC_CHECK_=3) failed"); return false; } /* * Tell asan to ignore SEGVs */ if (setenv ("ASAN_OPTIONS", "allow_user_segv_handler=1:handle_segv=0:abort_on_error=1:allocator_may_return_null=1", 1) == -1) { PLOG_E("setenv(ASAN_OPTIONS) failed"); return false; } const char *msan_options = "exit_code=" HF_MSAN_EXIT_CODE_STR ":report_umrs=0:wrap_signals=0:print_stats=1"; if (hfuzz->msanReportUMRS == true) { msan_options = "exit_code=" HF_MSAN_EXIT_CODE_STR ":report_umrs=1:wrap_signals=0:print_stats=1"; } if (setenv("MSAN_OPTIONS", msan_options, 1) == -1) { PLOG_E("setenv(MSAN_OPTIONS) failed"); return false; } /* * Disable ASLR */ if (hfuzz->disableRandomization && personality(ADDR_NO_RANDOMIZE) == -1) { PLOG_E("personality(ADDR_NO_RANDOMIZE) failed"); return false; } #define ARGS_MAX 512 char *args[ARGS_MAX + 2]; char argData[PATH_MAX] = { 0 }; int x; for (x = 0; x < ARGS_MAX && hfuzz->cmdline[x]; x++) { if (!hfuzz->fuzzStdin && strcmp(hfuzz->cmdline[x], _HF_FILE_PLACEHOLDER) == 0) { args[x] = fileName; } else if (!hfuzz->fuzzStdin && strstr(hfuzz->cmdline[x], _HF_FILE_PLACEHOLDER)) { const char *off = strstr(hfuzz->cmdline[x], _HF_FILE_PLACEHOLDER); snprintf(argData, PATH_MAX, "%.*s%s", (int)(off - hfuzz->cmdline[x]), hfuzz->cmdline[x], fileName); args[x] = argData; } else { args[x] = hfuzz->cmdline[x]; } } args[x++] = NULL; LOG_D("Launching '%s' on file '%s'", args[0], fileName); /* * Set timeout (prof), real timeout (2*prof), and rlimit_cpu (2*prof) */ if (hfuzz->tmOut) { /* * Set the CPU rlimit to twice the value of the time-out */ struct rlimit rl = { .rlim_cur = hfuzz->tmOut * 2, .rlim_max = hfuzz->tmOut * 2, }; if (setrlimit(RLIMIT_CPU, &rl) == -1) { PLOG_E("Couldn't enforce the RLIMIT_CPU resource limit"); return false; } } /* * The address space limit. If big enough - roughly the size of RAM used */ if (hfuzz->asLimit) { struct rlimit64 rl = { .rlim_cur = hfuzz->asLimit * 1024ULL * 1024ULL, .rlim_max = hfuzz->asLimit * 1024ULL * 1024ULL, }; if (prlimit64(0, RLIMIT_AS, &rl, NULL) == -1) { PLOG_D("Couldn't enforce the RLIMIT_AS resource limit, ignoring"); } } for (size_t i = 0; i < ARRAYSIZE(hfuzz->envs) && hfuzz->envs[i]; i++) { putenv(hfuzz->envs[i]); } if (hfuzz->nullifyStdio) { util_nullifyStdio(); } if (hfuzz->fuzzStdin) { /* * Uglyyyyyy ;) */ if (!util_redirectStdin(fileName)) { return false; } } /* * Wait for the ptrace to attach */ syscall(__NR_tkill, syscall(__NR_gettid), SIGSTOP); execvp(args[0], args); util_recoverStdio(); LOG_F("Failed to create new '%s' process", args[0]); return false; } static void arch_sigFunc(int signo, siginfo_t * si, void *dummy) { if (signo != SIGALRM) { LOG_E("Signal != SIGALRM (%d)", signo); } return; if (si == NULL) { return; } if (dummy == NULL) { return; } } static void arch_removeTimer(timer_t * timerid) { timer_delete(*timerid); } static bool arch_setTimer(timer_t * timerid) { struct sigevent sevp = { .sigev_value.sival_ptr = timerid, .sigev_signo = SIGALRM, .sigev_notify = SIGEV_THREAD_ID | SIGEV_SIGNAL, ._sigev_un._tid = syscall(__NR_gettid), }; if (timer_create(CLOCK_REALTIME, &sevp, timerid) == -1) { PLOG_E("timer_create(CLOCK_REALTIME) failed"); return false; } /* * Kick in every 200ms, starting with the next second */ const struct itimerspec ts = { .it_value = {.tv_sec = 1,.tv_nsec = 0}, .it_interval = {.tv_sec = 0,.tv_nsec = 200000000,}, }; if (timer_settime(*timerid, 0, &ts, NULL) == -1) { PLOG_E("timer_settime() failed"); timer_delete(*timerid); return false; } sigset_t smask; sigemptyset(&smask); struct sigaction sa = { .sa_handler = NULL, .sa_sigaction = arch_sigFunc, .sa_mask = smask, .sa_flags = SA_SIGINFO, .sa_restorer = NULL, }; if (sigaction(SIGALRM, &sa, NULL) == -1) { PLOG_E("sigaction(SIGALRM) failed"); return false; } return true; } static void arch_checkTimeLimit(honggfuzz_t * hfuzz, fuzzer_t * fuzzer) { int64_t curMillis = util_timeNowMillis(); int64_t diffMillis = curMillis - fuzzer->timeStartedMillis; if (diffMillis > (hfuzz->tmOut * 1000)) { LOG_W("PID %d took too much time (limit %ld s). Sending SIGKILL", fuzzer->pid, hfuzz->tmOut); kill(fuzzer->pid, SIGKILL); __sync_fetch_and_add(&hfuzz->timeoutedCnt, 1UL); } }
static bool fuzz_prepareFileExternally(honggfuzz_t * hfuzz, fuzzer_t * fuzzer, int rnd_index) { int dstfd = open(fuzzer->fileName, O_CREAT | O_EXCL | O_RDWR, 0644); if (dstfd == -1) { PLOG_E("Couldn't create a temporary file '%s'", fuzzer->fileName); return false; } LOG_D("Created '%s' as an input file", fuzzer->fileName); if (hfuzz->inputFile) { size_t fileSz = files_readFileToBufMax(hfuzz->files[rnd_index], fuzzer->dynamicFile, hfuzz->maxFileSz); if (fileSz == 0UL) { LOG_E("Couldn't read '%s'", hfuzz->files[rnd_index]); unlink(fuzzer->fileName); return false; } if (files_writeToFd(dstfd, fuzzer->dynamicFile, fileSz) == false) { close(dstfd); unlink(fuzzer->fileName); return false; } } close(dstfd); pid_t pid = arch_fork(hfuzz); if (pid == -1) { PLOG_E("Couldn't fork"); return false; } if (!pid) { /* * child performs the external file modifications */ execl(hfuzz->externalCommand, hfuzz->externalCommand, fuzzer->fileName, NULL); PLOG_F("Couldn't execute '%s %s'", hfuzz->externalCommand, fuzzer->fileName); return false; } /* * parent waits until child is done fuzzing the input file */ int childStatus; int flags = 0; #if defined(__WNOTHREAD) flags |= __WNOTHREAD; #endif /* defined(__WNOTHREAD) */ while (wait4(pid, &childStatus, flags, NULL) != pid) ; if (WIFEXITED(childStatus)) { LOG_D("External command exited with status %d", WEXITSTATUS(childStatus)); return true; } if (WIFSIGNALED(childStatus)) { LOG_E("External command terminated with signal %d", WTERMSIG(childStatus)); return false; } LOG_F("External command terminated abnormally, status: %d", childStatus); return false; abort(); /* NOTREACHED */ }