blob: a78c23af4585d0a886835bdccbebcbea6f2b9c99 [file] [log] [blame]
/*
honggfuzz - cmdline parsing
-----------------------------------------
Copyright 2014 Google Inc. All Rights Reserved.
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
*/
#include "cmdline.h"
#include <ctype.h>
#include <errno.h>
#include <getopt.h>
#include <inttypes.h>
#include <limits.h>
#include <stdlib.h>
#include <stdio.h>
#include <string.h>
#include <sys/queue.h>
#include <unistd.h>
#include "common.h"
#include "log.h"
#include "files.h"
#include "util.h"
struct custom_option {
struct option opt;
const char *descr;
};
static bool checkFor_FILE_PLACEHOLDER(char **args)
{
for (int x = 0; args[x]; x++) {
if (strstr(args[x], _HF_FILE_PLACEHOLDER))
return true;
}
return false;
}
static const char *cmdlineYesNo(bool yes)
{
return (yes ? "true" : "false");
}
static void cmdlineHelp(const char *pname, struct custom_option *opts)
{
LOG_HELP_BOLD("Usage: %s [options] -- path_to_command [args]", pname);
LOG_HELP_BOLD("Options:");
for (int i = 0; opts[i].opt.name; i++) {
if (isprint(opts[i].opt.val) && opts[i].opt.val < 0x80) {
LOG_HELP_BOLD(" --%s%s%c %s", opts[i].opt.name,
"|-", opts[i].opt.val,
opts[i].opt.has_arg == required_argument ? "VALUE" : "");
} else {
LOG_HELP_BOLD(" --%s %s", opts[i].opt.name,
opts[i].opt.has_arg == required_argument ? "VALUE" : "");
}
LOG_HELP("\t%s", opts[i].descr);
}
LOG_HELP_BOLD("\nExamples:");
LOG_HELP(" Run the binary over a mutated file chosen from the directory");
LOG_HELP_BOLD(" " PROG_NAME " -f input_dir -- /usr/bin/tiffinfo -D " _HF_FILE_PLACEHOLDER);
LOG_HELP(" As above, provide input over STDIN:");
LOG_HELP_BOLD(" " PROG_NAME " -f input_dir -s -- /usr/bin/djpeg");
LOG_HELP(" Use SANCOV to maximize code coverage:");
LOG_HELP_BOLD(" " PROG_NAME " -f input_dir -C -- /usr/bin/tiffinfo -D " _HF_FILE_PLACEHOLDER);
#if defined(_HF_ARCH_LINUX)
LOG_HELP(" Run the binary over a dynamic file, maximize total no. of instructions:");
LOG_HELP_BOLD(" " PROG_NAME " --linux_perf_instr -- /usr/bin/tiffinfo -D "
_HF_FILE_PLACEHOLDER);
LOG_HELP(" Run the binary over a dynamic file, maximize total no. of branches:");
LOG_HELP_BOLD(" " PROG_NAME " --linux_perf_branch -- /usr/bin/tiffinfo -D "
_HF_FILE_PLACEHOLDER);
LOG_HELP(" Run the binary over a dynamic file, maximize unique code blocks (coverage):");
LOG_HELP_BOLD(" " PROG_NAME " --linux_perf_ip -- /usr/bin/tiffinfo -D " _HF_FILE_PLACEHOLDER);
LOG_HELP(" Run the binary over a dynamic file, maximize unique branches (edges):");
LOG_HELP_BOLD(" " PROG_NAME " --linux_perf_ip_addr -- /usr/bin/tiffinfo -D "
_HF_FILE_PLACEHOLDER);
LOG_HELP(" Run the binary over a dynamic file, maximize custom counters (experimental):");
LOG_HELP_BOLD(" " PROG_NAME " --linux_perf_custom -- /usr/bin/tiffinfo -D "
_HF_FILE_PLACEHOLDER);
#endif /* defined(_HF_ARCH_LINUX) */
}
static void cmdlineUsage(const char *pname, struct custom_option *opts)
{
cmdlineHelp(pname, opts);
exit(0);
}
rlim_t cmdlineParseRLimit(int res, const char *optarg, unsigned long mul)
{
struct rlimit cur;
if (getrlimit(res, &cur) == -1) {
PLOG_F("getrlimit(%d)", res);
}
if (strcasecmp(optarg, "max") == 0) {
return cur.rlim_max;
}
if (strcasecmp(optarg, "def") == 0) {
return cur.rlim_cur;
}
if (util_isANumber(optarg) == false) {
LOG_F("RLIMIT %d needs a numeric or 'max'/'def' value ('%s' provided)", res, optarg);
}
rlim_t val = strtoul(optarg, NULL, 0) * mul;
if ((unsigned long)val == ULONG_MAX && errno != 0) {
PLOG_F("strtoul('%s', 0)", optarg);
}
return val;
}
bool cmdlineParse(int argc, char *argv[], honggfuzz_t * hfuzz)
{
/* *INDENT-OFF* */
(*hfuzz) = (honggfuzz_t) {
.cmdline = NULL,
.cmdline_txt[0] = '\0',
.inputFile = NULL,
.nullifyStdio = false,
.fuzzStdin = false,
.saveUnique = true,
.useScreen = true,
.useVerifier = false,
.timeStart = time(NULL),
.fileExtn = "fuzz",
.workDir = ".",
.origFlipRate = 0.001f,
.externalCommand = NULL,
.dictionaryFile = NULL,
.dictionary = NULL,
.dictionaryCnt = 0,
.blacklistFile = NULL,
.blacklistCnt = 0,
.blacklist = NULL,
.maxFileSz = (1024 * 1024),
.tmOut = 10,
.mutationsMax = 0,
.threadsFinished = 0,
.threadsMax = 2,
.reportFile = NULL,
.asLimit = 0ULL,
.files = NULL,
.fileCnt = 0,
.lastFileIndex = 0,
.doneFileIndex = 0,
.exeFd = -1,
.clearEnv = false,
.envs = {
[0 ... (ARRAYSIZE(hfuzz->envs) - 1)] = NULL,
},
.persistent = false,
.state = _HF_STATE_UNSET,
.bbMapSz = _HF_PERF_BITMAP_SIZE,
.bbMap = NULL,
.dynfileq_mutex = PTHREAD_MUTEX_INITIALIZER,
.dynfileqCnt = 0U,
.mutationsCnt = 0,
.crashesCnt = 0,
.uniqueCrashesCnt = 0,
.verifiedCrashesCnt = 0,
.blCrashesCnt = 0,
.timeoutedCnt = 0,
.dynFileMethod = _HF_DYNFILE_NONE,
.sanCovCnts = {
.hitBBCnt = 0ULL,
.totalBBCnt = 0ULL,
.dsoCnt = 0ULL,
.iDsoCnt = 0ULL,
.newBBCnt = 0ULL,
.crashesCnt = 0ULL,
},
.sanCov_mutex = PTHREAD_MUTEX_INITIALIZER,
.sanOpts = {
.asanOpts = NULL,
.msanOpts = NULL,
.ubsanOpts = NULL,
},
.useSanCov = false,
.covMetadata = NULL,
.msanReportUMRS = false,
/* Linux code */
.linux = {
.hwCnts = {
.cpuInstrCnt = 0ULL,
.cpuBranchCnt = 0ULL,
.customCnt = 0ULL,
.bbCnt = 0ULL,
.newBBCnt = 0ULL,
},
.dynamicCutOffAddr = ~(0ULL),
.disableRandomization = true,
.ignoreAddr = NULL,
.numMajorFrames = 7,
.pid = 0,
.pidFile = NULL,
.pidCmd = NULL,
},
};
/* *INDENT-ON* */
TAILQ_INIT(&hfuzz->dynfileq);
/* *INDENT-OFF* */
struct custom_option custom_opts[] = {
{{"help", no_argument, NULL, 'h'}, "Help plz.."},
{{"input", required_argument, NULL, 'f'}, "Path to the file corpus (file or a directory)"},
{{"nullify_stdio", no_argument, NULL, 'q'}, "Null-ify children's stdin, stdout, stderr; make them quiet"},
{{"timeout", required_argument, NULL, 't'}, "Timeout in seconds (default: '10')"},
{{"threads", required_argument, NULL, 'n'}, "Number of concurrent fuzzing threads (default: '2')"},
{{"stdin_input", no_argument, NULL, 's'}, "Provide fuzzing input on STDIN, instead of ___FILE___"},
{{"mutation_rate", required_argument, NULL, 'r'}, "Maximal mutation rate in relation to the file size, (default: '0.001')"},
{{"logfile", required_argument, NULL, 'l'}, "Log file"},
{{"verbose", no_argument, NULL, 'v'}, "Disable ANSI console; use simple log output"},
{{"verifier", no_argument, NULL, 'V'}, "Enable crashes verifier"},
{{"debug_level", required_argument, NULL, 'd'}, "Debug level (0 - FATAL ... 4 - DEBUG), (default: '3' [INFO])"},
{{"extension", required_argument, NULL, 'e'}, "Input file extension (e.g. 'swf'), (default: 'fuzz')"},
{{"workspace", required_argument, NULL, 'W'}, "Workspace directory to save crashes & runtime files (default: '.')"},
{{"wordlist", required_argument, NULL, 'w'}, "Wordlist file (tokens delimited by NUL-bytes)"},
{{"stackhash_bl", required_argument, NULL, 'B'}, "Stackhashes blacklist file (one entry per line)"},
{{"mutate_cmd", required_argument, NULL, 'c'}, "External command providing fuzz files, instead of mutating the input corpus"},
{{"iterations", required_argument, NULL, 'N'}, "Number of fuzzing iterations (default: '0' [no limit])"},
{{"rlimit_as", required_argument, NULL, 0x100}, "Per process memory limit in MiB (default: '0' [no limit])"},
{{"report", required_argument, NULL, 'R'}, "Write report to this file (default: '" _HF_REPORT_FILE "')"},
{{"max_file_size", required_argument, NULL, 'F'}, "Maximal size of files processed by the fuzzer in bytes (default: '1048576')"},
{{"clear_env", no_argument, NULL, 0x101}, "Clear all environment variables before executing the binary"},
{{"env", required_argument, NULL, 'E'}, "Pass this environment variable, can be used multiple times"},
{{"save_all", no_argument, NULL, 'u'}, "Save all test-cases (not only the unique ones) by appending the current time-stamp to the filenames"},
{{"sancov", no_argument, NULL, 'C'}, "Enable sanitizer coverage feedback"},
{{"msan_report_umrs", no_argument, NULL, 0x102}, "Report MSAN's UMRS (uninitialized memory access)"},
{{"persistent", no_argument, NULL, 'P'}, "Enable persistent fuzzing (link with tools/persistent.mode.main.c)"},
#if defined(_HF_ARCH_LINUX)
{{"linux_pid", required_argument, NULL, 'p'}, "Attach to a pid (and its thread group)"},
{{"linux_file_pid", required_argument, NULL, 0x502}, "Attach to pid (and its thread group) read from file"},
{{"linux_addr_low_limit", required_argument, NULL, 0x500}, "Address limit (from si.si_addr) below which crashes are not reported, (default: '0')"},
{{"linux_keep_aslr", no_argument, NULL, 0x501}, "Don't disable ASLR randomization, might be useful with MSAN"},
{{"linux_perf_ignore_above", required_argument, NULL, 0x503}, "Ignore perf events which report IPs above this address"},
{{"linux_perf_instr", no_argument, NULL, 0x510}, "Use PERF_COUNT_HW_INSTRUCTIONS perf"},
{{"linux_perf_branch", no_argument, NULL, 0x511}, "Use PERF_COUNT_HW_BRANCH_INSTRUCTIONS perf"},
{{"linux_perf_bts_block", no_argument, NULL, 0x512}, "Use Intel BTS to count unique blocks"},
{{"linux_perf_bts_edge", no_argument, NULL, 0x513}, "Use Intel BTS to count unique edges"},
{{"linux_perf_ipt_block", no_argument, NULL, 0x514}, "Use Intel Processor Trace to count unique blocks"},
{{"linux_perf_custom", no_argument, NULL, 0x520}, "Custom counter (see interceptor/stringmem.c)"},
#endif // defined(_HF_ARCH_LINUX)
{{0, 0, 0, 0}, NULL},
};
/* *INDENT-ON* */
struct option opts[ARRAYSIZE(custom_opts)];
for (unsigned i = 0; i < ARRAYSIZE(custom_opts); i++) {
opts[i] = custom_opts[i].opt;
}
enum llevel_t ll = INFO;
const char *logfile = NULL;
int opt_index = 0;
for (;;) {
int c = getopt_long(argc, argv, "-?hqvVsuPf:d:e:W:r:c:F:t:R:n:N:l:p:g:E:w:B:C", opts,
&opt_index);
if (c < 0)
break;
switch (c) {
case 'h':
case '?':
cmdlineUsage(argv[0], custom_opts);
break;
case 'f':
hfuzz->inputFile = optarg;
break;
case 'q':
hfuzz->nullifyStdio = true;
break;
case 'v':
hfuzz->useScreen = false;
break;
case 'V':
hfuzz->useVerifier = true;
break;
case 's':
hfuzz->fuzzStdin = true;
break;
case 'u':
hfuzz->saveUnique = false;
break;
case 'l':
logfile = optarg;
break;
case 'd':
ll = atoi(optarg);
break;
case 'e':
hfuzz->fileExtn = optarg;
break;
case 'W':
hfuzz->workDir = optarg;
break;
case 'r':
hfuzz->origFlipRate = strtod(optarg, NULL);
break;
case 'c':
hfuzz->externalCommand = optarg;
break;
case 'C':
hfuzz->useSanCov = true;
break;
case 'F':
hfuzz->maxFileSz = strtoul(optarg, NULL, 0);
break;
case 't':
hfuzz->tmOut = atol(optarg);
break;
case 'R':
hfuzz->reportFile = optarg;
break;
case 'n':
hfuzz->threadsMax = atol(optarg);
break;
case 'N':
hfuzz->mutationsMax = atol(optarg);
break;
case 0x100:
hfuzz->asLimit = strtoull(optarg, NULL, 0);
break;
case 0x101:
hfuzz->clearEnv = true;
break;
case 0x102:
hfuzz->msanReportUMRS = true;
break;
case 'P':
hfuzz->persistent = true;
break;
case 'p':
if (util_isANumber(optarg) == false) {
LOG_E("-p '%s' is not a number", optarg);
return false;
}
hfuzz->linux.pid = atoi(optarg);
if (hfuzz->linux.pid < 1) {
LOG_E("-p '%d' is invalid", hfuzz->linux.pid);
return false;
}
break;
case 0x502:
hfuzz->linux.pidFile = optarg;
break;
case 'E':
for (size_t i = 0; i < ARRAYSIZE(hfuzz->envs); i++) {
if (hfuzz->envs[i] == NULL) {
hfuzz->envs[i] = optarg;
break;
}
}
break;
case 'w':
hfuzz->dictionaryFile = optarg;
break;
case 'B':
hfuzz->blacklistFile = optarg;
break;
case 0x500:
hfuzz->linux.ignoreAddr = (void *)strtoul(optarg, NULL, 0);
break;
case 0x501:
hfuzz->linux.disableRandomization = false;
break;
case 0x503:
hfuzz->linux.dynamicCutOffAddr = strtoull(optarg, NULL, 0);
break;
case 0x510:
hfuzz->dynFileMethod |= _HF_DYNFILE_INSTR_COUNT;
break;
case 0x511:
hfuzz->dynFileMethod |= _HF_DYNFILE_BRANCH_COUNT;
break;
case 0x512:
hfuzz->dynFileMethod |= _HF_DYNFILE_BTS_BLOCK;
break;
case 0x513:
hfuzz->dynFileMethod |= _HF_DYNFILE_BTS_EDGE;
break;
case 0x514:
hfuzz->dynFileMethod |= _HF_DYNFILE_IPT_BLOCK;
break;
case 0x520:
hfuzz->dynFileMethod |= _HF_DYNFILE_CUSTOM;
break;
default:
cmdlineUsage(argv[0], custom_opts);
return false;
break;
}
}
if (logInitLogFile(logfile, ll) == false) {
return false;
}
hfuzz->cmdline = &argv[optind];
if (hfuzz->cmdline[0] == NULL) {
LOG_E("No fuzz command provided");
cmdlineUsage(argv[0], custom_opts);
return false;
}
if (!hfuzz->fuzzStdin && !hfuzz->persistent && !checkFor_FILE_PLACEHOLDER(hfuzz->cmdline)) {
LOG_E("You must specify '" _HF_FILE_PLACEHOLDER
"' when the -s (stdin fuzzing) or --persistent options are not set");
return false;
}
/* Sanity checks for timeout. Optimal ranges highly depend on target */
if (hfuzz->useSanCov && hfuzz->tmOut < 10) {
LOG_E("Timeout value (%ld) too small for sanitizer coverage feedback", (long)hfuzz->tmOut);
return false;
}
if (strchr(hfuzz->fileExtn, '/')) {
LOG_E("The file extension contains the '/' character: '%s'", hfuzz->fileExtn);
return false;
}
if (hfuzz->workDir[0] != '.' || strlen(hfuzz->workDir) > 2) {
if (!files_exists(hfuzz->workDir)) {
LOG_E("Provided workspace directory '%s' doesn't exist", hfuzz->workDir);
return false;
}
}
if (hfuzz->linux.pid > 0 || hfuzz->linux.pidFile) {
LOG_I("PID=%d specified, lowering maximum number of concurrent threads to 1",
hfuzz->linux.pid);
hfuzz->threadsMax = 1;
}
if (hfuzz->origFlipRate == 0.0L && hfuzz->useVerifier) {
LOG_I("Verifier enabled with 0.0 flipRate, activating dry run mode");
}
LOG_I("inputFile '%s', nullifyStdio: %s, fuzzStdin: %s, saveUnique: %s, flipRate: %lf, "
"externalCommand: '%s', tmOut: %ld, mutationsMax: %zu, threadsMax: %zu, fileExtn '%s', "
"memoryLimit: 0x%" PRIx64 "(MiB), fuzzExe: '%s', fuzzedPid: %d",
hfuzz->inputFile,
cmdlineYesNo(hfuzz->nullifyStdio), cmdlineYesNo(hfuzz->fuzzStdin),
cmdlineYesNo(hfuzz->saveUnique), hfuzz->origFlipRate,
hfuzz->externalCommand == NULL ? "NULL" : hfuzz->externalCommand, hfuzz->tmOut,
hfuzz->mutationsMax, hfuzz->threadsMax, hfuzz->fileExtn,
hfuzz->asLimit, hfuzz->cmdline[0], hfuzz->linux.pid);
snprintf(hfuzz->cmdline_txt, sizeof(hfuzz->cmdline_txt), "%s", hfuzz->cmdline[0]);
for (size_t i = 1; hfuzz->cmdline[i]; i++) {
util_ssnprintf(hfuzz->cmdline_txt, sizeof(hfuzz->cmdline_txt), " %s", hfuzz->cmdline[i]);
}
return true;
}