1.. _module-pw_fuzzer: 2 3--------- 4pw_fuzzer 5--------- 6``pw_fuzzer`` provides developers with tools to write `libFuzzer`_ based 7fuzzers. 8 9Fuzzing or fuzz testing is style of testing that stochastically generates inputs 10to targeted interfaces in order to automatically find defects and/or 11vulnerabilities. In other words, fuzzing is simply an automated way of testing 12APIs with generated data. 13 14A fuzzer is a program that is used to fuzz a interface. It typically has three 15steps that it executes repeatedly: 16 17#. Generate a new, context-free input. This is the *fuzzing engine*. For 18 ``pw_fuzzer``, this is `libFuzzer`_. 19#. Use the input to exercise the targeted interface, or code being tested. This 20 is the *fuzz target function*. For ``pw_fuzzer``, these are the GN 21 ``sources`` and/or ``deps`` that define `LLVMFuzzerTestOneInput`_. 22#. Monitor the code being tested for any abnormal conditions. This is the 23 *instrumentation*. For ``pw_fuzzer``, these are sanitizer runtimes from 24 LLVM's `compiler_rt`_. 25 26.. note:: 27 28 ``pw_fuzzer`` is currently only supported on Linux and MacOS using clang. 29 30.. image:: doc_resources/pw_fuzzer_coverage_guided.png 31 :alt: Coverage Guided Fuzzing with libFuzzer 32 :align: left 33 34Writing fuzzers 35=============== 36 37To write a fuzzer, a developer needs to write a fuzz target function follwing 38the `fuzz target function`__ guidelines given by libFuzzer: 39 40.. code:: 41 42 extern "C" int LLVMFuzzerTestOneInput(const uint8_t *data, size_t size) { 43 DoSomethingInterestingWithMyAPI(data, size); 44 return 0; // Non-zero return values are reserved for future use. 45 } 46 47.. __: LLVMFuzzerTestOneInput_ 48 49When writing you fuzz target function, you may want to consider: 50 51- It is acceptable to return early if the input doesn't mean some constraints, 52 e.g. it is too short. 53- If your fuzzer accepts data with a well-defined format, you can bootstrap 54 coverage by crafting examples and adding them to a `corpus`_. 55- There are tools to `split a fuzzing input`_ into multiple fields if needed; 56 the `FuzzedDataProvider`_ is particularly easy to use. 57- If your code acts on "transformed" inputs, such as encoded or compressed 58 inputs, you may want to try `structure aware fuzzing`. 59- You can do `startup initialization`_ if you need to. 60- If your code is non-deterministic or uses checksums, you may want to disable 61 those **only** when fuzzing by using LLVM's 62 `FUZZING_BUILD_MODE_UNSAFE_FOR_PRODUCTION`_ 63 64.. _build: 65 66Building fuzzers 67================ 68 69To build a fuzzer, do the following: 70 711. Add the GN target using ``pw_fuzzer`` GN template, and add it to your the 72 test group of the module: 73 74.. code:: 75 76 # In $dir_my_module/BUILD.gn 77 import("$dir_pw_fuzzer/fuzzer.gni") 78 79 pw_fuzzer("my_fuzzer") { 80 sources = [ "my_fuzzer.cc" ] 81 deps = [ ":my_lib" ] 82 } 83 84 pw_test_group("tests") { 85 tests = [ 86 ":existing_tests", ... 87 ":my_fuzzer", # <- Added! 88 ] 89 } 90 912. Select your choice of sanitizers ("address" is also the current default). 92 See LLVM for `valid options`_. 93 94.. code:: sh 95 96 $ gn gen out --args='pw_toolchain_SANITIZERS=["address"]' 97 983. Build normally, e.g. using ``pw watch``. 99 100.. _run: 101 102Running fuzzers locally 103======================= 104 105Based on the example above, the fuzzer output will be at 106``out/host/obj/my_module/my_fuzzer``. It can be invoked using the normal 107`libFuzzer options`_ and `sanitizer runtime flags`_. For even more details, see 108the libFuzzer section on `running a fuzzer`_. 109 110For example, the following invocation disables "one definition rule" detection, 111saves failing inputs to ``artifacts/``, treats any input that takes longer than 11210 seconds as a failure, and stores the working corpus in ``corpus/``. 113 114.. code:: 115 116 $ mkdir -p corpus 117 $ ASAN_OPTIONS=detect_odr_violation=0 \ 118 out/host_clang_fuzz/obj/pw_fuzzer/bin/toy_fuzzer \ 119 -artifact_prefix=artifacts/ \ 120 -timeout=10 \ 121 corpus 122 INFO: Seed: 305325345 123 INFO: Loaded 1 modules (46 inline 8-bit counters): 46 [0x38dfc0, 0x38dfee), 124 INFO: Loaded 1 PC tables (46 PCs): 46 [0x23aaf0,0x23add0), 125 INFO: 0 files found in corpus 126 INFO: -max_len is not provided; libFuzzer will not generate inputs larger than 4096 bytes 127 INFO: A corpus is not provided, starting from an empty corpus 128 #2 INITED cov: 2 ft: 3 corp: 1/1b exec/s: 0 rss: 27Mb 129 #4 NEW cov: 3 ft: 4 corp: 2/3b lim: 4 exec/s: 0 rss: 27Mb L: 2/2 MS: 2 ShuffleBytes-InsertByte- 130 #11 NEW cov: 7 ft: 8 corp: 3/7b lim: 4 exec/s: 0 rss: 27Mb L: 4/4 MS: 2 EraseBytes-CrossOver- 131 #27 REDUCE cov: 7 ft: 8 corp: 3/6b lim: 4 exec/s: 0 rss: 27Mb L: 3/3 MS: 1 EraseBytes- 132 #29 REDUCE cov: 7 ft: 8 corp: 3/5b lim: 4 exec/s: 0 rss: 27Mb L: 2/2 MS: 2 ChangeBit-EraseBytes- 133 #445 REDUCE cov: 9 ft: 10 corp: 4/13b lim: 8 exec/s: 0 rss: 27Mb L: 8/8 MS: 1 InsertRepeatedBytes- 134 #12104 NEW cov: 11 ft: 12 corp: 5/24b lim: 122 exec/s: 0 rss: 28Mb L: 11/11 MS: 4 CMP-InsertByte-ShuffleBytes-ChangeByte- DE: "\xff\xff"- 135 #12321 NEW cov: 12 ft: 13 corp: 6/31b lim: 122 exec/s: 0 rss: 28Mb L: 7/11 MS: 2 CopyPart-EraseBytes- 136 #12459 REDUCE cov: 12 ft: 13 corp: 6/28b lim: 122 exec/s: 0 rss: 28Mb L: 8/8 MS: 3 CMP-InsertByte-EraseBytes- DE: "\x00\x00"- 137 #12826 REDUCE cov: 12 ft: 13 corp: 6/26b lim: 122 exec/s: 0 rss: 28Mb L: 5/8 MS: 2 ShuffleBytes-EraseBytes- 138 #14824 REDUCE cov: 12 ft: 13 corp: 6/25b lim: 135 exec/s: 0 rss: 28Mb L: 4/8 MS: 3 PersAutoDict-ShuffleBytes-EraseBytes- DE: "\x00\x00"- 139 #15106 REDUCE cov: 12 ft: 13 corp: 6/24b lim: 135 exec/s: 0 rss: 28Mb L: 3/8 MS: 2 ChangeByte-EraseBytes- 140 ... 141 #197809 REDUCE cov: 35 ft: 36 corp: 22/129b lim: 1800 exec/s: 0 rss: 79Mb L: 9/9 MS: 1 InsertByte- 142 #216250 REDUCE cov: 35 ft: 36 corp: 22/128b lim: 1980 exec/s: 0 rss: 87Mb L: 8/8 MS: 1 EraseBytes- 143 #242761 REDUCE cov: 35 ft: 36 corp: 22/127b lim: 2237 exec/s: 0 rss: 101Mb L: 7/8 MS: 1 EraseBytes- 144 ==126148== ERROR: libFuzzer: deadly signal 145 #0 0x35b981 in __sanitizer_print_stack_trace ../recipe_cleanup/clangFu99hg/llvm_build_dir/tools/clang/stage2-bins/runtimes/runtimes-x86_64-unknown-linux-gnu-bins/compiler-rt/lib/asan/asan_stack.cpp:86:3 146 #1 0x2bcdb5 in fuzzer::PrintStackTrace() (/home/aarongreen/src/pigweed/out/host/obj/pw_fuzzer/toy_fuzzer+0x2bcdb5) 147 #2 0x2a2ac9 in fuzzer::Fuzzer::CrashCallback() (/home/aarongreen/src/pigweed/out/host/obj/pw_fuzzer/toy_fuzzer+0x2a2ac9) 148 #3 0x7f866684151f (/lib/x86_64-linux-gnu/libpthread.so.0+0x1351f) 149 #4 0x3831df in (anonymous namespace)::toy_example(char const*, char const*) /home/aarongreen/src/pigweed/out/host/../../pw_fuzzer/examples/toy_fuzzer.cc:49:15 150 #5 0x3831df in LLVMFuzzerTestOneInput /home/aarongreen/src/pigweed/out/host/../../pw_fuzzer/examples/toy_fuzzer.cc:80:3 151 #6 0x2a4025 in fuzzer::Fuzzer::ExecuteCallback(unsigned char const*, unsigned long) (/home/aarongreen/src/pigweed/out/host/obj/pw_fuzzer/toy_fuzzer+0x2a4025) 152 #7 0x2a3774 in fuzzer::Fuzzer::RunOne(unsigned char const*, unsigned long, bool, fuzzer::InputInfo*, bool*) (/home/aarongreen/src/pigweed/out/host/obj/pw_fuzzer/toy_fuzzer+0x2a3774) 153 #8 0x2a5769 in fuzzer::Fuzzer::MutateAndTestOne() (/home/aarongreen/src/pigweed/out/host/obj/pw_fuzzer/toy_fuzzer+0x2a5769) 154 #9 0x2a6185 in fuzzer::Fuzzer::Loop(std::__Fuzzer::vector<fuzzer::SizedFile, fuzzer::fuzzer_allocator<fuzzer::SizedFile> >&) (/home/aarongreen/src/pigweed/out/host/obj/pw_fuzzer/toy_fuzzer+0x2a6185) 155 #10 0x294c8a in fuzzer::FuzzerDriver(int*, char***, int (*)(unsigned char const*, unsigned long)) (/home/aarongreen/src/pigweed/out/host/obj/pw_fuzzer/toy_fuzzer+0x294c8a) 156 #11 0x2bd422 in main ../recipe_cleanup/clangFu99hg/llvm_build_dir/tools/clang/stage2-bins/runtimes/runtimes-x86_64-unknown-linux-gnu-bins/compiler-rt/lib/fuzzer/FuzzerMain.cpp:19:10 157 #12 0x7f8666684bba in __libc_start_main (/lib/x86_64-linux-gnu/libc.so.6+0x26bba) 158 #13 0x26ae19 in _start (/home/aarongreen/src/pigweed/out/host/obj/pw_fuzzer/toy_fuzzer+0x26ae19) 159 160 NOTE: libFuzzer has rudimentary signal handlers. 161 Combine libFuzzer with AddressSanitizer or similar for better crash reports. 162 SUMMARY: libFuzzer: deadly signal 163 MS: 1 CrossOver-; base unit: 9f479f7a6e3a21363397a25da3168218ba182a16 164 0x68,0x65,0x6c,0x6c,0x6f,0x0,0x77,0x6f,0x72,0x6c,0x64,0x0,0x0,0x0, 165 hello\x00world\x00\x00\x00 166 artifact_prefix='artifacts'; Test unit written to artifacts/crash-6e4fdc7ffd04131ea15dd243a0890b1b606f4831 167 Base64: aGVsbG8Ad29ybGQAAAA= 168 169Running fuzzers on OSS-Fuzz 170=========================== 171 172Pigweed is integrated with `OSS-Fuzz`_, a continuous fuzzing infrastructure for 173open source software. Fuzzers listed in in ``pw_test_groups`` will automatically 174start being run within a day or so of appearing in the git repository. 175 176Bugs produced by OSS-Fuzz can be found in its `Monorail instance`_. These bugs 177include: 178 179* A detailed report, including a symbolized backtrace. 180* A revision range indicating when the bug has been detected. 181* A minimized testcase, which is a fuzzer input that can be used to reproduce 182 the bug. 183 184To reproduce a bug: 185 186#. Build_ the fuzzers as described above. 187#. Download the minimized testcase. 188#. Run_ the fuzzer with the testcase as an argument. 189 190For example, if the testcase is saved as "~/Downloads/testcase" 191and the fuzzer is the same as in the examples above, you could run: 192 193.. code:: 194 195 $ ./out/host/obj/pw_fuzzer/toy_fuzzer ~/Downloads/testcase 196 197If you need to recreate the OSS-Fuzz environment locally, you can use its 198documentation on `reproducing`_ issues. 199 200In particular, you can recreate the OSS-Fuzz environment using: 201 202.. code:: 203 204 $ python infra/helper.py pull_images 205 $ python infra/helper.py build_image pigweed 206 $ python infra/helper.py build_fuzzers --sanitizer <address/undefined> pigweed 207 208With that environment, you can run the reproduce bugs using: 209 210.. code:: 211 212 python infra/helper.py reproduce pigweed <pw_module>_<fuzzer_name> ~/Downloads/testcase 213 214You can even verify fixes in your local source checkout: 215 216.. code:: 217 218 $ python infra/helper.py build_fuzzers --sanitizer <address/undefined> pigweed $PW_ROOT 219 $ python infra/helper.py reproduce pigweed <pw_module>_<fuzzer_name> ~/Downloads/testcase 220 221.. _compiler_rt: https://compiler-rt.llvm.org/ 222.. _corpus: https://llvm.org/docs/LibFuzzer.html#corpus 223.. _FUZZING_BUILD_MODE_UNSAFE_FOR_PRODUCTION: https://llvm.org/docs/LibFuzzer.html#fuzzer-friendly-build-mode 224.. _FuzzedDataProvider: https://github.com/llvm/llvm-project/blob/master/compiler-rt/include/fuzzer/FuzzedDataProvider.h 225.. _libFuzzer: https://llvm.org/docs/LibFuzzer.html 226.. _libFuzzer options: https://llvm.org/docs/LibFuzzer.html#options 227.. _LLVMFuzzerTestOneInput: https://llvm.org/docs/LibFuzzer.html#fuzz-target 228.. _monorail instance: https://bugs.chromium.org/p/oss-fuzz 229.. _oss-fuzz: https://github.com/google/oss-fuzz 230.. _reproducing: https://google.github.io/oss-fuzz/advanced-topics/reproducing/ 231.. _running a fuzzer: https://llvm.org/docs/LibFuzzer.html#running 232.. _sanitizer runtime flags: https://github.com/google/sanitizers/wiki/SanitizerCommonFlags 233.. _split a fuzzing input: https://github.com/google/fuzzing/blob/master/docs/split-inputs.md 234.. _startup initialization: https://llvm.org/docs/LibFuzzer.html#startup-initialization 235.. _structure aware fuzzing: https://github.com/google/fuzzing/blob/master/docs/structure-aware_fuzzing.md 236.. _valid options: https://gcc.gnu.org/onlinedocs/gcc/Instrumentation-Options.html 237