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