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