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