source: zzuf/trunk/src/zzuf.c @ 1537

Last change on this file since 1537 was 1537, checked in by Sam Hocevar, 14 years ago
  • Do not count debug bytes in the --max-bytes count.
  • Property svn:keywords set to Id
File size: 16.7 KB
Line 
1/*
2 *  zzuf - general purpose fuzzer
3 *  Copyright (c) 2006 Sam Hocevar <sam@zoy.org>
4 *                All Rights Reserved
5 *
6 *  $Id: zzuf.c 1537 2007-01-02 13:26:32Z sam $
7 *
8 *  This program is free software. It comes without any warranty, to
9 *  the extent permitted by applicable law. You can redistribute it
10 *  and/or modify it under the terms of the Do What The Fuck You Want
11 *  To Public License, Version 2, as published by Sam Hocevar. See
12 *  http://sam.zoy.org/wtfpl/COPYING for more details.
13 */
14
15/*
16 *  main.c: main program
17 */
18
19#include "config.h"
20
21#if defined HAVE_STDINT_H
22#   include <stdint.h>
23#elif defined HAVE_INTTYPES_H
24#   include <inttypes.h>
25#endif
26#if defined(HAVE_GETOPT_H)
27#   include <getopt.h>
28#endif
29#include <stdio.h>
30#include <stdlib.h>
31#include <unistd.h>
32#include <regex.h>
33#include <string.h>
34#include <signal.h>
35#include <errno.h>
36#include <sys/time.h>
37#include <time.h>
38#include <sys/wait.h>
39
40#include "random.h"
41#include "libzzuf.h"
42
43static void spawn_child(char **);
44static char *merge_regex(char *, char *);
45static char *merge_file(char *, char *);
46static void set_ld_preload(char const *);
47static void version(void);
48#if defined(HAVE_GETOPT_H)
49static void usage(void);
50#endif
51
52struct child_list
53{
54    enum status
55    {
56        STATUS_FREE,
57        STATUS_RUNNING,
58        STATUS_SIGTERM,
59        STATUS_SIGKILL,
60        STATUS_EOF,
61    } status;
62
63    pid_t pid;
64    int fd[3]; /* 0 is debug, 1 is stderr, 2 is stdout */
65    int bytes, seed;
66    time_t date;
67} *child_list;
68int parallel = 1, child_count = 0;
69
70int seed = 0;
71int endseed = 1;
72
73#define ZZUF_FD_SET(fd, p_fdset, maxfd) \
74    if(fd >= 0) \
75    { \
76        FD_SET(fd, p_fdset); \
77        if(fd > maxfd) \
78            maxfd = fd; \
79    }
80
81#define ZZUF_FD_ISSET(fd, p_fdset) \
82    ((fd >= 0) && (FD_ISSET(fd, p_fdset)))
83
84int main(int argc, char *argv[])
85{
86    char **newargv;
87    char *parser, *include = NULL, *exclude = NULL;
88    int i, j, quiet = 0, maxbytes = -1, cmdline = 0;
89    double maxtime = -1.0;
90
91#if defined(HAVE_GETOPT_H)
92    for(;;)
93    {
94#   ifdef HAVE_GETOPT_LONG
95#       define MOREINFO "Try `%s --help' for more information.\n"
96        int option_index = 0;
97        static struct option long_options[] =
98        {
99            /* Long option, needs arg, flag, short option */
100            { "max-bytes", 1, NULL, 'B' },
101            { "cmdline",   0, NULL, 'c' },
102            { "debug",     0, NULL, 'd' },
103            { "exclude",   1, NULL, 'E' },
104            { "fork",      1, NULL, 'F' },
105            { "help",      0, NULL, 'h' },
106            { "stdin",     0, NULL, 'i' },
107            { "include",   1, NULL, 'I' },
108            { "quiet",     0, NULL, 'q' },
109            { "ratio",     1, NULL, 'r' },
110            { "seed",      1, NULL, 's' },
111            { "signal",    0, NULL, 'S' },
112            { "max-time",  1, NULL, 'T' },
113            { "version",   0, NULL, 'v' },
114        };
115        int c = getopt_long(argc, argv, "B:cdE:F:hiI:qr:s:ST:v",
116                            long_options, &option_index);
117#   else
118#       define MOREINFO "Try `%s -h' for more information.\n"
119        int c = getopt(argc, argv, "B:cdE:F:hiI:qr:s:ST:v");
120#   endif
121        if(c == -1)
122            break;
123
124        switch(c)
125        {
126        case 'I': /* --include */
127            include = merge_regex(include, optarg);
128            if(!include)
129            {
130                printf("%s: invalid regex -- `%s'\n", argv[0], optarg);
131                return EXIT_FAILURE;
132            }
133            break;
134        case 'E': /* --exclude */
135            exclude = merge_regex(exclude, optarg);
136            if(!exclude)
137            {
138                printf("%s: invalid regex -- `%s'\n", argv[0], optarg);
139                return EXIT_FAILURE;
140            }
141            break;
142        case 'c': /* --cmdline */
143            cmdline = 1;
144            break;
145        case 'i': /* --stdin */
146            setenv("ZZUF_STDIN", "1", 1);
147            break;
148        case 's': /* --seed */
149            parser = strchr(optarg, ':');
150            seed = atoi(optarg);
151            endseed = parser ? atoi(parser + 1) : seed + 1;
152            break;
153        case 'r': /* --ratio */
154            setenv("ZZUF_RATIO", optarg, 1);
155            break;
156        case 'F': /* --fork */
157            parallel = atoi(optarg) > 1 ? atoi(optarg) : 1;
158            break;
159        case 'B': /* --max-bytes */
160            maxbytes = atoi(optarg);
161            break;
162        case 'T': /* --max-time */
163            maxtime = atof(optarg);
164            break;
165        case 'q': /* --quiet */
166            quiet = 1;
167            break;
168        case 'S': /* --signal */
169            setenv("ZZUF_SIGNAL", "1", 1);
170            break;
171        case 'd': /* --debug */
172            setenv("ZZUF_DEBUG", "1", 1);
173            break;
174        case 'h': /* --help */
175            usage();
176            return 0;
177        case 'v': /* --version */
178            version();
179            return 0;
180        default:
181            printf("%s: invalid option -- %c\n", argv[0], c);
182            printf(MOREINFO, argv[0]);
183            return EXIT_FAILURE;
184        }
185    }
186#else
187#   define MOREINFO "Usage: %s message...\n"
188    int optind = 1;
189#endif
190
191    if(optind >= argc)
192    {
193        printf("%s: missing argument\n", argv[0]);
194        printf(MOREINFO, argv[0]);
195        return EXIT_FAILURE;
196    }
197
198    if(cmdline)
199    {
200        int dashdash = 0;
201
202        for(i = optind + 1; i < argc; i++)
203        {
204            if(dashdash)
205                include = merge_file(include, argv[i]);
206            else if(!strcmp("--", argv[i]))
207                dashdash = 1;
208            else if(argv[i][0] != '-')
209                include = merge_file(include, argv[i]);
210        }
211    }
212
213    if(include)
214        setenv("ZZUF_INCLUDE", include, 1);
215    if(exclude)
216        setenv("ZZUF_EXCLUDE", exclude, 1);
217
218    /* Allocate memory for children handling */
219    child_list = malloc(parallel * sizeof(struct child_list));
220    for(i = 0; i < parallel; i++)
221        child_list[i].status = STATUS_FREE;
222    child_count = 0;
223
224    /* Preload libzzuf.so */
225    set_ld_preload(argv[0]);
226
227    /* Create new argv */
228    newargv = malloc((argc - optind + 1) * sizeof(char *));
229    memcpy(newargv, argv + optind, (argc - optind) * sizeof(char *));
230    newargv[argc - optind] = (char *)NULL;
231
232    /* Handle children in our way */
233    signal(SIGCHLD, SIG_DFL);
234
235    /* Main loop */
236    while(child_count || seed < endseed)
237    {
238        struct timeval tv;
239        time_t now = time(NULL);
240        fd_set fdset;
241        int ret, maxfd = 0;
242
243        /* Spawn a new child, if necessary */
244        if(child_count < parallel && seed < endseed)
245            spawn_child(newargv);
246
247        /* Terminate children if necessary */
248        for(i = 0; i < parallel; i++)
249        {
250            if(child_list[i].status == STATUS_RUNNING
251                && maxbytes >= 0 && child_list[i].bytes > maxbytes)
252            {
253                fprintf(stderr, "seed %i: data exceeded, sending SIGTERM\n",
254                        child_list[i].seed);
255                kill(child_list[i].pid, SIGTERM);
256                child_list[i].date = now;
257                child_list[i].status = STATUS_SIGTERM;
258            }
259
260            if(child_list[i].status == STATUS_RUNNING
261                && maxtime >= 0.0
262                && difftime(now, child_list[i].date) > maxtime)
263            {
264                fprintf(stderr, "seed %i: time exceeded, sending SIGTERM\n",
265                        child_list[i].seed);
266                kill(child_list[i].pid, SIGTERM);
267                child_list[i].date = now;
268                child_list[i].status = STATUS_SIGTERM;
269            }
270        }
271
272        /* Kill children if necessary */
273        for(i = 0; i < parallel; i++)
274        {
275            if(child_list[i].status == STATUS_SIGTERM
276                && difftime(now, child_list[i].date) > 2.0)
277            {
278                fprintf(stderr, "seed %i: not responding, sending SIGKILL\n",
279                        child_list[i].seed);
280                kill(child_list[i].pid, SIGKILL);
281                child_list[i].status = STATUS_SIGKILL;
282            }
283        }
284
285        /* Collect dead children */
286        for(i = 0; i < parallel; i++)
287        {
288            int status;
289            pid_t pid;
290
291            if(child_list[i].status != STATUS_SIGKILL
292                && child_list[i].status != STATUS_SIGTERM
293                && child_list[i].status != STATUS_EOF)
294                continue;
295
296            pid = waitpid(child_list[i].pid, &status, WNOHANG);
297            if(pid <= 0)
298                continue;
299
300            if(WIFEXITED(status) && WEXITSTATUS(status))
301                fprintf(stderr, "seed %i: exit %i\n",
302                        child_list[i].seed, WEXITSTATUS(status));
303            else if(WIFSIGNALED(status))
304                fprintf(stderr, "seed %i: signal %i\n",
305                        child_list[i].seed, WTERMSIG(status));
306
307            for(j = 0; j < 3; j++)
308                if(child_list[i].fd[j] >= 0)
309                    close(child_list[i].fd[j]);
310
311            child_list[i].status = STATUS_FREE;
312            child_count--;
313        }
314
315        /* Read data from all sockets */
316        FD_ZERO(&fdset);
317        for(i = 0; i < parallel; i++)
318        {
319            if(child_list[i].status != STATUS_RUNNING)
320                continue;
321
322            for(j = 0; j < 3; j++)
323                ZZUF_FD_SET(child_list[i].fd[j], &fdset, maxfd);
324        }
325        tv.tv_sec = 0;
326        tv.tv_usec = 1000;
327
328        ret = select(maxfd + 1, &fdset, NULL, NULL, &tv);
329        if(ret < 0)
330            perror("select");
331        if(ret <= 0)
332            continue;
333
334        /* XXX: cute (i, j) iterating hack */
335        for(i = 0, j = 0; i < parallel; i += (j == 2), j = (j + 1) % 3)
336        {
337            char buf[BUFSIZ];
338
339            if(child_list[i].status != STATUS_RUNNING)
340                continue;
341
342            if(!ZZUF_FD_ISSET(child_list[i].fd[j], &fdset))
343                continue;
344
345            ret = read(child_list[i].fd[j], buf, BUFSIZ - 1);
346            if(ret > 0)
347            {
348                /* We got data */
349                if(j != 0)
350                    child_list[i].bytes += ret;
351                if(!quiet || j == 0)
352                    write((j < 2) ? STDERR_FILENO : STDIN_FILENO, buf, ret);
353            }
354            else if(ret == 0)
355            {
356                /* End of file reached */
357                close(child_list[i].fd[j]);
358                child_list[i].fd[j] = -1;
359
360                if(child_list[i].fd[0] == -1 && child_list[i].fd[1] == -1
361                   && child_list[i].fd[2] == -1)
362                    child_list[i].status = STATUS_EOF;
363            }
364        }
365    }
366
367    /* Clean up */
368    free(newargv);
369    free(child_list);
370
371    return EXIT_SUCCESS;   
372}
373
374static char *merge_file(char *regex, char *file)
375{
376    char *newfile = malloc(1 + 2 * strlen(file) + 1 + 1), *tmp = newfile;
377
378    *tmp++ = '^';
379    while(*file)
380    {
381        if(strchr("^.[$()|*+?{\\", *file))
382            *tmp++ = '\\';
383        *tmp++ = *file++;
384    }
385    *tmp++ = '$';
386    *tmp++ = '\0';
387
388    tmp = merge_regex(regex, newfile);
389    free(newfile);
390    return tmp;
391}
392
393static char *merge_regex(char *regex, char *string)
394{
395    regex_t optre;
396
397    if(regex)
398    {
399        regex = realloc(regex, strlen(regex) + strlen(string) + 1 + 1);
400        sprintf(regex + strlen(regex) - 1, "|%s)", string);
401    }
402    else
403    {
404        regex = malloc(1 + strlen(string) + 1 + 1);
405        sprintf(regex, "(%s)", string);
406    }
407
408    if(regcomp(&optre, regex, REG_EXTENDED) != 0)
409    {
410        free(regex);
411        return NULL;
412    }
413    regfree(&optre);
414
415    return regex;
416}
417
418static void spawn_child(char **argv)
419{
420    static int const files[] = { DEBUG_FILENO, STDERR_FILENO, STDOUT_FILENO };
421    char buf[BUFSIZ];
422    int fd[3][2];
423    pid_t pid;
424    int i, j;
425
426    /* Find an empty slot */
427    for(i = 0; i < parallel; i++)
428        if(child_list[i].status == STATUS_FREE)
429            break;
430
431    /* Prepare communication pipe */
432    for(j = 0; j < 3; j++)
433        if(pipe(fd[j]) == -1)
434        {
435            perror("pipe");
436            return;
437        }
438
439    /* Fork and launch child */
440    pid = fork();
441    switch(pid)
442    {
443        case -1:
444            perror("fork");
445            return;
446        case 0:
447            /* We’re the child */
448            for(j = 0; j < 3; j++)
449            {
450                close(fd[j][0]);
451                dup2(fd[j][1], files[j]);
452                close(fd[j][1]);
453            }
454
455            /* Set environment variables */
456            sprintf(buf, "%i", seed);
457            setenv("ZZUF_SEED", buf, 1);
458
459            /* Run our process */
460            if(execvp(argv[0], argv))
461            {
462                perror(argv[0]);
463                exit(EXIT_FAILURE);
464            }
465            break;
466        default:
467            /* We’re the parent, acknowledge spawn */
468            child_list[i].date = time(NULL);
469            child_list[i].pid = pid;
470            for(j = 0; j < 3; j++)
471            {
472                close(fd[j][1]);
473                child_list[i].fd[j] = fd[j][0];
474            }
475            child_list[i].bytes = 0;
476            child_list[i].seed = seed;
477            child_list[i].status = STATUS_RUNNING;
478            child_count++;
479            seed++;
480            break;
481    }
482}
483
484static void set_ld_preload(char const *progpath)
485{
486    char *libpath, *tmp;
487    int len = strlen(progpath);
488
489    libpath = malloc(len + strlen("/.libs/libzzuf.so") + 1);
490    strcpy(libpath, progpath);
491    tmp = strrchr(libpath, '/');
492    strcpy(tmp ? tmp + 1 : libpath, ".libs/libzzuf.so");
493    if(access(libpath, R_OK) == 0)
494        setenv("LD_PRELOAD", libpath, 1);
495    else
496        setenv("LD_PRELOAD", LIBDIR "/libzzuf.so", 1);
497    free(libpath);
498}
499
500static void version(void)
501{
502    printf("zzuf %s\n", VERSION);
503    printf("Copyright (C) 2006 Sam Hocevar <sam@zoy.org>\n");
504    printf("This is free software.  You may redistribute copies of it under the\n");
505    printf("terms of the Do What The Fuck You Want To Public License, Version 2\n");
506    printf("<http://sam.zoy.org/wtfpl/>.\n");
507    printf("There is NO WARRANTY, to the extent permitted by law.\n");
508    printf("\n");
509    printf("Written by Sam Hocevar. Report bugs to <sam@zoy.org>.\n");
510}
511
512#if defined(HAVE_GETOPT_H)
513static void usage(void)
514{
515    printf("Usage: zzuf [ -vqdhic ] [ -r ratio ] [ -s seed | -s start:stop ]\n");
516    printf("                        [ -F children ] [ -B bytes ] [ -T seconds ]\n");
517    printf("                        [ -I include ] [ -E exclude ] COMMAND [ARGS]...\n");
518    printf("Run COMMAND and randomly fuzz its input.\n");
519    printf("\n");
520    printf("Mandatory arguments to long options are mandatory for short options too.\n");
521#   ifdef HAVE_GETOPT_LONG
522    printf("  -B, --max-bytes <n>      kill children that output more than <n> bytes\n");
523    printf("  -c, --cmdline            only fuzz files specified in the command line\n");
524    printf("  -d, --debug              print debug messages\n");
525    printf("  -E, --exclude <regex>    do not fuzz files matching <regex>\n");
526    printf("  -F, --fork <count>       number of concurrent children (default 1)\n");
527    printf("  -h, --help               display this help and exit\n");
528    printf("  -i, --stdin              fuzz standard input\n");
529    printf("  -I, --include <regex>    only fuzz files matching <regex>\n");
530    printf("  -q, --quiet              do not print children's messages\n");
531    printf("  -r, --ratio <ratio>      bit fuzzing ratio (default 0.004)\n");
532    printf("  -s, --seed <seed>        random seed (default 0)\n");
533    printf("      --seed <start:stop>  specify a seed range\n");
534    printf("  -S, --signal             prevent children from diverting crashing signals\n");
535    printf("  -T, --max-time <n>       kill children that run for more than <n> seconds\n");
536    printf("  -v, --version            output version information and exit\n");
537#   else
538    printf("  -B <n>           kill children that output more than <n> bytes\n");
539    printf("  -c               only fuzz files specified in the command line\n");
540    printf("  -d               print debug messages\n");
541    printf("  -E <regex>       do not fuzz files matching <regex>\n");
542    printf("  -F <count>       number of concurrent forks (default 1)\n");
543    printf("  -h               display this help and exit\n");
544    printf("  -i               fuzz standard input\n");
545    printf("  -I <regex>       only fuzz files matching <regex>\n");
546    printf("  -q               do not print the fuzzed application's messages\n");
547    printf("  -r <ratio>       bit fuzzing ratio (default 0.004)\n");
548    printf("  -s <seed>        random seed (default 0)\n");
549    printf("     <start:stop>  specify a seed range\n");
550    printf("  -S               prevent children from diverting crashing signals\n");
551    printf("  -T <n>           kill children that run for more than <n> seconds\n");
552    printf("  -v               output version information and exit\n");
553#   endif
554    printf("\n");
555    printf("Written by Sam Hocevar. Report bugs to <sam@zoy.org>.\n");
556}
557#endif
558
Note: See TracBrowser for help on using the repository browser.