| /* |
| * |
| * honggfuzz - sanitizer coverage feedback parsing |
| * ----------------------------------------------- |
| * |
| * 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. |
| * |
| */ |
| |
| /* |
| * Clang sanitizer coverage (sancov) data parsing functions. Supported methods: |
| * - raw unified data (preferred method) |
| * - individual data per executable/DSO (not preferred since lots of data lost if instrumented |
| * code exits abnormally or with sanitizer unhandled signal (common in Android OS) |
| * |
| * For raw-unpack method a global (shared across workers) Trie is created for the chosen |
| * initial seed and maintained until seed is replaced. Trie nodes store the loaded (as exposed |
| * from *.sancov.map file) execs/DSOs from target application using the map name as key. Trie node |
| * data struct (trieData_t) maintains information for each instrumented map including a bitmap with |
| * all hit relative BB addresses (realBBAddr - baseAddr to circumvent ASLR). Map's bitmap is updated while |
| * new areas on target application are discovered based on absolute elitism implemented at |
| * fuzz_sanCovFeedback(). |
| * |
| * For individual data files a pid (fuzzer's thread or remote process) based filename search is performed |
| * to identify all files belonging to examined execution. This method doesn't implement yet bitmap runtime |
| * data to detect newly discovered areas. It's mainly used so far as a comparison metric for raw-unpack method |
| * and stability check for sancov experimental features such as coverage counters: |
| * http://clang.llvm.org/docs/SanitizerCoverage.html |
| */ |
| |
| #include "common.h" |
| #include "sancov.h" |
| |
| #include <ctype.h> |
| #include <dirent.h> |
| #include <inttypes.h> |
| #include <stdio.h> |
| #include <stdlib.h> |
| #include <string.h> |
| #include <sys/mman.h> |
| #include <sys/stat.h> |
| #include <sys/types.h> |
| |
| #include "files.h" |
| #include "log.h" |
| #include "util.h" |
| |
| /* sancov files magic values */ |
| #define kMagic32 0xC0BFFFFFFFFFFF32 |
| #define kMagic64 0xC0BFFFFFFFFFFF64 |
| |
| /* Stringify */ |
| #define XSTR(x) #x |
| #define STR(x) XSTR(x) |
| |
| /* Common sanitizer flags */ |
| #if _HF_MONITOR_SIGABRT |
| #define ABORT_FLAG "abort_on_error=1" |
| #else |
| #define ABORT_FLAG "abort_on_error=0" |
| #endif |
| |
| #if defined(__ANDROID__) |
| /* |
| * symbolize: Disable symbolication since it changes logs (which are parsed) format |
| * start_deactivated: Enable on Android to reduce memory usage (useful when not all |
| * target's DSOs are compiled with sanitizer enabled |
| * abort_on_error: Disable for platforms where SIGABRT is not monitored |
| */ |
| #define kSAN_COMMON_ARCH "symbolize=0:"ABORT_FLAG":start_deactivated=1" |
| #else |
| #define kSAN_COMMON_ARCH "symbolize=0:"ABORT_FLAG |
| #endif |
| |
| /* Sanitizer specific flags (set 'abort_on_error has priority over exitcode') */ |
| #define kASAN_OPTS "allow_user_segv_handler=1:"\ |
| "handle_segv=0:"\ |
| "allocator_may_return_null=1:"\ |
| kSAN_COMMON_ARCH":exitcode=" STR(HF_ASAN_EXIT_CODE) |
| |
| #define kUBSAN_OPTS kSAN_COMMON_ARCH":exitcode=" STR(HF_UBSAN_EXIT_CODE) |
| |
| #define kMSAN_OPTS "exit_code=" STR(HF_MSAN_EXIT_CODE) ":"\ |
| "wrap_signals=0:print_stats=1" |
| |
| /* 'log_path' ouput directory for sanitizer reports */ |
| #define kSANLOGDIR "log_path=" |
| |
| /* 'coverage_dir' output directory for coverage data files is set dynamically */ |
| #define kSANCOVDIR "coverage_dir=" |
| |
| /* |
| * If the program ends with a signal that ASan does not handle (or can not |
| * handle at all, like SIGKILL), coverage data will be lost. This is a big |
| * problem on Android, where SIGKILL is a normal way of evicting applications |
| * from memory. With 'coverage_direct=1' coverage data is written to a |
| * memory-mapped file as soon as it collected. Non-Android targets can disable |
| * coverage direct when more coverage data collection methods are implemented. |
| */ |
| #if defined(__ANDROID__) |
| #define kSAN_COV_OPTS "coverage=1:coverage_direct=1" |
| #else |
| #define kSAN_COV_OPTS "coverage=1:coverage_direct=1" |
| #endif |
| |
| /* |
| * bitmap implementation |
| */ |
| static bitmap_t *sancov_newBitmap(uint32_t capacity) |
| { |
| bitmap_t *pBM = util_Malloc(sizeof(bitmap_t)); |
| pBM->capacity = capacity; |
| pBM->nChunks = (capacity + 31) / 32; |
| pBM->pChunks = util_Calloc(pBM->nChunks * sizeof(uint32_t)); |
| return pBM; |
| } |
| |
| static inline bool sancov_queryBitmap(bitmap_t * pBM, uint32_t index) |
| { |
| if (index > pBM->capacity) { |
| LOG_E("bitmap overflow (%u)", index); |
| return false; |
| } |
| if (pBM->pChunks[index / 32] & (1 << (index % 32))) { |
| return true; |
| } |
| return false; |
| } |
| |
| static inline void sancov_setBitmap(bitmap_t * pBM, uint32_t index) |
| { |
| /* This will be removed. So far checks only to verify accepted ranges. */ |
| if (index >= pBM->capacity) { |
| LOG_E("Out of range index (%u > %u)", index, pBM->capacity); |
| } |
| pBM->pChunks[index / 32] |= (1 << (index % 32)); |
| } |
| |
| static inline void sancov_destroyBitmap(bitmap_t * pBM) |
| { |
| free(pBM->pChunks); |
| free(pBM); |
| } |
| |
| /* |
| * Trie implementation |
| */ |
| static node_t *sancov_trieCreateNode(char key) |
| { |
| node_t *node = (node_t *) util_Malloc(sizeof(node_t)); |
| node->key = key; |
| node->next = NULL; |
| node->children = NULL; |
| node->parent = NULL; |
| node->prev = NULL; |
| |
| /* Zero init node's data struct */ |
| memset(&node->data, 0, sizeof(trieData_t)); |
| return node; |
| } |
| |
| static node_t *sancov_trieSearch(node_t * root, const char *key) |
| { |
| node_t *pNodeLevel = root, *pNodePtr = NULL; |
| int nodeLevelId = 0; |
| while (1) { |
| node_t *pNodeFound = NULL, *pCurNode = NULL; |
| for (pCurNode = pNodeLevel; pCurNode != NULL; pCurNode = pCurNode->next) { |
| if (pCurNode->key == *key) { |
| pNodeFound = pCurNode; |
| nodeLevelId++; |
| break; |
| } |
| } |
| if (pNodeFound == NULL) { |
| return NULL; |
| } |
| if (*key == '\0') { |
| pNodePtr = pCurNode; |
| return pNodePtr; |
| } |
| pNodeLevel = pNodeFound->children; |
| key++; |
| } |
| } |
| |
| static void sancov_trieAdd(node_t ** root, const char *key) |
| { |
| if (*root == NULL) { |
| LOG_E("Invalid Trie (NULL root node)"); |
| return; |
| } |
| |
| /* Traverse Trie */ |
| node_t *pTravNode = (*root)->children; |
| if (pTravNode == NULL) { |
| /* First node */ |
| for (pTravNode = *root; *key != '\0'; pTravNode = pTravNode->children) { |
| pTravNode->children = sancov_trieCreateNode(*key); |
| pTravNode->children->parent = pTravNode; |
| key++; |
| } |
| pTravNode->children = sancov_trieCreateNode('\0'); |
| pTravNode->children->parent = pTravNode; |
| return; |
| } |
| |
| while (*key != '\0') { |
| if (*key == pTravNode->key) { |
| key++; |
| pTravNode = pTravNode->children; |
| } else { |
| break; |
| } |
| } |
| while (pTravNode->next) { |
| if (*key == pTravNode->next->key) { |
| key++; |
| sancov_trieAdd(&(pTravNode->next), key); |
| return; |
| } |
| pTravNode = pTravNode->next; |
| } |
| if (*key) { |
| pTravNode->next = sancov_trieCreateNode(*key); |
| } else { |
| pTravNode->next = sancov_trieCreateNode(*key); |
| } |
| pTravNode->next->parent = pTravNode->parent; |
| pTravNode->next->prev = pTravNode; |
| if (!*key) { |
| return; |
| } |
| key++; |
| for (pTravNode = pTravNode->next; *key; pTravNode = pTravNode->children) { |
| pTravNode->children = sancov_trieCreateNode(*key); |
| pTravNode->children->parent = pTravNode; |
| key++; |
| } |
| pTravNode->children = sancov_trieCreateNode('\0'); |
| pTravNode->children->parent = pTravNode; |
| |
| return; |
| } |
| |
| static inline void sancov_trieFreeNode(node_t * node) |
| { |
| /* First destroy bitmap heap buffers allocated for instrumented maps */ |
| if (node->data.pBM) { |
| sancov_destroyBitmap(node->data.pBM); |
| } |
| free(node); |
| } |
| |
| static inline void sancov_trieCreate(node_t ** root) |
| { |
| /* Create root node if new Trie */ |
| *root = sancov_trieCreateNode('\0'); |
| } |
| |
| /* Destroy Trie - iterate nodes and free memory */ |
| UNUSED static void sancov_trieDestroy(node_t * root) |
| { |
| node_t *pNode = root; |
| node_t *pNodeTmp = root; |
| while (pNode) { |
| while (pNode->children) { |
| pNode = pNode->children; |
| } |
| |
| if (pNode->prev && pNode->next) { |
| pNodeTmp = pNode; |
| pNode->next->prev = pNode->prev; |
| pNode->prev->next = pNode->next; |
| sancov_trieFreeNode(pNodeTmp); |
| } else if (pNode->prev && !pNode->next) { |
| pNodeTmp = pNode; |
| pNode->prev->next = NULL; |
| sancov_trieFreeNode(pNodeTmp); |
| } else if (!pNode->prev && pNode->next) { |
| pNodeTmp = pNode; |
| pNode->parent->children = pNode->next; |
| pNode->next->prev = NULL; |
| pNode = pNode->next; |
| sancov_trieFreeNode(pNodeTmp); |
| } else { |
| pNodeTmp = pNode; |
| if (pNode->parent == NULL) { |
| /* Root */ |
| sancov_trieFreeNode(pNodeTmp); |
| return; |
| } |
| pNode = pNode->parent; |
| pNode->children = NULL; |
| sancov_trieFreeNode(pNodeTmp); |
| } |
| } |
| } |
| |
| /* Modified interpolation search algorithm to search for nearest address fit */ |
| static inline uint64_t sancov_interpSearch(uint64_t * buf, uint64_t size, uint64_t key) |
| { |
| /* Avoid extra checks assuming caller always provides non-zero array size */ |
| uint64_t low = 0; |
| uint64_t high = size - 1; |
| uint64_t mid = high; |
| |
| while (buf[high] != buf[low] && key >= buf[low] && key <= buf[high]) { |
| mid = low + (key - buf[low]) * ((high - low) / (buf[high] - buf[low])); |
| if (buf[mid] < key) { |
| low = mid + 1; |
| } else if (key < buf[mid]) { |
| high = mid - 1; |
| } else { |
| return mid; |
| } |
| } |
| return mid; |
| } |
| |
| /* qsort struct comparison function (memMap_t struct start addr field) */ |
| static int sancov_qsortCmp(const void *a, const void *b) |
| { |
| memMap_t *pA = (memMap_t *) a; |
| memMap_t *pB = (memMap_t *) b; |
| if (pA->start < pB->start) { |
| return -1; |
| } else if (pA->start > pB->start) { |
| return 1; |
| } else { |
| /* Normally we should never hit that case */ |
| LOG_W("Duplicate map start addr detected"); |
| return 0; |
| } |
| |
| } |
| |
| static bool sancov_sanCovParseRaw(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 }; |
| pid_t targetPid = (hfuzz->linux.pid > 0) ? hfuzz->linux.pid : fuzzer->pid; |
| |
| /* 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 */ |
| __block 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, |
| targetPid); |
| 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); |
| return false; |
| } |
| DEFER(fclose(fCovMap)); |
| |
| /* First line contains PC length (32/64-bit) */ |
| if (getline(&pLine, &lineSz, fCovMap) == -1) { |
| LOG_E("Invalid map file '%s'", covFile); |
| return false; |
| } |
| DEFER(free(pLine); |
| pLine = NULL); |
| |
| 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); |
| } |
| |
| /* See if #maps is available from previous run to avoid realloc inside loop */ |
| uint64_t prevMapsNum = ATOMIC_GET(hfuzz->sanCovCnts.dsoCnt); |
| if (prevMapsNum > 0) { |
| mapsBuf = util_Malloc(prevMapsNum * sizeof(memMap_t)); |
| } |
| /* It's OK to free(NULL) */ |
| DEFER(free(mapsBuf)); |
| |
| /* 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((int)*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_SCOPED_LOCK(&hfuzz->sanCov_mutex); |
| |
| /* Add entry to Trie with zero data if not already */ |
| if (!sancov_trieSearch(hfuzz->covMetadata->children, mapData.mapName)) { |
| sancov_trieAdd(&hfuzz->covMetadata, mapData.mapName); |
| } |
| } |
| |
| /* 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)); |
| return false; |
| } |
| } |
| |
| /* 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 */ |
| if (hfuzz->linux.pid == 0) { |
| unlink(covFile); |
| } |
| |
| /* Create a quick index array with maps start addresses */ |
| startMapsIndex = util_Malloc(mapsNum * sizeof(uint64_t)); |
| DEFER(free(startMapsIndex)); |
| |
| /* Sort quick maps index */ |
| qsort(mapsBuf, mapsNum, sizeof(memMap_t), sancov_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, |
| targetPid); |
| dataBuf = files_mapFile(covFile, &dataFileSz, &dataFd, false); |
| if (dataBuf == NULL) { |
| LOG_E("Couldn't open and map '%s' in R/O mode", covFile); |
| return false; |
| } |
| DEFER(munmap(dataBuf, dataFileSz); close(dataFd)); |
| |
| /* |
| * 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 = sancov_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_SCOPED_LOCK(&hfuzz->sanCov_mutex); |
| |
| curMap = |
| sancov_trieSearch(hfuzz->covMetadata->children, |
| mapsBuf[bestFit].mapName); |
| if (curMap == NULL) { |
| LOG_E("Corrupted Trie - '%s' not found", mapsBuf[bestFit].mapName); |
| 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 = sancov_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; |
| continue; |
| } |
| } |
| } |
| } |
| |
| /* If new relative BB addr update DSO's bitmap */ |
| uint32_t relAddr = (uint32_t) (bbAddr - mapsBuf[bestFit].base); |
| if (!sancov_queryBitmap(curMap->data.pBM, relAddr)) { |
| |
| /* Interaction with global Trie should mutex wrap to avoid threads races */ |
| { |
| MX_SCOPED_LOCK(&hfuzz->sanCov_mutex); |
| |
| sancov_setBitmap(curMap->data.pBM, relAddr); |
| } |
| |
| /* 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 %" PRId64, bbAddr, (uint64_t) 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) { |
| 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 */ |
| |
| if (hfuzz->linux.pid == 0) { |
| unlink(covFile); |
| } |
| return true; |
| } |
| |
| static bool sancov_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; |
| pid_t targetPid = (hfuzz->linux.pid > 0) ? hfuzz->linux.pid : fuzzer->pid; |
| |
| snprintf(covFile, sizeof(covFile), "%s/%s/%s.%d.sancov", hfuzz->workDir, _HF_SANCOV_DIR, |
| files_basename(hfuzz->cmdline[0]), targetPid); |
| if (!files_exists(covFile)) { |
| LOG_D("Target sancov file not found"); |
| return false; |
| } |
| |
| /* Local cache file suffix to use for file search of target pid data */ |
| char pidFSuffix[13] = { 0 }; |
| snprintf(pidFSuffix, sizeof(pidFSuffix), "%d.sancov", targetPid); |
| |
| /* Total BBs counter summarizes all DSOs */ |
| uint64_t nBBs = 0; |
| |
| /* Iterate sancov dir for files generated against target pid */ |
| snprintf(covFile, sizeof(covFile), "%s/%s", hfuzz->workDir, _HF_SANCOV_DIR); |
| pSanCovDir = opendir(covFile); |
| if (pSanCovDir == NULL) { |
| PLOG_E("opendir('%s')", covFile); |
| return false; |
| } |
| DEFER(closedir(pSanCovDir)); |
| |
| struct dirent *pDir = NULL; |
| while ((pDir = readdir(pSanCovDir)) != NULL) { |
| /* Parse files with target'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); |
| return false; |
| } |
| DEFER(munmap(dataBuf, dataFileSz); close(dataFd)); |
| |
| if (dataFileSz < 8) { |
| LOG_E("Coverage data file too short"); |
| return false; |
| } |
| |
| /* 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"); |
| return false; |
| } |
| 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; |
| |
| if (hfuzz->linux.pid == 0) { |
| unlink(covFile); |
| } |
| return true; |
| } |
| |
| /* |
| * Sanitizer coverage data are stored in FS can be parsed via two methods: |
| * raw unpack & separate bin/DSO sancov file. Separate bin/DSO sancov file |
| * method is usually avoided since coverage data are lost if sanitizer unhandled |
| * signal. Additionally, the FS I/O overhead is bigger compared to raw unpack |
| * method which uses runtime data structures. |
| * |
| * Enabled methods are controlled from sanitizer flags in arch.c |
| */ |
| void sancov_Analyze(honggfuzz_t * hfuzz, fuzzer_t * fuzzer) |
| { |
| if (!hfuzz->useSanCov) { |
| return; |
| } |
| |
| /* |
| * For now supported methods are implemented in fail-over nature. This will |
| * change in the future when best method is concluded. |
| */ |
| if (sancov_sanCovParseRaw(hfuzz, fuzzer) == false) { |
| sancov_sanCovParse(hfuzz, fuzzer); |
| } |
| } |
| |
| bool sancov_Init(honggfuzz_t * hfuzz) |
| { |
| if (hfuzz->useSanCov == false) { |
| return true; |
| } |
| |
| sancov_trieCreate(&hfuzz->covMetadata); |
| |
| if (hfuzz->linux.pid > 0) { |
| return true; |
| } |
| |
| char sanCovOutDir[PATH_MAX] = { 0 }; |
| snprintf(sanCovOutDir, sizeof(sanCovOutDir), "%s/%s", hfuzz->workDir, _HF_SANCOV_DIR); |
| if (!files_exists(sanCovOutDir)) { |
| if (mkdir(sanCovOutDir, S_IRWXU | S_IXGRP | S_IXOTH) != 0) { |
| PLOG_E("mkdir() '%s' failed", sanCovOutDir); |
| } |
| } |
| |
| /* Set sanitizer flags once to avoid performance overhead per worker spawn */ |
| size_t flagsSz = 0; |
| size_t bufSz = sizeof(kASAN_OPTS) + (2 * PATH_MAX); // Larger constant + 2 dynamic paths |
| char *san_opts = util_Calloc(bufSz); |
| DEFER(free(san_opts)); |
| |
| /* AddressSanitizer (ASan) */ |
| if (hfuzz->useSanCov) { |
| #if !_HF_MONITOR_SIGABRT |
| /* Write reports in FS only if abort_on_error is disabled */ |
| snprintf(san_opts, bufSz, "%s:%s:%s%s/%s:%s%s/%s", kASAN_OPTS, kSAN_COV_OPTS, |
| kSANCOVDIR, hfuzz->workDir, _HF_SANCOV_DIR, kSANLOGDIR, hfuzz->workDir, |
| kLOGPREFIX); |
| #else |
| snprintf(san_opts, bufSz, "%s:%s:%s%s/%s", kASAN_OPTS, kSAN_COV_OPTS, |
| kSANCOVDIR, hfuzz->workDir, _HF_SANCOV_DIR); |
| #endif |
| } else { |
| snprintf(san_opts, bufSz, "%s:%s%s/%s", kASAN_OPTS, kSANLOGDIR, hfuzz->workDir, kLOGPREFIX); |
| } |
| |
| flagsSz = strlen(san_opts) + 1; |
| hfuzz->sanOpts.asanOpts = util_Calloc(flagsSz); |
| memcpy(hfuzz->sanOpts.asanOpts, san_opts, flagsSz); |
| LOG_D("ASAN_OPTIONS=%s", hfuzz->sanOpts.asanOpts); |
| |
| /* Undefined Behavior (UBSan) */ |
| memset(san_opts, 0, bufSz); |
| if (hfuzz->useSanCov) { |
| #if !_HF_MONITOR_SIGABRT |
| /* Write reports in FS only if abort_on_error is disabled */ |
| snprintf(san_opts, bufSz, "%s:%s:%s%s/%s:%s%s/%s", kUBSAN_OPTS, kSAN_COV_OPTS, |
| kSANCOVDIR, hfuzz->workDir, _HF_SANCOV_DIR, kSANLOGDIR, hfuzz->workDir, |
| kLOGPREFIX); |
| #else |
| snprintf(san_opts, bufSz, "%s:%s:%s%s/%s", kUBSAN_OPTS, kSAN_COV_OPTS, |
| kSANCOVDIR, hfuzz->workDir, _HF_SANCOV_DIR); |
| #endif |
| } else { |
| snprintf(san_opts, bufSz, "%s:%s%s/%s", kUBSAN_OPTS, kSANLOGDIR, hfuzz->workDir, |
| kLOGPREFIX); |
| } |
| |
| flagsSz = strlen(san_opts) + 1; |
| hfuzz->sanOpts.ubsanOpts = util_Calloc(flagsSz); |
| memcpy(hfuzz->sanOpts.ubsanOpts, san_opts, flagsSz); |
| LOG_D("UBSAN_OPTIONS=%s", hfuzz->sanOpts.ubsanOpts); |
| |
| /* MemorySanitizer (MSan) */ |
| memset(san_opts, 0, bufSz); |
| const char *msan_reports_flag = "report_umrs=0"; |
| if (hfuzz->msanReportUMRS) { |
| msan_reports_flag = "report_umrs=1"; |
| } |
| |
| if (hfuzz->useSanCov) { |
| #if !_HF_MONITOR_SIGABRT |
| /* Write reports in FS only if abort_on_error is disabled */ |
| snprintf(san_opts, bufSz, "%s:%s:%s:%s%s/%s:%s%s/%s", kMSAN_OPTS, msan_reports_flag, |
| kSAN_COV_OPTS, kSANCOVDIR, hfuzz->workDir, _HF_SANCOV_DIR, kSANLOGDIR, |
| hfuzz->workDir, kLOGPREFIX); |
| #else |
| snprintf(san_opts, bufSz, "%s:%s:%s:%s%s/%s", kMSAN_OPTS, msan_reports_flag, |
| kSAN_COV_OPTS, kSANCOVDIR, hfuzz->workDir, _HF_SANCOV_DIR); |
| #endif |
| } else { |
| snprintf(san_opts, bufSz, "%s:%s:%s%s/%s", kMSAN_OPTS, msan_reports_flag, kSANLOGDIR, |
| hfuzz->workDir, kLOGPREFIX); |
| } |
| |
| flagsSz = strlen(san_opts) + 1; |
| hfuzz->sanOpts.msanOpts = util_Calloc(flagsSz); |
| memcpy(hfuzz->sanOpts.msanOpts, san_opts, flagsSz); |
| LOG_D("MSAN_OPTIONS=%s", hfuzz->sanOpts.msanOpts); |
| |
| return true; |
| } |
| |
| bool sancov_prepareExecve(honggfuzz_t * hfuzz) |
| { |
| /* Address Sanitizer (ASan) */ |
| if (hfuzz->sanOpts.asanOpts) { |
| if (setenv("ASAN_OPTIONS", hfuzz->sanOpts.asanOpts, 1) == -1) { |
| PLOG_E("setenv(ASAN_OPTIONS) failed"); |
| return false; |
| } |
| } |
| |
| /* Memory Sanitizer (MSan) */ |
| if (hfuzz->sanOpts.msanOpts) { |
| if (setenv("MSAN_OPTIONS", hfuzz->sanOpts.msanOpts, 1) == -1) { |
| PLOG_E("setenv(MSAN_OPTIONS) failed"); |
| return false; |
| } |
| } |
| |
| /* Undefined Behavior Sanitizer (UBSan) */ |
| if (hfuzz->sanOpts.ubsanOpts) { |
| if (setenv("UBSAN_OPTIONS", hfuzz->sanOpts.ubsanOpts, 1) == -1) { |
| PLOG_E("setenv(UBSAN_OPTIONS) failed"); |
| return false; |
| } |
| } |
| |
| return true; |
| } |