• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
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:: cpp
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 meet 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 with GN
67========================
68
69To build a fuzzer, do the following:
70
711. Add the GN target to the module using ``pw_fuzzer`` GN template. If you wish
72   to limit when the generated unit test is run, you can set `enable_test_if` in
73   the same manner as `enable_if` for `pw_test`:
74
75.. code::
76
77  # In $dir_my_module/BUILD.gn
78  import("$dir_pw_fuzzer/fuzzer.gni")
79
80  pw_fuzzer("my_fuzzer") {
81    sources = [ "my_fuzzer.cc" ]
82    deps = [ ":my_lib" ]
83    enable_test_if = device_has_1m_flash
84  }
85
862. Add the generated unit test to the module's test group. This test verifies
87   the fuzzer can build and run, even when not being built in a fuzzing
88   toolchain.
89
90.. code::
91
92  # In $dir_my_module/BUILD.gn
93  pw_test_group("tests") {
94    tests = [
95      ...
96      ":my_fuzzer_test",
97    ]
98  }
99
1003. If your module does not already have a group of fuzzers, add it and include
101   it in the top level fuzzers target. Depending on your project, the specific
102   toolchain may differ. Fuzzer toolchains are those with
103   ``pw_toolchain_FUZZING_ENABLED`` set to true. Examples include
104   ``host_clang_fuzz`` and any toolchains that extend it.
105
106.. code::
107
108  # In //BUILD.gn
109  group("fuzzers") {
110    deps = [
111      ...
112      "$dir_my_module:fuzzers($dir_pigweed/targets/host:host_clang_fuzz)",
113    ]
114  }
115
1164. Add your fuzzer to the module's group of fuzzers.
117
118.. code::
119
120  group("fuzzers") {
121    deps = [
122      ...
123      ":my_fuzzer",
124    ]
125  }
126
1275. If desired, select a sanitizer runtime. By default,
128   `//targets/host:host_clang_fuzz` uses "address" if no sanitizer is specified.
129   See LLVM for `valid options`_.
130
131.. code:: sh
132
133  $ gn gen out --args='pw_toolchain_SANITIZERS=["address"]'
134
1356. Build the fuzzers!
136
137.. code:: sh
138
139  $ ninja -C out fuzzers
140
141.. _bazel:
142
143Building and running fuzzers with Bazel
144=======================================
145To build a fuzzer, do the following:
146
1471. Add the Bazel target using ``pw_cc_fuzz_test`` macro.
148
149.. code:: py
150
151  load("@pigweed//pw_fuzzer:fuzzer.bzl", "pw_cc_fuzz_test")
152
153  pw_cc_fuzz_test(
154    name = "my_fuzz_test",
155    srcs = ["my_fuzzer.cc"],
156    deps = [
157      "@pigweed//pw_fuzzer",
158      ":my_lib",
159    ],
160  )
161
1622. Build and run the fuzzer.
163
164.. code:: sh
165
166  bazel test //my_module:my_fuzz_test
167
1683. Swap fuzzer backend to use ASAN fuzzing engine.
169
170.. code::
171
172  # .bazelrc
173  # Define the --config=asan-libfuzzer configuration.
174  build:asan-libfuzzer \
175    --@rules_fuzzing//fuzzing:cc_engine=@rules_fuzzing//fuzzing/engines:libfuzzer
176  build:asan-libfuzzer \
177    --@rules_fuzzing//fuzzing:cc_engine_instrumentation=libfuzzer
178  build:asan-libfuzzer --@rules_fuzzing//fuzzing:cc_engine_sanitizer=asan
179
1804. Re-run fuzz tests.
181
182.. code::
183
184  bazel test //my_module:my_fuzz_test --config asan-libfuzzer
185
186.. _run:
187
188Running fuzzers locally
189=======================
190
191Based on the example above, the fuzzer output will be at
192``out/host/obj/my_module/my_fuzzer``. It can be invoked using the normal
193`libFuzzer options`_ and `sanitizer runtime flags`_. For even more details, see
194the libFuzzer section on `running a fuzzer`_.
195
196For example, the following invocation disables "one definition rule" detection,
197saves failing inputs to ``artifacts/``, treats any input that takes longer than
19810 seconds as a failure, and stores the working corpus in ``corpus/``.
199
200.. code::
201
202  $ mkdir -p corpus
203  $ ASAN_OPTIONS=detect_odr_violation=0 \
204      out/host_clang_fuzz/obj/pw_fuzzer/bin/toy_fuzzer \
205      -artifact_prefix=artifacts/ \
206      -timeout=10 \
207      corpus
208  INFO: Seed: 305325345
209  INFO: Loaded 1 modules   (46 inline 8-bit counters): 46 [0x38dfc0, 0x38dfee),
210  INFO: Loaded 1 PC tables (46 PCs): 46 [0x23aaf0,0x23add0),
211  INFO:        0 files found in corpus
212  INFO: -max_len is not provided; libFuzzer will not generate inputs larger than 4096 bytes
213  INFO: A corpus is not provided, starting from an empty corpus
214  #2      INITED cov: 2 ft: 3 corp: 1/1b exec/s: 0 rss: 27Mb
215  #4      NEW    cov: 3 ft: 4 corp: 2/3b lim: 4 exec/s: 0 rss: 27Mb L: 2/2 MS: 2 ShuffleBytes-InsertByte-
216  #11     NEW    cov: 7 ft: 8 corp: 3/7b lim: 4 exec/s: 0 rss: 27Mb L: 4/4 MS: 2 EraseBytes-CrossOver-
217  #27     REDUCE cov: 7 ft: 8 corp: 3/6b lim: 4 exec/s: 0 rss: 27Mb L: 3/3 MS: 1 EraseBytes-
218  #29     REDUCE cov: 7 ft: 8 corp: 3/5b lim: 4 exec/s: 0 rss: 27Mb L: 2/2 MS: 2 ChangeBit-EraseBytes-
219  #445    REDUCE cov: 9 ft: 10 corp: 4/13b lim: 8 exec/s: 0 rss: 27Mb L: 8/8 MS: 1 InsertRepeatedBytes-
220  #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"-
221  #12321  NEW    cov: 12 ft: 13 corp: 6/31b lim: 122 exec/s: 0 rss: 28Mb L: 7/11 MS: 2 CopyPart-EraseBytes-
222  #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"-
223  #12826  REDUCE cov: 12 ft: 13 corp: 6/26b lim: 122 exec/s: 0 rss: 28Mb L: 5/8 MS: 2 ShuffleBytes-EraseBytes-
224  #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"-
225  #15106  REDUCE cov: 12 ft: 13 corp: 6/24b lim: 135 exec/s: 0 rss: 28Mb L: 3/8 MS: 2 ChangeByte-EraseBytes-
226  ...
227  #197809 REDUCE cov: 35 ft: 36 corp: 22/129b lim: 1800 exec/s: 0 rss: 79Mb L: 9/9 MS: 1 InsertByte-
228  #216250 REDUCE cov: 35 ft: 36 corp: 22/128b lim: 1980 exec/s: 0 rss: 87Mb L: 8/8 MS: 1 EraseBytes-
229  #242761 REDUCE cov: 35 ft: 36 corp: 22/127b lim: 2237 exec/s: 0 rss: 101Mb L: 7/8 MS: 1 EraseBytes-
230  ==126148== ERROR: libFuzzer: deadly signal
231      #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
232      #1 0x2bcdb5 in fuzzer::PrintStackTrace() (/home/aarongreen/src/pigweed/out/host/obj/pw_fuzzer/toy_fuzzer+0x2bcdb5)
233      #2 0x2a2ac9 in fuzzer::Fuzzer::CrashCallback() (/home/aarongreen/src/pigweed/out/host/obj/pw_fuzzer/toy_fuzzer+0x2a2ac9)
234      #3 0x7f866684151f  (/lib/x86_64-linux-gnu/libpthread.so.0+0x1351f)
235      #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
236      #5 0x3831df in LLVMFuzzerTestOneInput /home/aarongreen/src/pigweed/out/host/../../pw_fuzzer/examples/toy_fuzzer.cc:80:3
237      #6 0x2a4025 in fuzzer::Fuzzer::ExecuteCallback(unsigned char const*, unsigned long) (/home/aarongreen/src/pigweed/out/host/obj/pw_fuzzer/toy_fuzzer+0x2a4025)
238      #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)
239      #8 0x2a5769 in fuzzer::Fuzzer::MutateAndTestOne() (/home/aarongreen/src/pigweed/out/host/obj/pw_fuzzer/toy_fuzzer+0x2a5769)
240      #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)
241      #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)
242      #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
243      #12 0x7f8666684bba in __libc_start_main (/lib/x86_64-linux-gnu/libc.so.6+0x26bba)
244      #13 0x26ae19 in _start (/home/aarongreen/src/pigweed/out/host/obj/pw_fuzzer/toy_fuzzer+0x26ae19)
245
246  NOTE: libFuzzer has rudimentary signal handlers.
247        Combine libFuzzer with AddressSanitizer or similar for better crash reports.
248  SUMMARY: libFuzzer: deadly signal
249  MS: 1 CrossOver-; base unit: 9f479f7a6e3a21363397a25da3168218ba182a16
250  0x68,0x65,0x6c,0x6c,0x6f,0x0,0x77,0x6f,0x72,0x6c,0x64,0x0,0x0,0x0,
251  hello\x00world\x00\x00\x00
252  artifact_prefix='artifacts'; Test unit written to artifacts/crash-6e4fdc7ffd04131ea15dd243a0890b1b606f4831
253  Base64: aGVsbG8Ad29ybGQAAAA=
254
255Running fuzzers on OSS-Fuzz
256===========================
257
258Pigweed is integrated with `OSS-Fuzz`_, a continuous fuzzing infrastructure for
259open source software. Fuzzers listed in in ``pw_test_groups`` will automatically
260start being run within a day or so of appearing in the git repository.
261
262Bugs produced by OSS-Fuzz can be found in its `Monorail instance`_. These bugs
263include:
264
265* A detailed report, including a symbolized backtrace.
266* A revision range indicating when the bug has been detected.
267* A minimized testcase, which is a fuzzer input that can be used to reproduce
268  the bug.
269
270To reproduce a bug:
271
272#. Build_ the fuzzers as described above.
273#. Download the minimized testcase.
274#. Run_ the fuzzer with the testcase as an argument.
275
276For example, if the testcase is saved as "~/Downloads/testcase"
277and the fuzzer is the same as in the examples above, you could run:
278
279.. code::
280
281  $ ./out/host/obj/pw_fuzzer/toy_fuzzer ~/Downloads/testcase
282
283If you need to recreate the OSS-Fuzz environment locally, you can use its
284documentation on `reproducing`_ issues.
285
286In particular, you can recreate the OSS-Fuzz environment using:
287
288.. code::
289
290  $ python infra/helper.py pull_images
291  $ python infra/helper.py build_image pigweed
292  $ python infra/helper.py build_fuzzers --sanitizer <address/undefined> pigweed
293
294With that environment, you can run the reproduce bugs using:
295
296.. code::
297
298  python infra/helper.py reproduce pigweed <pw_module>_<fuzzer_name> ~/Downloads/testcase
299
300You can even verify fixes in your local source checkout:
301
302.. code::
303
304  $ python infra/helper.py build_fuzzers --sanitizer <address/undefined> pigweed $PW_ROOT
305  $ python infra/helper.py reproduce pigweed <pw_module>_<fuzzer_name> ~/Downloads/testcase
306
307.. _compiler_rt: https://compiler-rt.llvm.org/
308.. _corpus: https://llvm.org/docs/LibFuzzer.html#corpus
309.. _FUZZING_BUILD_MODE_UNSAFE_FOR_PRODUCTION: https://llvm.org/docs/LibFuzzer.html#fuzzer-friendly-build-mode
310.. _FuzzedDataProvider: https://github.com/llvm/llvm-project/blob/HEAD/compiler-rt/include/fuzzer/FuzzedDataProvider.h
311.. _libFuzzer: https://llvm.org/docs/LibFuzzer.html
312.. _libFuzzer options: https://llvm.org/docs/LibFuzzer.html#options
313.. _LLVMFuzzerTestOneInput: https://llvm.org/docs/LibFuzzer.html#fuzz-target
314.. _monorail instance: https://bugs.chromium.org/p/oss-fuzz
315.. _oss-fuzz: https://github.com/google/oss-fuzz
316.. _reproducing: https://google.github.io/oss-fuzz/advanced-topics/reproducing/
317.. _running a fuzzer: https://llvm.org/docs/LibFuzzer.html#running
318.. _sanitizer runtime flags: https://github.com/google/sanitizers/wiki/SanitizerCommonFlags
319.. _split a fuzzing input: https://github.com/google/fuzzing/blob/HEAD/docs/split-inputs.md
320.. _startup initialization: https://llvm.org/docs/LibFuzzer.html#startup-initialization
321.. _structure aware fuzzing: https://github.com/google/fuzzing/blob/HEAD/docs/structure-aware-fuzzing.md
322.. _valid options: https://gcc.gnu.org/onlinedocs/gcc/Instrumentation-Options.html
323