xref: /qemu/scripts/oss-fuzz/reorder_fuzzer_qtest_trace.py (revision 7c9b64ade9d1d3c69250ef1684db9c080a7b7092)
1*7c9b64adSAlexander Bulekov#!/usr/bin/env python3
2*7c9b64adSAlexander Bulekov# -*- coding: utf-8 -*-
3*7c9b64adSAlexander Bulekov
4*7c9b64adSAlexander Bulekov"""
5*7c9b64adSAlexander BulekovUse this to convert qtest log info from a generic fuzzer input into a qtest
6*7c9b64adSAlexander Bulekovtrace that you can feed into a standard qemu-system process. Example usage:
7*7c9b64adSAlexander Bulekov
8*7c9b64adSAlexander BulekovQEMU_FUZZ_ARGS="-machine q35,accel=qtest" QEMU_FUZZ_OBJECTS="*" \
9*7c9b64adSAlexander Bulekov        ./i386-softmmu/qemu-fuzz-i386 --fuzz-target=generic-pci-fuzz
10*7c9b64adSAlexander Bulekov# .. Finds some crash
11*7c9b64adSAlexander BulekovQTEST_LOG=1 FUZZ_SERIALIZE_QTEST=1 \
12*7c9b64adSAlexander BulekovQEMU_FUZZ_ARGS="-machine q35,accel=qtest" QEMU_FUZZ_OBJECTS="*" \
13*7c9b64adSAlexander Bulekov        ./i386-softmmu/qemu-fuzz-i386 --fuzz-target=generic-pci-fuzz
14*7c9b64adSAlexander Bulekov        /path/to/crash 2> qtest_log_output
15*7c9b64adSAlexander Bulekovscripts/oss-fuzz/reorder_fuzzer_qtest_trace.py qtest_log_output > qtest_trace
16*7c9b64adSAlexander Bulekov./i386-softmmu/qemu-fuzz-i386 -machine q35,accel=qtest \
17*7c9b64adSAlexander Bulekov        -qtest stdin < qtest_trace
18*7c9b64adSAlexander Bulekov
19*7c9b64adSAlexander Bulekov### Details ###
20*7c9b64adSAlexander Bulekov
21*7c9b64adSAlexander BulekovSome fuzzer make use of hooks that allow us to populate some memory range, just
22*7c9b64adSAlexander Bulekovbefore a DMA read from that range. This means that the fuzzer can produce
23*7c9b64adSAlexander Bulekovactivity that looks like:
24*7c9b64adSAlexander Bulekov    [start] read from mmio addr
25*7c9b64adSAlexander Bulekov    [end]   read from mmio addr
26*7c9b64adSAlexander Bulekov    [start] write to pio addr
27*7c9b64adSAlexander Bulekov        [start] fill a DMA buffer just in time
28*7c9b64adSAlexander Bulekov        [end]   fill a DMA buffer just in time
29*7c9b64adSAlexander Bulekov        [start] fill a DMA buffer just in time
30*7c9b64adSAlexander Bulekov        [end]   fill a DMA buffer just in time
31*7c9b64adSAlexander Bulekov    [end]   write to pio addr
32*7c9b64adSAlexander Bulekov    [start] read from mmio addr
33*7c9b64adSAlexander Bulekov    [end]   read from mmio addr
34*7c9b64adSAlexander Bulekov
35*7c9b64adSAlexander BulekovWe annotate these "nested" DMA writes, so with QTEST_LOG=1 the QTest trace
36*7c9b64adSAlexander Bulekovmight look something like:
37*7c9b64adSAlexander Bulekov[R +0.028431] readw 0x10000
38*7c9b64adSAlexander Bulekov[R +0.028434] outl 0xc000 0xbeef  # Triggers a DMA read from 0xbeef and 0xbf00
39*7c9b64adSAlexander Bulekov[DMA][R +0.034639] write 0xbeef 0x2 0xAAAA
40*7c9b64adSAlexander Bulekov[DMA][R +0.034639] write 0xbf00 0x2 0xBBBB
41*7c9b64adSAlexander Bulekov[R +0.028431] readw 0xfc000
42*7c9b64adSAlexander Bulekov
43*7c9b64adSAlexander BulekovThis script would reorder the above trace so it becomes:
44*7c9b64adSAlexander Bulekovreadw 0x10000
45*7c9b64adSAlexander Bulekovwrite 0xbeef 0x2 0xAAAA
46*7c9b64adSAlexander Bulekovwrite 0xbf00 0x2 0xBBBB
47*7c9b64adSAlexander Bulekovoutl 0xc000 0xbeef
48*7c9b64adSAlexander Bulekovreadw 0xfc000
49*7c9b64adSAlexander Bulekov
50*7c9b64adSAlexander BulekovI.e. by the time, 0xc000 tries to read from DMA, those DMA buffers have already
51*7c9b64adSAlexander Bulekovbeen set up, removing the need for the DMA hooks. We can simply provide this
52*7c9b64adSAlexander Bulekovreordered trace via -qtest stdio to reproduce the input
53*7c9b64adSAlexander Bulekov
54*7c9b64adSAlexander BulekovNote: this won't work for traces where the device tries to read from the same
55*7c9b64adSAlexander BulekovDMA region twice in between MMIO/PIO commands. E.g:
56*7c9b64adSAlexander Bulekov    [R +0.028434] outl 0xc000 0xbeef
57*7c9b64adSAlexander Bulekov    [DMA][R +0.034639] write 0xbeef 0x2 0xAAAA
58*7c9b64adSAlexander Bulekov    [DMA][R +0.034639] write 0xbeef 0x2 0xBBBB
59*7c9b64adSAlexander Bulekov
60*7c9b64adSAlexander BulekovThe fuzzer will annotate suspected double-fetches with [DOUBLE-FETCH]. This
61*7c9b64adSAlexander Bulekovscript looks for these tags and warns the users that the resulting trace might
62*7c9b64adSAlexander Bulekovnot reproduce the bug.
63*7c9b64adSAlexander Bulekov"""
64*7c9b64adSAlexander Bulekov
65*7c9b64adSAlexander Bulekovimport sys
66*7c9b64adSAlexander Bulekov
67*7c9b64adSAlexander Bulekov__author__     = "Alexander Bulekov <alxndr@bu.edu>"
68*7c9b64adSAlexander Bulekov__copyright__  = "Copyright (C) 2020, Red Hat, Inc."
69*7c9b64adSAlexander Bulekov__license__    = "GPL version 2 or (at your option) any later version"
70*7c9b64adSAlexander Bulekov
71*7c9b64adSAlexander Bulekov__maintainer__ = "Alexander Bulekov"
72*7c9b64adSAlexander Bulekov__email__      = "alxndr@bu.edu"
73*7c9b64adSAlexander Bulekov
74*7c9b64adSAlexander Bulekov
75*7c9b64adSAlexander Bulekovdef usage():
76*7c9b64adSAlexander Bulekov    sys.exit("Usage: {} /path/to/qtest_log_output".format((sys.argv[0])))
77*7c9b64adSAlexander Bulekov
78*7c9b64adSAlexander Bulekov
79*7c9b64adSAlexander Bulekovdef main(filename):
80*7c9b64adSAlexander Bulekov    with open(filename, "r") as f:
81*7c9b64adSAlexander Bulekov        trace = f.readlines()
82*7c9b64adSAlexander Bulekov
83*7c9b64adSAlexander Bulekov    # Leave only lines that look like logged qtest commands
84*7c9b64adSAlexander Bulekov    trace[:] = [x.strip() for x in trace if "[R +" in x
85*7c9b64adSAlexander Bulekov                or "[S +" in x and "CLOSED" not in x]
86*7c9b64adSAlexander Bulekov
87*7c9b64adSAlexander Bulekov    for i in range(len(trace)):
88*7c9b64adSAlexander Bulekov        if i+1 < len(trace):
89*7c9b64adSAlexander Bulekov            if "[DMA]" in trace[i+1]:
90*7c9b64adSAlexander Bulekov                if "[DOUBLE-FETCH]" in trace[i+1]:
91*7c9b64adSAlexander Bulekov                    sys.stderr.write("Warning: Likely double fetch on line"
92*7c9b64adSAlexander Bulekov                                     "{}.\n There will likely be problems "
93*7c9b64adSAlexander Bulekov                                     "reproducing behavior with the "
94*7c9b64adSAlexander Bulekov                                     "resulting qtest trace\n\n".format(i+1))
95*7c9b64adSAlexander Bulekov                trace[i], trace[i+1] = trace[i+1], trace[i]
96*7c9b64adSAlexander Bulekov    for line in trace:
97*7c9b64adSAlexander Bulekov        print(line.split("]")[-1].strip())
98*7c9b64adSAlexander Bulekov
99*7c9b64adSAlexander Bulekov
100*7c9b64adSAlexander Bulekovif __name__ == '__main__':
101*7c9b64adSAlexander Bulekov    if len(sys.argv) == 1:
102*7c9b64adSAlexander Bulekov        usage()
103*7c9b64adSAlexander Bulekov    main(sys.argv[1])
104