• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
1.. _module-pw_fuzzer-guides-using_fuzztest:
2
3=============================
4Adding Fuzzers Using FuzzTest
5=============================
6.. pigweed-module-subpage::
7   :name: pw_fuzzer
8
9.. note::
10
11  `FuzzTest`_ is currently only supported on Linux and MacOS using Clang.
12
13.. _module-pw_fuzzer-guides-using_fuzztest-toolchain:
14
15----------------------------------------
16Step 0: Set up FuzzTest for your project
17----------------------------------------
18.. note::
19
20   This workflow only needs to be done once for a project.
21
22FuzzTest and its dependencies are not included in Pigweed and need to be added.
23
24See the following:
25
26* :ref:`module-pw_third_party_abseil_cpp-using_upstream`
27* :ref:`module-pw_third_party_fuzztest-using_upstream`
28* :ref:`module-pw_third_party_googletest-using_upstream`
29
30.. tab-set::
31
32   .. tab-item:: GN
33      :sync: gn
34
35      You may not want to use upstream GoogleTest all the time. For example, it
36      may not be supported on your target device. In this case, you can limit it
37      to a specific toolchain used for fuzzing. For example:
38
39      .. code-block::
40
41         import("$dir_pw_toolchain/host/target_toolchains.gni")
42
43         my_toolchains = {
44           ...
45           clang_fuzz = {
46             name = "my_clang_fuzz"
47             forward_variables_from(pw_toolchain_host.clang_fuzz, "*", ["name"])
48             pw_unit_test_MAIN = "$dir_pw_fuzzer:fuzztest_main"
49             pw_unit_test_BACKEND = "$dir_pw_fuzzer:gtest"
50           }
51           ...
52         }
53
54   .. tab-item:: CMake
55      :sync: cmake
56
57      FuzzTest is enabled by setting several CMake variables. The easiest way to
58      set these is to extend your ``toolchain.cmake`` file. You need to specify
59      where FuzzTest and its dependencies are, include FuzzTest's build flags,
60      and set the unit test backend to use FuzzTest.
61
62      For example:
63
64      .. code-block::
65
66         set(dir_pw_third_party_abseil_cpp "path/to/abseil" CACHE INTERNAL "" FORCE)
67         set(dir_pw_third_party_googletest "path/to/googletest" CACHE INTERNAL "" FORCE)
68         set(dir_pw_third_party_fuzztest "path/to/fuzztest" CACHE INTERNAL "" FORCE)
69
70         pw_set_backend(pw_unit_test pw_unit_test.fuzztest)
71
72         # This line should come after adding the Pigweed subdirectory, but
73         # before any fuzz tests.
74         fuzztest_setup_fuzzing_flags()
75
76      You also must enable fuzzing when you build by passing the
77      ``-DFUZZTEST_FUZZING_MODE`` flag to ``cmake`` when building.
78
79   .. tab-item:: Bazel
80      :sync: bazel
81
82      Include Abseil-C++ and GoogleTest in your ``WORKSPACE`` file. For example:
83
84      .. code-block::
85
86         http_archive(
87            name = "com_google_absl",
88            sha256 = "338420448b140f0dfd1a1ea3c3ce71b3bc172071f24f4d9a57d59b45037da440",
89            strip_prefix = "abseil-cpp-20240116.0",
90            url = "https://github.com/abseil/abseil-cpp/releases/download/20240116.0/abseil-cpp-20240116.0.tar.gz",
91         )
92
93         git_repository(
94            name = "com_google_googletest",
95            commit = "3b6d48e8d5c1d9b3f9f10ac030a94008bfaf032b",
96            remote = "https://pigweed.googlesource.com/third_party/github/google/googletest",
97         )
98
99      Then, import the FuzzTest build configurations in your ``.bazelrc`` file
100      by adding and adapting the following:
101
102      .. code-block::
103
104         # Include FuzzTest build configurations.
105         try-import %workspace%/path/to/pigweed/pw_fuzzer/fuzztest.bazelrc
106
107----------------------------------------
108Step 1: Write a unit test for the target
109----------------------------------------
110
111As noted previously, the very first step is to identify one or more target
112behavior that would benefit from testing. See `FuzzTest Use Cases`_ for more
113details on how to identify this code.
114
115Once identified, it is useful to start from a unit test. You may already have a
116unit test writtern, but if not it is likely still be helpful to write one first.
117Many developers are more familiar with writing unit tests, and there are
118detailed guides available. See for example the `GoogleTest documentation`_.
119
120This guide will use code from ``//pw_fuzzer/examples/fuzztest/``. This code
121includes the following object as an example of code that would benefit from
122fuzzing for undefined behavior and from roundtrip fuzzing.
123
124.. note::
125
126   To keep the example simple, this code uses the standard library. As a result,
127   this code may not work with certain devices.
128
129.. literalinclude:: ../examples/fuzztest/metrics.h
130   :language: cpp
131   :linenos:
132   :start-after: [pwfuzzer_examples_fuzztest-metrics_h]
133   :end-before: [pwfuzzer_examples_fuzztest-metrics_h]
134
135Unit tests for this class might attempt to deserialize previously serialized
136objects and to deserialize invalid data:
137
138.. literalinclude:: ../examples/fuzztest/metrics_unittest.cc
139   :language: cpp
140   :linenos:
141   :start-after: [pwfuzzer_examples_fuzztest-metrics_unittest]
142   :end-before: [pwfuzzer_examples_fuzztest-metrics_unittest]
143
144--------------------------------------------
145Step 2: Convert your unit test to a function
146--------------------------------------------
147
148Examine your unit tests and identify any places you have fixed values that could
149vary. Turn your unit test into a function that takes those values as parameters.
150Since fuzzing may not occur on all targets, you should preserve your unit test
151by calling the new function with the previously fixed values.
152
153.. literalinclude:: ../examples/fuzztest/metrics_fuzztest.cc
154   :language: cpp
155   :linenos:
156   :start-after: [pwfuzzer_examples_fuzztest-metrics_fuzztest1]
157   :end-before: [pwfuzzer_examples_fuzztest-metrics_fuzztest1]
158
159.. literalinclude:: ../examples/fuzztest/metrics_fuzztest.cc
160   :language: cpp
161   :linenos:
162   :start-after: [pwfuzzer_examples_fuzztest-metrics_fuzztest3]
163   :end-before: [pwfuzzer_examples_fuzztest-metrics_fuzztest3]
164
165Note that in ``ArbitrarySerializeAndDeserialize`` we no longer assume the
166marshalling will always be successful, and exit early if it is not. You may need
167to make similar modifications to your unit tests if constraints on parameters
168are not expressed by `domains`__ as described below.
169
170.. __: `FuzzTest Domain Reference`_
171
172--------------------------------------------
173Step 3: Add a FUZZ_TEST macro invocation
174--------------------------------------------
175
176Now, include ``"fuzztest/fuzztest.h"`` and pass the test suite name and your
177function name to the ``FUZZ_TEST`` macro. Call ``WithDomains`` on the returned
178object to specify the input domain for each parameter of the function. For
179example:
180
181.. literalinclude:: ../examples/fuzztest/metrics_fuzztest.cc
182   :language: cpp
183   :linenos:
184   :start-after: [pwfuzzer_examples_fuzztest-metrics_fuzztest2]
185   :end-before: [pwfuzzer_examples_fuzztest-metrics_fuzztest2]
186
187.. literalinclude:: ../examples/fuzztest/metrics_fuzztest.cc
188   :language: cpp
189   :linenos:
190   :start-after: [pwfuzzer_examples_fuzztest-metrics_fuzztest4]
191   :end-before: [pwfuzzer_examples_fuzztest-metrics_fuzztest4]
192
193You may know of specific values that are "interesting", i.e. that represent
194boundary conditions, involve, special handling, etc. To guide the fuzzer towards
195these code paths, you can include them as `seeds`_. However, as noted in
196the comments of the examples, it is recommended to include a unit test with the
197original parameters to ensure the code is tested on target devices.
198
199FuzzTest provides more detailed documentation on these topics. For example:
200
201* Refer to `The FUZZ_TEST Macro`_ reference for more details on how to use this
202  macro.
203
204* Refer to the `FuzzTest Domain Reference`_ for details on all the different
205  types of domains supported by FuzzTest and how they can be combined.
206
207* Refer to the `Test Fixtures`_ reference for how to create fuzz tests from unit
208  tests that use GoogleTest fixtures.
209
210------------------------------------
211Step 4: Add the fuzzer to your build
212------------------------------------
213Next, indicate that the unit test includes one or more fuzz tests.
214
215.. tab-set::
216
217   .. tab-item:: GN
218      :sync: gn
219
220      The ``pw_fuzz_test`` template can be used to add the necessary FuzzTest
221      dependency and generate test metadata.
222
223      For example, consider the following ``BUILD.gn``:
224
225      .. literalinclude:: ../examples/fuzztest/BUILD.gn
226         :linenos:
227         :start-after: [pwfuzzer_examples_fuzztest-gn]
228         :end-before: [pwfuzzer_examples_fuzztest-gn]
229
230   .. tab-item:: CMake
231      :sync: cmake
232
233      Unit tests can support fuzz tests by simply adding a dependency on
234      FuzzTest.
235
236      For example, consider the following ``CMakeLists.txt``:
237
238      .. literalinclude:: ../examples/fuzztest/CMakeLists.txt
239         :linenos:
240         :start-after: [pwfuzzer_examples_fuzztest-cmake]
241         :end-before: [pwfuzzer_examples_fuzztest-cmake]
242
243   .. tab-item:: Bazel
244      :sync: bazel
245
246      Unit tests can support fuzz tests by simply adding a dependency on
247      FuzzTest.
248
249      For example, consider the following ``BUILD.bazel``:
250
251      .. literalinclude:: ../examples/fuzztest/BUILD.bazel
252         :linenos:
253         :start-after: [pwfuzzer_examples_fuzztest-bazel]
254         :end-before: [pwfuzzer_examples_fuzztest-bazel]
255
256------------------------
257Step 5: Build the fuzzer
258------------------------
259.. tab-set::
260
261   .. tab-item:: GN
262      :sync: gn
263
264      Build using ``ninja`` on a target that includes your fuzzer with a
265      :ref:`fuzzing toolchain<module-pw_fuzzer-guides-using_fuzztest-toolchain>`.
266
267      Pigweed includes a ``//:fuzzers`` target that builds all tests, including
268      those with fuzzers, using a fuzzing toolchain. You may wish to add a
269      similar top-level to your project. For example:
270
271      .. code-block::
272
273         group("fuzzers") {
274           deps = [ ":pw_module_tests.run($dir_pigweed/targets/host:host_clang_fuzz)" ]
275         }
276
277   .. tab-item:: CMake
278      :sync: cmake
279
280      Build using ``cmake`` with the FuzzTest and GoogleTest variables set. For
281      example:
282
283      .. code-block::
284
285         cmake ... \
286           -Ddir_pw_third_party_fuzztest=path/to/fuzztest \
287           -Ddir_pw_third_party_googletest=path/to/googletest \
288           -Dpw_unit_test_BACKEND=pw_third_party.fuzztest
289
290
291   .. tab-item:: Bazel
292      :sync: bazel
293
294      By default, ``bazel`` will simply omit the fuzz tests and build unit
295      tests. To build these tests as fuzz tests, specify the ``fuzztest``
296      config. For example:
297
298      .. code-block:: console
299
300         $ bazel build //... --config=fuzztest
301
302----------------------------------
303Step 6: Running the fuzzer locally
304----------------------------------
305.. TODO: b/281138993 - Add tooling to make it easier to find and run fuzzers.
306
307.. tab-set::
308
309   .. tab-item:: GN
310      :sync: gn
311
312      When building. Most toolchains will simply omit the fuzz tests and build
313      and run unit tests. A
314      :ref:`fuzzing toolchain<module-pw_fuzzer-guides-using_fuzztest-toolchain>`
315      will include the fuzzers, but only run them for a limited time. This makes
316      them suitable for automated testing as in CQ.
317
318      If you used the top-level ``//:fuzzers`` described in the previous
319      section, you can find available fuzzers using the generated JSON test
320      metadata file:
321
322      .. code-block:: console
323
324         $ jq '.[] | select(contains({tags: ["fuzztest"]}))' \
325         > out/host_clang_fuzz/obj/pw_module_tests.testinfo.json
326
327      To run a fuzz with different options, you can pass additional flags to the
328      fuzzer binary. This binary will be in a subdirectory related to the
329      toolchain. For example:
330
331      .. code-block:: console
332
333         $ out/host_clang_fuzz/obj/my_module/test/metrics_test \
334         > --fuzz=MetricsTest.Roundtrip
335
336      Additional `sanitizer flags`_ may be passed uisng environment variables.
337
338   .. tab-item:: CMake
339      :sync: cmake
340
341      When built with FuzzTest and GoogleTest, the fuzzer binaries can be run
342      directly from the CMake build directory. By default, the fuzzers will only
343      run for a limited time. This makes them suitable for automated testing as
344      in CQ. To run a fuzz with different options, you can pass additional flags
345      to the fuzzer binary.
346
347      For example:
348
349      .. code-block:: console
350
351         $ build/my_module/metrics_test --fuzz=MetricsTest.Roundtrip
352
353   .. tab-item:: Bazel
354      :sync: bazel
355
356      By default, ``bazel`` will simply omit the fuzz tests and build and run
357      unit tests. To build these tests as fuzz tests, specify the "fuzztest"
358      config. For example:
359
360      .. code-block:: console
361
362         $ bazel test //... --config=fuzztest
363
364      This will build the tests as fuzz tests, but only run them for a limited
365      time. This makes them suitable for automated testing as in CQ.
366
367      To run a fuzz with different options, you can use ``run`` and pass
368      additional flags to the fuzzer binary. For example:
369
370      .. code-block:: console
371
372         $ bazel run //my_module:metrics_test --config=fuzztest \
373         > --fuzz=MetricsTest.Roundtrip
374
375Running the fuzzer should produce output similar to the following:
376
377.. code-block::
378
379   [.] Sanitizer coverage enabled. Counter map size: 21290, Cmp map size: 262144
380   Note: Google Test filter = MetricsTest.Roundtrip
381   [==========] Running 1 test from 1 test suite.
382   [----------] Global test environment set-up.
383   [----------] 1 test from MetricsTest
384   [ RUN      ] MetricsTest.Roundtrip
385   [*] Corpus size:     1 | Edges covered:    131 | Fuzzing time:    504.798us | Total runs:  1.00e+00 | Runs/secs:     1
386   [*] Corpus size:     2 | Edges covered:    133 | Fuzzing time:    934.176us | Total runs:  3.00e+00 | Runs/secs:     3
387   [*] Corpus size:     3 | Edges covered:    134 | Fuzzing time:   2.384383ms | Total runs:  5.30e+01 | Runs/secs:    53
388   [*] Corpus size:     4 | Edges covered:    137 | Fuzzing time:   2.732274ms | Total runs:  5.40e+01 | Runs/secs:    54
389   [*] Corpus size:     5 | Edges covered:    137 | Fuzzing time:   7.275553ms | Total runs:  2.48e+02 | Runs/secs:   248
390
391.. TODO: b/282560789 - Add guides/improve_fuzzers.rst
392.. TODO: b/281139237 - Add guides/continuous_fuzzing.rst
393.. ----------
394.. Next steps
395.. ----------
396.. Once you have a working fuzzer, the next steps are to:
397
398.. * `Run it continuously on a fuzzing infrastructure <continuous_fuzzing>`_.
399.. * `Measure its code coverage and improve it <improve_fuzzers>`_.
400
401.. _FuzzTest: https://github.com/google/fuzztest
402.. _FuzzTest Domain Reference: https://github.com/google/fuzztest/blob/main/doc/domains-reference.md
403.. _FuzzTest Use Cases: https://github.com/google/fuzztest/blob/main/doc/use-cases.md
404.. _GoogleTest documentation: https://google.github.io/googletest/
405.. _Test Fixtures: https://github.com/google/fuzztest/blob/main/doc/fixtures.md
406.. _The FUZZ_TEST Macro: https://github.com/google/fuzztest/blob/main/doc/fuzz-test-macro.md
407.. _sanitizer flags: https://github.com/google/sanitizers/wiki/SanitizerCommonFlags
408.. _seeds: https://github.com/google/fuzztest/blob/main/doc/fuzz-test-macro.md#initial-seeds
409