1 /*
2 *
3 * honggfuzz - fuzzing routines
4 * -----------------------------------------
5 *
6 * Authors: Robert Swiecki <swiecki@google.com>
7 * Felix Gröbert <groebert@google.com>
8 *
9 * Copyright 2010-2018 by Google Inc. All Rights Reserved.
10 *
11 * Licensed under the Apache License, Version 2.0 (the "License"); you may
12 * not use this file except in compliance with the License. You may obtain
13 * a copy of the License at
14 *
15 * http://www.apache.org/licenses/LICENSE-2.0
16 *
17 * Unless required by applicable law or agreed to in writing, software
18 * distributed under the License is distributed on an "AS IS" BASIS,
19 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or
20 * implied. See the License for the specific language governing
21 * permissions and limitations under the License.
22 *
23 */
24
25 #include "fuzz.h"
26
27 #include <errno.h>
28 #include <fcntl.h>
29 #include <inttypes.h>
30 #include <libgen.h>
31 #include <pthread.h>
32 #include <signal.h>
33 #include <stddef.h>
34 #include <stdint.h>
35 #include <stdio.h>
36 #include <stdlib.h>
37 #include <string.h>
38 #include <sys/mman.h>
39 #include <sys/param.h>
40 #include <sys/stat.h>
41 #include <sys/time.h>
42 #include <sys/types.h>
43 #include <time.h>
44 #include <unistd.h>
45
46 #include "arch.h"
47 #include "honggfuzz.h"
48 #include "input.h"
49 #include "libhfcommon/common.h"
50 #include "libhfcommon/files.h"
51 #include "libhfcommon/log.h"
52 #include "libhfcommon/util.h"
53 #include "mangle.h"
54 #include "report.h"
55 #include "sanitizers.h"
56 #include "socketfuzzer.h"
57 #include "subproc.h"
58
59 static time_t termTimeStamp = 0;
60
fuzz_isTerminating(void)61 bool fuzz_isTerminating(void) {
62 if (ATOMIC_GET(termTimeStamp) != 0) {
63 return true;
64 }
65 return false;
66 }
67
fuzz_setTerminating(void)68 void fuzz_setTerminating(void) {
69 if (ATOMIC_GET(termTimeStamp) != 0) {
70 return;
71 }
72 ATOMIC_SET(termTimeStamp, time(NULL));
73 }
74
fuzz_shouldTerminate()75 bool fuzz_shouldTerminate() {
76 if (ATOMIC_GET(termTimeStamp) == 0) {
77 return false;
78 }
79 if ((time(NULL) - ATOMIC_GET(termTimeStamp)) > 5) {
80 return true;
81 }
82 return false;
83 }
84
fuzz_getState(honggfuzz_t * hfuzz)85 static fuzzState_t fuzz_getState(honggfuzz_t* hfuzz) {
86 return ATOMIC_GET(hfuzz->feedback.state);
87 }
88
fuzz_writeCovFile(const char * dir,const uint8_t * data,size_t len)89 static bool fuzz_writeCovFile(const char* dir, const uint8_t* data, size_t len) {
90 char fname[PATH_MAX];
91
92 uint64_t crc64f = util_CRC64(data, len);
93 uint64_t crc64r = util_CRC64Rev(data, len);
94 snprintf(fname, sizeof(fname), "%s/%016" PRIx64 "%016" PRIx64 ".%08" PRIx32 ".honggfuzz.cov",
95 dir, crc64f, crc64r, (uint32_t)len);
96
97 if (files_exists(fname)) {
98 LOG_D("File '%s' already exists in the output corpus directory '%s'", fname, dir);
99 return true;
100 }
101
102 LOG_D("Adding file '%s' to the corpus directory '%s'", fname, dir);
103
104 if (!files_writeBufToFile(fname, data, len, O_WRONLY | O_CREAT | O_EXCL | O_CLOEXEC)) {
105 LOG_W("Couldn't write buffer to file '%s'", fname);
106 return false;
107 }
108
109 return true;
110 }
111
fuzz_addFileToFileQ(honggfuzz_t * hfuzz,const uint8_t * data,size_t len)112 static void fuzz_addFileToFileQ(honggfuzz_t* hfuzz, const uint8_t* data, size_t len) {
113 ATOMIC_SET(hfuzz->timing.lastCovUpdate, time(NULL));
114
115 struct dynfile_t* dynfile = (struct dynfile_t*)util_Malloc(sizeof(struct dynfile_t));
116 dynfile->size = len;
117 dynfile->data = (uint8_t*)util_Malloc(len);
118 memcpy(dynfile->data, data, len);
119
120 MX_SCOPED_RWLOCK_WRITE(&hfuzz->io.dynfileq_mutex);
121 TAILQ_INSERT_TAIL(&hfuzz->io.dynfileq, dynfile, pointers);
122 hfuzz->io.dynfileqCnt++;
123
124 if (hfuzz->socketFuzzer.enabled) {
125 /* Don't add coverage data to files in socketFuzzer mode */
126 return;
127 }
128
129 if (!fuzz_writeCovFile(hfuzz->io.covDirAll, data, len)) {
130 LOG_E("Couldn't save the coverage data to '%s'", hfuzz->io.covDirAll);
131 }
132
133 /* No need to add files to the new coverage dir, if this is just the dry-run phase */
134 if (fuzz_getState(hfuzz) == _HF_STATE_DYNAMIC_DRY_RUN || hfuzz->io.covDirNew == NULL) {
135 return;
136 }
137
138 if (!fuzz_writeCovFile(hfuzz->io.covDirNew, data, len)) {
139 LOG_E("Couldn't save the new coverage data to '%s'", hfuzz->io.covDirNew);
140 }
141 }
142
fuzz_setDynamicMainState(run_t * run)143 static void fuzz_setDynamicMainState(run_t* run) {
144 /* All threads need to indicate willingness to switch to the DYNAMIC_MAIN state. Count them! */
145 static uint32_t cnt = 0;
146 ATOMIC_PRE_INC(cnt);
147
148 static pthread_mutex_t state_mutex = PTHREAD_MUTEX_INITIALIZER;
149 MX_SCOPED_LOCK(&state_mutex);
150
151 if (fuzz_getState(run->global) == _HF_STATE_DYNAMIC_MAIN) {
152 return;
153 }
154
155 for (;;) {
156 /* Check if all threads have already reported in for changing state */
157 if (ATOMIC_GET(cnt) == run->global->threads.threadsMax) {
158 break;
159 }
160 if (fuzz_isTerminating()) {
161 return;
162 }
163 usleep(1000 * 10); /* Check every 10ms */
164 }
165
166 LOG_I("Entering phase 2/2: Dynamic Main");
167 snprintf(run->origFileName, sizeof(run->origFileName), "[DYNAMIC]");
168 ATOMIC_SET(run->global->feedback.state, _HF_STATE_DYNAMIC_MAIN);
169
170 /*
171 * If the initial fuzzing yielded no useful coverage, just add a single 1-byte file to the
172 * dynamic corpus, so the dynamic phase doesn't fail because of lack of useful inputs
173 */
174 if (run->global->io.dynfileqCnt == 0) {
175 const char* single_byte = run->global->cfg.only_printable ? " " : "\0";
176 fuzz_addFileToFileQ(run->global, (const uint8_t*)single_byte, 1U);
177 }
178 }
179
fuzz_perfFeedback(run_t * run)180 static void fuzz_perfFeedback(run_t* run) {
181 if (run->global->feedback.skipFeedbackOnTimeout && run->tmOutSignaled) {
182 return;
183 }
184
185 LOG_D("New file size: %zu, Perf feedback new/cur (instr,branch): %" PRIu64 "/%" PRIu64
186 "/%" PRIu64 "/%" PRIu64 ", BBcnt new/total: %" PRIu64 "/%" PRIu64,
187 run->dynamicFileSz, run->linux.hwCnts.cpuInstrCnt, run->global->linux.hwCnts.cpuInstrCnt,
188 run->linux.hwCnts.cpuBranchCnt, run->global->linux.hwCnts.cpuBranchCnt,
189 run->linux.hwCnts.newBBCnt, run->global->linux.hwCnts.bbCnt);
190
191 MX_SCOPED_LOCK(&run->global->feedback.feedback_mutex);
192
193 uint64_t softCntPc = 0;
194 uint64_t softCntEdge = 0;
195 uint64_t softCntCmp = 0;
196 if (run->global->feedback.bbFd != -1) {
197 softCntPc = ATOMIC_GET(run->global->feedback.feedbackMap->pidFeedbackPc[run->fuzzNo]);
198 ATOMIC_CLEAR(run->global->feedback.feedbackMap->pidFeedbackPc[run->fuzzNo]);
199 softCntEdge = ATOMIC_GET(run->global->feedback.feedbackMap->pidFeedbackEdge[run->fuzzNo]);
200 ATOMIC_CLEAR(run->global->feedback.feedbackMap->pidFeedbackEdge[run->fuzzNo]);
201 softCntCmp = ATOMIC_GET(run->global->feedback.feedbackMap->pidFeedbackCmp[run->fuzzNo]);
202 ATOMIC_CLEAR(run->global->feedback.feedbackMap->pidFeedbackCmp[run->fuzzNo]);
203 }
204
205 int64_t diff0 = run->global->linux.hwCnts.cpuInstrCnt - run->linux.hwCnts.cpuInstrCnt;
206 int64_t diff1 = run->global->linux.hwCnts.cpuBranchCnt - run->linux.hwCnts.cpuBranchCnt;
207
208 /* Any increase in coverage (edge, pc, cmp, hw) counters forces adding input to the corpus */
209 if (run->linux.hwCnts.newBBCnt > 0 || softCntPc > 0 || softCntEdge > 0 || softCntCmp > 0 ||
210 diff0 < 0 || diff1 < 0) {
211 if (diff0 < 0) {
212 run->global->linux.hwCnts.cpuInstrCnt = run->linux.hwCnts.cpuInstrCnt;
213 }
214 if (diff1 < 0) {
215 run->global->linux.hwCnts.cpuBranchCnt = run->linux.hwCnts.cpuBranchCnt;
216 }
217 run->global->linux.hwCnts.bbCnt += run->linux.hwCnts.newBBCnt;
218 run->global->linux.hwCnts.softCntPc += softCntPc;
219 run->global->linux.hwCnts.softCntEdge += softCntEdge;
220 run->global->linux.hwCnts.softCntCmp += softCntCmp;
221
222 LOG_I("Size:%zu (i,b,hw,edge,ip,cmp): %" PRIu64 "/%" PRIu64 "/%" PRIu64 "/%" PRIu64
223 "/%" PRIu64 "/%" PRIu64 ", Tot:%" PRIu64 "/%" PRIu64 "/%" PRIu64 "/%" PRIu64
224 "/%" PRIu64 "/%" PRIu64,
225 run->dynamicFileSz, run->linux.hwCnts.cpuInstrCnt, run->linux.hwCnts.cpuBranchCnt,
226 run->linux.hwCnts.newBBCnt, softCntEdge, softCntPc, softCntCmp,
227 run->global->linux.hwCnts.cpuInstrCnt, run->global->linux.hwCnts.cpuBranchCnt,
228 run->global->linux.hwCnts.bbCnt, run->global->linux.hwCnts.softCntEdge,
229 run->global->linux.hwCnts.softCntPc, run->global->linux.hwCnts.softCntCmp);
230
231 fuzz_addFileToFileQ(run->global, run->dynamicFile, run->dynamicFileSz);
232
233 if (run->global->socketFuzzer.enabled) {
234 LOG_D("SocketFuzzer: fuzz: new BB (perf)");
235 fuzz_notifySocketFuzzerNewCov(run->global);
236 }
237 }
238 }
239
240 /* Return value indicates whether report file should be updated with the current verified crash */
fuzz_runVerifier(run_t * run)241 static bool fuzz_runVerifier(run_t* run) {
242 if (!run->crashFileName[0] || !run->backtrace) {
243 return false;
244 }
245
246 uint64_t backtrace = run->backtrace;
247
248 char origCrashPath[PATH_MAX];
249 snprintf(origCrashPath, sizeof(origCrashPath), "%s", run->crashFileName);
250 /* Workspace is inherited, just append a extra suffix */
251 char verFile[PATH_MAX];
252 snprintf(verFile, sizeof(verFile), "%s.verified", origCrashPath);
253
254 if (files_exists(verFile)) {
255 LOG_D("Crash file to verify '%s' is already verified as '%s'", origCrashPath, verFile);
256 return false;
257 }
258
259 for (int i = 0; i < _HF_VERIFIER_ITER; i++) {
260 LOG_I("Launching verifier for HASH: %" PRIx64 " (iteration: %d out of %d)", run->backtrace,
261 i + 1, _HF_VERIFIER_ITER);
262 run->timeStartedMillis = 0;
263 run->backtrace = 0;
264 run->access = 0;
265 run->exception = 0;
266 run->mainWorker = false;
267
268 if (!subproc_Run(run)) {
269 LOG_F("subproc_Run()");
270 }
271
272 /* If stack hash doesn't match skip name tag and exit */
273 if (run->backtrace != backtrace) {
274 LOG_E("Verifier stack mismatch: (original) %" PRIx64 " != (new) %" PRIx64, backtrace,
275 run->backtrace);
276 run->backtrace = backtrace;
277 return true;
278 }
279
280 LOG_I("Verifier for HASH: %" PRIx64 " (iteration: %d, left: %d). MATCH!", run->backtrace,
281 i + 1, _HF_VERIFIER_ITER - i - 1);
282 }
283
284 /* Copy file with new suffix & remove original copy */
285 int fd = TEMP_FAILURE_RETRY(open(verFile, O_CREAT | O_EXCL | O_WRONLY, 0600));
286 if (fd == -1 && errno == EEXIST) {
287 LOG_I("It seems that '%s' already exists, skipping", verFile);
288 return false;
289 }
290 if (fd == -1) {
291 PLOG_E("Couldn't create '%s'", verFile);
292 return true;
293 }
294 defer {
295 close(fd);
296 };
297 if (!files_writeToFd(fd, run->dynamicFile, run->dynamicFileSz)) {
298 LOG_E("Couldn't save verified file as '%s'", verFile);
299 unlink(verFile);
300 return true;
301 }
302
303 LOG_I("Verified crash for HASH: %" PRIx64 " and saved it as '%s'", backtrace, verFile);
304 ATOMIC_PRE_INC(run->global->cnts.verifiedCrashesCnt);
305
306 return true;
307 }
308
fuzz_fetchInput(run_t * run)309 static bool fuzz_fetchInput(run_t* run) {
310 if (fuzz_getState(run->global) == _HF_STATE_DYNAMIC_DRY_RUN) {
311 run->mutationsPerRun = 0U;
312 if (input_prepareStaticFile(run, /* rewind= */ false)) {
313 return true;
314 }
315 fuzz_setDynamicMainState(run);
316 run->mutationsPerRun = run->global->mutate.mutationsPerRun;
317 }
318
319 if (fuzz_getState(run->global) == _HF_STATE_DYNAMIC_MAIN) {
320 if (run->global->exe.externalCommand) {
321 if (!input_prepareExternalFile(run)) {
322 LOG_E("input_prepareFileExternally() failed");
323 return false;
324 }
325 } else if (!input_prepareDynamicInput(run)) {
326 LOG_E("input_prepareFileDynamically() failed");
327 return false;
328 }
329 }
330
331 if (fuzz_getState(run->global) == _HF_STATE_STATIC) {
332 if (run->global->exe.externalCommand) {
333 if (!input_prepareExternalFile(run)) {
334 LOG_E("input_prepareFileExternally() failed");
335 return false;
336 }
337 } else if (!input_prepareStaticFile(run, true /* rewind */)) {
338 LOG_E("input_prepareFile() failed");
339 return false;
340 }
341 }
342
343 if (run->global->exe.postExternalCommand && !input_postProcessFile(run)) {
344 LOG_E("input_postProcessFile() failed");
345 return false;
346 }
347
348 return true;
349 }
350
fuzz_fuzzLoop(run_t * run)351 static void fuzz_fuzzLoop(run_t* run) {
352 run->timeStartedMillis = 0;
353 run->crashFileName[0] = '\0';
354 run->pc = 0;
355 run->backtrace = 0;
356 run->access = 0;
357 run->exception = 0;
358 run->report[0] = '\0';
359 run->mainWorker = true;
360 run->mutationsPerRun = run->global->mutate.mutationsPerRun;
361 run->dynamicFileSz = 0;
362 run->dynamicFileCopyFd = -1;
363 run->tmOutSignaled = false;
364
365 run->linux.hwCnts.cpuInstrCnt = 0;
366 run->linux.hwCnts.cpuBranchCnt = 0;
367 run->linux.hwCnts.bbCnt = 0;
368 run->linux.hwCnts.newBBCnt = 0;
369
370 if (!fuzz_fetchInput(run)) {
371 LOG_F("Cound't prepare input for fuzzing");
372 }
373 if (!subproc_Run(run)) {
374 LOG_F("Couldn't run fuzzed command");
375 }
376
377 if (run->global->feedback.dynFileMethod != _HF_DYNFILE_NONE) {
378 fuzz_perfFeedback(run);
379 }
380 if (run->global->cfg.useVerifier && !fuzz_runVerifier(run)) {
381 return;
382 }
383 report_Report(run);
384 }
385
fuzz_fuzzLoopSocket(run_t * run)386 static void fuzz_fuzzLoopSocket(run_t* run) {
387 run->pid = 0;
388 run->timeStartedMillis = 0;
389 run->crashFileName[0] = '\0';
390 run->pc = 0;
391 run->backtrace = 0;
392 run->access = 0;
393 run->exception = 0;
394 run->report[0] = '\0';
395 run->mainWorker = true;
396 run->mutationsPerRun = run->global->mutate.mutationsPerRun;
397 run->dynamicFileSz = 0;
398 run->dynamicFileCopyFd = -1;
399 run->tmOutSignaled = false;
400
401 run->linux.hwCnts.cpuInstrCnt = 0;
402 run->linux.hwCnts.cpuBranchCnt = 0;
403 run->linux.hwCnts.bbCnt = 0;
404 run->linux.hwCnts.newBBCnt = 0;
405
406 LOG_I("------------------------------------------------------");
407
408 /* First iteration: Start target
409 Other iterations: re-start target, if necessary
410 subproc_Run() will decide by itself if a restart is necessary, via
411 subproc_New()
412 */
413 LOG_D("------[ 1: subproc_run");
414 if (!subproc_Run(run)) {
415 LOG_W("Couldn't run server");
416 }
417
418 /* Tell the external fuzzer to send data to target
419 The fuzzer will notify us when finished; block until then.
420 */
421 LOG_D("------[ 2: fetch input");
422 if (!fuzz_waitForExternalInput(run)) {
423 /* Fuzzer could not connect to target, and told us to
424 restart it. Do it on the next iteration. */
425 LOG_D("------[ 2.1: Target down, will restart it");
426 return;
427 }
428
429 LOG_D("------[ 3: feedback");
430 if (run->global->feedback.dynFileMethod != _HF_DYNFILE_NONE) {
431 fuzz_perfFeedback(run);
432 }
433 if (run->global->cfg.useVerifier && !fuzz_runVerifier(run)) {
434 return;
435 }
436
437 report_Report(run);
438 }
439
fuzz_threadNew(void * arg)440 static void* fuzz_threadNew(void* arg) {
441 honggfuzz_t* hfuzz = (honggfuzz_t*)arg;
442 unsigned int fuzzNo = ATOMIC_POST_INC(hfuzz->threads.threadsActiveCnt);
443 LOG_I("Launched new fuzzing thread, no. #%" PRId32, fuzzNo);
444
445 run_t run = {
446 .global = hfuzz,
447 .pid = 0,
448 .dynfileqCurrent = NULL,
449 .dynamicFile = NULL,
450 .dynamicFileFd = -1,
451 .fuzzNo = fuzzNo,
452 .persistentSock = -1,
453 .tmOutSignaled = false,
454 .origFileName = "[DYNAMIC]",
455 };
456
457 /* Do not try to handle input files with socketfuzzer */
458 if (!hfuzz->socketFuzzer.enabled) {
459 if (!(run.dynamicFile = files_mapSharedMem(hfuzz->mutate.maxFileSz, &run.dynamicFileFd,
460 "hfuzz-input", run.global->io.workDir))) {
461 LOG_F("Couldn't create an input file of size: %zu", hfuzz->mutate.maxFileSz);
462 }
463 }
464 defer {
465 if (run.dynamicFileFd != -1) {
466 close(run.dynamicFileFd);
467 }
468 };
469
470 if (!arch_archThreadInit(&run)) {
471 LOG_F("Could not initialize the thread");
472 }
473
474 for (;;) {
475 /* Check if dry run mode with verifier enabled */
476 if (run.global->mutate.mutationsPerRun == 0U && run.global->cfg.useVerifier &&
477 !hfuzz->socketFuzzer.enabled) {
478 if (ATOMIC_POST_INC(run.global->cnts.mutationsCnt) >= run.global->io.fileCnt) {
479 break;
480 }
481 }
482 /* Check for max iterations limit if set */
483 else if ((ATOMIC_POST_INC(run.global->cnts.mutationsCnt) >=
484 run.global->mutate.mutationsMax) &&
485 run.global->mutate.mutationsMax) {
486 break;
487 }
488
489 input_setSize(&run, run.global->mutate.maxFileSz);
490 if (hfuzz->socketFuzzer.enabled) {
491 fuzz_fuzzLoopSocket(&run);
492 } else {
493 fuzz_fuzzLoop(&run);
494 }
495
496 if (fuzz_isTerminating()) {
497 break;
498 }
499
500 if (run.global->cfg.exitUponCrash && ATOMIC_GET(run.global->cnts.crashesCnt) > 0) {
501 LOG_I("Seen a crash. Terminating all fuzzing threads");
502 fuzz_setTerminating();
503 break;
504 }
505 }
506
507 if (run.pid) {
508 kill(run.pid, SIGKILL);
509 }
510
511 LOG_I("Terminating thread no. #%" PRId32 ", left: %zu", fuzzNo,
512 hfuzz->threads.threadsMax - ATOMIC_GET(run.global->threads.threadsFinished));
513 ATOMIC_POST_INC(run.global->threads.threadsFinished);
514 return NULL;
515 }
516
fuzz_threadsStart(honggfuzz_t * hfuzz)517 void fuzz_threadsStart(honggfuzz_t* hfuzz) {
518 if (!arch_archInit(hfuzz)) {
519 LOG_F("Couldn't prepare arch for fuzzing");
520 }
521 if (!sanitizers_Init(hfuzz)) {
522 LOG_F("Couldn't prepare sanitizer options");
523 }
524
525 if (hfuzz->socketFuzzer.enabled) {
526 /* Don't do dry run with socketFuzzer */
527 LOG_I("Entering phase - Feedback Driven Mode (SocketFuzzer)");
528 hfuzz->feedback.state = _HF_STATE_DYNAMIC_MAIN;
529 } else if (hfuzz->feedback.dynFileMethod != _HF_DYNFILE_NONE) {
530 LOG_I("Entering phase 1/2: Dry Run");
531 hfuzz->feedback.state = _HF_STATE_DYNAMIC_DRY_RUN;
532 } else {
533 LOG_I("Entering phase: Static");
534 hfuzz->feedback.state = _HF_STATE_STATIC;
535 }
536
537 for (size_t i = 0; i < hfuzz->threads.threadsMax; i++) {
538 if (!subproc_runThread(hfuzz, &hfuzz->threads.threads[i], fuzz_threadNew)) {
539 PLOG_F("Couldn't run a thread #%zu", i);
540 }
541 }
542 }
543