1 /*
2
3 honggfuzz - cmdline parsing
4
5 -----------------------------------------
6
7 Copyright 2014 Google Inc. All Rights Reserved.
8
9 Licensed under the Apache License, Version 2.0 (the "License");
10 you may not use this file except in compliance with the License.
11 You may obtain a copy of the License at
12
13 http://www.apache.org/licenses/LICENSE-2.0
14
15 Unless required by applicable law or agreed to in writing, software
16 distributed under the License is distributed on an "AS IS" BASIS,
17 WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
18 See the License for the specific language governing permissions and
19 limitations under the License.
20
21 */
22
23 #include "cmdline.h"
24
25 #include <ctype.h>
26 #include <errno.h>
27 #include <getopt.h>
28 #include <inttypes.h>
29 #include <limits.h>
30 #include <stdlib.h>
31 #include <stdio.h>
32 #include <string.h>
33 #include <sys/queue.h>
34 #include <unistd.h>
35
36 #include "common.h"
37 #include "log.h"
38 #include "files.h"
39 #include "util.h"
40
41 struct custom_option {
42 struct option opt;
43 const char *descr;
44 };
45
checkFor_FILE_PLACEHOLDER(char ** args)46 static bool checkFor_FILE_PLACEHOLDER(char **args)
47 {
48 for (int x = 0; args[x]; x++) {
49 if (strstr(args[x], _HF_FILE_PLACEHOLDER))
50 return true;
51 }
52 return false;
53 }
54
cmdlineYesNo(bool yes)55 static const char *cmdlineYesNo(bool yes)
56 {
57 return (yes ? "true" : "false");
58 }
59
cmdlineHelp(const char * pname,struct custom_option * opts)60 static void cmdlineHelp(const char *pname, struct custom_option *opts)
61 {
62 LOG_HELP_BOLD("Usage: %s [options] -- path_to_command [args]", pname);
63 LOG_HELP_BOLD("Options:");
64 for (int i = 0; opts[i].opt.name; i++) {
65 if (isprint(opts[i].opt.val)) {
66 LOG_HELP_BOLD(" --%s%s%c %s", opts[i].opt.name,
67 "|-", opts[i].opt.val,
68 opts[i].opt.has_arg == required_argument ? "VALUE" : "");
69 } else {
70 LOG_HELP_BOLD(" --%s %s", opts[i].opt.name,
71 opts[i].opt.has_arg == required_argument ? "VALUE" : "");
72 }
73 LOG_HELP("\t%s", opts[i].descr);
74 }
75 LOG_HELP_BOLD("\nExamples:");
76 LOG_HELP(" Run the binary over a mutated file chosen from the directory");
77 LOG_HELP_BOLD(" " PROG_NAME " -f input_dir -- /usr/bin/tiffinfo -D " _HF_FILE_PLACEHOLDER);
78 LOG_HELP(" As above, provide input over STDIN:");
79 LOG_HELP_BOLD(" " PROG_NAME " -f input_dir -s -- /usr/bin/djpeg");
80 #if defined(_HF_ARCH_LINUX)
81 LOG_HELP(" Run the binary over a dynamic file, maximize total no. of instructions:");
82 LOG_HELP_BOLD(" " PROG_NAME " --linux_perf_instr -- /usr/bin/tiffinfo -D "
83 _HF_FILE_PLACEHOLDER);
84 LOG_HELP(" Run the binary over a dynamic file, maximize total no. of branches:");
85 LOG_HELP_BOLD(" " PROG_NAME " --linux_perf_branch -- /usr/bin/tiffinfo -D "
86 _HF_FILE_PLACEHOLDER);
87 LOG_HELP(" Run the binary over a dynamic file, maximize unique code blocks (coverage):");
88 LOG_HELP_BOLD(" " PROG_NAME " --linux_perf_ip -- /usr/bin/tiffinfo -D " _HF_FILE_PLACEHOLDER);
89 LOG_HELP(" Run the binary over a dynamic file, maximize unique branches (edges):");
90 LOG_HELP_BOLD(" " PROG_NAME " --linux_perf_ip_addr -- /usr/bin/tiffinfo -D "
91 _HF_FILE_PLACEHOLDER);
92 LOG_HELP(" Run the binary over a dynamic file, maximize custom counters (experimental):");
93 LOG_HELP_BOLD(" " PROG_NAME " --linux_perf_custom -- /usr/bin/tiffinfo -D "
94 _HF_FILE_PLACEHOLDER);
95 #endif /* defined(_HF_ARCH_LINUX) */
96 }
97
cmdlineUsage(const char * pname,struct custom_option * opts)98 static void cmdlineUsage(const char *pname, struct custom_option *opts)
99 {
100 cmdlineHelp(pname, opts);
101 exit(0);
102 }
103
cmdlineParseRLimit(int res,const char * optarg,unsigned long mul)104 rlim_t cmdlineParseRLimit(int res, const char *optarg, unsigned long mul)
105 {
106 struct rlimit cur;
107 if (getrlimit(res, &cur) == -1) {
108 PLOG_F("getrlimit(%d)", res);
109 }
110 if (strcasecmp(optarg, "max") == 0) {
111 return cur.rlim_max;
112 }
113 if (strcasecmp(optarg, "def") == 0) {
114 return cur.rlim_cur;
115 }
116 if (util_isANumber(optarg) == false) {
117 LOG_F("RLIMIT %d needs a numeric or 'max'/'def' value ('%s' provided)", res, optarg);
118 }
119 rlim_t val = strtoul(optarg, NULL, 0) * mul;
120 if ((unsigned long)val == ULONG_MAX && errno != 0) {
121 PLOG_F("strtoul('%s', 0)", optarg);
122 }
123 return val;
124 }
125
cmdlineParse(int argc,char * argv[],honggfuzz_t * hfuzz)126 bool cmdlineParse(int argc, char *argv[], honggfuzz_t * hfuzz)
127 {
128 /* *INDENT-OFF* */
129 (*hfuzz) = (honggfuzz_t) {
130 .cmdline = NULL,
131 .cmdline_txt[0] = '\0',
132 .inputFile = NULL,
133 .nullifyStdio = false,
134 .fuzzStdin = false,
135 .saveUnique = true,
136 .useScreen = true,
137 .useVerifier = false,
138 .timeStart = time(NULL),
139 .fileExtn = "fuzz",
140 .workDir = ".",
141 .origFlipRate = 0.001f,
142 .externalCommand = NULL,
143 .dictionaryFile = NULL,
144 .dictionary = NULL,
145 .dictionaryCnt = 0,
146 .blacklistFile = NULL,
147 .blacklistCnt = 0,
148 .blacklist = NULL,
149 .maxFileSz = (1024 * 1024),
150 .tmOut = 3,
151 .mutationsMax = 0,
152 .threadsFinished = 0,
153 .threadsMax = 2,
154 .reportFile = NULL,
155 .asLimit = 0ULL,
156 .files = NULL,
157 .fileCnt = 0,
158 .lastFileIndex = 0,
159 .doneFileIndex = 0,
160 .exeFd = -1,
161 .clearEnv = false,
162 .envs = {[0 ... (ARRAYSIZE(hfuzz->envs) - 1)] = NULL,},
163
164 .state = _HF_STATE_UNSET,
165 .bbMapSz = _HF_PERF_BITMAP_SIZE,
166 .bbMap = util_Malloc(_HF_PERF_BITMAP_SIZE),
167 .dynfileq_mutex = PTHREAD_MUTEX_INITIALIZER,
168 .dynfileqCnt = 0U,
169
170 .mutationsCnt = 0,
171 .crashesCnt = 0,
172 .uniqueCrashesCnt = 0,
173 .verifiedCrashesCnt = 0,
174 .blCrashesCnt = 0,
175 .timeoutedCnt = 0,
176
177 .dynFileMethod = _HF_DYNFILE_NONE,
178 .sanCovCnts = {
179 .hitBBCnt = 0ULL,
180 .totalBBCnt = 0ULL,
181 .dsoCnt = 0ULL,
182 .iDsoCnt = 0ULL,
183 .newBBCnt = 0ULL,
184 .crashesCnt = 0ULL,
185 },
186
187 .sanCov_mutex = PTHREAD_MUTEX_INITIALIZER,
188 .sanOpts = {
189 .asanOpts = NULL,
190 .msanOpts = NULL,
191 .ubsanOpts = NULL,
192 },
193 .useSanCov = false,
194 .covMetadata = NULL,
195
196 /* Linux code */
197 .hwCnts = {
198 .cpuInstrCnt = 0ULL,
199 .cpuBranchCnt = 0ULL,
200 .customCnt = 0ULL,
201 .bbCnt = 0ULL,
202 },
203 .dynamicCutOffAddr = ~(0ULL),
204 .disableRandomization = true,
205 .msanReportUMRS = false,
206 .ignoreAddr = NULL,
207 .numMajorFrames = 7,
208 .pid = 0,
209 .pidFile = NULL,
210 .pidCmd = NULL,
211 };
212 /* *INDENT-ON* */
213
214 TAILQ_INIT(&hfuzz->dynfileq);
215
216 /* *INDENT-OFF* */
217 struct custom_option custom_opts[] = {
218 {{"help", no_argument, NULL, 'h'}, "Help plz.."},
219 {{"input", required_argument, NULL, 'f'}, "Path to the file corpus (file or a directory)"},
220 {{"nullify_stdio", no_argument, NULL, 'q'}, "Null-ify children's stdin, stdout, stderr; make them quiet"},
221 {{"stdin_input", no_argument, NULL, 's'}, "Provide fuzzing input on STDIN, instead of ___FILE___"},
222 {{"save_all", no_argument, NULL, 'u'}, "Save all test-cases (not only the unique ones) by appending the current time-stamp to the filenames"},
223 {{"logfile", required_argument, NULL, 'l'}, "Log file"},
224 {{"verbose", no_argument, NULL, 'v'}, "Disable ANSI console; use simple log output"},
225 #if defined(_HF_ARCH_LINUX) || defined(_HF_ARCH_DARWIN)
226 {{"verifier", no_argument, NULL, 'V'}, "Enable crashes verifier"},
227 #endif
228 {{"debug_level", required_argument, NULL, 'd'}, "Debug level (0 - FATAL ... 4 - DEBUG), (default: '3' [INFO])"},
229 {{"extension", required_argument, NULL, 'e'}, "Input file extension (e.g. 'swf'), (default: 'fuzz')"},
230 {{"wokspace", required_argument, NULL, 'W'}, "Workspace directory to save crashes & runtime files (default: '.')"},
231 {{"flip_rate", required_argument, NULL, 'r'}, "Maximal flip rate, (default: '0.001')"},
232 {{"wordlist", required_argument, NULL, 'w'}, "Wordlist file (tokens delimited by NUL-bytes)"},
233 {{"stackhash_bl", required_argument, NULL, 'B'}, "Stackhashes blacklist file (one entry per line)"},
234 {{"mutate_cmd", required_argument, NULL, 'c'}, "External command modifying the input corpus of files, instead of -r/-m parameters"},
235 {{"timeout", required_argument, NULL, 't'}, "Timeout in seconds (default: '3')"},
236 {{"threads", required_argument, NULL, 'n'}, "Number of concurrent fuzzing threads (default: '2')"},
237 {{"iterations", required_argument, NULL, 'N'}, "Number of fuzzing iterations (default: '0' [no limit])"},
238 {{"rlimit_as", required_argument, NULL, 0x100}, "Per process memory limit in MiB (default: '0' [no limit])"},
239 {{"report", required_argument, NULL, 'R'}, "Write report to this file (default: '" _HF_REPORT_FILE "')"},
240 {{"max_file_size", required_argument, NULL, 'F'}, "Maximal size of files processed by the fuzzer in bytes (default: '1048576')"},
241 {{"clear_env", no_argument, NULL, 0x101}, "Clear all environment variables before executing the binary"},
242 {{"env", required_argument, NULL, 'E'}, "Pass this environment variable, can be used multiple times"},
243 {{"sancov", no_argument, NULL, 'C'}, "Enable sanitizer coverage feedback"},
244
245 #if defined(_HF_ARCH_LINUX)
246 {{"linux_pid", required_argument, NULL, 'p'}, "Attach to a pid (and its thread group)"},
247 {{"linux_file_pid", required_argument, NULL, 'P'}, "Attach to pid (and its thread group) read from file"},
248 {{"linux_addr_low_limit", required_argument, NULL, 0x500}, "Address limit (from si.si_addr) below which crashes are not reported, (default: '0')"},
249 {{"linux_keep_aslr", no_argument, NULL, 0x501}, "Don't disable ASLR randomization, might be useful with MSAN"},
250 {{"linux_report_msan_umrs", no_argument, NULL, 0x502}, "Report MSAN's UMRS (uninitialized memory access)"},
251 {{"linux_perf_ignore_above", required_argument, NULL, 0x503}, "Ignore perf events which report IPs above this address"},
252 {{"linux_perf_instr", no_argument, NULL, 0x510}, "Use PERF_COUNT_HW_INSTRUCTIONS perf"},
253 {{"linux_perf_branch", no_argument, NULL, 0x511}, "Use PERF_COUNT_HW_BRANCH_INSTRUCTIONS perf"},
254 {{"linux_perf_bts_block", no_argument, NULL, 0x512}, "Use Intel BTS to count unique blocks"},
255 {{"linux_perf_bts_edge", no_argument, NULL, 0x513}, "Use Intel BTS to count unique edges"},
256 {{"linux_perf_ipt_block", no_argument, NULL, 0x514}, "Use Intel Processor Trace to count unique blocks"},
257 {{"linux_perf_custom", no_argument, NULL, 0x520}, "Custom counter (see the interceptor/ directory for examples)"},
258 #endif // defined(_HF_ARCH_LINUX)
259 {{0, 0, 0, 0}, NULL},
260 };
261 /* *INDENT-ON* */
262
263 struct option opts[ARRAYSIZE(custom_opts)];
264 for (unsigned i = 0; i < ARRAYSIZE(custom_opts); i++) {
265 opts[i] = custom_opts[i].opt;
266 }
267
268 enum llevel_t ll = INFO;
269 const char *logfile = NULL;
270 int opt_index = 0;
271 for (;;) {
272 int c = getopt_long(argc, argv, "-?hqvVsuf:d:e:W:r:c:F:t:R:n:N:l:p:P:g:E:w:B:C", opts,
273 &opt_index);
274 if (c < 0)
275 break;
276
277 switch (c) {
278 case 'h':
279 case '?':
280 cmdlineUsage(argv[0], custom_opts);
281 break;
282 case 'f':
283 hfuzz->inputFile = optarg;
284 break;
285 case 'q':
286 hfuzz->nullifyStdio = true;
287 break;
288 case 'v':
289 hfuzz->useScreen = false;
290 break;
291 case 'V':
292 hfuzz->useVerifier = true;
293 break;
294 case 's':
295 hfuzz->fuzzStdin = true;
296 break;
297 case 'u':
298 hfuzz->saveUnique = false;
299 break;
300 case 'l':
301 logfile = optarg;
302 break;
303 case 'd':
304 ll = atoi(optarg);
305 break;
306 case 'e':
307 hfuzz->fileExtn = optarg;
308 break;
309 case 'W':
310 hfuzz->workDir = optarg;
311 break;
312 case 'r':
313 hfuzz->origFlipRate = strtod(optarg, NULL);
314 break;
315 case 'c':
316 hfuzz->externalCommand = optarg;
317 break;
318 case 'C':
319 hfuzz->useSanCov = true;
320 break;
321 case 'F':
322 hfuzz->maxFileSz = strtoul(optarg, NULL, 0);
323 break;
324 case 't':
325 hfuzz->tmOut = atol(optarg);
326 break;
327 case 'R':
328 hfuzz->reportFile = optarg;
329 break;
330 case 'n':
331 hfuzz->threadsMax = atol(optarg);
332 break;
333 case 'N':
334 hfuzz->mutationsMax = atol(optarg);
335 break;
336 case 0x100:
337 hfuzz->asLimit = strtoull(optarg, NULL, 0);
338 break;
339 case 0x101:
340 hfuzz->clearEnv = true;
341 break;
342 case 'p':
343 if (util_isANumber(optarg) == false) {
344 LOG_E("-p '%s' is not a number", optarg);
345 return false;
346 }
347 hfuzz->pid = atoi(optarg);
348 if (hfuzz->pid < 1) {
349 LOG_E("-p '%d' is invalid", hfuzz->pid);
350 return false;
351 }
352 break;
353 case 'P':
354 hfuzz->pidFile = optarg;
355 break;
356 case 'E':
357 for (size_t i = 0; i < ARRAYSIZE(hfuzz->envs); i++) {
358 if (hfuzz->envs[i] == NULL) {
359 hfuzz->envs[i] = optarg;
360 break;
361 }
362 }
363 break;
364 case 'w':
365 hfuzz->dictionaryFile = optarg;
366 break;
367 case 'B':
368 hfuzz->blacklistFile = optarg;
369 break;
370 case 0x500:
371 hfuzz->ignoreAddr = (void *)strtoul(optarg, NULL, 0);
372 break;
373 case 0x501:
374 hfuzz->disableRandomization = false;
375 break;
376 case 0x502:
377 hfuzz->msanReportUMRS = true;
378 break;
379 case 0x503:
380 hfuzz->dynamicCutOffAddr = strtoull(optarg, NULL, 0);
381 break;
382 case 0x510:
383 hfuzz->dynFileMethod |= _HF_DYNFILE_INSTR_COUNT;
384 break;
385 case 0x511:
386 hfuzz->dynFileMethod |= _HF_DYNFILE_BRANCH_COUNT;
387 break;
388 case 0x512:
389 hfuzz->dynFileMethod |= _HF_DYNFILE_BTS_BLOCK;
390 break;
391 case 0x513:
392 hfuzz->dynFileMethod |= _HF_DYNFILE_BTS_EDGE;
393 break;
394 case 0x514:
395 hfuzz->dynFileMethod |= _HF_DYNFILE_IPT_BLOCK;
396 break;
397 case 0x520:
398 hfuzz->dynFileMethod |= _HF_DYNFILE_CUSTOM;
399 break;
400 default:
401 cmdlineUsage(argv[0], custom_opts);
402 return false;
403 break;
404 }
405 }
406
407 if (logInitLogFile(logfile, ll) == false) {
408 return false;
409 }
410
411 hfuzz->cmdline = &argv[optind];
412 if (hfuzz->cmdline[0] == NULL) {
413 LOG_E("No fuzz command provided");
414 cmdlineUsage(argv[0], custom_opts);
415 return false;
416 }
417
418 if (!hfuzz->fuzzStdin && !checkFor_FILE_PLACEHOLDER(hfuzz->cmdline)) {
419 LOG_E("You must specify '" _HF_FILE_PLACEHOLDER
420 "' when the -s (stdin fuzzing) option is not set");
421 return false;
422 }
423
424 if (hfuzz->dynFileMethod != _HF_DYNFILE_NONE && hfuzz->useSanCov) {
425 LOG_E("You cannot enable sanitizer coverage & perf feedback at the same time");
426 return false;
427 }
428
429 /* Sanity checks for timeout. Optimal ranges highly depend on target */
430 if (hfuzz->useSanCov && hfuzz->tmOut < 15) {
431 LOG_E("Timeout value (%ld) too small for sanitizer coverage feedback", hfuzz->tmOut);
432 return false;
433 }
434
435 if (strchr(hfuzz->fileExtn, '/')) {
436 LOG_E("The file extension contains the '/' character: '%s'", hfuzz->fileExtn);
437 return false;
438 }
439
440 if (hfuzz->workDir[0] != '.' || strlen(hfuzz->workDir) > 2) {
441 if (!files_exists(hfuzz->workDir)) {
442 LOG_E("Provided workspace directory '%s' doesn't exist", hfuzz->workDir);
443 return false;
444 }
445 }
446
447 if (hfuzz->pid > 0 || hfuzz->pidFile) {
448 LOG_I("PID=%d specified, lowering maximum number of concurrent threads to 1", hfuzz->pid);
449 hfuzz->threadsMax = 1;
450 }
451
452 if (hfuzz->origFlipRate == 0.0L && hfuzz->useVerifier) {
453 LOG_I("Verifier enabled with 0.0 flipRate, activating dry run mode");
454 }
455
456 LOG_I("inputFile '%s', nullifyStdio: %s, fuzzStdin: %s, saveUnique: %s, flipRate: %lf, "
457 "externalCommand: '%s', tmOut: %ld, mutationsMax: %zu, threadsMax: %zu, fileExtn '%s', ignoreAddr: %p, "
458 "memoryLimit: 0x%" PRIx64 "(MiB), fuzzExe: '%s', fuzzedPid: %d",
459 hfuzz->inputFile,
460 cmdlineYesNo(hfuzz->nullifyStdio), cmdlineYesNo(hfuzz->fuzzStdin),
461 cmdlineYesNo(hfuzz->saveUnique), hfuzz->origFlipRate,
462 hfuzz->externalCommand == NULL ? "NULL" : hfuzz->externalCommand, hfuzz->tmOut,
463 hfuzz->mutationsMax, hfuzz->threadsMax, hfuzz->fileExtn, hfuzz->ignoreAddr,
464 hfuzz->asLimit, hfuzz->cmdline[0], hfuzz->pid);
465
466 snprintf(hfuzz->cmdline_txt, sizeof(hfuzz->cmdline_txt), "%s", hfuzz->cmdline[0]);
467 for (size_t i = 1; hfuzz->cmdline[i]; i++) {
468 util_ssnprintf(hfuzz->cmdline_txt, sizeof(hfuzz->cmdline_txt), " %s", hfuzz->cmdline[i]);
469 }
470
471 return true;
472 }
473