summaryrefslogtreecommitdiffstats
path: root/scripts/oss-fuzz/output_reproducer.py
blob: e8ef76b3413812313ec2bb0e1c5e9df8cb6be48a (plain) (blame)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
#!/usr/bin/env python3
# -*- coding: utf-8 -*-

"""
Convert plain qtest traces to C or Bash reproducers

Use this to help build bug-reports or create in-tree reproducers for bugs.
Note: This will not format C code for you. Pipe the output through
clang-format -style="{BasedOnStyle: llvm, IndentWidth: 4, ColumnLimit: 90}"
or similar
"""

import sys
import os
import argparse
import textwrap
from datetime import date

__author__     = "Alexander Bulekov <alxndr@bu.edu>"
__copyright__  = "Copyright (C) 2021, Red Hat, Inc."
__license__    = "GPL version 2 or (at your option) any later version"

__maintainer__ = "Alexander Bulekov"
__email__      = "alxndr@bu.edu"


def c_header(owner):
    return """/*
 * Autogenerated Fuzzer Test Case
 *
 * Copyright (c) {date} {owner}
 *
 * This work is licensed under the terms of the GNU GPL, version 2 or later.
 * See the COPYING file in the top-level directory.
 */

#include "qemu/osdep.h"

#include "libqtest.h"

    """.format(date=date.today().year, owner=owner)

def c_comment(s):
    """ Return a multi-line C comment. Assume the text is already wrapped """
    return "/*\n * " + "\n * ".join(s.splitlines()) + "\n*/"

def print_c_function(s):
    print("/* ")
    for l in s.splitlines():
        print(" * {}".format(l))

def bash_reproducer(path, args, trace):
    result = '\\\n'.join(textwrap.wrap("cat << EOF | {} {}".format(path, args),
                                       72, break_on_hyphens=False,
                                       drop_whitespace=False))
    for l in trace.splitlines():
        result += "\n" + '\\\n'.join(textwrap.wrap(l,72,drop_whitespace=False))
    result += "\nEOF"
    return result

def c_reproducer(name, args, trace):
    result = []
    result.append("""static void {}(void)\n{{""".format(name))

    # libqtest will add its own qtest args, so get rid of them
    args = args.replace("-accel qtest","")
    args = args.replace(",accel=qtest","")
    args = args.replace("-machine accel=qtest","")
    args = args.replace("-qtest stdio","")
    result.append("""QTestState *s = qtest_init("{}");""".format(args))
    for l in trace.splitlines():
        param = l.split()
        cmd = param[0]
        if cmd == "write":
            buf = param[3][2:] #Get the 0x... buffer and trim the "0x"
            assert len(buf)%2 == 0
            bufbytes = [buf[i:i+2] for i in range(0, len(buf), 2)]
            bufstring = '\\x'+'\\x'.join(bufbytes)
            addr = param[1]
            size = param[2]
            result.append("""qtest_bufwrite(s, {}, "{}", {});""".format(
                          addr, bufstring, size))
        elif cmd.startswith("in") or cmd.startswith("read"):
            result.append("qtest_{}(s, {});".format(
                          cmd, param[1]))
        elif cmd.startswith("out") or cmd.startswith("write"):
            result.append("qtest_{}(s, {}, {});".format(
                          cmd, param[1], param[2]))
        elif cmd == "clock_step":
            if len(param) ==1:
                result.append("qtest_clock_step_next(s);")
            else:
                result.append("qtest_clock_step(s, {});".format(param[1]))
    result.append("qtest_quit(s);\n}")
    return "\n".join(result)

def c_main(name, arch):
    return """int main(int argc, char **argv)
{{
    const char *arch = qtest_get_arch();

    g_test_init(&argc, &argv, NULL);

   if (strcmp(arch, "{arch}") == 0) {{
        qtest_add_func("fuzz/{name}",{name});
   }}

   return g_test_run();
}}""".format(name=name, arch=arch)

def main():
    parser = argparse.ArgumentParser()
    group = parser.add_mutually_exclusive_group()
    group.add_argument("-bash", help="Only output a copy-pastable bash command",
                        action="store_true")
    group.add_argument("-c", help="Only output a c function",
                        action="store_true")
    parser.add_argument('-owner', help="If generating complete C source code, \
                        this specifies the Copyright owner",
                        nargs='?', default="<name of author>")
    parser.add_argument("-no_comment", help="Don't include a bash reproducer \
                        as a comment in the C reproducers",
                        action="store_true")
    parser.add_argument('-name', help="The name of the c function",
                        nargs='?', default="test_fuzz")
    parser.add_argument('input_trace', help="input QTest command sequence \
                        (stdin by default)",
                        nargs='?', type=argparse.FileType('r'),
                        default=sys.stdin)
    args = parser.parse_args()

    qemu_path = os.getenv("QEMU_PATH")
    qemu_args = os.getenv("QEMU_ARGS")
    if not qemu_args or not qemu_path:
        print("Please set QEMU_PATH and QEMU_ARGS environment variables")
        sys.exit(1)

    bash_args = qemu_args
    if " -qtest stdio" not in  qemu_args:
        bash_args += " -qtest stdio"

    arch = qemu_path.split("-")[-1]
    trace = args.input_trace.read().strip()

    if args.bash :
        print(bash_reproducer(qemu_path, bash_args, trace))
    else:
        output = ""
        if not args.c:
            output += c_header(args.owner) + "\n"
        if not args.no_comment:
            output += c_comment(bash_reproducer(qemu_path, bash_args, trace))
        output += c_reproducer(args.name, qemu_args, trace)
        if not args.c:
            output += c_main(args.name, arch)
        print(output)


if __name__ == '__main__':
    main()