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

Last change on this file since 1529 was 1529, checked in by Sam Hocevar, 14 years ago
  • Switch to REG_EXTENDED.
  • Implement -c (--cmdline).
  • Updated documentation accordingly.
  • Property svn:keywords set to Id
File size: 16.3 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 1529 2007-01-01 18:56:13Z 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            { "include",   1, NULL, 'I' },
100            { "exclude",   1, NULL, 'E' },
101            { "cmdline",   0, NULL, 'c' },
102            { "stdin",     0, NULL, 'i' },
103            { "seed",      1, NULL, 's' },
104            { "ratio",     1, NULL, 'r' },
105            { "fork",      1, NULL, 'F' },
106            { "max-bytes", 1, NULL, 'B' },
107            { "max-time",  1, NULL, 'T' },
108            { "quiet",     0, NULL, 'q' },
109            { "debug",     0, NULL, 'd' },
110            { "help",      0, NULL, 'h' },
111            { "version",   0, NULL, 'v' },
112        };
113        int c = getopt_long(argc, argv, "I:E:cis:r:F:B:T:qdhv",
114                            long_options, &option_index);
115#   else
116#       define MOREINFO "Try `%s -h' for more information.\n"
117        int c = getopt(argc, argv, "I:E:cis:r:F:B:T:qdhv");
118#   endif
119        if(c == -1)
120            break;
121
122        switch(c)
123        {
124        case 'I': /* --include */
125            include = merge_regex(include, optarg);
126            if(!include)
127            {
128                printf("%s: invalid regex -- `%s'\n", argv[0], optarg);
129                return EXIT_FAILURE;
130            }
131            break;
132        case 'E': /* --exclude */
133            exclude = merge_regex(exclude, optarg);
134            if(!exclude)
135            {
136                printf("%s: invalid regex -- `%s'\n", argv[0], optarg);
137                return EXIT_FAILURE;
138            }
139            break;
140        case 'c': /* --cmdline */
141            cmdline = 1;
142            break;
143        case 'i': /* --stdin */
144            setenv("ZZUF_STDIN", "1", 1);
145            break;
146        case 's': /* --seed */
147            parser = strchr(optarg, ':');
148            seed = atoi(optarg);
149            endseed = parser ? atoi(parser + 1) : seed + 1;
150            break;
151        case 'r': /* --ratio */
152            setenv("ZZUF_RATIO", optarg, 1);
153            break;
154        case 'F': /* --fork */
155            parallel = atoi(optarg) > 1 ? atoi(optarg) : 1;
156            break;
157        case 'B': /* --max-bytes */
158            maxbytes = atoi(optarg);
159            break;
160        case 'T': /* --max-time */
161            maxtime = atof(optarg);
162            break;
163        case 'q': /* --quiet */
164            quiet = 1;
165            break;
166        case 'd': /* --debug */
167            setenv("ZZUF_DEBUG", "1", 1);
168            break;
169        case 'h': /* --help */
170            usage();
171            return 0;
172        case 'v': /* --version */
173            version();
174            return 0;
175        default:
176            printf("%s: invalid option -- %c\n", argv[0], c);
177            printf(MOREINFO, argv[0]);
178            return EXIT_FAILURE;
179        }
180    }
181#else
182#   define MOREINFO "Usage: %s message...\n"
183    int optind = 1;
184#endif
185
186    if(optind >= argc)
187    {
188        printf("%s: missing argument\n", argv[0]);
189        printf(MOREINFO, argv[0]);
190        return EXIT_FAILURE;
191    }
192
193    if(cmdline)
194    {
195        int dashdash = 0;
196
197        for(i = optind + 1; i < argc; i++)
198        {
199            if(dashdash)
200                include = merge_file(include, argv[i]);
201            else if(!strcmp("--", argv[i]))
202                dashdash = 1;
203            else if(argv[i][0] != '-')
204                include = merge_file(include, argv[i]);
205        }
206    }
207
208    if(include)
209        setenv("ZZUF_INCLUDE", include, 1);
210    if(exclude)
211        setenv("ZZUF_EXCLUDE", exclude, 1);
212
213    /* Allocate memory for children handling */
214    child_list = malloc(parallel * sizeof(struct child_list));
215    for(i = 0; i < parallel; i++)
216        child_list[i].status = STATUS_FREE;
217    child_count = 0;
218
219    /* Preload libzzuf.so */
220    set_ld_preload(argv[0]);
221
222    /* Create new argv */
223    newargv = malloc((argc - optind + 1) * sizeof(char *));
224    memcpy(newargv, argv + optind, (argc - optind) * sizeof(char *));
225    newargv[argc - optind] = (char *)NULL;
226
227    /* Handle children in our way */
228    signal(SIGCHLD, SIG_DFL);
229
230    /* Main loop */
231    while(child_count || seed < endseed)
232    {
233        struct timeval tv;
234        time_t now = time(NULL);
235        fd_set fdset;
236        int ret, maxfd = 0;
237
238        /* Spawn a new child, if necessary */
239        if(child_count < parallel && seed < endseed)
240            spawn_child(newargv);
241
242        /* Terminate children if necessary */
243        for(i = 0; i < parallel; i++)
244        {
245            if(child_list[i].status == STATUS_RUNNING
246                && maxbytes >= 0 && child_list[i].bytes > maxbytes)
247            {
248                fprintf(stderr, "seed %i: data exceeded, sending SIGTERM\n",
249                        child_list[i].seed);
250                kill(child_list[i].pid, SIGTERM);
251                child_list[i].date = now;
252                child_list[i].status = STATUS_SIGTERM;
253            }
254
255            if(child_list[i].status == STATUS_RUNNING
256                && maxtime >= 0.0
257                && difftime(now, child_list[i].date) > maxtime)
258            {
259                fprintf(stderr, "seed %i: time exceeded, sending SIGTERM\n",
260                        child_list[i].seed);
261                kill(child_list[i].pid, SIGTERM);
262                child_list[i].date = now;
263                child_list[i].status = STATUS_SIGTERM;
264            }
265        }
266
267        /* Kill children if necessary */
268        for(i = 0; i < parallel; i++)
269        {
270            if(child_list[i].status == STATUS_SIGTERM
271                && difftime(now, child_list[i].date) > 2.0)
272            {
273                fprintf(stderr, "seed %i: not responding, sending SIGKILL\n",
274                        child_list[i].seed);
275                kill(child_list[i].pid, SIGKILL);
276                child_list[i].status = STATUS_SIGKILL;
277            }
278        }
279
280        /* Collect dead children */
281        for(i = 0; i < parallel; i++)
282        {
283            int status;
284            pid_t pid;
285
286            if(child_list[i].status != STATUS_SIGKILL
287                && child_list[i].status != STATUS_SIGTERM
288                && child_list[i].status != STATUS_EOF)
289                continue;
290
291            pid = waitpid(child_list[i].pid, &status, WNOHANG);
292            if(pid <= 0)
293                continue;
294
295            if(WIFEXITED(status) && WEXITSTATUS(status))
296                fprintf(stderr, "seed %i: exit %i\n",
297                        child_list[i].seed, WEXITSTATUS(status));
298            else if(WIFSIGNALED(status))
299                fprintf(stderr, "seed %i: signal %i\n",
300                        child_list[i].seed, WTERMSIG(status));
301
302            if(child_list[i].outfd >= 0)
303                close(child_list[i].outfd);
304
305            if(child_list[i].errfd >= 0)
306                close(child_list[i].errfd);
307
308            child_list[i].status = STATUS_FREE;
309            child_count--;
310        }
311
312        /* Read data from all sockets */
313        FD_ZERO(&fdset);
314        for(i = 0; i < parallel; i++)
315        {
316            if(child_list[i].status != STATUS_RUNNING)
317                continue;
318
319            ZZUF_FD_SET(child_list[i].outfd, &fdset, maxfd);
320            ZZUF_FD_SET(child_list[i].errfd, &fdset, maxfd);
321        }
322        tv.tv_sec = 0;
323        tv.tv_usec = 1000;
324
325        ret = select(maxfd + 1, &fdset, NULL, NULL, &tv);
326        if(ret < 0)
327            perror("select");
328        if(ret <= 0)
329            continue;
330
331        for(i = 0, j = 0; i < parallel; i += j, j = (j + 1) & 1)
332        {
333            char buf[BUFSIZ];
334            int fd;
335
336            if(child_list[i].status != STATUS_RUNNING)
337                continue;
338
339            fd = j ? child_list[i].outfd : child_list[i].errfd;
340
341            if(!ZZUF_FD_ISSET(fd, &fdset))
342                continue;
343
344            ret = read(fd, buf, BUFSIZ - 1);
345            if(ret > 0)
346            {
347                /* We got data */
348                child_list[i].bytes += ret;
349                if(!quiet)
350                    fwrite(buf, ret, 1, j ? stdout : stderr);
351            }
352            else if(ret == 0)
353            {
354                /* End of file reached */
355                close(fd);
356                if(j)
357                    child_list[i].outfd = -1;
358                else
359                    child_list[i].errfd = -1;
360
361                if(child_list[i].outfd == -1 && child_list[i].errfd == -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    char buf[BUFSIZ];
421    int outfd[2], errfd[2];
422    pid_t pid;
423    int i;
424
425    /* Find an empty slot */
426    for(i = 0; i < parallel; i++)
427        if(child_list[i].status == STATUS_FREE)
428            break;
429
430    /* Prepare communication pipe */
431    if(pipe(outfd) == -1 || pipe(errfd) == -1)
432    {
433        perror("pipe");
434        return;
435    }
436
437    /* Fork and launch child */
438    pid = fork();
439    switch(pid)
440    {
441        case -1:
442            perror("fork");
443            return;
444        case 0:
445            /* We’re the child */
446            close(outfd[0]);
447            close(errfd[0]);
448            dup2(outfd[1], STDOUT_FILENO);
449            dup2(errfd[1], STDERR_FILENO);
450            close(outfd[1]);
451            close(errfd[1]);
452
453            /* Set environment variables */
454            sprintf(buf, "%i", seed);
455            setenv("ZZUF_SEED", buf, 1);
456
457            /* Run our process */
458            if(execvp(argv[0], argv))
459            {
460                perror(argv[0]);
461                exit(EXIT_FAILURE);
462            }
463            break;
464        default:
465            /* We’re the parent, acknowledge spawn */
466            close(outfd[1]);
467            close(errfd[1]);
468            child_list[i].date = time(NULL);
469            child_list[i].pid = pid;
470            child_list[i].outfd = outfd[0];
471            child_list[i].errfd = errfd[0];
472            child_list[i].bytes = 0;
473            child_list[i].seed = seed;
474            child_list[i].status = STATUS_RUNNING;
475            child_count++;
476            seed++;
477            break;
478    }
479}
480
481static void set_ld_preload(char const *progpath)
482{
483    char *libpath, *tmp;
484    int len = strlen(progpath);
485
486    libpath = malloc(len + strlen("/.libs/libzzuf.so") + 1);
487    strcpy(libpath, progpath);
488    tmp = strrchr(libpath, '/');
489    strcpy(tmp ? tmp + 1 : libpath, ".libs/libzzuf.so");
490    if(access(libpath, R_OK) == 0)
491        setenv("LD_PRELOAD", libpath, 1);
492    else
493        setenv("LD_PRELOAD", LIBDIR "/libzzuf.so", 1);
494    free(libpath);
495}
496
497static void version(void)
498{
499    printf("zzuf %s\n", VERSION);
500    printf("Copyright (C) 2006 Sam Hocevar <sam@zoy.org>\n");
501    printf("This is free software.  You may redistribute copies of it under the\n");
502    printf("terms of the Do What The Fuck You Want To Public License, Version 2\n");
503    printf("<http://sam.zoy.org/wtfpl/>.\n");
504    printf("There is NO WARRANTY, to the extent permitted by law.\n");
505    printf("\n");
506    printf("Written by Sam Hocevar. Report bugs to <sam@zoy.org>.\n");
507}
508
509#if defined(HAVE_GETOPT_H)
510static void usage(void)
511{
512    printf("Usage: zzuf [ -vqdhic ] [ -r ratio ] [ -s seed | -s start:stop ]\n");
513    printf("                        [ -F children ] [ -B bytes ] [ -T seconds ]\n");
514    printf("                        [ -I include ] [ -E exclude ] COMMAND [ARGS]...\n");
515    printf("Run COMMAND and randomly fuzz its input.\n");
516    printf("\n");
517    printf("Mandatory arguments to long options are mandatory for short options too.\n");
518#   ifdef HAVE_GETOPT_LONG
519    printf("  -r, --ratio <ratio>      bit fuzzing ratio (default 0.004)\n");
520    printf("  -s, --seed <seed>        random seed (default 0)\n");
521    printf("      --seed <start:stop>  specify a seed range\n");
522    printf("  -F, --fork <count>       number of concurrent children (default 1)\n");
523    printf("  -B, --max-bytes <n>      kill children that output more than <n> bytes\n");
524    printf("  -T, --max-time <n>       kill children that run for more than <n> seconds\n");
525    printf("  -q, --quiet              do not print children's messages\n");
526    printf("  -i, --stdin              fuzz standard input\n");
527    printf("  -I, --include <regex>    only fuzz files matching <regex>\n");
528    printf("  -c, --cmdline            only fuzz files specified in the command line\n");
529    printf("  -E, --exclude <regex>    do not fuzz files matching <regex>\n");
530    printf("  -d, --debug              print debug messages\n");
531    printf("  -h, --help               display this help and exit\n");
532    printf("  -v, --version            output version information and exit\n");
533#   else
534    printf("  -r <ratio>       bit fuzzing ratio (default 0.004)\n");
535    printf("  -s <seed>        random seed (default 0)\n");
536    printf("     <start:stop>  specify a seed range\n");
537    printf("  -F <count>       number of concurrent forks (default 1)\n");
538    printf("  -B <n>           kill children that output more than <n> bytes\n");
539    printf("  -T <n>           kill children that run for more than <n> seconds\n");
540    printf("  -q               do not print the fuzzed application's messages\n");
541    printf("  -i               fuzz standard input\n");
542    printf("  -I <regex>       only fuzz files matching <regex>\n");
543    printf("  -c               only fuzz files specified in the command line\n");
544    printf("  -E <regex>       do not fuzz files matching <regex>\n");
545    printf("  -d               print debug messages\n");
546    printf("  -h               display this help and exit\n");
547    printf("  -v               output version information and exit\n");
548#   endif
549    printf("\n");
550    printf("Written by Sam Hocevar. Report bugs to <sam@zoy.org>.\n");
551}
552#endif
553
Note: See TracBrowser for help on using the repository browser.