/* badinput.c: do bisect-quest to find minimal input which breaks the target command execution Copyright (C) 2014 Masatake YAMATO This program is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation; either version 2 of the License, or (at your option) any later version. This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with this program. If not, see . Build ======================================================================= $ gcc -Wall badinput.c -o badinput Usage ======================================================================= $ badinput CMDLINE_TEMPLATE INPUT OUTPUT Description ======================================================================= Consider a situation that a process execve'd from CMDLINE_TEMPLATE crashes or enters into an infinite-loop when the process deals with INPUT file. This program truncates both the head and tail of the INPUT file and runs CMDLINE_TEMPLATE repeatedly till the process exits normally(== 0); and reports the shortest input which causes the crash or infinite-loop. Here is an example: $ misc/badinput "timeout 1 ./ctags -o - --language-force=Ada %s > /dev/null" Test/1880687.js /tmp/output.txt Ada parser of ctags enters an infinite-loop when Test/1880687.js is given. The size of original Test/1880687.js is 2258 bytes. $ misc/badinput "timeout 1 ./ctags -o - --language-force=Ada %s > /dev/null" Test/1880687.js /tmp/output.txt [0, 2448]...31744 [0, 0]...0 step(end): 0 [0, 2448]...31744 step(end): 1 [0, 1224]...31744 step(end): 2 [0, 612]...0 step(end): 3 [0, 918]...0 step(end): 4 [0, 1071]...0 step(end): 5 [0, 1147]...31744 step(end): 6 [0, 1109]...0 step(end): 7 [0, 1128]...31744 step(end): 8 [0, 1119]...0 step(end): 9 [0, 1123]...31744 step(end): 10 [0, 1121]...0 step(end): 11 [0, 1122]...31744 step(start): 0 [0, 1122]...31744 step(start): 1 [561, 1122]...31744 step(start): 2 [841, 1122]...31744 step(start): 3 [981, 1122]...31744 step(start): 4 [1051, 1122]...31744 step(start): 5 [1086, 1122]...0 step(start): 6 [1069, 1122]...31744 step(start): 7 [1077, 1122]...31744 step(start): 8 [1081, 1122]...31744 step(start): 9 [1083, 1122]...0 step(start): 10 [1082, 1122]...0 Minimal bad input: function baz() { } } function g( $ New shorter input, only 38 bytes, which can reproduce the issue is reported at the end. This new input is useful for debugging. The result is shown in stdout and is recorded to the file specified as OUTPUT. */ #define _GNU_SOURCE #include #include #include #include #include #include #include #include static void print_help(const char *prog, FILE *fp, int status) { fprintf(fp, "Usage:\n"); fprintf(fp, " %s --help|-h\n", prog); fprintf(fp, " %s CMDLINE_TEMPLATE INPUT OUTPUT\n", prog); exit (status); } static void load (const char* input_file, char** input, size_t* len) { int input_fd; struct stat stat_buf; input_fd = open (input_file, O_RDONLY); if (input_fd < 0) { perror ("open(input)"); exit(1); } if (fstat(input_fd, &stat_buf) < 0) { perror ("fstat"); exit(1); } *len = stat_buf.st_size; *input = malloc (*len); if (!*input) { fprintf(stderr, "memory exhausted\n"); exit (1); } if (read (input_fd, *input, *len) != *len) { perror ("read"); exit (1); } } static void prepare(int output_fd, char * input, size_t len) { if (lseek (output_fd, 0, SEEK_SET == -1)) { perror("lseek"); exit (1); } if (ftruncate(output_fd, 0) == -1) { perror ("truncate"); exit (1); } if (write (output_fd, input, len) != len) { perror ("write"); exit (1); } } static int test (char* cmdline, char * input, off_t start, size_t len, int output_fd) { int r; prepare (output_fd, input + start, len); fprintf (stderr, "[%lu, %lu]...", start, start + len); r = system(cmdline); fprintf(stderr, "%d\n", r); return r; } static int bisect(char* cmdline, char * input, size_t len, int output_fd) { off_t end; off_t start; unsigned int step; int delta; off_t failed = len; off_t successful = 0; end = len; failed = len; successful = 0; for (step = 0; 1; step++) { fprintf(stderr, "step(end): %d ", step); delta = (len >> (step + 1)); if (delta == 0) delta = 1; if (test (cmdline, input, 0, end, output_fd) == 0) { successful = end; if (end + 1 == failed) { end = failed; break; } else end += delta; } else { failed = end; if (successful + 1 == end) break; else end -= delta; } } len = end; start = 0; failed = 0; successful = end; for (step = 0; 1; step++) { fprintf(stderr, "step(start): %d ", step); delta = (len >> (step + 1)); if (delta == 0) delta = 1; if (test (cmdline, input, start, end - start, output_fd) == 0) { successful = start; if (start - 1 == failed) { start--; break; } else start -= delta; } else { failed = start; if (successful - 1 == start) break; else start += delta; } } len = end - start; fprintf(stderr, "Minimal bad input:\n"); fwrite(input + start, 1, len, stdout); prepare (output_fd, input + start, len); printf("\n"); return 0; } int main(int argc, char** argv) { char* cmdline_template; char* input_file; char* output_file; char* cmdline; char * input; size_t len; int output_fd; if (argc == 2 && ((!strcmp(argv[2], "--help")) || (!strcmp(argv[2], "-h")))) print_help(argv[0], stdout, 0); else if (argc != 4) { fprintf(stderr,"wrong number of arguments\n"); exit (1); } cmdline_template = argv[1]; input_file = argv[2]; output_file = argv[3]; if (!strstr (cmdline_template, "%s")) { fprintf(stderr, "no %%s is found in command line template\n"); exit (1); } load (input_file, &input, &len); output_fd = open (output_file, O_WRONLY|O_CREAT, 0666); if (output_fd < 0) { perror ("open(output)"); exit (1); } if (asprintf (&cmdline, cmdline_template, output_file) == -1) { fprintf(stderr, "error in asprintf\n"); exit (1); } if (test (cmdline, input, 0, len, output_fd) == 0) { fprintf(stderr, "the target command line exits normally against the original input\n"); exit (1); } if (test (cmdline, input, 0, 0, output_fd) != 0) { fprintf(stderr, "the target command line exits normally against the empty input\n"); exit (1); } return bisect(cmdline, input, len, output_fd); }