]>
Commit | Line | Data |
---|---|---|
e5c59355 AB |
1 | = Fuzzing = |
2 | ||
3 | == Introduction == | |
4 | ||
5 | This document describes the virtual-device fuzzing infrastructure in QEMU and | |
6 | how to use it to implement additional fuzzers. | |
7 | ||
8 | == Basics == | |
9 | ||
10 | Fuzzing operates by passing inputs to an entry point/target function. The | |
11 | fuzzer tracks the code coverage triggered by the input. Based on these | |
12 | findings, the fuzzer mutates the input and repeats the fuzzing. | |
13 | ||
14 | To fuzz QEMU, we rely on libfuzzer. Unlike other fuzzers such as AFL, libfuzzer | |
15 | is an _in-process_ fuzzer. For the developer, this means that it is their | |
16 | responsibility to ensure that state is reset between fuzzing-runs. | |
17 | ||
18 | == Building the fuzzers == | |
19 | ||
20 | NOTE: If possible, build a 32-bit binary. When forking, the 32-bit fuzzer is | |
21 | much faster, since the page-map has a smaller size. This is due to the fact that | |
22 | AddressSanitizer mmaps ~20TB of memory, as part of its detection. This results | |
23 | in a large page-map, and a much slower fork(). | |
24 | ||
25 | To build the fuzzers, install a recent version of clang: | |
26 | Configure with (substitute the clang binaries with the version you installed): | |
27 | ||
28 | CC=clang-8 CXX=clang++-8 /path/to/configure --enable-fuzzing | |
29 | ||
30 | Fuzz targets are built similarly to system/softmmu: | |
31 | ||
32 | make i386-softmmu/fuzz | |
33 | ||
34 | This builds ./i386-softmmu/qemu-fuzz-i386 | |
35 | ||
36 | The first option to this command is: --fuzz_taget=FUZZ_NAME | |
37 | To list all of the available fuzzers run qemu-fuzz-i386 with no arguments. | |
38 | ||
39 | eg: | |
40 | ./i386-softmmu/qemu-fuzz-i386 --fuzz-target=virtio-net-fork-fuzz | |
41 | ||
42 | Internally, libfuzzer parses all arguments that do not begin with "--". | |
43 | Information about these is available by passing -help=1 | |
44 | ||
45 | Now the only thing left to do is wait for the fuzzer to trigger potential | |
46 | crashes. | |
47 | ||
48 | == Adding a new fuzzer == | |
49 | Coverage over virtual devices can be improved by adding additional fuzzers. | |
50 | Fuzzers are kept in tests/qtest/fuzz/ and should be added to | |
51 | tests/qtest/fuzz/Makefile.include | |
52 | ||
53 | Fuzzers can rely on both qtest and libqos to communicate with virtual devices. | |
54 | ||
55 | 1. Create a new source file. For example ``tests/qtest/fuzz/foo-device-fuzz.c``. | |
56 | ||
57 | 2. Write the fuzzing code using the libqtest/libqos API. See existing fuzzers | |
58 | for reference. | |
59 | ||
60 | 3. Register the fuzzer in ``tests/fuzz/Makefile.include`` by appending the | |
61 | corresponding object to fuzz-obj-y | |
62 | ||
63 | Fuzzers can be more-or-less thought of as special qtest programs which can | |
64 | modify the qtest commands and/or qtest command arguments based on inputs | |
65 | provided by libfuzzer. Libfuzzer passes a byte array and length. Commonly the | |
66 | fuzzer loops over the byte-array interpreting it as a list of qtest commands, | |
67 | addresses, or values. | |
68 | ||
69 | = Implementation Details = | |
70 | ||
71 | == The Fuzzer's Lifecycle == | |
72 | ||
73 | The fuzzer has two entrypoints that libfuzzer calls. libfuzzer provides it's | |
74 | own main(), which performs some setup, and calls the entrypoints: | |
75 | ||
76 | LLVMFuzzerInitialize: called prior to fuzzing. Used to initialize all of the | |
77 | necessary state | |
78 | ||
79 | LLVMFuzzerTestOneInput: called for each fuzzing run. Processes the input and | |
80 | resets the state at the end of each run. | |
81 | ||
82 | In more detail: | |
83 | ||
84 | LLVMFuzzerInitialize parses the arguments to the fuzzer (must start with two | |
85 | dashes, so they are ignored by libfuzzer main()). Currently, the arguments | |
86 | select the fuzz target. Then, the qtest client is initialized. If the target | |
87 | requires qos, qgraph is set up and the QOM/LIBQOS modules are initialized. | |
88 | Then the QGraph is walked and the QEMU cmd_line is determined and saved. | |
89 | ||
90 | After this, the vl.c:qemu__main is called to set up the guest. There are | |
91 | target-specific hooks that can be called before and after qemu_main, for | |
92 | additional setup(e.g. PCI setup, or VM snapshotting). | |
93 | ||
94 | LLVMFuzzerTestOneInput: Uses qtest/qos functions to act based on the fuzz | |
95 | input. It is also responsible for manually calling the main loop/main_loop_wait | |
96 | to ensure that bottom halves are executed and any cleanup required before the | |
97 | next input. | |
98 | ||
99 | Since the same process is reused for many fuzzing runs, QEMU state needs to | |
100 | be reset at the end of each run. There are currently two implemented | |
101 | options for resetting state: | |
102 | 1. Reboot the guest between runs. | |
103 | Pros: Straightforward and fast for simple fuzz targets. | |
104 | Cons: Depending on the device, does not reset all device state. If the | |
105 | device requires some initialization prior to being ready for fuzzing | |
106 | (common for QOS-based targets), this initialization needs to be done after | |
107 | each reboot. | |
108 | Example target: i440fx-qtest-reboot-fuzz | |
109 | 2. Run each test case in a separate forked process and copy the coverage | |
110 | information back to the parent. This is fairly similar to AFL's "deferred" | |
111 | fork-server mode [3] | |
112 | Pros: Relatively fast. Devices only need to be initialized once. No need | |
113 | to do slow reboots or vmloads. | |
114 | Cons: Not officially supported by libfuzzer. Does not work well for devices | |
115 | that rely on dedicated threads. | |
116 | Example target: virtio-net-fork-fuzz |