diff --git a/doc/zzuf.1 b/doc/zzuf.1 index 82ee0e2..6b14962 100644 --- a/doc/zzuf.1 +++ b/doc/zzuf.1 @@ -2,13 +2,13 @@ .SH NAME zzuf \- multiple purpose fuzzer .SH SYNOPSIS -\fBzzuf\fR [\fB\-AcdiMnqSvx\fR] [\fB\-s\fR \fIseed\fR|\fB\-s\fR \fIstart:stop\fR] [\fB\-r\fR \fIratio\fR|\fB\-r\fR \fImin:max\fR] +\fBzzuf\fR [\fB\-AcdimnqSvx\fR] [\fB\-s\fR \fIseed\fR|\fB\-s\fR \fIstart:stop\fR] [\fB\-r\fR \fIratio\fR|\fB\-r\fR \fImin:max\fR] .br - [\fB\-D\fR \fIdelay\fR] [\fB\-F\fR \fIforks\fR] [\fB\-C\fR \fIcrashes\fR] [\fB\-B\fR \fIbytes\fR] + [\fB\-D\fR \fIdelay\fR] [\fB\-F\fR \fIforks\fR] [\fB\-C\fR \fIcrashes\fR] [\fB\-B\fR \fIbytes\fR] [\fB\-T\fR \fIseconds\fR] .br - [\fB\-T\fR \fIseconds\fR] [\fB\-M\fR \fImegabytes\fR] [\fB\-P\fR \fIprotect\fR] [\fB\-R\fR \fIrefuse\fR] + [\fB\-M\fR \fImegabytes\fR] [\fB\-b\fR \fIranges\fR] [\fB\-P\fR \fIprotect\fR] [\fB\-R\fR \fIrefuse\fR] .br - [\fB\-I\fR \fIinclude\fR] [\fB\-E\fR \fIexclude\fR] [\fIPROGRAM\fR [\fB\-\-\fR] [\fIARGS\fR]...] + [\fB\-I\fR \fIinclude\fR] [\fB\-E\fR \fIexclude\fR] [\fIPROGRAM\fR [\fB\-\-\fR] [\fIARGS\fR]...] .br \fBzzuf \-h\fR | \fB\-\-help\fR .br @@ -43,6 +43,17 @@ Increment random seed each time a new file is opened. This is only required if one instance of the application is expected to open the same file several times and you want to test a different seed each time. .TP +\fB\-b\fR, \fB\-\-bytes\fR=\fIranges\fR +Restrict fuzzing to bytes whose offsets in the file are within \fIranges\fR. + +Range values start at zero and are inclusive. Use dashes between range values +and commas between ranges. If the right-hand part of a range is ommited, it +means end of file. For instance, to restrict fuzzing to bytes 0, 3, 4, 5 and +all bytes after offset 31, use \(oq\fB\-r0,3-5,31-\fR\(cq. + +This option is useful to preserve file headers or corrupt only a specific +portion of a file. +.TP \fB\-B\fR, \fB\-\-max\-bytes\fR=\fIn\fR Automatically terminate child processes that output more than \fIn\fR bytes on the standard output and standard error channels. This is useful to detect @@ -149,12 +160,12 @@ backslash (\(oq\\\(cq) .RE .IP You can use \(oq\fB\-\fR\(cq to specify ranges. For instance, to protect all -bytes from \(oq\\001\(cq to \(oq/\(cq, use \(oq\fB\-P\ \(dq\\001\-/\(dq\fR\(cq. +bytes from \(oq\\001\(cq to \(oq/\(cq, use \(oq\fB\-P\ \(aq\\001\-/\(aq\fR\(cq. The statistical outcome of this option should not be overlooked: if characters are protected, the effect of the \(oq\fB\-r\fR\(cq flag will vary depending on the data being fuzzed. For instance, asking to fuzz 1% of input bits -(\fB\-r\ 0.01\fR) and to protect lowercase characters (\fB\-P\ a\-z\fR) will +(\fB\-r0.01\fR) and to protect lowercase characters (\fB\-P\ a\-z\fR) will result in an actual average fuzzing ratio of 0.9% with truly random data, 0.3% with random ASCII data and 0.2% with standard English text. @@ -241,7 +252,7 @@ Fuzz the input of the \fBcat\fR program using default settings: .PP Fuzz 1% of the input bits of the \fBcat\fR program using seed 94324: .PP -\fB zzuf \-s 94324 \-r 0.01 cat /etc/motd\fR +\fB zzuf \-s94324 \-r0.01 cat /etc/motd\fR .PP Fuzz the input of the \fBcat\fR program but do not fuzz newline characters and prevent non-ASCII characters from appearing in the output: @@ -261,21 +272,22 @@ and restricting fuzzing to filenames that appear on the command line can be read by VLC to reproduce the same behaviour without using \fBzzuf\fR: .PP -\fB zzuf \-c \-s 87423 \-r 0.01 vlc movie.avi\fR +\fB zzuf \-c \-s87423 \-r0.01 vlc movie.avi\fR .br -\fB zzuf \-c \-s 87423 \-r 0.01 fuzzy\-movie.avi\fR +\fB zzuf \-c \-s87423 \-r0.01 fuzzy\-movie.avi\fR .br \fB vlc fuzzy\-movie.avi\fR .PP -Fuzz between 0.1% and 2% of MPlayer's input bits (\fB\-r\ 0.001:0.02\fR) -with seeds 0 to 9999 (\fB\-s\ 0:10000\fR), disabling its standard output -messages (\fB\-q\fR), launching up to five simultaneous child processes -(\fB\-F\ 5\fR) but wait at least half a second between launches -(\fB\-D\ 0.5\fR), killing MPlayer if it takes more than one minute to -read the file (\fB\-T\ 60\fR) and disabling its \fBSIGSEGV\fR signal handler +Fuzz between 0.1% and 2% of MPlayer's input bits (\fB\-r0.001:0.02\fR) +with seeds 0 to 9999 (\fB\-s0:10000\fR), preserving the AVI 4-byte header +by restricting fuzzing to offsets after 4 (\fB\-b4\-\fR), disabling its +standard output messages (\fB\-q\fR), launching up to five simultaneous child +processes (\fB\-F5\fR) but waiting at least half a second between launches +(\fB\-D0.5\fR), killing MPlayer if it takes more than one minute to +read the file (\fB\-T60\fR) and disabling its \fBSIGSEGV\fR signal handler (\fB\-S\fR): .PP -\fB zzuf \-c \-r 0.001:0.02 \-q \-s 0:10000 \-F 5 \-D 0.5 \-T 60 \-S \\\fR +\fB zzuf \-c \-r0.001:0.02 \-s0:10000 \-b4\- \-q \-F5 \-D0.5 \-T60 \-S \\\fR .br \fB mplayer \-\- \-benchmark \-vo null \-fps 1000 movie.avi\fR .PP @@ -286,7 +298,7 @@ and open it in Firefox\(tm in auto-increment mode (\fB\-A\fR): .br (or: \fBjot -w \(aq\(aq 200 1 > hello.html\fR) .br -\fB zzuf -A -I \(aqhello[.]jpg\(aq -r 0.001 firefox hello.html\fR +\fB zzuf -A -I \(aqhello[.]jpg\(aq -r0.001 firefox hello.html\fR .SH RESTRICTIONS .PP Due to \fBzzuf\fR using shared object preloading (\fBLD_PRELOAD\fR, diff --git a/src/fuzz.c b/src/fuzz.c index b205aee..e24e4ad 100644 --- a/src/fuzz.c +++ b/src/fuzz.c @@ -24,6 +24,7 @@ # include #endif #include +#include #include #include "libzzuf.h" @@ -35,13 +36,52 @@ #define MAGIC1 0x33ea84f7 #define MAGIC2 0x783bc31f -/* Fuzzing variables */ +/* Per-offset byte protection */ +static unsigned int *ranges = NULL; +static unsigned int ranges_static[512]; + +/* Per-value byte protection */ static int protect[256]; static int refuse[256]; /* Local prototypes */ static void readchars(int *, char const *); +void _zz_bytes(char const *list) +{ + char const *parser; + unsigned int i, chunks; + + /* Count commas */ + for(parser = list, chunks = 1; *parser; parser++) + if(*parser == ',') + chunks++; + + /* TODO: free(ranges) if ranges != ranges_static */ + if(chunks >= 256) + ranges = malloc((chunks + 1) * 2 * sizeof(unsigned int)); + else + ranges = ranges_static; + + /* Fill ranges list */ + for(parser = list, i = 0; i < chunks; i++) + { + char const *comma = strchr(parser, ','); + char const *dash = strchr(parser, '-'); + + ranges[i * 2] = (dash == parser) ? 0 : atoi(parser); + if(dash && (dash + 1 == comma || dash[1] == '\0')) + ranges[i * 2 + 1] = ranges[i * 2]; /* special case */ + else if(dash && (!comma || dash < comma)) + ranges[i * 2 + 1] = atoi(dash + 1) + 1; + else + ranges[i * 2 + 1] = ranges[i * 2] + 1; + parser = comma + 1; + } + + ranges[i * 2] = ranges[i * 2 + 1] = 0; +} + void _zz_protect(char const *list) { readchars(protect, list); @@ -65,8 +105,8 @@ void _zz_fuzz(int fd, volatile uint8_t *buf, uint64_t len) (unsigned long int)pos); #endif - fuzz = _zz_getfuzz(fd); aligned_buf = buf - pos; + fuzz = _zz_getfuzz(fd); for(i = pos / CHUNKBYTES; i < (pos + len + CHUNKBYTES - 1) / CHUNKBYTES; @@ -102,7 +142,20 @@ void _zz_fuzz(int fd, volatile uint8_t *buf, uint64_t len) for(j = start; j < stop; j++) { - uint8_t byte = aligned_buf[j]; + unsigned int *r; + uint8_t byte; + + if(!ranges) + goto range_ok; + + for(r = ranges; r[1]; r += 2) + if(j >= r[0] && (r[0] == r[1] || j < r[1])) + goto range_ok; + + continue; /* Not in a range */ + + range_ok: + byte = aligned_buf[j]; if(protect[byte]) continue; diff --git a/src/fuzz.h b/src/fuzz.h index 8f4db43..aac3a72 100644 --- a/src/fuzz.h +++ b/src/fuzz.h @@ -16,6 +16,7 @@ * fuzz.h: fuzz functions */ +extern void _zz_bytes(char const *); extern void _zz_protect(char const *); extern void _zz_refuse(char const *); diff --git a/src/libzzuf.c b/src/libzzuf.c index 0419f88..37256de 100644 --- a/src/libzzuf.c +++ b/src/libzzuf.c @@ -78,6 +78,10 @@ void _zz_init(void) if(tmp && *tmp == '1') _zz_setautoinc(); + tmp = getenv("ZZUF_BYTES"); + if(tmp && *tmp) + _zz_bytes(tmp); + tmp = getenv("ZZUF_PROTECT"); if(tmp && *tmp) _zz_protect(tmp); diff --git a/src/opts.c b/src/opts.c index d164eba..68a7f07 100644 --- a/src/opts.c +++ b/src/opts.c @@ -33,7 +33,7 @@ void _zz_opts_init(struct opts *opts) { - opts->protect = opts->refuse = NULL; + opts->bytes = opts->protect = opts->refuse = NULL; opts->seed = DEFAULT_SEED; opts->endseed = DEFAULT_SEED + 1; opts->minratio = opts->maxratio = DEFAULT_RATIO; diff --git a/src/opts.h b/src/opts.h index 5eb68bd..ee2f501 100644 --- a/src/opts.h +++ b/src/opts.h @@ -19,7 +19,7 @@ struct opts { char const **newargv; - char *protect, *refuse; + char *bytes, *protect, *refuse; uint32_t seed; uint32_t endseed; double minratio; diff --git a/src/zzuf.c b/src/zzuf.c index ebe3d9d..38a47d6 100644 --- a/src/zzuf.c +++ b/src/zzuf.c @@ -125,9 +125,9 @@ int main(int argc, char *argv[]) for(;;) { # if defined HAVE_REGEX_H -# define OPTSTR "AB:cC:dD:E:F:iI:mM:nP:qr:R:s:ST:vxhV" +# define OPTSTR "Ab:B:cC:dD:E:F:iI:mM:nP:qr:R:s:ST:vxhV" # else -# define OPTSTR "AB:C:dD:F:imM:nP:qr:R:s:ST:vxhV" +# define OPTSTR "Ab:B:C:dD:F:imM:nP:qr:R:s:ST:vxhV" # endif # if defined HAVE_GETOPT_LONG # define MOREINFO "Try `%s --help' for more information.\n" @@ -136,6 +136,7 @@ int main(int argc, char *argv[]) { /* Long option, needs arg, flag, short option */ { "autoinc", 0, NULL, 'A' }, + { "bytes", 1, NULL, 'b' }, { "max-bytes", 1, NULL, 'B' }, #if defined HAVE_REGEX_H { "cmdline", 0, NULL, 'c' }, @@ -180,6 +181,9 @@ int main(int argc, char *argv[]) case 'A': /* --autoinc */ setenv("ZZUF_AUTOINC", "1", 1); break; + case 'b': /* --bytes */ + opts->bytes = optarg; + break; case 'B': /* --max-bytes */ opts->maxbytes = atoi(optarg); break; @@ -296,6 +300,11 @@ int main(int argc, char *argv[]) /* If asked to read from the standard input */ if(optind >= argc) { + if(opts->bytes) + _zz_bytes(opts->bytes); + + /* FIXME: protect and refuse are ignored */ + if(opts->endseed != opts->seed + 1) { printf("%s: seed ranges are incompatible with stdin fuzzing\n", @@ -334,6 +343,8 @@ int main(int argc, char *argv[]) setenv("ZZUF_EXCLUDE", exclude, 1); #endif + if(opts->bytes) + setenv("ZZUF_BYTES", opts->bytes, 1); if(opts->protect) setenv("ZZUF_PROTECT", opts->protect, 1); if(opts->refuse) @@ -1020,14 +1031,15 @@ static void usage(void) { #if defined HAVE_REGEX_H printf("Usage: zzuf [-AcdimnqSvx] [-s seed|-s start:stop] [-r ratio|-r min:max]\n"); - printf(" [-D delay] [-F forks] [-C crashes] [-B bytes]\n"); - printf(" [-T seconds] [-M bytes] [-P protect] [-R refuse]\n"); - printf(" [-I include] [-E exclude] [PROGRAM [--] [ARGS]...]\n"); #else printf("Usage: zzuf [-AdimnqSvx] [-s seed|-s start:stop] [-r ratio|-r min:max]\n"); - printf(" [-D delay] [-F forks] [-C crashes] [-B bytes]\n"); - printf(" [-T seconds] [-M bytes] [-P protect] [-R refuse]\n"); - printf(" [PROGRAM [--] [ARGS]...]\n"); +#endif + printf(" [-D delay] [-F forks] [-C crashes] [-B bytes] [-T seconds]\n"); + printf(" [-M bytes] [-b ranges] [-P protect] [-R refuse]\n"); +#if defined HAVE_REGEX_H + printf(" [-I include] [-E exclude] [PROGRAM [--] [ARGS]...]\n"); +#else + printf(" [PROGRAM [--] [ARGS]...]\n"); #endif # if defined HAVE_GETOPT_LONG printf(" zzuf -h | --help\n"); @@ -1041,6 +1053,7 @@ static void usage(void) printf("Mandatory arguments to long options are mandatory for short options too.\n"); # if defined HAVE_GETOPT_LONG printf(" -A, --autoinc increment seed each time a new file is opened\n"); + printf(" -b, --bytes only fuzz bytes at offsets within \n"); printf(" -B, --max-bytes kill children that output more than bytes\n"); #if defined HAVE_REGEX_H printf(" -c, --cmdline only fuzz files specified in the command line\n"); @@ -1076,6 +1089,7 @@ static void usage(void) printf(" -V, --version output version information and exit\n"); # else printf(" -A increment seed each time a new file is opened\n"); + printf(" -b only fuzz bytes at offsets within \n"); printf(" -B kill children that output more than bytes\n"); #if defined HAVE_REGEX_H printf(" -c only fuzz files specified in the command line\n");