1 /*
2  *
3  * honggfuzz - sanitizer coverage feedback parsing
4  * -----------------------------------------------
5  *
6  * Licensed under the Apache License, Version 2.0 (the "License"); you may
7  * not use this file except in compliance with the License. You may obtain
8  * a copy of the License at
9  *
10  * http://www.apache.org/licenses/LICENSE-2.0
11  *
12  * Unless required by applicable law or agreed to in writing, software
13  * distributed under the License is distributed on an "AS IS" BASIS,
14  * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or
15  * implied. See the License for the specific language governing
16  * permissions and limitations under the License.
17  *
18  */
19 
20 /*
21  * Clang sanitizer coverage (sancov) data parsing functions. Supported methods:
22  * - raw unified data (preferred method)
23  * - individual data per executable/DSO (not preferred since lots of data lost if instrumented
24  *   code exits abnormally or with sanitizer unhandled signal (common in Android OS)
25  *
26  * For raw-unpack method a global (shared across workers) Trie is created for the chosen
27  * initial seed and maintained until seed is replaced. Trie nodes store the loaded (as exposed
28  * from *.sancov.map file) execs/DSOs from target application using the map name as key. Trie node
29  * data struct (trieData_t) maintains information for each instrumented map including a bitmap with
30  * all hit relative BB addresses (realBBAddr - baseAddr to circumvent ASLR). Map's bitmap is updated while
31  * new areas on target application are discovered based on absolute elitism implemented at
32  * fuzz_sanCovFeedback().
33  *
34  * For individual data files a pid (fuzzer's thread or remote process) based filename search is performed
35  * to identify all files belonging to examined execution. This method doesn't implement yet bitmap runtime
36  * data to detect newly discovered areas. It's mainly used so far as a comparison metric for raw-unpack method
37  * and stability check for sancov experimental features such as coverage counters:
38  * http://clang.llvm.org/docs/SanitizerCoverage.html
39  */
40 
41 #include "common.h"
42 #include "sancov.h"
43 
44 #include <ctype.h>
45 #include <dirent.h>
46 #include <inttypes.h>
47 #include <stdio.h>
48 #include <stdlib.h>
49 #include <string.h>
50 #include <sys/mman.h>
51 #include <sys/stat.h>
52 #include <sys/types.h>
53 
54 #include "files.h"
55 #include "log.h"
56 #include "util.h"
57 
58 /* sancov files magic values */
59 #define kMagic32 0xC0BFFFFFFFFFFF32
60 #define kMagic64 0xC0BFFFFFFFFFFF64
61 
62 /* Stringify */
63 #define XSTR(x)         #x
64 #define STR(x)          XSTR(x)
65 
66 /* Common sanitizer flags */
67 #if _HF_MONITOR_SIGABRT
68 #define ABORT_FLAG        "abort_on_error=1"
69 #else
70 #define ABORT_FLAG        "abort_on_error=0"
71 #endif
72 
73 #if defined(__ANDROID__)
74 /*
75  * symbolize: Disable symbolication since it changes logs (which are parsed) format
76  * start_deactivated: Enable on Android to reduce memory usage (useful when not all
77  *                    target's DSOs are compiled with sanitizer enabled
78  * abort_on_error: Disable for platforms where SIGABRT is not monitored
79  */
80 #define kSAN_COMMON_ARCH    "symbolize=0:"ABORT_FLAG":start_deactivated=1"
81 #else
82 #define kSAN_COMMON_ARCH    "symbolize=0:"ABORT_FLAG
83 #endif
84 
85 /* Sanitizer specific flags (set 'abort_on_error has priority over exitcode') */
86 #define kASAN_OPTS          "allow_user_segv_handler=1:"\
87                             "handle_segv=0:"\
88                             "allocator_may_return_null=1:"\
89                             kSAN_COMMON_ARCH":exitcode=" STR(HF_ASAN_EXIT_CODE)
90 
91 #define kUBSAN_OPTS         kSAN_COMMON_ARCH":exitcode=" STR(HF_UBSAN_EXIT_CODE)
92 
93 #define kMSAN_OPTS          "exit_code=" STR(HF_MSAN_EXIT_CODE) ":"\
94                             "wrap_signals=0:print_stats=1"
95 
96 /* 'log_path' ouput directory for sanitizer reports */
97 #define kSANLOGDIR          "log_path="
98 
99 /* 'coverage_dir' output directory for coverage data files is set dynamically */
100 #define kSANCOVDIR          "coverage_dir="
101 
102 /*
103  * If the program ends with a signal that ASan does not handle (or can not
104  * handle at all, like SIGKILL), coverage data will be lost. This is a big
105  * problem on Android, where SIGKILL is a normal way of evicting applications
106  * from memory. With 'coverage_direct=1' coverage data is written to a
107  * memory-mapped file as soon as it collected. Non-Android targets can disable
108  * coverage direct when more coverage data collection methods are implemented.
109  */
110 #if defined(__ANDROID__)
111 #define kSAN_COV_OPTS  "coverage=1:coverage_direct=1"
112 #else
113 #define kSAN_COV_OPTS  "coverage=1:coverage_direct=1"
114 #endif
115 
116 /*
117  * bitmap implementation
118  */
sancov_newBitmap(uint32_t capacity)119 static bitmap_t *sancov_newBitmap(uint32_t capacity)
120 {
121     bitmap_t *pBM = malloc(sizeof(bitmap_t));
122     if (pBM == NULL) {
123         PLOG_E("malloc(%zu) failed", sizeof(bitmap_t));
124         return NULL;
125     }
126     pBM->capacity = capacity;
127     pBM->nChunks = (capacity + 31) / 32;
128     pBM->pChunks = malloc(pBM->nChunks * sizeof(uint32_t));
129     if (pBM->pChunks == NULL) {
130         PLOG_E("malloc(%zu) failed", pBM->nChunks * sizeof(uint32_t));
131         return NULL;
132     }
133     memset(pBM->pChunks, 0, pBM->nChunks * sizeof(uint32_t));
134     return pBM;
135 }
136 
sancov_queryBitmap(bitmap_t * pBM,uint32_t index)137 static inline bool sancov_queryBitmap(bitmap_t * pBM, uint32_t index)
138 {
139     if (index > pBM->capacity) {
140         LOG_E("bitmap overflow (%u)", index);
141         return false;
142     }
143     if (pBM->pChunks[index / 32] & (1 << (index % 32))) {
144         return true;
145     }
146     return false;
147 }
148 
sancov_setBitmap(bitmap_t * pBM,uint32_t index)149 static inline void sancov_setBitmap(bitmap_t * pBM, uint32_t index)
150 {
151     /* This will be removed. So far checks only to verify accepted ranges. */
152     if (index >= pBM->capacity) {
153         LOG_E("Out of range index (%u > %u)", index, pBM->capacity);
154     }
155     pBM->pChunks[index / 32] |= (1 << (index % 32));
156 }
157 
sancov_destroyBitmap(bitmap_t * pBM)158 static inline void sancov_destroyBitmap(bitmap_t * pBM)
159 {
160     free(pBM->pChunks);
161     free(pBM);
162 }
163 
164 /*
165  * Trie implementation
166  */
sancov_trieCreateNode(char key)167 static node_t *sancov_trieCreateNode(char key)
168 {
169     node_t *node = (node_t *) malloc(sizeof(node_t));
170     if (node == NULL) {
171         PLOG_E("malloc(%zu) failed", sizeof(node_t));
172         return node;
173     }
174     node->key = key;
175     node->next = NULL;
176     node->children = NULL;
177     node->parent = NULL;
178     node->prev = NULL;
179 
180     /* Zero init node's data struct */
181     memset(&node->data, 0, sizeof(trieData_t));
182     return node;
183 }
184 
sancov_trieSearch(node_t * root,const char * key)185 static node_t *sancov_trieSearch(node_t * root, const char *key)
186 {
187     node_t *pNodeLevel = root, *pNodePtr = NULL;
188     int nodeLevelId = 0;
189     while (1) {
190         node_t *pNodeFound = NULL, *pCurNode = NULL;
191         for (pCurNode = pNodeLevel; pCurNode != NULL; pCurNode = pCurNode->next) {
192             if (pCurNode->key == *key) {
193                 pNodeFound = pCurNode;
194                 nodeLevelId++;
195                 break;
196             }
197         }
198         if (pNodeFound == NULL) {
199             return NULL;
200         }
201         if (*key == '\0') {
202             pNodePtr = pCurNode;
203             return pNodePtr;
204         }
205         pNodeLevel = pNodeFound->children;
206         key++;
207     }
208 }
209 
sancov_trieAdd(node_t ** root,const char * key)210 static void sancov_trieAdd(node_t ** root, const char *key)
211 {
212     if (*root == NULL) {
213         LOG_E("Invalid Trie (NULL root node)");
214         return;
215     }
216 
217     /* Traverse Trie */
218     node_t *pTravNode = (*root)->children;
219     if (pTravNode == NULL) {
220         /* First node */
221         for (pTravNode = *root; *key != '\0'; pTravNode = pTravNode->children) {
222             pTravNode->children = sancov_trieCreateNode(*key);
223             pTravNode->children->parent = pTravNode;
224             key++;
225         }
226         pTravNode->children = sancov_trieCreateNode('\0');
227         pTravNode->children->parent = pTravNode;
228         return;
229     }
230 
231     while (*key != '\0') {
232         if (*key == pTravNode->key) {
233             key++;
234             pTravNode = pTravNode->children;
235         } else {
236             break;
237         }
238     }
239     while (pTravNode->next) {
240         if (*key == pTravNode->next->key) {
241             key++;
242             sancov_trieAdd(&(pTravNode->next), key);
243             return;
244         }
245         pTravNode = pTravNode->next;
246     }
247     if (*key) {
248         pTravNode->next = sancov_trieCreateNode(*key);
249     } else {
250         pTravNode->next = sancov_trieCreateNode(*key);
251     }
252     pTravNode->next->parent = pTravNode->parent;
253     pTravNode->next->prev = pTravNode;
254     if (!*key) {
255         return;
256     }
257     key++;
258     for (pTravNode = pTravNode->next; *key; pTravNode = pTravNode->children) {
259         pTravNode->children = sancov_trieCreateNode(*key);
260         pTravNode->children->parent = pTravNode;
261         key++;
262     }
263     pTravNode->children = sancov_trieCreateNode('\0');
264     pTravNode->children->parent = pTravNode;
265 
266     return;
267 }
268 
sancov_trieFreeNode(node_t * node)269 static inline void sancov_trieFreeNode(node_t * node)
270 {
271     /* First destroy bitmap heap buffers allocated for instrumented maps */
272     if (node->data.pBM) {
273         sancov_destroyBitmap(node->data.pBM);
274     }
275     free(node);
276 }
277 
sancov_trieCreate(node_t ** root)278 static inline void sancov_trieCreate(node_t ** root)
279 {
280     /* Create root node if new Trie */
281     *root = sancov_trieCreateNode('\0');
282 }
283 
284 /* Destroy Trie - iterate nodes and free memory */
sancov_trieDestroy(node_t * root)285 UNUSED static void sancov_trieDestroy(node_t * root)
286 {
287     node_t *pNode = root;
288     node_t *pNodeTmp = root;
289     while (pNode) {
290         while (pNode->children) {
291             pNode = pNode->children;
292         }
293 
294         if (pNode->prev && pNode->next) {
295             pNodeTmp = pNode;
296             pNode->next->prev = pNode->prev;
297             pNode->prev->next = pNode->next;
298             sancov_trieFreeNode(pNodeTmp);
299         } else if (pNode->prev && !pNode->next) {
300             pNodeTmp = pNode;
301             pNode->prev->next = NULL;
302             sancov_trieFreeNode(pNodeTmp);
303         } else if (!pNode->prev && pNode->next) {
304             pNodeTmp = pNode;
305             pNode->parent->children = pNode->next;
306             pNode->next->prev = NULL;
307             pNode = pNode->next;
308             sancov_trieFreeNode(pNodeTmp);
309         } else {
310             pNodeTmp = pNode;
311             if (pNode->parent == NULL) {
312                 /* Root */
313                 sancov_trieFreeNode(pNodeTmp);
314                 return;
315             }
316             pNode = pNode->parent;
317             pNode->children = NULL;
318             sancov_trieFreeNode(pNodeTmp);
319         }
320     }
321 }
322 
323 /* Modified interpolation search algorithm to search for nearest address fit */
sancov_interpSearch(uint64_t * buf,uint64_t size,uint64_t key)324 static inline uint64_t sancov_interpSearch(uint64_t * buf, uint64_t size, uint64_t key)
325 {
326     /* Avoid extra checks assuming caller always provides non-zero array size */
327     uint64_t low = 0;
328     uint64_t high = size - 1;
329     uint64_t mid = high;
330 
331     while (buf[high] != buf[low] && key >= buf[low] && key <= buf[high]) {
332         mid = low + (key - buf[low]) * ((high - low) / (buf[high] - buf[low]));
333         if (buf[mid] < key) {
334             low = mid + 1;
335         } else if (key < buf[mid]) {
336             high = mid - 1;
337         } else {
338             return mid;
339         }
340     }
341     return mid;
342 }
343 
344 /* qsort struct comparison function (memMap_t struct start addr field) */
sancov_qsortCmp(const void * a,const void * b)345 static int sancov_qsortCmp(const void *a, const void *b)
346 {
347     memMap_t *pA = (memMap_t *) a;
348     memMap_t *pB = (memMap_t *) b;
349     if (pA->start < pB->start) {
350         return -1;
351     } else if (pA->start > pB->start) {
352         return 1;
353     } else {
354         /* Normally we should never hit that case */
355         LOG_W("Duplicate map start addr detected");
356         return 0;
357     }
358 
359 }
360 
sancov_sanCovParseRaw(honggfuzz_t * hfuzz,fuzzer_t * fuzzer)361 static bool sancov_sanCovParseRaw(honggfuzz_t * hfuzz, fuzzer_t * fuzzer)
362 {
363     int dataFd = -1;
364     uint8_t *dataBuf = NULL;
365     off_t dataFileSz = 0, pos = 0;
366     bool is32bit = true, ret = false;
367     char covFile[PATH_MAX] = { 0 };
368     pid_t targetPid = (hfuzz->pid > 0) ? hfuzz->pid : fuzzer->pid;
369 
370     /* Fuzzer local runtime data structs - need free() before exit */
371     uint64_t *startMapsIndex = NULL;
372     memMap_t *mapsBuf = NULL;
373 
374     /* Local counters */
375     uint64_t nBBs = 0;          /* Total BB hits found in raw file */
376     uint64_t nZeroBBs = 0;      /* Number of non-hit instrumented BBs */
377     uint64_t mapsNum = 0;       /* Total number of entries in map file */
378     uint64_t noCovMapsNum = 0;  /* Loaded DSOs not compiled with coverage */
379 
380     /* File line-by-line read help buffers */
381     char *pLine = NULL;
382     size_t lineSz = 0;
383 
384     /* Coverage data analysis starts by parsing map file listing */
385     snprintf(covFile, sizeof(covFile), "%s/%s/%d.sancov.map", hfuzz->workDir, _HF_SANCOV_DIR,
386              targetPid);
387     if (!files_exists(covFile)) {
388         LOG_D("sancov map file not found");
389         return false;
390     }
391     FILE *fCovMap = fopen(covFile, "rb");
392     if (fCovMap == NULL) {
393         PLOG_E("Couldn't open '%s' - R/O mode", covFile);
394         goto bail;
395     }
396 
397     /* First line contains PC length (32/64-bit) */
398     if (getline(&pLine, &lineSz, fCovMap) == -1) {
399         LOG_E("Invalid map file '%s'", covFile);
400         fclose(fCovMap);
401         goto bail;
402     }
403     int pcLen = atoi(pLine);
404     if (pcLen == 32) {
405         is32bit = true;
406     } else if (pcLen == 64) {
407         is32bit = false;
408     } else {
409         LOG_E("Invalid PC length (%d) in map file '%s'", pcLen, covFile);
410     }
411 
412     /* See if #maps is available from previous run to avoid realloc inside loop */
413     uint64_t prevMapsNum = __sync_fetch_and_add(&hfuzz->sanCovCnts.dsoCnt, 0UL);
414     if (prevMapsNum > 0) {
415         if ((mapsBuf = malloc(prevMapsNum * sizeof(memMap_t))) == NULL) {
416             PLOG_E("malloc failed (sz=%" PRIu64 ")", prevMapsNum * sizeof(memMap_t));
417             /* This will be picked-up later from realloc branch */
418             prevMapsNum = 0;
419         }
420     }
421 
422     /* Iterate map entries */
423     for (;;) {
424         if (getline(&pLine, &lineSz, fCovMap) == -1) {
425             break;
426         }
427 
428         /* Trim trailing whitespaces, not sure if needed copied from upstream sancov.py */
429         char *lineEnd = pLine + strlen(pLine) - 1;
430         while (lineEnd > pLine && isspace((int)*lineEnd)) {
431             lineEnd--;
432         }
433         *(lineEnd + 1) = 0;
434 
435         /*
436          * Each line has following format:
437          * Start    End      Base     bin/DSO name
438          * b5843000 b584e6ac b5843000 liblog.so
439          */
440         memMap_t mapData = {.start = 0 };
441         char *savePtr = NULL;
442         mapData.start = strtoull(strtok_r(pLine, " ", &savePtr), NULL, 16);
443         mapData.end = strtoull(strtok_r(NULL, " ", &savePtr), NULL, 16);
444         mapData.base = strtoull(strtok_r(NULL, " ", &savePtr), NULL, 16);
445         char *mapName = strtok_r(NULL, " ", &savePtr);
446         memcpy(mapData.mapName, mapName, strlen(mapName));
447 
448         /* Interaction with global Trie should mutex wrap to avoid threads races */
449         {
450             MX_LOCK(&hfuzz->sanCov_mutex);
451             DEFER(MX_UNLOCK(&hfuzz->sanCov_mutex));
452             /* Add entry to Trie with zero data if not already */
453             if (!sancov_trieSearch(hfuzz->covMetadata->children, mapData.mapName)) {
454                 sancov_trieAdd(&hfuzz->covMetadata, mapData.mapName);
455             }
456         }
457 
458         /* If not DSO number history (first run) or new DSO loaded, realloc local maps metadata buf */
459         if (prevMapsNum == 0 || prevMapsNum < mapsNum) {
460             if ((mapsBuf = realloc(mapsBuf, (size_t) (mapsNum + 1) * sizeof(memMap_t))) == NULL) {
461                 PLOG_E("realloc failed (sz=%" PRIu64 ")", (mapsNum + 1) * sizeof(memMap_t));
462                 goto bail;
463             }
464         }
465 
466         /* Add entry to local maps metadata array */
467         memcpy(&mapsBuf[mapsNum], &mapData, sizeof(memMap_t));
468 
469         /* Increase loaded maps counter (includes non-instrumented DSOs too) */
470         mapsNum++;
471     }
472 
473     /* Delete .sancov.map file */
474     fclose(fCovMap);
475     if (hfuzz->pid == 0) {
476         unlink(covFile);
477     }
478 
479     /* Create a quick index array with maps start addresses */
480     startMapsIndex = malloc(mapsNum * sizeof(uint64_t));
481     if (startMapsIndex == NULL) {
482         PLOG_E("malloc failed (sz=%" PRIu64 ")", mapsNum * sizeof(uint64_t));
483         goto bail;
484     }
485 
486     /* Sort quick maps index */
487     qsort(mapsBuf, mapsNum, sizeof(memMap_t), sancov_qsortCmp);
488     for (size_t i = 0; i < mapsNum; i++) {
489         startMapsIndex[i] = mapsBuf[i].start;
490     }
491 
492     /* mmap() .sancov.raw file */
493     snprintf(covFile, sizeof(covFile), "%s/%s/%d.sancov.raw", hfuzz->workDir, _HF_SANCOV_DIR,
494              targetPid);
495     dataBuf = files_mapFile(covFile, &dataFileSz, &dataFd, false);
496     if (dataBuf == NULL) {
497         LOG_E("Couldn't open and map '%s' in R/O mode", covFile);
498         goto bail;
499     }
500 
501     /*
502      * Avoid cost of size checks inside raw data read loop by defining the read function
503      * & pivot size based on PC length.
504      */
505     uint64_t(*pReadRawBBAddrFunc) (const uint8_t *) = NULL;
506     uint8_t pivot = 0;
507     if (is32bit) {
508         pReadRawBBAddrFunc = &util_getUINT32;
509         pivot = 4;
510     } else {
511         pReadRawBBAddrFunc = &util_getUINT64;
512         pivot = 8;
513     }
514 
515     /*
516      * Take advantage of data locality (next processed addr is very likely to belong
517      * to same map) to avoid Trie node search for each read entry.
518      */
519     node_t *curMap = NULL;
520     uint64_t prevIndex = 0;
521 
522     /* Iterate over data buffer containing list of hit BB addresses */
523     while (pos < dataFileSz) {
524         uint64_t bbAddr = pReadRawBBAddrFunc(dataBuf + pos);
525         pos += pivot;
526         /* Don't bother for zero BB addr (inserted checks without hit) */
527         if (bbAddr == 0x0) {
528             nZeroBBs++;
529             continue;
530         } else {
531             /* Find best hit based on start addr & verify range for errors */
532             uint64_t bestFit = sancov_interpSearch(startMapsIndex, mapsNum, bbAddr);
533             if (bbAddr >= mapsBuf[bestFit].start && bbAddr < mapsBuf[bestFit].end) {
534                 /* Increase exe/DSO total BB counter */
535                 mapsBuf[bestFit].bbCnt++;
536 
537                 /* Update current Trie node if map changed */
538                 if (curMap == NULL || (prevIndex != bestFit)) {
539                     prevIndex = bestFit;
540 
541                     /* Interaction with global Trie should mutex wrap to avoid threads races */
542                     {
543                         MX_LOCK(&hfuzz->sanCov_mutex);
544                         DEFER(MX_UNLOCK(&hfuzz->sanCov_mutex));
545                         curMap =
546                             sancov_trieSearch(hfuzz->covMetadata->children,
547                                               mapsBuf[bestFit].mapName);
548                         if (curMap == NULL) {
549                             LOG_E("Corrupted Trie - '%s' not found", mapsBuf[bestFit].mapName);
550                             continue;
551                         }
552 
553                         /* Maintain bitmaps only for exec/DSOs with coverage enabled - allocate on first use */
554                         if (curMap->data.pBM == NULL) {
555                             LOG_D("Allocating bitmap for map '%s'", mapsBuf[bestFit].mapName);
556                             curMap->data.pBM = sancov_newBitmap(_HF_BITMAP_SIZE);
557 
558                             /*
559                              * If bitmap allocation failed, unset cached Trie node ptr
560                              * to execute this selection branch again.
561                              */
562                             if (curMap->data.pBM == NULL) {
563                                 curMap = NULL;
564                                 continue;
565                             }
566                         }
567                     }
568                 }
569 
570                 /* If new relative BB addr update DSO's bitmap */
571                 uint32_t relAddr = (uint32_t) (bbAddr - mapsBuf[bestFit].base);
572                 if (!sancov_queryBitmap(curMap->data.pBM, relAddr)) {
573 
574                     /* Interaction with global Trie should mutex wrap to avoid threads races */
575                     {
576                         MX_LOCK(&hfuzz->sanCov_mutex);
577                         DEFER(MX_UNLOCK(&hfuzz->sanCov_mutex));
578                         sancov_setBitmap(curMap->data.pBM, relAddr);
579                     }
580 
581                     /* Also increase new BBs counter at worker's thread runtime data */
582                     mapsBuf[bestFit].newBBCnt++;
583                 }
584             } else {
585                 /*
586                  * Normally this should never get executed. If hit, sanitizer
587                  * coverage data collection come across some kind of bug.
588                  */
589                 LOG_E("Invalid BB addr (%#" PRIx64 ") at offset %" PRId64, bbAddr, (uint64_t) pos);
590             }
591         }
592         nBBs++;
593     }
594 
595     /* Finally iterate over all instrumented maps to sum-up the number of newly met BB addresses */
596     for (uint64_t i = 0; i < mapsNum; i++) {
597         if (mapsBuf[i].bbCnt > 0) {
598             fuzzer->sanCovCnts.newBBCnt += mapsBuf[i].newBBCnt;
599         } else {
600             noCovMapsNum++;
601         }
602     }
603 
604     /* Successful parsing - update fuzzer worker's counters */
605     fuzzer->sanCovCnts.hitBBCnt = nBBs;
606     fuzzer->sanCovCnts.totalBBCnt = nBBs + nZeroBBs;
607     fuzzer->sanCovCnts.dsoCnt = mapsNum;
608     fuzzer->sanCovCnts.iDsoCnt = mapsNum - noCovMapsNum;        /* Instrumented DSOs */
609     ret = true;
610 
611  bail:
612     if (hfuzz->pid == 0) {
613         unlink(covFile);
614     }
615     if (dataBuf) {
616         munmap(dataBuf, dataFileSz);
617     }
618     if (dataFd != -1) {
619         close(dataFd);
620     }
621     if (mapsBuf) {
622         free(mapsBuf);
623     }
624     if (startMapsIndex) {
625         free(startMapsIndex);
626     }
627     if (pLine) {
628         free(pLine);
629     }
630     return ret;
631 }
632 
sancov_sanCovParse(honggfuzz_t * hfuzz,fuzzer_t * fuzzer)633 static bool sancov_sanCovParse(honggfuzz_t * hfuzz, fuzzer_t * fuzzer)
634 {
635     int dataFd = -1;
636     uint8_t *dataBuf = NULL;
637     off_t dataFileSz = 0, pos = 0;
638     bool is32bit = true;
639     char covFile[PATH_MAX] = { 0 };
640     DIR *pSanCovDir = NULL;
641     bool ret = false;
642     pid_t targetPid = (hfuzz->pid > 0) ? hfuzz->pid : fuzzer->pid;
643 
644     snprintf(covFile, sizeof(covFile), "%s/%s/%s.%d.sancov", hfuzz->workDir, _HF_SANCOV_DIR,
645              files_basename(hfuzz->cmdline[0]), targetPid);
646     if (!files_exists(covFile)) {
647         LOG_D("Target sancov file not found");
648         return false;
649     }
650 
651     /* Local cache file suffix to use for file search of target pid data */
652     char pidFSuffix[13] = { 0 };
653     snprintf(pidFSuffix, sizeof(pidFSuffix), "%d.sancov", targetPid);
654 
655     /* Total BBs counter summarizes all DSOs */
656     uint64_t nBBs = 0;
657 
658     /* Iterate sancov dir for files generated against target pid */
659     snprintf(covFile, sizeof(covFile), "%s/%s", hfuzz->workDir, _HF_SANCOV_DIR);
660     pSanCovDir = opendir(covFile);
661     struct dirent *pDir = NULL;
662     while ((pDir = readdir(pSanCovDir)) != NULL) {
663         /* Parse files with target's pid */
664         if (strstr(pDir->d_name, pidFSuffix)) {
665             snprintf(covFile, sizeof(covFile), "%s/%s/%s", hfuzz->workDir, _HF_SANCOV_DIR,
666                      pDir->d_name);
667             dataBuf = files_mapFile(covFile, &dataFileSz, &dataFd, false);
668             if (dataBuf == NULL) {
669                 LOG_E("Couldn't open and map '%s' in R/O mode", covFile);
670                 goto bail;
671             }
672 
673             if (dataFileSz < 8) {
674                 LOG_E("Coverage data file too short");
675                 goto bail;
676             }
677 
678             /* Check magic values & derive PC length */
679             uint64_t magic = util_getUINT64(dataBuf);
680             if (magic == kMagic32) {
681                 is32bit = true;
682             } else if (magic == kMagic64) {
683                 is32bit = false;
684             } else {
685                 LOG_E("Invalid coverage data file");
686                 goto bail;
687             }
688             pos += 8;
689 
690             /*
691              * Avoid cost of size checks inside raw data read loop by defining the read function
692              * & pivot size based on PC length.
693              */
694             uint64_t(*pReadRawBBAddrFunc) (const uint8_t *) = NULL;
695             uint8_t pivot = 0;
696             if (is32bit) {
697                 pReadRawBBAddrFunc = &util_getUINT32;
698                 pivot = 4;
699             } else {
700                 pReadRawBBAddrFunc = &util_getUINT64;
701                 pivot = 8;
702             }
703 
704             while (pos < dataFileSz) {
705                 uint32_t bbAddr = pReadRawBBAddrFunc(dataBuf + pos);
706                 pos += pivot;
707                 if (bbAddr == 0x0) {
708                     continue;
709                 }
710                 nBBs++;
711             }
712         }
713     }
714 
715     /* Successful parsing - update fuzzer worker counters */
716     fuzzer->sanCovCnts.hitBBCnt = nBBs;
717     ret = true;
718 
719  bail:
720     if (hfuzz->pid == 0) {
721         unlink(covFile);
722     }
723     if (dataBuf) {
724         munmap(dataBuf, dataFileSz);
725     }
726     if (dataFd != -1) {
727         close(dataFd);
728     }
729     if (pSanCovDir) {
730         closedir(pSanCovDir);
731     }
732     return ret;
733 }
734 
735 /*
736  * Sanitizer coverage data are stored in FS can be parsed via two methods:
737  * raw unpack & separate bin/DSO sancov file. Separate bin/DSO sancov file
738  * method is usually avoided since coverage data are lost if sanitizer unhandled
739  * signal. Additionally, the FS I/O overhead is bigger compared to raw unpack
740  * method which uses runtime data structures.
741  *
742  * Enabled methods are controlled from sanitizer flags in arch.c
743  */
sancov_Analyze(honggfuzz_t * hfuzz,fuzzer_t * fuzzer)744 void sancov_Analyze(honggfuzz_t * hfuzz, fuzzer_t * fuzzer)
745 {
746     if (!hfuzz->useSanCov) {
747         return;
748     }
749 
750     /*
751      * For now supported methods are implemented in fail-over nature. This will
752      * change in the future when best method is concluded.
753      */
754     if (sancov_sanCovParseRaw(hfuzz, fuzzer) == false) {
755         sancov_sanCovParse(hfuzz, fuzzer);
756     }
757 }
758 
sancov_Init(honggfuzz_t * hfuzz)759 bool sancov_Init(honggfuzz_t * hfuzz)
760 {
761     if (hfuzz->useSanCov == false) {
762         return true;
763     }
764 
765     sancov_trieCreate(&hfuzz->covMetadata);
766 
767     if (hfuzz->pid > 0) {
768         return true;
769     }
770 
771     char sanCovOutDir[PATH_MAX] = { 0 };
772     snprintf(sanCovOutDir, sizeof(sanCovOutDir), "%s/%s", hfuzz->workDir, _HF_SANCOV_DIR);
773     if (!files_exists(sanCovOutDir)) {
774         if (mkdir(sanCovOutDir, S_IRWXU | S_IXGRP | S_IXOTH) != 0) {
775             PLOG_E("mkdir() '%s' failed", sanCovOutDir);
776         }
777     }
778 
779     /* Set sanitizer flags once to avoid performance overhead per worker spawn */
780     size_t flagsSz = 0;
781     size_t bufSz = sizeof(kASAN_OPTS) + (2 * PATH_MAX); // Larger constant + 2 dynamic paths
782     char *san_opts = malloc(bufSz);
783     if (san_opts == NULL) {
784         PLOG_E("malloc(%zu) failed", bufSz);
785         return false;
786     }
787     DEFER(free(san_opts));
788 
789     /* AddressSanitizer (ASan) */
790     memset(san_opts, 0, bufSz);
791     if (hfuzz->useSanCov) {
792 #if !_HF_MONITOR_SIGABRT
793         /* Write reports in FS only if abort_on_error is disabled */
794         snprintf(san_opts, bufSz, "%s:%s:%s%s/%s:%s%s/%s", kASAN_OPTS, kSAN_COV_OPTS,
795                  kSANCOVDIR, hfuzz->workDir, _HF_SANCOV_DIR, kSANLOGDIR, hfuzz->workDir,
796                  kLOGPREFIX);
797 #else
798         snprintf(san_opts, bufSz, "%s:%s:%s%s/%s", kASAN_OPTS, kSAN_COV_OPTS,
799                  kSANCOVDIR, hfuzz->workDir, _HF_SANCOV_DIR);
800 #endif
801     } else {
802         snprintf(san_opts, bufSz, "%s:%s%s/%s", kASAN_OPTS, kSANLOGDIR, hfuzz->workDir, kLOGPREFIX);
803     }
804 
805     flagsSz = strlen(san_opts) + 1;
806     hfuzz->sanOpts.asanOpts = malloc(flagsSz);
807     if (hfuzz->sanOpts.asanOpts == NULL) {
808         PLOG_E("malloc(%zu) failed", flagsSz);
809         return false;
810     }
811     memset(hfuzz->sanOpts.asanOpts, 0, flagsSz);
812     memcpy(hfuzz->sanOpts.asanOpts, san_opts, flagsSz);
813     LOG_D("ASAN_OPTIONS=%s", hfuzz->sanOpts.asanOpts);
814 
815     /* Undefined Behavior (UBSan) */
816     memset(san_opts, 0, bufSz);
817     if (hfuzz->useSanCov) {
818 #if !_HF_MONITOR_SIGABRT
819         /* Write reports in FS only if abort_on_error is disabled */
820         snprintf(san_opts, bufSz, "%s:%s:%s%s/%s:%s%s/%s", kUBSAN_OPTS, kSAN_COV_OPTS,
821                  kSANCOVDIR, hfuzz->workDir, _HF_SANCOV_DIR, kSANLOGDIR, hfuzz->workDir,
822                  kLOGPREFIX);
823 #else
824         snprintf(san_opts, bufSz, "%s:%s:%s%s/%s", kUBSAN_OPTS, kSAN_COV_OPTS,
825                  kSANCOVDIR, hfuzz->workDir, _HF_SANCOV_DIR);
826 #endif
827     } else {
828         snprintf(san_opts, bufSz, "%s:%s%s/%s", kUBSAN_OPTS, kSANLOGDIR, hfuzz->workDir,
829                  kLOGPREFIX);
830     }
831 
832     flagsSz = strlen(san_opts) + 1;
833     hfuzz->sanOpts.ubsanOpts = malloc(flagsSz);
834     if (hfuzz->sanOpts.ubsanOpts == NULL) {
835         PLOG_E("malloc(%zu) failed", flagsSz);
836         return false;
837     }
838     memset(hfuzz->sanOpts.ubsanOpts, 0, flagsSz);
839     memcpy(hfuzz->sanOpts.ubsanOpts, san_opts, flagsSz);
840     LOG_D("UBSAN_OPTIONS=%s", hfuzz->sanOpts.ubsanOpts);
841 
842     /* MemorySanitizer (MSan) */
843     memset(san_opts, 0, bufSz);
844     const char *msan_reports_flag = "report_umrs=0";
845     if (hfuzz->msanReportUMRS) {
846         msan_reports_flag = "report_umrs=1";
847     }
848 
849     if (hfuzz->useSanCov) {
850 #if !_HF_MONITOR_SIGABRT
851         /* Write reports in FS only if abort_on_error is disabled */
852         snprintf(san_opts, bufSz, "%s:%s:%s:%s%s/%s:%s%s/%s", kMSAN_OPTS, msan_reports_flag,
853                  kSAN_COV_OPTS, kSANCOVDIR, hfuzz->workDir, _HF_SANCOV_DIR, kSANLOGDIR,
854                  hfuzz->workDir, kLOGPREFIX);
855 #else
856         snprintf(san_opts, bufSz, "%s:%s:%s:%s%s/%s", kMSAN_OPTS, msan_reports_flag,
857                  kSAN_COV_OPTS, kSANCOVDIR, hfuzz->workDir, _HF_SANCOV_DIR);
858 #endif
859     } else {
860         snprintf(san_opts, bufSz, "%s:%s:%s%s/%s", kMSAN_OPTS, msan_reports_flag, kSANLOGDIR,
861                  hfuzz->workDir, kLOGPREFIX);
862     }
863 
864     flagsSz = strlen(san_opts) + 1;
865     hfuzz->sanOpts.msanOpts = malloc(flagsSz);
866     if (hfuzz->sanOpts.msanOpts == NULL) {
867         PLOG_E("malloc(%zu) failed", flagsSz);
868         return false;
869     }
870     memset(hfuzz->sanOpts.msanOpts, 0, flagsSz);
871     memcpy(hfuzz->sanOpts.msanOpts, san_opts, flagsSz);
872     LOG_D("MSAN_OPTIONS=%s", hfuzz->sanOpts.msanOpts);
873 
874     return true;
875 }
876 
sancov_prepareExecve(honggfuzz_t * hfuzz)877 bool sancov_prepareExecve(honggfuzz_t * hfuzz)
878 {
879     /* Address Sanitizer (ASan) */
880     if (hfuzz->sanOpts.asanOpts) {
881         if (setenv("ASAN_OPTIONS", hfuzz->sanOpts.asanOpts, 1) == -1) {
882             PLOG_E("setenv(ASAN_OPTIONS) failed");
883             return false;
884         }
885     }
886 
887     /* Memory Sanitizer (MSan) */
888     if (hfuzz->sanOpts.msanOpts) {
889         if (setenv("MSAN_OPTIONS", hfuzz->sanOpts.msanOpts, 1) == -1) {
890             PLOG_E("setenv(MSAN_OPTIONS) failed");
891             return false;
892         }
893     }
894 
895     /* Undefined Behavior Sanitizer (UBSan) */
896     if (hfuzz->sanOpts.ubsanOpts) {
897         if (setenv("UBSAN_OPTIONS", hfuzz->sanOpts.ubsanOpts, 1) == -1) {
898             PLOG_E("setenv(UBSAN_OPTIONS) failed");
899             return false;
900         }
901     }
902 
903     return true;
904 }
905