zzuf/src/zzuf.c

1305 lines
40 KiB
C
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

/*
* zzuf - general purpose fuzzer
*
* Copyright © 2002—2015 Sam Hocevar <sam@hocevar.net>
* 2012 Kévin Szkudłapski <kszkudlapski@quarkslab.com>
*
* This program is free software. It comes without any warranty, to
* the extent permitted by applicable law. You can redistribute it
* and/or modify it under the terms of the Do What the Fuck You Want
* to Public License, Version 2, as published by the WTFPL Task Force.
* See http://www.wtfpl.net/ for more details.
*/
/*
* main.c: main program
*/
#include "config.h"
#define _INCLUDE_POSIX_SOURCE /* for STDERR_FILENO on HP-UX */
#define _POSIX_SOURCE /* for kill() on glibc systems */
#define _BSD_SOURCE /* for setenv() on glibc systems */
#if defined HAVE_STDINT_H
# include <stdint.h>
#elif defined HAVE_INTTYPES_H
# include <inttypes.h>
#endif
#include <stdio.h>
#include <stdlib.h>
#if defined HAVE_UNISTD_H
# include <unistd.h> /* for read(), write(), close() */
#endif
#if defined HAVE_REGEX_H
# if _WIN32
# include "util/regex.h"
# else
# include <regex.h>
# endif
#endif
#if defined HAVE_WINSOCK2_H
# include <winsock2.h> /* for fd_set */
#endif
#if defined HAVE_IO_H
# include <io.h>
#endif
#include <string.h>
#include <errno.h>
#include <signal.h>
#if defined HAVE_SYS_TIME_H
# include <sys/time.h>
#endif
#if defined HAVE_SYS_WAIT_H
# include <sys/wait.h>
#endif
#if defined HAVE_SYS_RESOURCE_H
# include <sys/resource.h> /* for RLIMIT_AS */
#endif
#include "common.h"
#include "opts.h"
#include "random.h"
#include "fd.h"
#include "fuzz.h"
#include "myfork.h"
#include "timer.h"
#include "util/getopt.h"
#include "util/md5.h"
#include "util/hex.h"
#if !defined SIGKILL
# define SIGKILL 9
#endif
#if defined RLIMIT_AS
# define ZZUF_RLIMIT_MEM RLIMIT_AS
#elif defined RLIMIT_VMEM
# define ZZUF_RLIMIT_MEM RLIMIT_VMEM
#elif defined RLIMIT_DATA
# define ZZUF_RLIMIT_MEM RLIMIT_DATA
#else
# undef ZZUF_RLIMIT_MEM
#endif
#if defined RLIMIT_CPU
# define ZZUF_RLIMIT_CPU RLIMIT_CPU
#else
# undef ZZUF_RLIMIT_CPU
#endif
static void loop_stdin(zzuf_opts_t *);
static void spawn_children(zzuf_opts_t *);
static void clean_children(zzuf_opts_t *);
static void read_children(zzuf_opts_t *);
#if !defined HAVE_SETENV
static void setenv(char const *, char const *, int);
#endif
#if defined HAVE_WAITPID
static char const *sig2name(int);
#endif
static void finfo(FILE *, zzuf_opts_t *, uint32_t);
#if defined HAVE_REGEX_H
static char *merge_regex(char *, char *);
static char *merge_file(char *, char *);
#endif
static void version(void);
static void usage(void);
#define ZZUF_FD_SET(fd, p_fdset, maxfd) \
if (fd >= 0) \
{ \
FD_SET((unsigned int)fd, p_fdset); \
if (fd > maxfd) \
maxfd = fd; \
}
#define ZZUF_FD_ISSET(fd, p_fdset) \
((fd >= 0) && (FD_ISSET(fd, p_fdset)))
#if defined _WIN32
static zzuf_mutex_t pipe_mutex = 0;
#endif
int main(int argc, char *argv[])
{
char *tmp;
#if defined HAVE_REGEX_H
char *include = NULL, *exclude = NULL;
int b_cmdline = 0;
#endif
int debug = 0, b_network = 0;
zzuf_opts_t *opts = zzuf_create_opts();
for (;;)
{
#if defined HAVE_REGEX_H
# define OPTSTR_REGEX "cE:I:"
#else
# define OPTSTR_REGEX ""
#endif
#if defined HAVE_SETRLIMIT && defined ZZUF_RLIMIT_MEM
# define OPTSTR_RLIMIT_MEM "M:"
#else
# define OPTSTR_RLIMIT_MEM ""
#endif
#if defined HAVE_SETRLIMIT && defined ZZUF_RLIMIT_CPU
# define OPTSTR_RLIMIT_CPU "T:"
#else
# define OPTSTR_RLIMIT_CPU ""
#endif
#define OPTSTR "+" OPTSTR_REGEX OPTSTR_RLIMIT_MEM OPTSTR_RLIMIT_CPU \
"a:Ab:B:C:dD:e:f:F:ij:l:mnO:p:P:qr:R:s:St:U:vxXhV"
#define MOREINFO "Try `%s --help' for more information.\n"
int option_index = 0;
static zzuf_option_t long_options[] =
{
/* Long option, needs arg, flag, short option */
{ "allow", 1, NULL, 'a' },
{ "autoinc", 0, NULL, 'A' },
{ "bytes", 1, NULL, 'b' },
{ "max-bytes", 1, NULL, 'B' },
#if defined HAVE_REGEX_H
{ "cmdline", 0, NULL, 'c' },
#endif
{ "max-crashes", 1, NULL, 'C' },
{ "debug", 0, NULL, 'd' },
{ "delay", 1, NULL, 'D' },
#if defined HAVE_REGEX_H
{ "exclude", 1, NULL, 'E' },
#endif
{ "fuzzing", 1, NULL, 'f' },
{ "stdin", 0, NULL, 'i' },
#if defined HAVE_REGEX_H
{ "include", 1, NULL, 'I' },
#endif
{ "jobs", 1, NULL, 'j' },
{ "list", 1, NULL, 'l' },
{ "md5", 0, NULL, 'm' },
#if defined HAVE_SETRLIMIT && defined ZZUF_RLIMIT_MEM
{ "max-memory", 1, NULL, 'M' },
#endif
{ "network", 0, NULL, 'n' },
{ "opmode", 1, NULL, 'O' },
{ "ports", 1, NULL, 'p' },
{ "protect", 1, NULL, 'P' },
{ "quiet", 0, NULL, 'q' },
{ "ratio", 1, NULL, 'r' },
{ "refuse", 1, NULL, 'R' },
{ "seed", 1, NULL, 's' },
{ "signal", 0, NULL, 'S' },
{ "max-time", 1, NULL, 't' },
#if defined HAVE_SETRLIMIT && defined ZZUF_RLIMIT_CPU
{ "max-cputime", 1, NULL, 'T' },
#endif
{ "max-usertime", 1, NULL, 'U' },
{ "verbose", 0, NULL, 'v' },
{ "check-exit", 0, NULL, 'x' },
{ "hex", 0, NULL, 'X' },
{ "help", 0, NULL, 'h' },
{ "version", 0, NULL, 'V' },
{ NULL, 0, NULL, 0 }
};
int c = zz_getopt(argc, argv, OPTSTR, long_options, &option_index);
if (c == -1)
break;
switch (c)
{
case 'a': /* --allow */
opts->allow = zz_optarg;
break;
case 'A': /* --autoinc */
setenv("ZZUF_AUTOINC", "1", 1);
break;
case 'b': /* --bytes */
opts->bytes = zz_optarg;
break;
case 'B': /* --max-bytes */
if (zz_optarg[0] == '=')
zz_optarg++;
opts->maxbytes = atoi(zz_optarg);
break;
#if defined HAVE_REGEX_H
case 'c': /* --cmdline */
b_cmdline = 1;
break;
#endif
case 'C': /* --max-crashes */
if (zz_optarg[0] == '=')
zz_optarg++;
opts->maxcrashes = atoi(zz_optarg);
if (opts->maxcrashes <= 0)
opts->maxcrashes = 0;
break;
case 'd': /* --debug */
debug++;
break;
case 'D': /* --delay */
if (zz_optarg[0] == '=')
zz_optarg++;
opts->delay = (int64_t)(atof(zz_optarg) * 1000000.0);
break;
#if defined HAVE_REGEX_H
case 'E': /* --exclude */
exclude = merge_regex(exclude, zz_optarg);
if (!exclude)
{
fprintf(stderr, "%s: invalid regex -- `%s'\n",
argv[0], zz_optarg);
zzuf_destroy_opts(opts);
return EXIT_FAILURE;
}
break;
#endif
case 'f': /* --fuzzing */
opts->fuzzing = zz_optarg;
break;
case 'F':
fprintf(stderr, "%s: `-F' is deprecated, use `-j'\n", argv[0]);
zzuf_destroy_opts(opts);
return EXIT_FAILURE;
case 'i': /* --stdin */
setenv("ZZUF_STDIN", "1", 1);
break;
#if defined HAVE_REGEX_H
case 'I': /* --include */
include = merge_regex(include, zz_optarg);
if (!include)
{
fprintf(stderr, "%s: invalid regex -- `%s'\n",
argv[0], zz_optarg);
zzuf_destroy_opts(opts);
return EXIT_FAILURE;
}
break;
#endif
case 'j': /* --jobs */
if (zz_optarg[0] == '=')
zz_optarg++;
opts->maxchild = atoi(zz_optarg) > 1 ? atoi(zz_optarg) : 1;
break;
case 'l': /* --list */
opts->list = zz_optarg;
break;
case 'm': /* --md5 */
opts->b_md5 = 1;
break;
#if defined HAVE_SETRLIMIT && defined ZZUF_RLIMIT_MEM
case 'M': /* --max-memory */
if (zz_optarg[0] == '=')
zz_optarg++;
opts->maxmem = atoi(zz_optarg);
break;
#endif
case 'n': /* --network */
setenv("ZZUF_NETWORK", "1", 1);
b_network = 1;
break;
case 'O': /* --opmode */
if (zz_optarg[0] == '=')
zz_optarg++;
if (!strcmp(zz_optarg, "preload"))
opts->opmode = OPMODE_PRELOAD;
else if (!strcmp(zz_optarg, "copy"))
opts->opmode = OPMODE_COPY;
else if (!strcmp(zz_optarg, "null"))
opts->opmode = OPMODE_NULL;
else
{
fprintf(stderr, "%s: invalid operating mode -- `%s'\n",
argv[0], zz_optarg);
zzuf_destroy_opts(opts);
return EXIT_FAILURE;
}
break;
case 'p': /* --ports */
opts->ports = zz_optarg;
break;
case 'P': /* --protect */
opts->protect = zz_optarg;
break;
case 'q': /* --quiet */
opts->b_quiet = 1;
break;
case 'r': /* --ratio */
if (zz_optarg[0] == '=')
zz_optarg++;
tmp = strchr(zz_optarg, ':');
opts->minratio = atof(zz_optarg);
opts->maxratio = tmp ? atof(tmp + 1) : opts->minratio;
break;
case 'R': /* --refuse */
opts->refuse = zz_optarg;
break;
case 's': /* --seed */
if (zz_optarg[0] == '=')
zz_optarg++;
tmp = strchr(zz_optarg, ':');
opts->seed = atol(zz_optarg);
opts->endseed = tmp ? tmp[1] ? (uint32_t)atol(tmp + 1)
: (uint32_t)-1L
: opts->seed + 1;
break;
case 'S': /* --signal */
setenv("ZZUF_SIGNAL", "1", 1);
break;
case 't': /* --max-time */
if (zz_optarg[0] == '=')
zz_optarg++;
opts->maxtime = (int64_t)atoi(zz_optarg) * 1000000;
break;
#if defined HAVE_SETRLIMIT && defined ZZUF_RLIMIT_CPU
case 'T': /* --max-cputime */
if (zz_optarg[0] == '=')
zz_optarg++;
opts->maxcpu = (int)(atof(zz_optarg) + 0.5);
break;
#endif
case 'U': /* --max-usertime */
if (zz_optarg[0] == '=')
zz_optarg++;
opts->maxusertime = (int64_t)(atof(zz_optarg) * 1000000.0);
break;
case 'x': /* --check-exit */
opts->b_checkexit = 1;
break;
case 'X': /* --hex */
opts->b_hex = 1;
break;
case 'v': /* --verbose */
opts->b_verbose = 1;
break;
case 'h': /* --help */
usage();
zzuf_destroy_opts(opts);
return 0;
case 'V': /* --version */
version();
zzuf_destroy_opts(opts);
return 0;
default:
fprintf(stderr, "%s: invalid option -- %c\n", argv[0], c);
printf(MOREINFO, argv[0]);
zzuf_destroy_opts(opts);
return EXIT_FAILURE;
}
}
if (opts->b_md5 && opts->b_hex)
{
fprintf(stderr, "%s: MD5 hash (-m) and hexadecimal dump (-X) are "
"incompatible\n", argv[0]);
printf(MOREINFO, argv[0]);
zzuf_destroy_opts(opts);
return EXIT_FAILURE;
}
if (opts->ports && !b_network)
{
fprintf(stderr, "%s: port option (-p) requires network fuzzing (-n)\n",
argv[0]);
printf(MOREINFO, argv[0]);
zzuf_destroy_opts(opts);
return EXIT_FAILURE;
}
if (opts->allow && !b_network)
{
fprintf(stderr, "%s: allow option (-a) requires network fuzzing (-n)\n",
argv[0]);
printf(MOREINFO, argv[0]);
zzuf_destroy_opts(opts);
return EXIT_FAILURE;
}
zzuf_set_ratio(opts->minratio, opts->maxratio);
zzuf_set_seed(opts->seed);
if (opts->fuzzing)
_zz_fuzzing(opts->fuzzing);
if (opts->bytes)
_zz_bytes(opts->bytes);
if (opts->list)
_zz_list(opts->list);
if (opts->protect)
zzuf_protect_range(opts->protect);
if (opts->refuse)
zzuf_refuse_range(opts->refuse);
/* Needed for stdin mode and for copy opmode. */
_zz_fd_init();
/*
* Mode 1: asked to read from the standard input
*/
if (zz_optind >= argc)
{
if (opts->b_verbose)
{
finfo(stderr, opts, opts->seed);
fprintf(stderr, "reading from stdin\n");
}
if (opts->endseed != opts->seed + 1)
{
fprintf(stderr, "%s: seed ranges are incompatible with "
"stdin fuzzing\n", argv[0]);
printf(MOREINFO, argv[0]);
zzuf_destroy_opts(opts);
return EXIT_FAILURE;
}
loop_stdin(opts);
}
/*
* Mode 2: asked to launch programs
*/
else
{
#if defined HAVE_REGEX_H
if (b_cmdline)
{
int dashdash = 0;
for (int i = zz_optind + 1; i < argc; ++i)
{
if (dashdash)
include = merge_file(include, argv[i]);
else if (!strcmp("--", argv[i]))
dashdash = 1;
else if (argv[i][0] != '-')
include = merge_file(include, argv[i]);
}
}
if (include)
setenv("ZZUF_INCLUDE", include, 1);
if (exclude)
setenv("ZZUF_EXCLUDE", exclude, 1);
#endif
setenv("ZZUF_DEBUG", debug ? debug > 1 ? "2" : "1" : "0", 1);
if (opts->fuzzing)
setenv("ZZUF_FUZZING", opts->fuzzing, 1);
if (opts->bytes)
setenv("ZZUF_BYTES", opts->bytes, 1);
if (opts->list)
setenv("ZZUF_LIST", opts->list, 1);
if (opts->ports)
setenv("ZZUF_PORTS", opts->ports, 1);
if (opts->allow && opts->allow[0] == '!')
setenv("ZZUF_DENY", opts->allow, 1);
else if (opts->allow)
setenv("ZZUF_ALLOW", opts->allow, 1);
if (opts->protect)
setenv("ZZUF_PROTECT", opts->protect, 1);
if (opts->refuse)
setenv("ZZUF_REFUSE", opts->refuse, 1);
#if defined HAVE_SETRLIMIT && defined ZZUF_RLIMIT_MEM
if (opts->maxmem >= 0)
{
char buf[32];
snprintf(buf, 32, "%i", opts->maxmem);
setenv("ZZUF_MEMORY", buf, 1);
}
#endif
/* Allocate memory for children handling */
opts->child = malloc(opts->maxchild * sizeof(zzuf_child_t));
for (int i = 0; i < opts->maxchild; ++i)
{
opts->child[i].status = STATUS_FREE;
memset(opts->child[i].fd, -1, sizeof(opts->child->fd));
}
opts->nchild = 0;
/* Create new argv */
opts->oldargc = argc;
opts->oldargv = argv;
for (int i = 0; i < opts->maxchild; ++i)
{
int len = argc - zz_optind;
opts->child[i].newargv = malloc((len + 1) * sizeof(char *));
memcpy(opts->child[i].newargv, argv + zz_optind,
len * sizeof(char *));
opts->child[i].newargv[len] = (char *)NULL;
}
/* Main loop */
while (opts->nchild || opts->seed < opts->endseed)
{
/* Spawn new children, if necessary */
spawn_children(opts);
/* Cleanup dead or dying children */
clean_children(opts);
/* Read data from children */
read_children(opts);
if (opts->maxcrashes && opts->crashes >= opts->maxcrashes
&& opts->nchild == 0)
{
if (opts->b_verbose)
fprintf(stderr,
"zzuf: maximum crash count reached, exiting\n");
break;
}
if (opts->maxtime && zzuf_time() - opts->starttime >= opts->maxtime
&& opts->nchild == 0)
{
if (opts->b_verbose)
fprintf(stderr,
"zzuf: maximum running time reached, exiting\n");
break;
}
}
}
int ret = opts->crashes ? EXIT_FAILURE : EXIT_SUCCESS;
/* Clean up */
_zz_fd_fini();
zzuf_destroy_opts(opts);
return ret;
}
static void loop_stdin(zzuf_opts_t *opts)
{
zzuf_md5sum_t *md5 = NULL;
zzuf_hexdump_t *hex = NULL;
if (opts->b_md5)
md5 = zzuf_create_md5();
else if (opts->b_hex)
hex = zzuf_create_hex();
_zz_register(0);
for (int total = 0; ; )
{
uint8_t buf[BUFSIZ];
int toread = BUFSIZ, off = 0;
if (opts->maxbytes >= 0)
{
if (total >= opts->maxbytes)
break;
if (total + BUFSIZ >= opts->maxbytes)
toread = opts->maxbytes - total;
}
int ret = read(0, buf, toread);
if (ret <= 0)
break;
total += ret;
_zz_fuzz(0, buf, ret);
_zz_addpos(0, ret);
if (opts->b_md5)
zz_md5_add(md5, buf, ret);
else if (opts->b_hex)
zz_hex_add(hex, buf, ret);
else while (ret)
{
int nw = 0;
if ((nw = write(1, buf + off, (unsigned int)ret)) < 0)
break;
ret -= nw;
off += nw;
}
}
if (opts->b_md5)
{
uint8_t md5sum[16];
zzuf_destroy_md5(md5sum, md5);
finfo(stdout, opts, opts->seed);
fprintf(stdout, "%.02x%.02x%.02x%.02x%.02x%.02x%.02x%.02x%.02x%.02x"
"%.02x%.02x%.02x%.02x%.02x%.02x\n", md5sum[0], md5sum[1],
md5sum[2], md5sum[3], md5sum[4], md5sum[5], md5sum[6],
md5sum[7], md5sum[8], md5sum[9], md5sum[10], md5sum[11],
md5sum[12], md5sum[13], md5sum[14], md5sum[15]);
fflush(stdout);
}
else if (opts->b_hex)
{
zzuf_destroy_hex(hex);
}
_zz_unregister(0);
}
static void finfo(FILE *fp, zzuf_opts_t *opts, uint32_t seed)
{
if (opts->minratio == opts->maxratio)
fprintf(fp, "zzuf[s=%i,r=%g]: ", seed, opts->minratio);
else
fprintf(fp, "zzuf[s=%i,r=%g:%g]: ", seed,
opts->minratio, opts->maxratio);
}
#if defined HAVE_REGEX_H
static char *merge_file(char *regex, char *file)
{
char *newfile = malloc(5 + 2 * strlen(file) + 1 + 1);
char *tmp = newfile;
*tmp++ = '(';
*tmp++ = '^';
*tmp++ = '|';
*tmp++ = '/';
*tmp++ = ')';
while (*file)
{
if (strchr("^.[$()|*+?{\\", *file))
*tmp++ = '\\';
*tmp++ = *file++;
}
*tmp++ = '$';
*tmp++ = '\0';
tmp = merge_regex(regex, newfile);
free(newfile);
return tmp;
}
static char *merge_regex(char *regex, char *string)
{
if (regex)
{
regex = realloc(regex, strlen(regex) + strlen(string) + 1 + 1);
sprintf(regex + strlen(regex) - 1, "|%s)", string);
}
else
{
regex = malloc(1 + strlen(string) + 1 + 1);
sprintf(regex, "(%s)", string);
}
regex_t optre;
if (regcomp(&optre, regex, REG_EXTENDED) != 0)
{
free(regex);
return NULL;
}
regfree(&optre);
return regex;
}
#endif
static void spawn_children(zzuf_opts_t *opts)
{
int64_t now = zzuf_time();
if (opts->nchild == opts->maxchild)
return; /* no slot */
if (opts->seed == opts->endseed)
return; /* job finished */
if (opts->maxcrashes && opts->crashes >= opts->maxcrashes)
return; /* all jobs crashed */
if (opts->maxtime && now - opts->starttime >= opts->maxtime)
return; /* run time exceeded */
if (opts->delay > 0 && opts->lastlaunch + opts->delay > now)
return; /* too early */
/* Find the empty slot */
int slot = 0;
while (slot < opts->maxchild && opts->child[slot].status != STATUS_FREE)
++slot;
/* Prepare required files, if necessary */
if (opts->opmode == OPMODE_COPY)
{
char tmpname[4096];
char *tmpdir;
tmpdir = getenv("TEMP");
if (!tmpdir || !*tmpdir)
tmpdir = "/tmp";
int k = 0;
for (int j = zz_optind + 1; j < opts->oldargc; ++j)
{
FILE *fpin = fopen(opts->oldargv[j], "r");
if (!fpin)
continue;
#ifdef _WIN32
sprintf(tmpname, "%s/zzuf.%i.XXXXXX", tmpdir, GetCurrentProcessId());
int fdout = _open(mktemp(tmpname), _O_RDWR, 0600);
#else
sprintf(tmpname, "%s/zzuf.%i.XXXXXX", tmpdir, (int)getpid());
int fdout = mkstemp(tmpname);
#endif
if (fdout < 0)
{
fclose(fpin);
continue;
}
opts->child[slot].newargv[j - zz_optind] = strdup(tmpname);
_zz_register(k);
while (!feof(fpin))
{
uint8_t buf[BUFSIZ];
size_t n = fread(buf, 1, BUFSIZ, fpin);
if (n <= 0)
break;
_zz_fuzz(k, buf, n);
_zz_addpos(k, n);
write(fdout, buf, n);
}
_zz_unregister(k);
fclose(fpin);
close(fdout);
++k;
}
}
/* Launch process */
if (myfork(&opts->child[slot], opts) < 0)
{
fprintf(stderr, "error launching `%s'\n", opts->child[slot].newargv[0]);
opts->seed++;
/* FIXME: clean up OPMODE_COPY files here */
return;
}
/* Were the parent, acknowledge spawn */
opts->child[slot].date = now;
opts->child[slot].bytes = 0;
opts->child[slot].seed = opts->seed;
opts->child[slot].ratio = zzuf_get_ratio();
opts->child[slot].status = STATUS_RUNNING;
if (opts->b_md5)
opts->child[slot].md5 = zzuf_create_md5();
else if (opts->b_hex)
opts->child[slot].hex = zzuf_create_hex();
if (opts->b_verbose)
{
finfo(stderr, opts, opts->child[slot].seed);
fprintf(stderr, "launched `%s'\n", opts->child[slot].newargv[0]);
}
opts->lastlaunch = now;
opts->nchild++;
opts->seed++;
zzuf_set_seed(opts->seed);
}
static void clean_children(zzuf_opts_t *opts)
{
#if defined HAVE_KILL || defined HAVE_WINDOWS_H
int64_t now = zzuf_time();
#endif
#if defined HAVE_KILL || defined HAVE_WINDOWS_H
/* Terminate children if necessary */
for (int i = 0; i < opts->maxchild; ++i)
{
if (opts->child[i].status == STATUS_RUNNING
&& opts->maxbytes >= 0
&& opts->child[i].bytes > opts->maxbytes)
{
if (opts->b_verbose)
{
finfo(stderr, opts, opts->child[i].seed);
fprintf(stderr, "data output exceeded, sending SIGTERM\n");
}
#if defined HAVE_KILL
kill(opts->child[i].pid, SIGTERM);
#else
/* We must invalidate fd */
memset(opts->child[i].fd, -1, sizeof(opts->child[i].fd));
TerminateProcess(opts->child[i].process_handle, 0x0);
#endif
opts->child[i].date = now;
opts->child[i].status = STATUS_SIGTERM;
}
if (opts->child[i].status == STATUS_RUNNING
&& opts->maxusertime >= 0
&& now > opts->child[i].date + opts->maxusertime)
{
if (opts->b_verbose)
{
finfo(stderr, opts, opts->child[i].seed);
fprintf(stderr, "running time exceeded, sending SIGTERM\n");
}
#if defined HAVE_KILL
kill(opts->child[i].pid, SIGTERM);
#else
/* We must invalidate fd */
memset(opts->child[i].fd, -1, sizeof(opts->child[i].fd));
TerminateProcess(opts->child[i].process_handle, 0x0);
#endif
opts->child[i].date = now;
opts->child[i].status = STATUS_SIGTERM;
}
}
/* Kill children if necessary (still there after 2 seconds) */
for (int i = 0; i < opts->maxchild; ++i)
{
if (opts->child[i].status == STATUS_SIGTERM
&& now > opts->child[i].date + 2000000)
{
if (opts->b_verbose)
{
finfo(stderr, opts, opts->child[i].seed);
fprintf(stderr, "not responding, sending SIGKILL\n");
}
#if defined HAVE_KILL
kill(opts->child[i].pid, SIGKILL);
#else
TerminateProcess(opts->child[i].process_handle, 0x0);
#endif
opts->child[i].status = STATUS_SIGKILL;
}
}
#endif
/* Collect dead children */
for (int i = 0; i < opts->maxchild; ++i)
{
uint8_t md5sum[16];
#if defined HAVE_WAITPID
int status;
pid_t pid;
#endif
if (opts->child[i].status != STATUS_SIGKILL
&& opts->child[i].status != STATUS_SIGTERM
&& opts->child[i].status != STATUS_EOF)
continue;
#if defined HAVE_WAITPID
pid = waitpid(opts->child[i].pid, &status, WNOHANG);
if (pid <= 0)
continue;
if (opts->b_checkexit && WIFEXITED(status) && WEXITSTATUS(status))
{
finfo(stderr, opts, opts->child[i].seed);
fprintf(stderr, "exit %i\n", WEXITSTATUS(status));
opts->crashes++;
}
else if (WIFSIGNALED(status)
&& !(WTERMSIG(status) == SIGTERM
&& opts->child[i].status == STATUS_SIGTERM))
{
char const *message = "";
if (WTERMSIG(status) == SIGKILL && opts->maxmem >= 0)
message = " (memory exceeded?)";
# if defined SIGXCPU
else if (WTERMSIG(status) == SIGXCPU && opts->maxcpu >= 0)
message = " (CPU time exceeded?)";
# endif
else if (WTERMSIG(status) == SIGKILL && opts->maxcpu >= 0)
message = " (CPU time exceeded?)";
finfo(stderr, opts, opts->child[i].seed);
fprintf(stderr, "signal %i%s%s\n",
WTERMSIG(status), sig2name(WTERMSIG(status)), message);
opts->crashes++;
}
else if (opts->b_verbose)
{
finfo(stderr, opts, opts->child[i].seed);
if (WIFSIGNALED(status))
fprintf(stderr, "signal %i%s\n",
WTERMSIG(status), sig2name(WTERMSIG(status)));
else
fprintf(stderr, "exit %i\n", WEXITSTATUS(status));
}
#elif defined _WIN32
{
DWORD exit_code;
if (GetExitCodeProcess(opts->child[i].process_handle, &exit_code))
{
if (exit_code == STILL_ACTIVE) continue; /* The process is still active, we don't do anything */
/*
* The main problem with GetExitCodeProcess is it returns either returned parameter value of
* ExitProcess/TerminateProcess, or the unhandled exception (which is what we're looking for)
*/
switch (exit_code)
{
case EXCEPTION_ACCESS_VIOLATION: fprintf(stderr, "child(%d) unhandled exception: Access Violation\n", opts->child[i].pid); break;
default: fprintf(stderr, "child(%d) exited with code %#08x\n", opts->child[i].pid, exit_code); break;
}
}
if (opts->child[i].status != STATUS_RUNNING)
{
TerminateProcess(opts->child[i].process_handle, 0);
}
}
#else
/* waitpid() is not available. Don't kill the process. */
continue;
#endif
for (int j = 0; j < 3; ++j)
if (opts->child[i].fd[j] >= 0)
close(opts->child[i].fd[j]);
if (opts->opmode == OPMODE_COPY)
{
for (int j = zz_optind + 1; j < opts->oldargc; ++j)
{
if (opts->child[i].newargv[j - zz_optind] != opts->oldargv[j])
{
unlink(opts->child[i].newargv[j - zz_optind]);
free(opts->child[i].newargv[j - zz_optind]);
opts->child[i].newargv[j - zz_optind] = opts->oldargv[j];
}
}
}
if (opts->b_md5)
{
zzuf_destroy_md5(md5sum, opts->child[i].md5);
finfo(stdout, opts, opts->child[i].seed);
fprintf(stdout, "%.02x%.02x%.02x%.02x%.02x%.02x%.02x%.02x%.02x"
"%.02x%.02x%.02x%.02x%.02x%.02x%.02x\n", md5sum[0],
md5sum[1], md5sum[2], md5sum[3], md5sum[4], md5sum[5],
md5sum[6], md5sum[7], md5sum[8], md5sum[9], md5sum[10],
md5sum[11], md5sum[12], md5sum[13], md5sum[14], md5sum[15]);
fflush(stdout);
}
else if (opts->b_hex)
{
zzuf_destroy_hex(opts->child[i].hex);
}
opts->child[i].status = STATUS_FREE;
opts->nchild--;
}
}
#ifdef _WIN32
/* This structure contains useful information about data sent from fuzzed applications */
struct child_overlapped
{
OVERLAPPED overlapped;
uint8_t buf[BUFSIZ];
zzuf_opts_t * opts;
int child_no;
int fd_no;
};
/* This callback is called when fuzzed applications write in fd out, err or debug */
static void __stdcall read_child(DWORD err_code, DWORD nbr_of_bytes_transfered,
LPOVERLAPPED overlapped)
{
struct child_overlapped * co = (struct child_overlapped *)overlapped;
/* TODO: handle more cases like ERROR_MORE_DATA */
if (err_code != ERROR_SUCCESS)
return;
zzuf_mutex_lock(&pipe_mutex);
switch (co->fd_no)
{
case 0: /* debug fd */
write(1, "dbg: ", 4);
case 1: /* out */
write(1, co->buf, nbr_of_bytes_transfered); break;
case 2: /* err */
write(2, co->buf, nbr_of_bytes_transfered); break;
default: break;
}
zzuf_mutex_unlock(&pipe_mutex);
if (co->fd_no != 0) /* either out or err fd */
co->opts->child[co->child_no].bytes += nbr_of_bytes_transfered;
if (co->opts->b_md5 && co->fd_no == 2)
zz_md5_add(co->opts->child[co->child_no].md5, co->buf, nbr_of_bytes_transfered);
else if (co->opts->b_hex && co->fd_no == 2)
zz_hex_add(co->opts->child[co->child_no].hex, co->buf, nbr_of_bytes_transfered);
free(co); /* clean up allocated data */
}
/* Since on windows select doesn't support file HANDLE, we use IOCP */
static void read_children(zzuf_opts_t *opts)
{
HANDLE *children_handle, * cur_child_handle;
size_t fd_number = opts->maxchild * 3;
cur_child_handle = children_handle = malloc(sizeof(*children_handle) * fd_number);
for (size_t i = 0; i < fd_number; ++i)
children_handle[i] = INVALID_HANDLE_VALUE;
for (int i = 0; i < opts->maxchild; ++i)
for (int j = 0; j < 3; ++j)
{
struct child_overlapped * co;
HANDLE h = (opts->child[i].fd[j] == -1) ? INVALID_HANDLE_VALUE : (HANDLE)_get_osfhandle(opts->child[i].fd[j]);
if (opts->child[i].status != STATUS_RUNNING
|| opts->child[i].fd[j] == -1
|| h == INVALID_HANDLE_VALUE)
{
fd_number--;
continue;
}
co = malloc(sizeof(*co));
ZeroMemory(co, sizeof(*co));
*cur_child_handle = co->overlapped.hEvent = CreateEvent(NULL, FALSE, FALSE, NULL);
co->child_no = i;
co->fd_no = j;
co->opts = opts;
if (!ReadFileEx(h, co->buf, sizeof(co->buf), (LPOVERLAPPED)co, read_child))
{
/* End of file reached */
close(opts->child[i].fd[j]);
opts->child[i].fd[j] = -1;
if (opts->child[i].fd[0] == -1
&& opts->child[i].fd[1] == -1
&& opts->child[i].fd[2] == -1)
opts->child[i].status = STATUS_EOF;
}
cur_child_handle++;
}
if (fd_number == 0)
return;
/* FIXME: handle error */
WaitForMultipleObjectsEx(fd_number, children_handle, FALSE, 1000, TRUE);
}
#else
static void read_children(zzuf_opts_t *opts)
{
struct timeval tv;
fd_set fdset;
int maxfd = 0;
/* Read data from all sockets */
FD_ZERO(&fdset);
for (int i = 0; i < opts->maxchild; ++i)
{
if (opts->child[i].status != STATUS_RUNNING)
continue;
for (int j = 0; j < 3; ++j)
ZZUF_FD_SET(opts->child[i].fd[j], &fdset, maxfd);
}
tv.tv_sec = 0;
tv.tv_usec = 1000;
errno = 0;
int ret = select(maxfd + 1, &fdset, NULL, NULL, &tv);
if (ret < 0 && errno)
perror("select");
if (ret <= 0)
return;
for (int i = 0; i < opts->maxchild; ++i)
for (int j = 0; j < 3; ++j)
{
uint8_t buf[BUFSIZ];
if (opts->child[i].status != STATUS_RUNNING)
continue;
if (!ZZUF_FD_ISSET(opts->child[i].fd[j], &fdset))
continue;
ret = read(opts->child[i].fd[j], buf, BUFSIZ - 1);
if (ret > 0)
{
/* We got data */
if (j != 0)
opts->child[i].bytes += ret;
if (opts->b_md5 && j == 2)
zz_md5_add(opts->child[i].md5, buf, ret);
else if (opts->b_hex && j == 2)
zz_hex_add(opts->child[i].hex, buf, ret);
else if (!opts->b_quiet || j == 0)
write((j < 2) ? STDERR_FILENO : STDOUT_FILENO, buf, ret);
}
else if (ret == 0)
{
/* End of file reached */
close(opts->child[i].fd[j]);
opts->child[i].fd[j] = -1;
if (opts->child[i].fd[0] == -1
&& opts->child[i].fd[1] == -1
&& opts->child[i].fd[2] == -1)
opts->child[i].status = STATUS_EOF;
}
}
}
#endif
#if !defined HAVE_SETENV
static void setenv(char const *name, char const *value, int overwrite)
{
if (!overwrite && getenv(name))
return;
char *str = malloc(strlen(name) + 1 + strlen(value) + 1);
sprintf(str, "%s=%s", name, value);
putenv(str);
}
#endif
#if defined HAVE_WAITPID
static char const *sig2name(int signum)
{
switch (signum)
{
#ifdef SIGQUIT
case SIGQUIT: return " (SIGQUIT)"; /* 3 */
#endif
case SIGILL: return " (SIGILL)"; /* 4 */
#ifdef SIGTRAP
case SIGTRAP: return " (SIGTRAP)"; /* 5 */
#endif
case SIGABRT: return " (SIGABRT)"; /* 6 */
#ifdef SIGBUS
case SIGBUS: return " (SIGBUS)"; /* 7 */
#endif
case SIGFPE: return " (SIGFPE)"; /* 8 */
case SIGSEGV: return " (SIGSEGV)"; /* 11 */
case SIGPIPE: return " (SIGPIPE)"; /* 13 */
#ifdef SIGEMT
case SIGEMT: return " (SIGEMT)"; /* ? */
#endif
#ifdef SIGXCPU
case SIGXCPU: return " (SIGXCPU)"; /* 24 */
#endif
#ifdef SIGXFSZ
case SIGXFSZ: return " (SIGXFSZ)"; /* 25 */
#endif
#ifdef SIGSYS
case SIGSYS: return " (SIGSYS)"; /* 31 */
#endif
}
return "";
}
#endif
static void version(void)
{
printf("zzuf %s\n", PACKAGE_VERSION);
printf("Copyright © 2002—2015 Sam Hocevar <sam@hocevar.net>\n");
printf("This program is free software. It comes without any warranty, to the extent\n");
printf("permitted by applicable law. You can redistribute it and/or modify it under\n");
printf("the terms of the Do What the Fuck You Want to Public License, Version 2, as\n");
printf("published by the WTFPL Task Force. See http://www.wtfpl.net/ for more details.\n");
printf("\n");
printf("Written by Sam Hocevar. Report bugs to <sam@hocevar.net>.\n");
}
static void usage(void)
{
#if defined HAVE_REGEX_H
printf("Usage: zzuf [-aAcdimnqSvx] [-s seed|-s start:stop] [-r ratio|-r min:max]\n");
#else
printf("Usage: zzuf [-aAdimnqSvx] [-s seed|-s start:stop] [-r ratio|-r min:max]\n");
#endif
printf(" [-f mode] [-D delay] [-j jobs] [-C crashes] [-B bytes] [-a list]\n");
printf(" [-t seconds]");
#if defined HAVE_SETRLIMIT && defined ZZUF_RLIMIT_CPU
printf( " [-T seconds]");
#endif
#if defined HAVE_SETRLIMIT && defined ZZUF_RLIMIT_MEM
printf( " [-M mebibytes]");
#endif
printf( " [-b ranges] [-p ports]\n");
printf(" [-P protect] [-R refuse] [-l list]");
#if defined HAVE_REGEX_H
printf( " [-I include] [-E exclude]");
#endif
printf(" [-O mode]\n");
printf("\n");
printf(" [PROGRAM [--] [ARGS]...]\n");
printf(" zzuf -h | --help\n");
printf(" zzuf -V | --version\n");
printf("Run PROGRAM with optional arguments ARGS and fuzz its input.\n");
printf("\n");
printf("Mandatory arguments to long options are mandatory for short options too.\n");
printf(" -a, --allow <list> only fuzz network input for IPs in <list>\n");
printf(" ... !<list> do not fuzz network input for IPs in <list>\n");
printf(" -A, --autoinc increment seed each time a new file is opened\n");
printf(" -b, --bytes <ranges> only fuzz bytes at offsets within <ranges>\n");
printf(" -B, --max-bytes <n> kill children that output more than <n> bytes\n");
#if defined HAVE_REGEX_H
printf(" -c, --cmdline only fuzz files specified in the command line\n");
#endif
printf(" -C, --max-crashes <n> stop after <n> children have crashed (default 1)\n");
printf(" -d, --debug print debug messages (twice for more verbosity)\n");
printf(" -D, --delay delay between forks\n");
#if defined HAVE_REGEX_H
printf(" -E, --exclude <regex> do not fuzz files matching <regex>\n");
#endif
printf(" -f, --fuzzing <mode> use fuzzing mode <mode> ([xor] set unset)\n");
printf(" -i, --stdin fuzz standard input\n");
#if defined HAVE_REGEX_H
printf(" -I, --include <regex> only fuzz files matching <regex>\n");
#endif
printf(" -j, --jobs <n> number of simultaneous jobs (default 1)\n");
printf(" -l, --list <list> only fuzz Nth descriptor with N in <list>\n");
printf(" -m, --md5 compute the output's MD5 hash\n");
#if defined HAVE_SETRLIMIT && defined ZZUF_RLIMIT_MEM
printf(" -M, --max-memory <n> maximum child virtual memory in MiB (default %u)\n", DEFAULT_MEM);
#endif
printf(" -n, --network fuzz network input\n");
printf(" -O, --opmode <mode> use operating mode <mode> ([preload] copy null)\n");
printf(" -p, --ports <list> only fuzz network destination ports in <list>\n");
printf(" -P, --protect <list> protect bytes and characters in <list>\n");
printf(" -q, --quiet do not print children's messages\n");
printf(" -r, --ratio <ratio> bit fuzzing ratio (default %g)\n", DEFAULT_RATIO);
printf(" ... <start:stop> specify a ratio range\n");
printf(" -R, --refuse <list> refuse bytes and characters in <list>\n");
printf(" -s, --seed <seed> random seed (default %i)\n", DEFAULT_SEED);
printf(" ... <start:stop> specify a seed range\n");
printf(" -S, --signal prevent children from diverting crashing signals\n");
printf(" -t, --max-time <n> stop spawning children after <n> seconds\n");
printf(" -T, --max-cputime <n> kill children that use more than <n> CPU seconds\n");
printf(" -U, --max-usertime <n> kill children that run for more than <n> seconds\n");
printf(" -v, --verbose print information during the run\n");
printf(" -x, --check-exit report processes that exit with a non-zero status\n");
printf(" -X, --hex convert program output to hexadecimal\n");
printf(" -h, --help display this help and exit\n");
printf(" -V, --version output version information and exit\n");
printf("\n");
printf("Written by Sam Hocevar. Report bugs to <sam@hocevar.net>.\n");
}