/* * * honggfuzz - file operations * ----------------------------------------- * * Author: Robert Swiecki * * 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 "input.h" #include #include #include #include #include #include #include #include #include #include #include #include #include #include "libhfcommon/common.h" #include "libhfcommon/files.h" #include "mangle.h" #include "subproc.h" #if defined(_HF_ARCH_LINUX) #include #if defined(__NR_memfd_create) #include #endif /* defined(__NR_memfd_create) */ #endif /* defined(_HF_ARCH_LINUX) */ #include "libhfcommon/log.h" #include "libhfcommon/util.h" void input_setSize(run_t* run, size_t sz) { if (sz > run->global->mutate.maxFileSz) { PLOG_F("Too large size requested: %zu > maxSize: %zu", sz, run->global->mutate.maxFileSz); } /* ftruncate of a mmaped file fails under CygWin */ #if !defined(__CYGWIN__) /* ftruncate for each change of a dynamic file size might be expensive though */ if (TEMP_FAILURE_RETRY(ftruncate(run->dynamicFileFd, sz)) == -1) { PLOG_W("ftruncate(run->dynamicFileFd=%d, sz=%zu)", run->dynamicFileFd, sz); } #endif /* !defined(__CYGWIN__) */ run->dynamicFileSz = sz; } static bool input_getDirStatsAndRewind(honggfuzz_t* hfuzz) { rewinddir(hfuzz->io.inputDirPtr); size_t maxSize = 0U; size_t fileCnt = 0U; for (;;) { errno = 0; struct dirent* entry = readdir(hfuzz->io.inputDirPtr); if (entry == NULL && errno == EINTR) { continue; } if (entry == NULL && errno != 0) { PLOG_W("readdir('%s')", hfuzz->io.inputDir); return false; } if (entry == NULL) { break; } char fname[PATH_MAX]; snprintf(fname, sizeof(fname), "%s/%s", hfuzz->io.inputDir, entry->d_name); LOG_D("Analyzing file '%s'", fname); struct stat st; if (stat(fname, &st) == -1) { LOG_W("Couldn't stat() the '%s' file", fname); continue; } if (!S_ISREG(st.st_mode)) { LOG_D("'%s' is not a regular file, skipping", fname); continue; } if (hfuzz->mutate.maxFileSz != 0UL && st.st_size > (off_t)hfuzz->mutate.maxFileSz) { LOG_D("File '%s' is bigger than maximal defined file size (-F): %" PRId64 " > %" PRId64, fname, (int64_t)st.st_size, (int64_t)hfuzz->mutate.maxFileSz); } if ((size_t)st.st_size > maxSize) { maxSize = st.st_size; } fileCnt++; } ATOMIC_SET(hfuzz->io.fileCnt, fileCnt); if (hfuzz->mutate.maxFileSz == 0U) { if (maxSize < 8192) { hfuzz->mutate.maxFileSz = 8192; } else if (maxSize > _HF_INPUT_MAX_SIZE) { hfuzz->mutate.maxFileSz = _HF_INPUT_MAX_SIZE; } else { hfuzz->mutate.maxFileSz = maxSize; } } if (hfuzz->io.fileCnt == 0U) { LOG_W("No usable files in the input directory '%s'", hfuzz->io.inputDir); } LOG_D("Re-read the '%s', maxFileSz:%zu, number of usable files:%zu", hfuzz->io.inputDir, hfuzz->mutate.maxFileSz, hfuzz->io.fileCnt); rewinddir(hfuzz->io.inputDirPtr); return true; } bool input_getNext(run_t* run, char* fname, bool rewind) { static pthread_mutex_t input_mutex = PTHREAD_MUTEX_INITIALIZER; MX_SCOPED_LOCK(&input_mutex); if (run->global->io.fileCnt == 0U) { LOG_W("No useful files in the input directory"); return false; } for (;;) { errno = 0; struct dirent* entry = readdir(run->global->io.inputDirPtr); if (entry == NULL && errno == EINTR) { continue; } if (entry == NULL && errno != 0) { PLOG_W("readdir_r('%s')", run->global->io.inputDir); return false; } if (entry == NULL && rewind == false) { return false; } if (entry == NULL && rewind == true) { if (input_getDirStatsAndRewind(run->global) == false) { LOG_E("input_getDirStatsAndRewind('%s')", run->global->io.inputDir); return false; } continue; } snprintf(fname, PATH_MAX, "%s/%s", run->global->io.inputDir, entry->d_name); struct stat st; if (stat(fname, &st) == -1) { LOG_W("Couldn't stat() the '%s' file", fname); continue; } if (!S_ISREG(st.st_mode)) { LOG_D("'%s' is not a regular file, skipping", fname); continue; } return true; } } bool input_init(honggfuzz_t* hfuzz) { hfuzz->io.fileCnt = 0U; if (!hfuzz->io.inputDir) { LOG_W("No input file/dir specified"); return false; } int dir_fd = open(hfuzz->io.inputDir, O_DIRECTORY | O_RDONLY | O_CLOEXEC); if (dir_fd == -1) { PLOG_W("open('%s', O_DIRECTORY|O_RDONLY|O_CLOEXEC)", hfuzz->io.inputDir); return false; } if ((hfuzz->io.inputDirPtr = fdopendir(dir_fd)) == NULL) { close(dir_fd); PLOG_W("opendir('%s')", hfuzz->io.inputDir); return false; } if (input_getDirStatsAndRewind(hfuzz) == false) { hfuzz->io.fileCnt = 0U; LOG_W("input_getDirStatsAndRewind('%s')", hfuzz->io.inputDir); return false; } return true; } bool input_parseDictionary(honggfuzz_t* hfuzz) { FILE* fDict = fopen(hfuzz->mutate.dictionaryFile, "rb"); if (fDict == NULL) { PLOG_W("Couldn't open '%s' - R/O mode", hfuzz->mutate.dictionaryFile); return false; } defer { fclose(fDict); }; char* lineptr = NULL; size_t n = 0; defer { free(lineptr); }; for (;;) { ssize_t len = getdelim(&lineptr, &n, '\n', fDict); if (len == -1) { break; } if (len > 1 && lineptr[len - 1] == '\n') { lineptr[len - 1] = '\0'; len--; } if (lineptr[0] == '#') { continue; } if (lineptr[0] == '\n') { continue; } if (lineptr[0] == '\0') { continue; } char bufn[1025] = {}; char bufv[1025] = {}; if (sscanf(lineptr, "\"%1024s", bufv) != 1 && sscanf(lineptr, "%1024[^=]=\"%1024s", bufn, bufv) != 2) { LOG_W("Incorrect dictionary entry: '%s'. Skipping", lineptr); continue; } LOG_D("Parsing word: '%s'", bufv); char* s = util_StrDup(bufv); struct strings_t* str = (struct strings_t*)util_Malloc(sizeof(struct strings_t)); str->len = util_decodeCString(s); str->s = s; hfuzz->mutate.dictionaryCnt += 1; TAILQ_INSERT_TAIL(&hfuzz->mutate.dictq, str, pointers); LOG_D("Dictionary: loaded word: '%s' (len=%zu)", str->s, str->len); } LOG_I("Loaded %zu words from the dictionary", hfuzz->mutate.dictionaryCnt); return true; } bool input_parseBlacklist(honggfuzz_t* hfuzz) { FILE* fBl = fopen(hfuzz->feedback.blacklistFile, "rb"); if (fBl == NULL) { PLOG_W("Couldn't open '%s' - R/O mode", hfuzz->feedback.blacklistFile); return false; } defer { fclose(fBl); }; char* lineptr = NULL; /* lineptr can be NULL, but it's fine for free() */ defer { free(lineptr); }; size_t n = 0; for (;;) { if (getline(&lineptr, &n, fBl) == -1) { break; } if ((hfuzz->feedback.blacklist = util_Realloc(hfuzz->feedback.blacklist, (hfuzz->feedback.blacklistCnt + 1) * sizeof(hfuzz->feedback.blacklist[0]))) == NULL) { PLOG_W("realloc failed (sz=%zu)", (hfuzz->feedback.blacklistCnt + 1) * sizeof(hfuzz->feedback.blacklist[0])); return false; } hfuzz->feedback.blacklist[hfuzz->feedback.blacklistCnt] = strtoull(lineptr, 0, 16); LOG_D("Blacklist: loaded %'" PRIu64 "'", hfuzz->feedback.blacklist[hfuzz->feedback.blacklistCnt]); /* Verify entries are sorted so we can use interpolation search */ if (hfuzz->feedback.blacklistCnt > 1) { if (hfuzz->feedback.blacklist[hfuzz->feedback.blacklistCnt - 1] > hfuzz->feedback.blacklist[hfuzz->feedback.blacklistCnt]) { LOG_F("Blacklist file not sorted. Use 'tools/createStackBlacklist.sh' to sort " "records"); return false; } } hfuzz->feedback.blacklistCnt += 1; } if (hfuzz->feedback.blacklistCnt > 0) { LOG_I("Loaded %zu stack hash(es) from the blacklist file", hfuzz->feedback.blacklistCnt); } else { LOG_F("Empty stack hashes blacklist file '%s'", hfuzz->feedback.blacklistFile); } return true; } bool input_prepareDynamicInput(run_t* run) { { MX_SCOPED_RWLOCK_READ(&run->global->io.dynfileq_mutex); if (run->global->io.dynfileqCnt == 0) { LOG_F("The dynamic file corpus is empty. This shouldn't happen"); } if (run->dynfileqCurrent == NULL) { run->dynfileqCurrent = TAILQ_FIRST(&run->global->io.dynfileq); } else { if (run->dynfileqCurrent == TAILQ_LAST(&run->global->io.dynfileq, dyns_t)) { run->dynfileqCurrent = TAILQ_FIRST(&run->global->io.dynfileq); } else { run->dynfileqCurrent = TAILQ_NEXT(run->dynfileqCurrent, pointers); } } } input_setSize(run, run->dynfileqCurrent->size); memcpy(run->dynamicFile, run->dynfileqCurrent->data, run->dynfileqCurrent->size); mangle_mangleContent(run); return true; } bool input_prepareStaticFile(run_t* run, bool rewind) { char fname[PATH_MAX]; if (!input_getNext(run, fname, /* rewind= */ rewind)) { return false; } snprintf(run->origFileName, sizeof(run->origFileName), "%s", fname); input_setSize(run, run->global->mutate.maxFileSz); ssize_t fileSz = files_readFileToBufMax(fname, run->dynamicFile, run->global->mutate.maxFileSz); if (fileSz < 0) { LOG_E("Couldn't read contents of '%s'", fname); return false; } input_setSize(run, fileSz); mangle_mangleContent(run); return true; } bool input_prepareExternalFile(run_t* run) { snprintf(run->origFileName, sizeof(run->origFileName), "[EXTERNAL]"); int fd = files_writeBufToTmpFile(run->global->io.workDir, (const uint8_t*)"", 0, 0); if (fd == -1) { LOG_E("Couldn't write input file to a temporary buffer"); return false; } defer { close(fd); }; char fname[PATH_MAX]; snprintf(fname, sizeof(fname), "/dev/fd/%d", fd); const char* const argv[] = {run->global->exe.externalCommand, fname, 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); input_setSize(run, run->global->mutate.maxFileSz); ssize_t sz = files_readFromFdSeek(fd, run->dynamicFile, run->global->mutate.maxFileSz, 0); if (sz == -1) { LOG_E("Couldn't read file from fd=%d", fd); return false; } input_setSize(run, (size_t)sz); return true; } bool input_postProcessFile(run_t* run) { int fd = files_writeBufToTmpFile(run->global->io.workDir, run->dynamicFile, run->dynamicFileSz, 0); if (fd == -1) { LOG_E("Couldn't write input file to a temporary buffer"); return false; } defer { close(fd); }; char fname[PATH_MAX]; snprintf(fname, sizeof(fname), "/dev/fd/%d", fd); const char* const argv[] = {run->global->exe.postExternalCommand, fname, 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); input_setSize(run, run->global->mutate.maxFileSz); ssize_t sz = files_readFromFdSeek(fd, run->dynamicFile, run->global->mutate.maxFileSz, 0); if (sz == -1) { LOG_E("Couldn't read file from fd=%d", fd); return false; } input_setSize(run, (size_t)sz); return true; }