1803588053d79b35a1c829fd00c09dae5cd4a2c7dDaniel Dunbar#!/usr/bin/env python
2803588053d79b35a1c829fd00c09dae5cd4a2c7dDaniel Dunbar
3803588053d79b35a1c829fd00c09dae5cd4a2c7dDaniel Dunbar"""
4803588053d79b35a1c829fd00c09dae5cd4a2c7dDaniel DunbarThis is a generic fuzz testing tool, see --help for more information.
5803588053d79b35a1c829fd00c09dae5cd4a2c7dDaniel Dunbar"""
6803588053d79b35a1c829fd00c09dae5cd4a2c7dDaniel Dunbar
7803588053d79b35a1c829fd00c09dae5cd4a2c7dDaniel Dunbarimport os
8803588053d79b35a1c829fd00c09dae5cd4a2c7dDaniel Dunbarimport sys
9803588053d79b35a1c829fd00c09dae5cd4a2c7dDaniel Dunbarimport random
10803588053d79b35a1c829fd00c09dae5cd4a2c7dDaniel Dunbarimport subprocess
11803588053d79b35a1c829fd00c09dae5cd4a2c7dDaniel Dunbarimport itertools
12803588053d79b35a1c829fd00c09dae5cd4a2c7dDaniel Dunbar
13803588053d79b35a1c829fd00c09dae5cd4a2c7dDaniel Dunbarclass TestGenerator:
14803588053d79b35a1c829fd00c09dae5cd4a2c7dDaniel Dunbar    def __init__(self, inputs, delete, insert, replace,
15803588053d79b35a1c829fd00c09dae5cd4a2c7dDaniel Dunbar                 insert_strings, pick_input):
16803588053d79b35a1c829fd00c09dae5cd4a2c7dDaniel Dunbar        self.inputs = [(s, open(s).read()) for s in inputs]
17803588053d79b35a1c829fd00c09dae5cd4a2c7dDaniel Dunbar
18803588053d79b35a1c829fd00c09dae5cd4a2c7dDaniel Dunbar        self.delete = bool(delete)
19803588053d79b35a1c829fd00c09dae5cd4a2c7dDaniel Dunbar        self.insert = bool(insert)
20803588053d79b35a1c829fd00c09dae5cd4a2c7dDaniel Dunbar        self.replace = bool(replace)
21803588053d79b35a1c829fd00c09dae5cd4a2c7dDaniel Dunbar        self.pick_input = bool(pick_input)
22803588053d79b35a1c829fd00c09dae5cd4a2c7dDaniel Dunbar        self.insert_strings = list(insert_strings)
23803588053d79b35a1c829fd00c09dae5cd4a2c7dDaniel Dunbar
24803588053d79b35a1c829fd00c09dae5cd4a2c7dDaniel Dunbar        self.num_positions = sum([len(d) for _,d in self.inputs])
25803588053d79b35a1c829fd00c09dae5cd4a2c7dDaniel Dunbar        self.num_insert_strings = len(insert_strings)
26803588053d79b35a1c829fd00c09dae5cd4a2c7dDaniel Dunbar        self.num_tests = ((delete + (insert + replace)*self.num_insert_strings)
27803588053d79b35a1c829fd00c09dae5cd4a2c7dDaniel Dunbar                          * self.num_positions)
28803588053d79b35a1c829fd00c09dae5cd4a2c7dDaniel Dunbar        self.num_tests += 1
29803588053d79b35a1c829fd00c09dae5cd4a2c7dDaniel Dunbar
30803588053d79b35a1c829fd00c09dae5cd4a2c7dDaniel Dunbar        if self.pick_input:
31803588053d79b35a1c829fd00c09dae5cd4a2c7dDaniel Dunbar            self.num_tests *= self.num_positions
32803588053d79b35a1c829fd00c09dae5cd4a2c7dDaniel Dunbar
33803588053d79b35a1c829fd00c09dae5cd4a2c7dDaniel Dunbar    def position_to_source_index(self, position):
34803588053d79b35a1c829fd00c09dae5cd4a2c7dDaniel Dunbar        for i,(s,d) in enumerate(self.inputs):
35803588053d79b35a1c829fd00c09dae5cd4a2c7dDaniel Dunbar            n = len(d)
36803588053d79b35a1c829fd00c09dae5cd4a2c7dDaniel Dunbar            if position < n:
37803588053d79b35a1c829fd00c09dae5cd4a2c7dDaniel Dunbar                return (i,position)
38803588053d79b35a1c829fd00c09dae5cd4a2c7dDaniel Dunbar            position -= n
39803588053d79b35a1c829fd00c09dae5cd4a2c7dDaniel Dunbar        raise ValueError,'Invalid position.'
40803588053d79b35a1c829fd00c09dae5cd4a2c7dDaniel Dunbar
41803588053d79b35a1c829fd00c09dae5cd4a2c7dDaniel Dunbar    def get_test(self, index):
42803588053d79b35a1c829fd00c09dae5cd4a2c7dDaniel Dunbar        assert 0 <= index < self.num_tests
43803588053d79b35a1c829fd00c09dae5cd4a2c7dDaniel Dunbar
44803588053d79b35a1c829fd00c09dae5cd4a2c7dDaniel Dunbar        picked_position = None
45803588053d79b35a1c829fd00c09dae5cd4a2c7dDaniel Dunbar        if self.pick_input:
46803588053d79b35a1c829fd00c09dae5cd4a2c7dDaniel Dunbar            index,picked_position = divmod(index, self.num_positions)
47803588053d79b35a1c829fd00c09dae5cd4a2c7dDaniel Dunbar            picked_position = self.position_to_source_index(picked_position)
48803588053d79b35a1c829fd00c09dae5cd4a2c7dDaniel Dunbar
49803588053d79b35a1c829fd00c09dae5cd4a2c7dDaniel Dunbar        if index == 0:
50803588053d79b35a1c829fd00c09dae5cd4a2c7dDaniel Dunbar            return ('nothing', None, None, picked_position)
51803588053d79b35a1c829fd00c09dae5cd4a2c7dDaniel Dunbar
52803588053d79b35a1c829fd00c09dae5cd4a2c7dDaniel Dunbar        index -= 1
53803588053d79b35a1c829fd00c09dae5cd4a2c7dDaniel Dunbar        index,position = divmod(index, self.num_positions)
54803588053d79b35a1c829fd00c09dae5cd4a2c7dDaniel Dunbar        position = self.position_to_source_index(position)
55803588053d79b35a1c829fd00c09dae5cd4a2c7dDaniel Dunbar        if self.delete:
56803588053d79b35a1c829fd00c09dae5cd4a2c7dDaniel Dunbar            if index == 0:
57803588053d79b35a1c829fd00c09dae5cd4a2c7dDaniel Dunbar                return ('delete', position, None, picked_position)
58803588053d79b35a1c829fd00c09dae5cd4a2c7dDaniel Dunbar            index -= 1
59803588053d79b35a1c829fd00c09dae5cd4a2c7dDaniel Dunbar
60803588053d79b35a1c829fd00c09dae5cd4a2c7dDaniel Dunbar        index,insert_index = divmod(index, self.num_insert_strings)
61803588053d79b35a1c829fd00c09dae5cd4a2c7dDaniel Dunbar        insert_str = self.insert_strings[insert_index]
62803588053d79b35a1c829fd00c09dae5cd4a2c7dDaniel Dunbar        if self.insert:
63803588053d79b35a1c829fd00c09dae5cd4a2c7dDaniel Dunbar            if index == 0:
64803588053d79b35a1c829fd00c09dae5cd4a2c7dDaniel Dunbar                return ('insert', position, insert_str, picked_position)
65803588053d79b35a1c829fd00c09dae5cd4a2c7dDaniel Dunbar            index -= 1
66803588053d79b35a1c829fd00c09dae5cd4a2c7dDaniel Dunbar
67803588053d79b35a1c829fd00c09dae5cd4a2c7dDaniel Dunbar        assert self.replace
68803588053d79b35a1c829fd00c09dae5cd4a2c7dDaniel Dunbar        assert index == 0
69803588053d79b35a1c829fd00c09dae5cd4a2c7dDaniel Dunbar        return ('replace', position, insert_str, picked_position)
70803588053d79b35a1c829fd00c09dae5cd4a2c7dDaniel Dunbar
71803588053d79b35a1c829fd00c09dae5cd4a2c7dDaniel Dunbarclass TestApplication:
72803588053d79b35a1c829fd00c09dae5cd4a2c7dDaniel Dunbar    def __init__(self, tg, test):
73803588053d79b35a1c829fd00c09dae5cd4a2c7dDaniel Dunbar        self.tg = tg
74803588053d79b35a1c829fd00c09dae5cd4a2c7dDaniel Dunbar        self.test = test
75803588053d79b35a1c829fd00c09dae5cd4a2c7dDaniel Dunbar
76803588053d79b35a1c829fd00c09dae5cd4a2c7dDaniel Dunbar    def apply(self):
77803588053d79b35a1c829fd00c09dae5cd4a2c7dDaniel Dunbar        if self.test[0] == 'nothing':
78803588053d79b35a1c829fd00c09dae5cd4a2c7dDaniel Dunbar            pass
79803588053d79b35a1c829fd00c09dae5cd4a2c7dDaniel Dunbar        else:
80803588053d79b35a1c829fd00c09dae5cd4a2c7dDaniel Dunbar            i,j = self.test[1]
81803588053d79b35a1c829fd00c09dae5cd4a2c7dDaniel Dunbar            name,data = self.tg.inputs[i]
82803588053d79b35a1c829fd00c09dae5cd4a2c7dDaniel Dunbar            if self.test[0] == 'delete':
83803588053d79b35a1c829fd00c09dae5cd4a2c7dDaniel Dunbar                data = data[:j] + data[j+1:]
84803588053d79b35a1c829fd00c09dae5cd4a2c7dDaniel Dunbar            elif self.test[0] == 'insert':
85803588053d79b35a1c829fd00c09dae5cd4a2c7dDaniel Dunbar                data = data[:j] + self.test[2] + data[j:]
86803588053d79b35a1c829fd00c09dae5cd4a2c7dDaniel Dunbar            elif self.test[0] == 'replace':
87803588053d79b35a1c829fd00c09dae5cd4a2c7dDaniel Dunbar                data = data[:j] + self.test[2] + data[j+1:]
88803588053d79b35a1c829fd00c09dae5cd4a2c7dDaniel Dunbar            else:
89803588053d79b35a1c829fd00c09dae5cd4a2c7dDaniel Dunbar                raise ValueError,'Invalid test %r' % self.test
90803588053d79b35a1c829fd00c09dae5cd4a2c7dDaniel Dunbar            open(name,'wb').write(data)
91803588053d79b35a1c829fd00c09dae5cd4a2c7dDaniel Dunbar
92803588053d79b35a1c829fd00c09dae5cd4a2c7dDaniel Dunbar    def revert(self):
93803588053d79b35a1c829fd00c09dae5cd4a2c7dDaniel Dunbar        if self.test[0] != 'nothing':
94803588053d79b35a1c829fd00c09dae5cd4a2c7dDaniel Dunbar            i,j = self.test[1]
95803588053d79b35a1c829fd00c09dae5cd4a2c7dDaniel Dunbar            name,data = self.tg.inputs[i]
96803588053d79b35a1c829fd00c09dae5cd4a2c7dDaniel Dunbar            open(name,'wb').write(data)
97803588053d79b35a1c829fd00c09dae5cd4a2c7dDaniel Dunbar
98803588053d79b35a1c829fd00c09dae5cd4a2c7dDaniel Dunbardef quote(str):
99803588053d79b35a1c829fd00c09dae5cd4a2c7dDaniel Dunbar    return '"' + str + '"'
100803588053d79b35a1c829fd00c09dae5cd4a2c7dDaniel Dunbar        
101803588053d79b35a1c829fd00c09dae5cd4a2c7dDaniel Dunbardef run_one_test(test_application, index, input_files, args):
102803588053d79b35a1c829fd00c09dae5cd4a2c7dDaniel Dunbar    test = test_application.test
103803588053d79b35a1c829fd00c09dae5cd4a2c7dDaniel Dunbar
104803588053d79b35a1c829fd00c09dae5cd4a2c7dDaniel Dunbar    # Interpolate arguments.
105803588053d79b35a1c829fd00c09dae5cd4a2c7dDaniel Dunbar    options = { 'index' : index,
106803588053d79b35a1c829fd00c09dae5cd4a2c7dDaniel Dunbar                'inputs' : ' '.join(quote(f) for f in input_files) }
107803588053d79b35a1c829fd00c09dae5cd4a2c7dDaniel Dunbar
108803588053d79b35a1c829fd00c09dae5cd4a2c7dDaniel Dunbar    # Add picked input interpolation arguments, if used.
109803588053d79b35a1c829fd00c09dae5cd4a2c7dDaniel Dunbar    if test[3] is not None:
110803588053d79b35a1c829fd00c09dae5cd4a2c7dDaniel Dunbar        pos = test[3][1]
111803588053d79b35a1c829fd00c09dae5cd4a2c7dDaniel Dunbar        options['picked_input'] = input_files[test[3][0]]
112803588053d79b35a1c829fd00c09dae5cd4a2c7dDaniel Dunbar        options['picked_input_pos'] = pos
113803588053d79b35a1c829fd00c09dae5cd4a2c7dDaniel Dunbar        # Compute the line and column.
114803588053d79b35a1c829fd00c09dae5cd4a2c7dDaniel Dunbar        file_data = test_application.tg.inputs[test[3][0]][1]
115803588053d79b35a1c829fd00c09dae5cd4a2c7dDaniel Dunbar        line = column = 1
116803588053d79b35a1c829fd00c09dae5cd4a2c7dDaniel Dunbar        for i in range(pos):
117803588053d79b35a1c829fd00c09dae5cd4a2c7dDaniel Dunbar            c = file_data[i]
118803588053d79b35a1c829fd00c09dae5cd4a2c7dDaniel Dunbar            if c == '\n':
119803588053d79b35a1c829fd00c09dae5cd4a2c7dDaniel Dunbar                line += 1
120803588053d79b35a1c829fd00c09dae5cd4a2c7dDaniel Dunbar                column = 1
121803588053d79b35a1c829fd00c09dae5cd4a2c7dDaniel Dunbar            else:
122803588053d79b35a1c829fd00c09dae5cd4a2c7dDaniel Dunbar                column += 1
123803588053d79b35a1c829fd00c09dae5cd4a2c7dDaniel Dunbar        options['picked_input_line'] = line
124803588053d79b35a1c829fd00c09dae5cd4a2c7dDaniel Dunbar        options['picked_input_col'] = column
125803588053d79b35a1c829fd00c09dae5cd4a2c7dDaniel Dunbar        
126803588053d79b35a1c829fd00c09dae5cd4a2c7dDaniel Dunbar    test_args = [a % options for a in args]
127803588053d79b35a1c829fd00c09dae5cd4a2c7dDaniel Dunbar    if opts.verbose:
128803588053d79b35a1c829fd00c09dae5cd4a2c7dDaniel Dunbar        print '%s: note: executing %r' % (sys.argv[0], test_args)
129803588053d79b35a1c829fd00c09dae5cd4a2c7dDaniel Dunbar
130803588053d79b35a1c829fd00c09dae5cd4a2c7dDaniel Dunbar    stdout = None
131803588053d79b35a1c829fd00c09dae5cd4a2c7dDaniel Dunbar    stderr = None
132803588053d79b35a1c829fd00c09dae5cd4a2c7dDaniel Dunbar    if opts.log_dir:
133803588053d79b35a1c829fd00c09dae5cd4a2c7dDaniel Dunbar        stdout_log_path = os.path.join(opts.log_dir, '%s.out' % index)
134803588053d79b35a1c829fd00c09dae5cd4a2c7dDaniel Dunbar        stderr_log_path = os.path.join(opts.log_dir, '%s.err' % index)
135803588053d79b35a1c829fd00c09dae5cd4a2c7dDaniel Dunbar        stdout = open(stdout_log_path, 'wb')
136803588053d79b35a1c829fd00c09dae5cd4a2c7dDaniel Dunbar        stderr = open(stderr_log_path, 'wb')
137803588053d79b35a1c829fd00c09dae5cd4a2c7dDaniel Dunbar    else:
138803588053d79b35a1c829fd00c09dae5cd4a2c7dDaniel Dunbar        sys.stdout.flush()
139803588053d79b35a1c829fd00c09dae5cd4a2c7dDaniel Dunbar    p = subprocess.Popen(test_args, stdout=stdout, stderr=stderr)
140803588053d79b35a1c829fd00c09dae5cd4a2c7dDaniel Dunbar    p.communicate()
141803588053d79b35a1c829fd00c09dae5cd4a2c7dDaniel Dunbar    exit_code = p.wait()
142803588053d79b35a1c829fd00c09dae5cd4a2c7dDaniel Dunbar
143803588053d79b35a1c829fd00c09dae5cd4a2c7dDaniel Dunbar    test_result = (exit_code == opts.expected_exit_code or
144803588053d79b35a1c829fd00c09dae5cd4a2c7dDaniel Dunbar                   exit_code in opts.extra_exit_codes)
145803588053d79b35a1c829fd00c09dae5cd4a2c7dDaniel Dunbar
146803588053d79b35a1c829fd00c09dae5cd4a2c7dDaniel Dunbar    if stdout is not None:
147803588053d79b35a1c829fd00c09dae5cd4a2c7dDaniel Dunbar        stdout.close()
148803588053d79b35a1c829fd00c09dae5cd4a2c7dDaniel Dunbar        stderr.close()
149803588053d79b35a1c829fd00c09dae5cd4a2c7dDaniel Dunbar
150803588053d79b35a1c829fd00c09dae5cd4a2c7dDaniel Dunbar        # Remove the logs for passes, unless logging all results.
151803588053d79b35a1c829fd00c09dae5cd4a2c7dDaniel Dunbar        if not opts.log_all and test_result:
152803588053d79b35a1c829fd00c09dae5cd4a2c7dDaniel Dunbar            os.remove(stdout_log_path)
153803588053d79b35a1c829fd00c09dae5cd4a2c7dDaniel Dunbar            os.remove(stderr_log_path)
154803588053d79b35a1c829fd00c09dae5cd4a2c7dDaniel Dunbar
155803588053d79b35a1c829fd00c09dae5cd4a2c7dDaniel Dunbar    if not test_result:
156803588053d79b35a1c829fd00c09dae5cd4a2c7dDaniel Dunbar        print 'FAIL: %d' % index
157803588053d79b35a1c829fd00c09dae5cd4a2c7dDaniel Dunbar    elif not opts.succinct:
158803588053d79b35a1c829fd00c09dae5cd4a2c7dDaniel Dunbar        print 'PASS: %d' % index
1592a6dc143b6da657ca0839acec1ac36e12cd1b295Argyrios Kyrtzidis    return test_result
160803588053d79b35a1c829fd00c09dae5cd4a2c7dDaniel Dunbar
161803588053d79b35a1c829fd00c09dae5cd4a2c7dDaniel Dunbardef main():
162803588053d79b35a1c829fd00c09dae5cd4a2c7dDaniel Dunbar    global opts
163803588053d79b35a1c829fd00c09dae5cd4a2c7dDaniel Dunbar    from optparse import OptionParser, OptionGroup
164803588053d79b35a1c829fd00c09dae5cd4a2c7dDaniel Dunbar    parser = OptionParser("""%prog [options] ... test command args ...
165803588053d79b35a1c829fd00c09dae5cd4a2c7dDaniel Dunbar
166803588053d79b35a1c829fd00c09dae5cd4a2c7dDaniel Dunbar%prog is a tool for fuzzing inputs and testing them.
167803588053d79b35a1c829fd00c09dae5cd4a2c7dDaniel Dunbar
168803588053d79b35a1c829fd00c09dae5cd4a2c7dDaniel DunbarThe most basic usage is something like:
169803588053d79b35a1c829fd00c09dae5cd4a2c7dDaniel Dunbar
170803588053d79b35a1c829fd00c09dae5cd4a2c7dDaniel Dunbar  $ %prog --file foo.txt ./test.sh
171803588053d79b35a1c829fd00c09dae5cd4a2c7dDaniel Dunbar
172803588053d79b35a1c829fd00c09dae5cd4a2c7dDaniel Dunbarwhich will run a default list of fuzzing strategies on the input. For each
173803588053d79b35a1c829fd00c09dae5cd4a2c7dDaniel Dunbarfuzzed input, it will overwrite the input files (in place), run the test script,
174803588053d79b35a1c829fd00c09dae5cd4a2c7dDaniel Dunbarthen restore the files back to their original contents.
175803588053d79b35a1c829fd00c09dae5cd4a2c7dDaniel Dunbar
176803588053d79b35a1c829fd00c09dae5cd4a2c7dDaniel DunbarNOTE: You should make sure you have a backup copy of your inputs, in case
177803588053d79b35a1c829fd00c09dae5cd4a2c7dDaniel Dunbarsomething goes wrong!!!
178803588053d79b35a1c829fd00c09dae5cd4a2c7dDaniel Dunbar
179803588053d79b35a1c829fd00c09dae5cd4a2c7dDaniel DunbarYou can cause the fuzzing to not restore the original files with
180803588053d79b35a1c829fd00c09dae5cd4a2c7dDaniel Dunbar'--no-revert'. Generally this is used with '--test <index>' to run one failing
181803588053d79b35a1c829fd00c09dae5cd4a2c7dDaniel Dunbartest and then leave the fuzzed inputs in place to examine the failure.
182803588053d79b35a1c829fd00c09dae5cd4a2c7dDaniel Dunbar
183803588053d79b35a1c829fd00c09dae5cd4a2c7dDaniel DunbarFor each fuzzed input, %prog will run the test command given on the command
184803588053d79b35a1c829fd00c09dae5cd4a2c7dDaniel Dunbarline. Each argument in the command is subject to string interpolation before
185803588053d79b35a1c829fd00c09dae5cd4a2c7dDaniel Dunbarbeing executed. The syntax is "%(VARIABLE)FORMAT" where FORMAT is a standard
1869df08bb7661779a1703d29681833b79e62336c22Daniel Dunbarprintf format, and VARIABLE is one of:
187803588053d79b35a1c829fd00c09dae5cd4a2c7dDaniel Dunbar
188803588053d79b35a1c829fd00c09dae5cd4a2c7dDaniel Dunbar  'index' - the test index being run
189803588053d79b35a1c829fd00c09dae5cd4a2c7dDaniel Dunbar  'inputs' - the full list of test inputs
190803588053d79b35a1c829fd00c09dae5cd4a2c7dDaniel Dunbar  'picked_input'      - (with --pick-input) the selected input file
191803588053d79b35a1c829fd00c09dae5cd4a2c7dDaniel Dunbar  'picked_input_pos'  - (with --pick-input) the selected input position
192803588053d79b35a1c829fd00c09dae5cd4a2c7dDaniel Dunbar  'picked_input_line' - (with --pick-input) the selected input line
193803588053d79b35a1c829fd00c09dae5cd4a2c7dDaniel Dunbar  'picked_input_col'  - (with --pick-input) the selected input column
194803588053d79b35a1c829fd00c09dae5cd4a2c7dDaniel Dunbar
195803588053d79b35a1c829fd00c09dae5cd4a2c7dDaniel DunbarBy default, the script will run forever continually picking new tests to
196803588053d79b35a1c829fd00c09dae5cd4a2c7dDaniel Dunbarrun. You can limit the number of tests that are run with '--max-tests <number>',
197803588053d79b35a1c829fd00c09dae5cd4a2c7dDaniel Dunbarand you can run a particular test with '--test <index>'.
1982a6dc143b6da657ca0839acec1ac36e12cd1b295Argyrios Kyrtzidis
1992a6dc143b6da657ca0839acec1ac36e12cd1b295Argyrios KyrtzidisYou can specify '--stop-on-fail' to stop the script on the first failure
2002a6dc143b6da657ca0839acec1ac36e12cd1b295Argyrios Kyrtzidiswithout reverting the changes.
2012a6dc143b6da657ca0839acec1ac36e12cd1b295Argyrios Kyrtzidis
202803588053d79b35a1c829fd00c09dae5cd4a2c7dDaniel Dunbar""")
203803588053d79b35a1c829fd00c09dae5cd4a2c7dDaniel Dunbar    parser.add_option("-v", "--verbose", help="Show more output",
204803588053d79b35a1c829fd00c09dae5cd4a2c7dDaniel Dunbar                      action='store_true', dest="verbose", default=False)
205803588053d79b35a1c829fd00c09dae5cd4a2c7dDaniel Dunbar    parser.add_option("-s", "--succinct",  help="Reduce amount of output",
206803588053d79b35a1c829fd00c09dae5cd4a2c7dDaniel Dunbar                      action="store_true", dest="succinct", default=False)
207803588053d79b35a1c829fd00c09dae5cd4a2c7dDaniel Dunbar
208803588053d79b35a1c829fd00c09dae5cd4a2c7dDaniel Dunbar    group = OptionGroup(parser, "Test Execution")
209803588053d79b35a1c829fd00c09dae5cd4a2c7dDaniel Dunbar    group.add_option("", "--expected-exit-code", help="Set expected exit code",
210803588053d79b35a1c829fd00c09dae5cd4a2c7dDaniel Dunbar                     type=int, dest="expected_exit_code",
211803588053d79b35a1c829fd00c09dae5cd4a2c7dDaniel Dunbar                     default=0)
212803588053d79b35a1c829fd00c09dae5cd4a2c7dDaniel Dunbar    group.add_option("", "--extra-exit-code",
213803588053d79b35a1c829fd00c09dae5cd4a2c7dDaniel Dunbar                     help="Set additional expected exit code",
214803588053d79b35a1c829fd00c09dae5cd4a2c7dDaniel Dunbar                     type=int, action="append", dest="extra_exit_codes",
215803588053d79b35a1c829fd00c09dae5cd4a2c7dDaniel Dunbar                     default=[])
216803588053d79b35a1c829fd00c09dae5cd4a2c7dDaniel Dunbar    group.add_option("", "--log-dir",
217803588053d79b35a1c829fd00c09dae5cd4a2c7dDaniel Dunbar                     help="Capture test logs to an output directory",
218803588053d79b35a1c829fd00c09dae5cd4a2c7dDaniel Dunbar                     type=str, dest="log_dir",
219803588053d79b35a1c829fd00c09dae5cd4a2c7dDaniel Dunbar                     default=None)
220803588053d79b35a1c829fd00c09dae5cd4a2c7dDaniel Dunbar    group.add_option("", "--log-all",
221803588053d79b35a1c829fd00c09dae5cd4a2c7dDaniel Dunbar                     help="Log all outputs (not just failures)",
222803588053d79b35a1c829fd00c09dae5cd4a2c7dDaniel Dunbar                     action="store_true", dest="log_all", default=False)
223803588053d79b35a1c829fd00c09dae5cd4a2c7dDaniel Dunbar    parser.add_option_group(group)
224803588053d79b35a1c829fd00c09dae5cd4a2c7dDaniel Dunbar
225803588053d79b35a1c829fd00c09dae5cd4a2c7dDaniel Dunbar    group = OptionGroup(parser, "Input Files")
226803588053d79b35a1c829fd00c09dae5cd4a2c7dDaniel Dunbar    group.add_option("", "--file", metavar="PATH",
227803588053d79b35a1c829fd00c09dae5cd4a2c7dDaniel Dunbar                     help="Add an input file to fuzz",
228803588053d79b35a1c829fd00c09dae5cd4a2c7dDaniel Dunbar                     type=str, action="append", dest="input_files", default=[])
229803588053d79b35a1c829fd00c09dae5cd4a2c7dDaniel Dunbar    group.add_option("", "--filelist", metavar="LIST",
230803588053d79b35a1c829fd00c09dae5cd4a2c7dDaniel Dunbar                     help="Add a list of inputs files to fuzz (one per line)",
23100f1f27664c0fcb4b536aa5835094ad09b1a5371Argyrios Kyrtzidis                     type=str, action="append", dest="filelists", default=[])
232803588053d79b35a1c829fd00c09dae5cd4a2c7dDaniel Dunbar    parser.add_option_group(group)
233803588053d79b35a1c829fd00c09dae5cd4a2c7dDaniel Dunbar
234803588053d79b35a1c829fd00c09dae5cd4a2c7dDaniel Dunbar    group = OptionGroup(parser, "Fuzz Options")
235803588053d79b35a1c829fd00c09dae5cd4a2c7dDaniel Dunbar    group.add_option("", "--replacement-chars", dest="replacement_chars",
236803588053d79b35a1c829fd00c09dae5cd4a2c7dDaniel Dunbar                     help="Characters to insert/replace",
237803588053d79b35a1c829fd00c09dae5cd4a2c7dDaniel Dunbar                     default="0{}[]<>\;@#$^%& ")
238803588053d79b35a1c829fd00c09dae5cd4a2c7dDaniel Dunbar    group.add_option("", "--replacement-string", dest="replacement_strings",
239803588053d79b35a1c829fd00c09dae5cd4a2c7dDaniel Dunbar                     action="append", help="Add a replacement string to use",
240803588053d79b35a1c829fd00c09dae5cd4a2c7dDaniel Dunbar                     default=[])
241c53a844e22c5a635c64ede7e1ac31fb035e52701Daniel Dunbar    group.add_option("", "--replacement-list", dest="replacement_lists",
242c53a844e22c5a635c64ede7e1ac31fb035e52701Daniel Dunbar                     help="Add a list of replacement strings (one per line)",
243c53a844e22c5a635c64ede7e1ac31fb035e52701Daniel Dunbar                     action="append", default=[])
244803588053d79b35a1c829fd00c09dae5cd4a2c7dDaniel Dunbar    group.add_option("", "--no-delete", help="Don't delete characters",
245803588053d79b35a1c829fd00c09dae5cd4a2c7dDaniel Dunbar                     action='store_false', dest="enable_delete", default=True)
246803588053d79b35a1c829fd00c09dae5cd4a2c7dDaniel Dunbar    group.add_option("", "--no-insert", help="Don't insert strings",
247803588053d79b35a1c829fd00c09dae5cd4a2c7dDaniel Dunbar                     action='store_false', dest="enable_insert", default=True)
248803588053d79b35a1c829fd00c09dae5cd4a2c7dDaniel Dunbar    group.add_option("", "--no-replace", help="Don't replace strings",
249803588053d79b35a1c829fd00c09dae5cd4a2c7dDaniel Dunbar                     action='store_false', dest="enable_replace", default=True)
250803588053d79b35a1c829fd00c09dae5cd4a2c7dDaniel Dunbar    group.add_option("", "--no-revert", help="Don't revert changes",
251803588053d79b35a1c829fd00c09dae5cd4a2c7dDaniel Dunbar                     action='store_false', dest="revert", default=True)
2522a6dc143b6da657ca0839acec1ac36e12cd1b295Argyrios Kyrtzidis    group.add_option("", "--stop-on-fail", help="Stop on first failure",
2532a6dc143b6da657ca0839acec1ac36e12cd1b295Argyrios Kyrtzidis                     action='store_true', dest="stop_on_fail", default=False)
254803588053d79b35a1c829fd00c09dae5cd4a2c7dDaniel Dunbar    parser.add_option_group(group)
255803588053d79b35a1c829fd00c09dae5cd4a2c7dDaniel Dunbar
256803588053d79b35a1c829fd00c09dae5cd4a2c7dDaniel Dunbar    group = OptionGroup(parser, "Test Selection")
257803588053d79b35a1c829fd00c09dae5cd4a2c7dDaniel Dunbar    group.add_option("", "--test", help="Run a particular test",
258803588053d79b35a1c829fd00c09dae5cd4a2c7dDaniel Dunbar                     type=int, dest="test", default=None, metavar="INDEX")
259803588053d79b35a1c829fd00c09dae5cd4a2c7dDaniel Dunbar    group.add_option("", "--max-tests", help="Maximum number of tests",
2603cb8e2f5e9ce7677ea01adf5746a6b529fae934bArgyrios Kyrtzidis                     type=int, dest="max_tests", default=None, metavar="COUNT")
261803588053d79b35a1c829fd00c09dae5cd4a2c7dDaniel Dunbar    group.add_option("", "--pick-input",
262803588053d79b35a1c829fd00c09dae5cd4a2c7dDaniel Dunbar                     help="Randomly select an input byte as well as fuzzing",
263803588053d79b35a1c829fd00c09dae5cd4a2c7dDaniel Dunbar                     action='store_true', dest="pick_input", default=False)
264803588053d79b35a1c829fd00c09dae5cd4a2c7dDaniel Dunbar    parser.add_option_group(group)
265803588053d79b35a1c829fd00c09dae5cd4a2c7dDaniel Dunbar
266803588053d79b35a1c829fd00c09dae5cd4a2c7dDaniel Dunbar    parser.disable_interspersed_args()
267803588053d79b35a1c829fd00c09dae5cd4a2c7dDaniel Dunbar
268803588053d79b35a1c829fd00c09dae5cd4a2c7dDaniel Dunbar    (opts, args) = parser.parse_args()
269803588053d79b35a1c829fd00c09dae5cd4a2c7dDaniel Dunbar
270803588053d79b35a1c829fd00c09dae5cd4a2c7dDaniel Dunbar    if not args:
271803588053d79b35a1c829fd00c09dae5cd4a2c7dDaniel Dunbar        parser.error("Invalid number of arguments")
272803588053d79b35a1c829fd00c09dae5cd4a2c7dDaniel Dunbar
273803588053d79b35a1c829fd00c09dae5cd4a2c7dDaniel Dunbar    # Collect the list of inputs.
274803588053d79b35a1c829fd00c09dae5cd4a2c7dDaniel Dunbar    input_files = list(opts.input_files)
275803588053d79b35a1c829fd00c09dae5cd4a2c7dDaniel Dunbar    for filelist in opts.filelists:
276803588053d79b35a1c829fd00c09dae5cd4a2c7dDaniel Dunbar        f = open(filelist)
277803588053d79b35a1c829fd00c09dae5cd4a2c7dDaniel Dunbar        try:
278803588053d79b35a1c829fd00c09dae5cd4a2c7dDaniel Dunbar            for ln in f:
279803588053d79b35a1c829fd00c09dae5cd4a2c7dDaniel Dunbar                ln = ln.strip()
280803588053d79b35a1c829fd00c09dae5cd4a2c7dDaniel Dunbar                if ln:
281803588053d79b35a1c829fd00c09dae5cd4a2c7dDaniel Dunbar                    input_files.append(ln)
282803588053d79b35a1c829fd00c09dae5cd4a2c7dDaniel Dunbar        finally:
283803588053d79b35a1c829fd00c09dae5cd4a2c7dDaniel Dunbar            f.close()
284803588053d79b35a1c829fd00c09dae5cd4a2c7dDaniel Dunbar    input_files.sort()
285803588053d79b35a1c829fd00c09dae5cd4a2c7dDaniel Dunbar
286803588053d79b35a1c829fd00c09dae5cd4a2c7dDaniel Dunbar    if not input_files:
287803588053d79b35a1c829fd00c09dae5cd4a2c7dDaniel Dunbar        parser.error("No input files!")
288803588053d79b35a1c829fd00c09dae5cd4a2c7dDaniel Dunbar
289803588053d79b35a1c829fd00c09dae5cd4a2c7dDaniel Dunbar    print '%s: note: fuzzing %d files.' % (sys.argv[0], len(input_files))
290803588053d79b35a1c829fd00c09dae5cd4a2c7dDaniel Dunbar
291803588053d79b35a1c829fd00c09dae5cd4a2c7dDaniel Dunbar    # Make sure the log directory exists if used.
292803588053d79b35a1c829fd00c09dae5cd4a2c7dDaniel Dunbar    if opts.log_dir:
293803588053d79b35a1c829fd00c09dae5cd4a2c7dDaniel Dunbar        if not os.path.exists(opts.log_dir):
294803588053d79b35a1c829fd00c09dae5cd4a2c7dDaniel Dunbar            try:
295803588053d79b35a1c829fd00c09dae5cd4a2c7dDaniel Dunbar                os.mkdir(opts.log_dir)
296803588053d79b35a1c829fd00c09dae5cd4a2c7dDaniel Dunbar            except OSError:
297803588053d79b35a1c829fd00c09dae5cd4a2c7dDaniel Dunbar                print "%s: error: log directory couldn't be created!" % (
298803588053d79b35a1c829fd00c09dae5cd4a2c7dDaniel Dunbar                    sys.argv[0],)
299803588053d79b35a1c829fd00c09dae5cd4a2c7dDaniel Dunbar                raise SystemExit,1
300803588053d79b35a1c829fd00c09dae5cd4a2c7dDaniel Dunbar
301803588053d79b35a1c829fd00c09dae5cd4a2c7dDaniel Dunbar    # Get the list if insert/replacement strings.
302803588053d79b35a1c829fd00c09dae5cd4a2c7dDaniel Dunbar    replacements = list(opts.replacement_chars)
303803588053d79b35a1c829fd00c09dae5cd4a2c7dDaniel Dunbar    replacements.extend(opts.replacement_strings)
304c53a844e22c5a635c64ede7e1ac31fb035e52701Daniel Dunbar    for replacement_list in opts.replacement_lists:
305c53a844e22c5a635c64ede7e1ac31fb035e52701Daniel Dunbar        f = open(replacement_list)
306c53a844e22c5a635c64ede7e1ac31fb035e52701Daniel Dunbar        try:
307c53a844e22c5a635c64ede7e1ac31fb035e52701Daniel Dunbar            for ln in f:
308c53a844e22c5a635c64ede7e1ac31fb035e52701Daniel Dunbar                ln = ln[:-1]
309c53a844e22c5a635c64ede7e1ac31fb035e52701Daniel Dunbar                if ln:
310c53a844e22c5a635c64ede7e1ac31fb035e52701Daniel Dunbar                    replacements.append(ln)
311c53a844e22c5a635c64ede7e1ac31fb035e52701Daniel Dunbar        finally:
312c53a844e22c5a635c64ede7e1ac31fb035e52701Daniel Dunbar            f.close()
313c53a844e22c5a635c64ede7e1ac31fb035e52701Daniel Dunbar
314c53a844e22c5a635c64ede7e1ac31fb035e52701Daniel Dunbar    # Unique and order the replacement list.
315c53a844e22c5a635c64ede7e1ac31fb035e52701Daniel Dunbar    replacements = list(set(replacements))
316c53a844e22c5a635c64ede7e1ac31fb035e52701Daniel Dunbar    replacements.sort()
317803588053d79b35a1c829fd00c09dae5cd4a2c7dDaniel Dunbar
318803588053d79b35a1c829fd00c09dae5cd4a2c7dDaniel Dunbar    # Create the test generator.
319803588053d79b35a1c829fd00c09dae5cd4a2c7dDaniel Dunbar    tg = TestGenerator(input_files, opts.enable_delete, opts.enable_insert,
320803588053d79b35a1c829fd00c09dae5cd4a2c7dDaniel Dunbar                       opts.enable_replace, replacements, opts.pick_input)
321803588053d79b35a1c829fd00c09dae5cd4a2c7dDaniel Dunbar
322803588053d79b35a1c829fd00c09dae5cd4a2c7dDaniel Dunbar    print '%s: note: %d input bytes.' % (sys.argv[0], tg.num_positions)
323803588053d79b35a1c829fd00c09dae5cd4a2c7dDaniel Dunbar    print '%s: note: %d total tests.' % (sys.argv[0], tg.num_tests)
324803588053d79b35a1c829fd00c09dae5cd4a2c7dDaniel Dunbar    if opts.test is not None:
325803588053d79b35a1c829fd00c09dae5cd4a2c7dDaniel Dunbar        it = [opts.test]
326803588053d79b35a1c829fd00c09dae5cd4a2c7dDaniel Dunbar    elif opts.max_tests is not None:
327803588053d79b35a1c829fd00c09dae5cd4a2c7dDaniel Dunbar        it = itertools.imap(random.randrange,
328803588053d79b35a1c829fd00c09dae5cd4a2c7dDaniel Dunbar                            itertools.repeat(tg.num_tests, opts.max_tests))
329803588053d79b35a1c829fd00c09dae5cd4a2c7dDaniel Dunbar    else:
330803588053d79b35a1c829fd00c09dae5cd4a2c7dDaniel Dunbar        it = itertools.imap(random.randrange, itertools.repeat(tg.num_tests))
331803588053d79b35a1c829fd00c09dae5cd4a2c7dDaniel Dunbar    for test in it:
332803588053d79b35a1c829fd00c09dae5cd4a2c7dDaniel Dunbar        t = tg.get_test(test)
333803588053d79b35a1c829fd00c09dae5cd4a2c7dDaniel Dunbar
334803588053d79b35a1c829fd00c09dae5cd4a2c7dDaniel Dunbar        if opts.verbose:
335803588053d79b35a1c829fd00c09dae5cd4a2c7dDaniel Dunbar            print '%s: note: running test %d: %r' % (sys.argv[0], test, t)
336803588053d79b35a1c829fd00c09dae5cd4a2c7dDaniel Dunbar        ta = TestApplication(tg, t)
337803588053d79b35a1c829fd00c09dae5cd4a2c7dDaniel Dunbar        try:
338803588053d79b35a1c829fd00c09dae5cd4a2c7dDaniel Dunbar            ta.apply()
3392a6dc143b6da657ca0839acec1ac36e12cd1b295Argyrios Kyrtzidis            test_result = run_one_test(ta, test, input_files, args)
3402a6dc143b6da657ca0839acec1ac36e12cd1b295Argyrios Kyrtzidis            if not test_result and opts.stop_on_fail:
3412a6dc143b6da657ca0839acec1ac36e12cd1b295Argyrios Kyrtzidis                opts.revert = False
3422a6dc143b6da657ca0839acec1ac36e12cd1b295Argyrios Kyrtzidis                sys.exit(1)
343803588053d79b35a1c829fd00c09dae5cd4a2c7dDaniel Dunbar        finally:
344803588053d79b35a1c829fd00c09dae5cd4a2c7dDaniel Dunbar            if opts.revert:
345803588053d79b35a1c829fd00c09dae5cd4a2c7dDaniel Dunbar                ta.revert()
346803588053d79b35a1c829fd00c09dae5cd4a2c7dDaniel Dunbar
347803588053d79b35a1c829fd00c09dae5cd4a2c7dDaniel Dunbar        sys.stdout.flush()
348803588053d79b35a1c829fd00c09dae5cd4a2c7dDaniel Dunbar
349803588053d79b35a1c829fd00c09dae5cd4a2c7dDaniel Dunbarif __name__ == '__main__':
350803588053d79b35a1c829fd00c09dae5cd4a2c7dDaniel Dunbar    main()
351