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

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