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

Last change on this file since 1538 was 1538, checked in by Sam Hocevar, 14 years ago
  • Output process status to stdout, not stderr.
  • 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 1538 2007-01-02 14:51:55Z 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(stdout, "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(stdout, "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(stdout, "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(stdout, "seed %i: exit %i\n",
302                        child_list[i].seed, WEXITSTATUS(status));
303            else if(WIFSIGNALED(status))
304                fprintf(stdout, "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        fflush(stdout);
316
317        /* Read data from all sockets */
318        FD_ZERO(&fdset);
319        for(i = 0; i < parallel; i++)
320        {
321            if(child_list[i].status != STATUS_RUNNING)
322                continue;
323
324            for(j = 0; j < 3; j++)
325                ZZUF_FD_SET(child_list[i].fd[j], &fdset, maxfd);
326        }
327        tv.tv_sec = 0;
328        tv.tv_usec = 1000;
329
330        ret = select(maxfd + 1, &fdset, NULL, NULL, &tv);
331        if(ret < 0)
332            perror("select");
333        if(ret <= 0)
334            continue;
335
336        /* XXX: cute (i, j) iterating hack */
337        for(i = 0, j = 0; i < parallel; i += (j == 2), j = (j + 1) % 3)
338        {
339            char buf[BUFSIZ];
340
341            if(child_list[i].status != STATUS_RUNNING)
342                continue;
343
344            if(!ZZUF_FD_ISSET(child_list[i].fd[j], &fdset))
345                continue;
346
347            ret = read(child_list[i].fd[j], buf, BUFSIZ - 1);
348            if(ret > 0)
349            {
350                /* We got data */
351                if(j != 0)
352                    child_list[i].bytes += ret;
353                if(!quiet || j == 0)
354                    write((j < 2) ? STDERR_FILENO : STDIN_FILENO, buf, ret);
355            }
356            else if(ret == 0)
357            {
358                /* End of file reached */
359                close(child_list[i].fd[j]);
360                child_list[i].fd[j] = -1;
361
362                if(child_list[i].fd[0] == -1 && child_list[i].fd[1] == -1
363                   && child_list[i].fd[2] == -1)
364                    child_list[i].status = STATUS_EOF;
365            }
366        }
367    }
368
369    /* Clean up */
370    free(newargv);
371    free(child_list);
372
373    return EXIT_SUCCESS;   
374}
375
376static char *merge_file(char *regex, char *file)
377{
378    char *newfile = malloc(1 + 2 * strlen(file) + 1 + 1), *tmp = newfile;
379
380    *tmp++ = '^';
381    while(*file)
382    {
383        if(strchr("^.[$()|*+?{\\", *file))
384            *tmp++ = '\\';
385        *tmp++ = *file++;
386    }
387    *tmp++ = '$';
388    *tmp++ = '\0';
389
390    tmp = merge_regex(regex, newfile);
391    free(newfile);
392    return tmp;
393}
394
395static char *merge_regex(char *regex, char *string)
396{
397    regex_t optre;
398
399    if(regex)
400    {
401        regex = realloc(regex, strlen(regex) + strlen(string) + 1 + 1);
402        sprintf(regex + strlen(regex) - 1, "|%s)", string);
403    }
404    else
405    {
406        regex = malloc(1 + strlen(string) + 1 + 1);
407        sprintf(regex, "(%s)", string);
408    }
409
410    if(regcomp(&optre, regex, REG_EXTENDED) != 0)
411    {
412        free(regex);
413        return NULL;
414    }
415    regfree(&optre);
416
417    return regex;
418}
419
420static void spawn_child(char **argv)
421{
422    static int const files[] = { DEBUG_FILENO, STDERR_FILENO, STDOUT_FILENO };
423    char buf[BUFSIZ];
424    int fd[3][2];
425    pid_t pid;
426    int i, j;
427
428    /* Find an empty slot */
429    for(i = 0; i < parallel; i++)
430        if(child_list[i].status == STATUS_FREE)
431            break;
432
433    /* Prepare communication pipe */
434    for(j = 0; j < 3; j++)
435        if(pipe(fd[j]) == -1)
436        {
437            perror("pipe");
438            return;
439        }
440
441    /* Fork and launch child */
442    pid = fork();
443    switch(pid)
444    {
445        case -1:
446            perror("fork");
447            return;
448        case 0:
449            /* We’re the child */
450            for(j = 0; j < 3; j++)
451            {
452                close(fd[j][0]);
453                dup2(fd[j][1], files[j]);
454                close(fd[j][1]);
455            }
456
457            /* Set environment variables */
458            sprintf(buf, "%i", seed);
459            setenv("ZZUF_SEED", buf, 1);
460
461            /* Run our process */
462            if(execvp(argv[0], argv))
463            {
464                perror(argv[0]);
465                exit(EXIT_FAILURE);
466            }
467            break;
468        default:
469            /* We’re the parent, acknowledge spawn */
470            child_list[i].date = time(NULL);
471            child_list[i].pid = pid;
472            for(j = 0; j < 3; j++)
473            {
474                close(fd[j][1]);
475                child_list[i].fd[j] = fd[j][0];
476            }
477            child_list[i].bytes = 0;
478            child_list[i].seed = seed;
479            child_list[i].status = STATUS_RUNNING;
480            child_count++;
481            seed++;
482            break;
483    }
484}
485
486static void set_ld_preload(char const *progpath)
487{
488    char *libpath, *tmp;
489    int len = strlen(progpath);
490
491    libpath = malloc(len + strlen("/.libs/libzzuf.so") + 1);
492    strcpy(libpath, progpath);
493    tmp = strrchr(libpath, '/');
494    strcpy(tmp ? tmp + 1 : libpath, ".libs/libzzuf.so");
495    if(access(libpath, R_OK) == 0)
496        setenv("LD_PRELOAD", libpath, 1);
497    else
498        setenv("LD_PRELOAD", LIBDIR "/libzzuf.so", 1);
499    free(libpath);
500}
501
502static void version(void)
503{
504    printf("zzuf %s\n", VERSION);
505    printf("Copyright (C) 2006 Sam Hocevar <sam@zoy.org>\n");
506    printf("This is free software.  You may redistribute copies of it under the\n");
507    printf("terms of the Do What The Fuck You Want To Public License, Version 2\n");
508    printf("<http://sam.zoy.org/wtfpl/>.\n");
509    printf("There is NO WARRANTY, to the extent permitted by law.\n");
510    printf("\n");
511    printf("Written by Sam Hocevar. Report bugs to <sam@zoy.org>.\n");
512}
513
514#if defined(HAVE_GETOPT_H)
515static void usage(void)
516{
517    printf("Usage: zzuf [ -vqdhic ] [ -r ratio ] [ -s seed | -s start:stop ]\n");
518    printf("                        [ -F children ] [ -B bytes ] [ -T seconds ]\n");
519    printf("                        [ -I include ] [ -E exclude ] COMMAND [ARGS]...\n");
520    printf("Run COMMAND and randomly fuzz its input.\n");
521    printf("\n");
522    printf("Mandatory arguments to long options are mandatory for short options too.\n");
523#   ifdef HAVE_GETOPT_LONG
524    printf("  -B, --max-bytes <n>      kill children that output more than <n> bytes\n");
525    printf("  -c, --cmdline            only fuzz files specified in the command line\n");
526    printf("  -d, --debug              print debug messages\n");
527    printf("  -E, --exclude <regex>    do not fuzz files matching <regex>\n");
528    printf("  -F, --fork <count>       number of concurrent children (default 1)\n");
529    printf("  -h, --help               display this help and exit\n");
530    printf("  -i, --stdin              fuzz standard input\n");
531    printf("  -I, --include <regex>    only fuzz files matching <regex>\n");
532    printf("  -q, --quiet              do not print children's messages\n");
533    printf("  -r, --ratio <ratio>      bit fuzzing ratio (default 0.004)\n");
534    printf("  -s, --seed <seed>        random seed (default 0)\n");
535    printf("      --seed <start:stop>  specify a seed range\n");
536    printf("  -S, --signal             prevent children from diverting crashing signals\n");
537    printf("  -T, --max-time <n>       kill children that run for more than <n> seconds\n");
538    printf("  -v, --version            output version information and exit\n");
539#   else
540    printf("  -B <n>           kill children that output more than <n> bytes\n");
541    printf("  -c               only fuzz files specified in the command line\n");
542    printf("  -d               print debug messages\n");
543    printf("  -E <regex>       do not fuzz files matching <regex>\n");
544    printf("  -F <count>       number of concurrent forks (default 1)\n");
545    printf("  -h               display this help and exit\n");
546    printf("  -i               fuzz standard input\n");
547    printf("  -I <regex>       only fuzz files matching <regex>\n");
548    printf("  -q               do not print the fuzzed application's messages\n");
549    printf("  -r <ratio>       bit fuzzing ratio (default 0.004)\n");
550    printf("  -s <seed>        random seed (default 0)\n");
551    printf("     <start:stop>  specify a seed range\n");
552    printf("  -S               prevent children from diverting crashing signals\n");
553    printf("  -T <n>           kill children that run for more than <n> seconds\n");
554    printf("  -v               output version information and exit\n");
555#   endif
556    printf("\n");
557    printf("Written by Sam Hocevar. Report bugs to <sam@zoy.org>.\n");
558}
559#endif
560
Note: See TracBrowser for help on using the repository browser.