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()
|