The following example demonstrates how to use libFuzzer to write a simple fuzz test. Each fuzzer function is defined using LLVMFuzzerTestOneInput(const uint8_t * data, size_t len)
.
The Fuzzer must be located in a Test Folder : src/some_directory/tests/
#include <cstddef> #include <cstdint> /** * @file * This file describes a Fuzzer for ... */ extern "C" int LLVMFuzzerTestOneInput(const uint8_t * data, size_t len) { // Instantiate values as needed // Call target function for the fuzzer with the fuzzing input (data and len) return 0; }
See FuzzBase38Decode.cpp for an example of a simple fuzz test.
Add to src/some_directory/tests/BUILD.gn
Example
import("${chip_root}/build/chip/fuzz_test.gni") if (enable_fuzz_test_targets) { chip_fuzz_target("FuzzTargetName1") { sources = [ "Fuzzer1.cpp" ] public_deps = [ // Dependencies go here. ] } chip_fuzz_target("FuzzTargetName2") { sources = [ "Fuzzer2.cpp" ] public_deps = [ // Dependencies go here. ] } }
Another example: src/setup_payload/tests/BUILD.gn
Add to ${chip_root}/BUILD.gn
Add the Fuzzing Target in this part of the code : ${chip_root}/BUILD.gn
Add Fuzzing Target like that
if (enable_fuzz_test_targets) { group("fuzz_tests") { deps = [ "${chip_root}/src/credentials/tests:fuzz-chip-cert", "${chip_root}/src/lib/core/tests:fuzz-tlv-reader", "${chip_root}/src/lib/dnssd/minimal_mdns/tests:fuzz-minmdns-packet-parsing", "${chip_root}/src/lib/format/tests:fuzz-payload-decoder", "${chip_root}/src/setup_payload/tests:fuzz-setup-payload-base38", "${chip_root}/src/setup_payload/tests:fuzz-setup-payload-base38-decode", // ADD HERE YOUR FUZZING TARGET "${chip_root}/some_directory/tests:FuzzTargetName" ] } }
Build all fuzzers
./scripts/build/build_examples.py --target <host>-<compiler>-tests-asan-libfuzzer-clang build
e.g.
./scripts/build/build_examples.py --target darwin-arm64-tests-asan-libfuzzer-clang build
** Make sure to put the right host and compiler
Fuzzers binaries are compiled into:
out/<host>-<compiler>-tests-asan-libfuzzer-clang/tests
darwin-arm64-tests-asan-libfuzzer-clang
Running the fuzzer with a corpus
path_to_fuzzer_in_test_folder path_to_corpus
Google's FuzzTest
Finding Undefined Behavior with Sanitizers:
Find Correctness Bugs using Assertions:
Keywords: Property Function, Input Domain
FUZZ_TEST
:FUZZ_TEST(TLVReader, FuzzTlvReader).WithDomains(fuzztest::Arbitrary<std::vector<std::uint8_t>>());
The Macro invocation calls the Property Function, which is FuzzTlvReader
above.
The input domains define the range and type of inputs that the property function will receive during fuzzing, specified using the .WithDomains()
clause.
In the macro above, FuzzTest will generate a wide range of possible byte vectors to thoroughly test the FuzzTlvReader
function.
// The Property Function void FuzzTlvRead(const std::vector<std::uint8_t> & bytes) { TLVReader reader; reader.Init(bytes.data(), bytes.size()); chip::TLV::Utilities::Iterate(reader, FuzzIterator, nullptr); }
The Property Functions must return a void
The function will be run with many different inputs in the same process, trying to trigger a crash.
It is possible to include Assertions such as during Round-Trip Fuzzing
More Information: https://github.com/google/fuzztest/blob/main/doc/fuzz-test-macro.md#the-property-function
fuzztest::
namespace.Arbitrary<T>()
:FUZZ_TEST(Base38Decoder, FuzzQRCodeSetupPayloadParser).WithDomains(Arbitrary<std::string>());
.WithMaxSize()
or .WithMinSize()
, as shown below:FUZZ_TEST(MinimalmDNS, TxtResponderFuzz).WithDomains(Arbitrary<vector<uint8_t>>().WithMaxSize(254));
ElementOf
is particularly useful as it allows us to define a domain by explicitly enumerating the set of values in it and passing it to FuzzTest invocation. Example:auto AnyProtocolID() { return ElementOf({ chip::Protocols::SecureChannel::Id, chip::Protocols::InteractionModel::Id, chip::Protocols::BDX::Id, chip::Protocols::UserDirectedCommissioning::Id }); } FUZZ_TEST(PayloadDecoder, RunDecodeFuzz).WithDomains(Arbitrary<std::vector<std::uint8_t>>(), AnyProtocolID(), Arbitrary<uint8_t>());
Map
documented in FuzzTest's official documentation Aggregate Combinators#MapAnyValidationContext()
used in FUZZ_TEST(FuzzCASE, HandleSigma3b)
Using initial seeds is very useful when fuzzing functions that take complex inputs, such as large byte arrays
The fuzzing engine starts by mutating these initial seeds instead of generating completely random inputs
This helps the fuzzing engine explore more realistic and meaningful code paths faster, making it more likely to uncover issues
Adding .WithSeeds()
to the Input Domains within a FUZZ_TEST Macro invocation allow us to use initial seeds.
Two Ways to use .WithSeeds()
:
Using variables as inputs: Examples of this usage are in FuzzCASE.cpp
in the lambda SeededEncodedSigma1()
used in the Fuzz Test Case FUZZ_TEST(FuzzCASE, ParseSigma1_RawPayload)
Using files as inputs with fuzztest::ReadFilesFromDirectory()
:
seedProvider
in FuzzChipCertPW.cpp
to unpack the tuples and extract contentsstd::vector<std::string>
to be used with std::string
domain as shown below:FUZZ_TEST(FuzzChipCert, ConvertX509CertToChipCertFuzz).WithDomains(Arbitrary<std::string>().WithSeeds(seedProvider(isDerFile)));
There are several ways to run the tests:
./fuzz-chip-cert-pw
$ ./fuzz-chip-cert-pw --list_fuzz_tests [.] Sanitizer coverage enabled. Counter map size: 11134, Cmp map size: 262144 [*] Fuzz test: ChipCert.ChipCertFuzzer [*] Fuzz test: ChipCert.DecodeChipCertFuzzer $ ./fuzz-chip-cert-pw --fuzz=ChipCert.DecodeChipCertFuzzer
#both Fuzz Tests will be run for 10 minutes each ./fuzz-chip-cert-pw --fuzz_for=10m
# FuzzTest related help ./fuzz-chip-cert-pw --helpfull # gtest related help ./fuzz-chip-cert-pw --help
[!TIP]
Use Coverage Reports to get more insights while writing FuzzTests.
Build FuzzTests with coverage instrumentation Building pw_fuzzer FuzzTests.
Run These FuzzTests using scripts/tests/run_fuzztest_coverage.py
Continuous Fuzzing Mode
for as long as possible to get max coverageThe path for the HTML Coverage Report will be output after generation
Fuzz Blockers
.Screenshot below shows how we can use a Coverage Report to identify a Fuzz Blocker
We can see the number of executions of each line in the report.
Line (#2159) was not reached, in at least 129,452 executions.
The line (#2156) just above it is possibly a Fuzz Blocker.
data.fabricId
check is always failing and it is blocking the execution of the function that follows it.Thus, we can adapt our FuzzTest in a way to be able to pass that check.
Solution: One approach will be to:
NOC
with a valid NOC CertificateFabricId
FabricId
using the same valid FabricId
included in the valid NOC Cert.After doing this, Screenshot below shows Line #2159 is now reached; We have increased our coverage and we are sure that our FuzzTest is more effective:
This approach was used FuzzTest Case FUZZ_TEST(FuzzCASE, HandleSigma3b)
pw_fuzzer
with FuzzTest?pw_fuzzer
, which has several dependencies. These dependencies are listed here: Step 0: Set up FuzzTest for your project.bazel
and CMake
build systems and do not support GN, Pigweed maintainers use a script to generate GN files for these dependencies.