pw_fuzzer: Adding Fuzzers Using LibFuzzer#
pw_fuzzer: Better C++ code through easier fuzzing
Note
libFuzzer is currently only supported on Linux and MacOS using clang.
Step 0: Set up libFuzzer for your project#
Note
This workflow only needs to be done once for a project.
libFuzzer is a LLVM compiler runtime and should included with your clang
installation. In order to use it, you only need to define a suitable toolchain.
Use pw_toolchain_host_clang
, or derive a new toolchain from it.
For example:
import("$dir_pw_toolchain/host/target_toolchains.gni")
my_toolchains = {
...
clang_fuzz = {
name = "my_clang_fuzz"
forward_variables_from(pw_toolchain_host.clang_fuzz, "*", ["name"])
}
...
}
LibFuzzer-style fuzzers are not currently supported by Pigweed when using CMake.
Include rules_fuzzing
in your MODULE.bazel
file. For example:
bazel_dep(name = "rules_fuzzing", version = "0.5.2")
Then, import the libFuzzer build configurations in your .bazelrc
file
by adding and adapting the following:
# Include FuzzTest build configurations.
import %workspace%/path/to/pigweed/pw_fuzzer/libfuzzer.bazelrc
Step 1: Write a fuzz target function#
To write a fuzzer, a developer needs to write a fuzz target function following the guidelines given by libFuzzer:
extern "C" int LLVMFuzzerTestOneInput(const uint8_t *data, size_t size) {
DoSomethingInterestingWithMyAPI(data, size);
return 0; // Non-zero return values are reserved for future use.
}
When writing your fuzz target function, you may want to consider:
It is acceptable to return early if the input doesn’t meet some constraints, e.g. it is too short.
If your fuzzer accepts data with a well-defined format, you can bootstrap coverage by crafting examples and adding them to a corpus.
There are tools to split a fuzzing input into multiple fields if needed; the FuzzedDataProvider is particularly easy to use.
If your code acts on “transformed” inputs, such as encoded or compressed inputs, you may want to try structure aware fuzzing.
You can do startup initialization if you need to.
If your code is non-deterministic or uses checksums, you may want to disable those only when fuzzing by using LLVM’s FUZZING_BUILD_MODE_UNSAFE_FOR_PRODUCTION
Step 2: Add the fuzzer to your build#
To build a fuzzer, do the following:
Add the GN target to the module using pw_fuzzer
GN template. If you
wish to limit when the generated unit test is run, you can set
enable_test_if
in the same manner as enable_if
for pw_test:
# In $dir_my_module/BUILD.gn
import("$dir_pw_fuzzer/fuzzer.gni")
pw_fuzzer("my_fuzzer") {
sources = [ "my_fuzzer.cc" ]
deps = [ ":my_lib" ]
enable_test_if = device_has_1m_flash
}
Add the fuzzer GN target to the module’s group of fuzzers. Create this group if it does not exist.
# In $dir_my_module/BUILD.gn
group("fuzzers") {
deps = [
...
":my_fuzzer",
]
}
Make sure this group is referenced from a top-level fuzzers
target in
your project, with the appropriate
fuzzing toolchain.
For example:
# In //BUILD.gn
group("fuzzers") {
deps = [
...
"$dir_my_module:fuzzers(//my_toolchains:host_clang_fuzz)",
]
}
LibFuzzer-style fuzzers are not currently supported by Pigweed when using CMake.
Add a Bazel target to the module using the pw_cc_fuzz_test
rule. For
example:
# In $dir_my_module/BUILD.bazel
pw_cc_fuzz_test(
name = "my_fuzzer",
srcs = ["my_fuzzer.cc"],
deps = [":my_lib"]
)
Step 3: Add the fuzzer unit test to your build#
Pigweed automatically generates unit tests for libFuzzer-based fuzzers in some build systems.
The generated unit test will be suffixed by _test
and needs to be
added to the module’s test group. This test verifies the fuzzer can build
and run, even when not being built in a
fuzzing toolchain.
For example, for a fuzzer called my_fuzzer
, add the following:
# In $dir_my_module/BUILD.gn
pw_test_group("tests") {
tests = [
...
":my_fuzzer_test",
]
}
LibFuzzer-style fuzzers are not currently supported by Pigweed when using CMake.
Fuzzer unit tests are included automatically in Pigweed’s Bazel build.
Step 4: Build the fuzzer#
LibFuzzer-style fuzzers require the compiler to add instrumentation and runtimes when building.
Select a sanitizer runtime. See LLVM for valid options.
$ gn gen out --args='pw_toolchain_SANITIZERS=["address"]'
Some toolchains may set a default for fuzzers if none is specified. For example, //targets/host:host_clang_fuzz defaults to “address”.
Build the fuzzers using ninja
directly.
$ ninja -C out fuzzers
LibFuzzer-style fuzzers are not currently supported by Pigweed when using CMake.
Specify the libFuzzer config and a sanitizer config when building fuzzers.
$ bazel build //my_module:my_fuzzer --config=asan --config=libfuzzer
Step 5: Running the fuzzer locally#
The fuzzer binary will be in a subdirectory related to the toolchain. Additional libFuzzer options and corpus arguments can be passed on the command line. For example:
$ out/host_clang_fuzz/obj/my_module/bin/my_fuzzer -seed=1 path/to/corpus
Additional sanitizer flags may be passed uisng environment variables.
LibFuzzer-style fuzzers are not currently supported by Pigweed when using CMake.
Specify the libFuzzer config and a sanitizer config when building and
running fuzzers. For each fuzzer build rule with a given name,
rules_fuzzing produces a <name>_run
target. For example:
$ bazel run //my_module:my_fuzzer_run --config=asan --config=libfuzzer\
-- --timeout_secs=60
Running the fuzzer should produce output similar to the following:
INFO: Seed: 305325345
INFO: Loaded 1 modules (46 inline 8-bit counters): 46 [0x38dfc0, 0x38dfee),
INFO: Loaded 1 PC tables (46 PCs): 46 [0x23aaf0,0x23add0),
INFO: 0 files found in corpus
INFO: -max_len is not provided; libFuzzer will not generate inputs larger than 4096 bytes
INFO: A corpus is not provided, starting from an empty corpus
#2 INITED cov: 2 ft: 3 corp: 1/1b exec/s: 0 rss: 27Mb
#4 NEW cov: 3 ft: 4 corp: 2/3b lim: 4 exec/s: 0 rss: 27Mb L: 2/2 MS: 2 ShuffleBytes-InsertByte-
#11 NEW cov: 7 ft: 8 corp: 3/7b lim: 4 exec/s: 0 rss: 27Mb L: 4/4 MS: 2 EraseBytes-CrossOver-
#27 REDUCE cov: 7 ft: 8 corp: 3/6b lim: 4 exec/s: 0 rss: 27Mb L: 3/3 MS: 1 EraseBytes-
#29 REDUCE cov: 7 ft: 8 corp: 3/5b lim: 4 exec/s: 0 rss: 27Mb L: 2/2 MS: 2 ChangeBit-EraseBytes-
#445 REDUCE cov: 9 ft: 10 corp: 4/13b lim: 8 exec/s: 0 rss: 27Mb L: 8/8 MS: 1 InsertRepeatedBytes-
...