Integration of pw_fuzzer using FuzzTest (#34274)
* latest trial to build pw_fuzz
* migrating FuzzPayloadDecoder FuzzTest
* fix error related to latomic
* adding template for pw_fuzz_tests
* fix for linux_sysroot issue
* adding FuzzTests
* fixing warning issue
* adding support to build pw-fuzztests with build_examples.py
* Restyled by whitespace
* Restyled by clang-format
* adding pw_fuzz_tests to default target
* fixing build_examples test golden standard
* Adding Fuzzing Targets
* Adding Documentation
* cleaning-up tests
* spelling mistakes
* integrating comments
---------
Co-authored-by: Restyled.io <commits@restyled.io>
diff --git a/.github/.wordlist.txt b/.github/.wordlist.txt
index 1533458..4444d82 100644
--- a/.github/.wordlist.txt
+++ b/.github/.wordlist.txt
@@ -37,6 +37,7 @@
AE
aef
AES
+AFL
AIDL
algs
alloc
@@ -570,6 +571,7 @@
ftd
fullclean
fuzzer
+fuzztest
FW
gbl
gcloud
@@ -1008,6 +1010,7 @@
optionsMask
optionsOverride
orgs
+OSS
OTA
OTADownloader
otaDownloadPath
diff --git a/.gitmodules b/.gitmodules
index 78a6cab..40801ec 100644
--- a/.gitmodules
+++ b/.gitmodules
@@ -329,3 +329,19 @@
path = third_party/infineon/psoc6/psoc6_sdk/libs/lwip-network-interface-integration
url = https://github.com/Infineon/lwip-network-interface-integration.git
platforms = infineon
+[submodule "third_party/abseil-cpp/src"]
+ path = third_party/abseil-cpp/src
+ url = https://github.com/abseil/abseil-cpp.git
+ platforms = linux,darwin
+[submodule "third_party/fuzztest"]
+ path = third_party/fuzztest
+ url = https://github.com/google/fuzztest.git
+ platforms = linux,darwin
+[submodule "third_party/googletest"]
+ path = third_party/googletest
+ url = https://github.com/google/googletest
+ platforms = linux,darwin
+[submodule "third_party/re2/src"]
+ path = third_party/re2/src
+ url = https://github.com/google/re2.git
+ platforms = linux,darwin
diff --git a/BUILD.gn b/BUILD.gn
index c8e4197..4efa250 100644
--- a/BUILD.gn
+++ b/BUILD.gn
@@ -62,6 +62,18 @@
}
}
+ if (pw_enable_fuzz_test_targets) {
+ group("pw_fuzz_tests") {
+ deps = [
+ "${chip_root}/src/credentials/tests:fuzz-chip-cert-pw(//build/toolchain/pw_fuzzer:chip_pw_fuzztest)",
+ "${chip_root}/src/lib/core/tests:fuzz-tlv-reader-pw(//build/toolchain/pw_fuzzer:chip_pw_fuzztest)",
+ "${chip_root}/src/lib/dnssd/minimal_mdns/tests:fuzz-minmdns-packet-parsing-pw(//build/toolchain/pw_fuzzer:chip_pw_fuzztest)",
+ "${chip_root}/src/lib/format/tests:fuzz-payload-decoder-pw(//build/toolchain/pw_fuzzer:chip_pw_fuzztest)",
+ "${chip_root}/src/setup_payload/tests:fuzz-setup-payload-base38-pw(//build/toolchain/pw_fuzzer:chip_pw_fuzztest)",
+ ]
+ }
+ }
+
# Matter's in-tree pw_python_package or pw_python_distribution targets.
_matter_python_packages = [
"//examples/chef",
@@ -140,6 +152,10 @@
deps += [ "//:fuzz_tests" ]
}
+ if (pw_enable_fuzz_test_targets) {
+ deps += [ "//:pw_fuzz_tests" ]
+ }
+
if (chip_device_platform != "none") {
deps += [ "${chip_root}/src/app/server" ]
}
diff --git a/build/chip/fuzz_test.gni b/build/chip/fuzz_test.gni
index 784ed60..98def1d 100644
--- a/build/chip/fuzz_test.gni
+++ b/build/chip/fuzz_test.gni
@@ -14,12 +14,17 @@
import("//build_overrides/build.gni")
import("//build_overrides/chip.gni")
+import("//build_overrides/pigweed.gni")
+
import("${build_root}/config/compiler/compiler.gni")
import("${chip_root}/build/chip/tests.gni")
+import("${dir_pw_unit_test}/test.gni")
declare_args() {
enable_fuzz_test_targets = is_clang && chip_build_tests &&
(current_os == "linux" || current_os == "mac")
+
+ pw_enable_fuzz_test_targets = false
}
# Define a fuzz target for chip.
@@ -66,3 +71,57 @@
}
}
}
+
+# Define a fuzz target for Matter using pw_fuzzer and Google FuzzTest Framework.
+#
+# Google FuzzTest is only supported on Linux and MacOS using Clang:
+#
+# Sample usage
+#
+# chip_pw_fuzz_target("fuzz-target-name") {
+# test_source = [
+# "FuzzTarget.cpp", # Fuzz target
+# ]
+#
+# public_deps = [
+# "${chip_root}/src/lib/foo", # add dependencies here
+# ]
+# }
+#
+#
+template("chip_pw_fuzz_target") {
+ if (defined(invoker.test_source)) {
+ _test_output_dir = "${root_out_dir}/tests"
+
+ if (defined(invoker.output_dir)) {
+ _test_output_dir = invoker.output_dir
+ }
+
+ pw_test(target_name) {
+ forward_variables_from(invoker,
+ [
+ "deps",
+ "public_deps",
+ "cflags",
+ "configs",
+ "remove_configs",
+ ])
+
+ # TODO: remove this after pw_fuzzer's integration with OSS-Fuzz is complete.
+ #just a test for running FuzzTest with libfuzzer-compatibility mode, since this is the mode supported by OSS-fuzz
+ # defines = [
+ # "FUZZTEST_COMPATIBILITY_MODE=libfuzzer",
+ # "MAKE_BUILD_TYPE=RelWithDebug",
+ # ]
+
+ sources = invoker.test_source
+ output_dir = _test_output_dir
+
+ deps = [ "$dir_pw_fuzzer:fuzztest" ]
+
+ # this is necessary so FuzzTest is compiled into an executable in third_party/pigweed/repo/pw_unit_test/test.gni
+ # otherwise it will be built successfully but with FuzzTarget.DISABLED.ninja and no executable.
+ enable_if = true
+ }
+ }
+}
diff --git a/build/toolchain/pw_fuzzer/BUILD.gn b/build/toolchain/pw_fuzzer/BUILD.gn
new file mode 100644
index 0000000..385e57c
--- /dev/null
+++ b/build/toolchain/pw_fuzzer/BUILD.gn
@@ -0,0 +1,70 @@
+# Copyright (c) 2024 Project CHIP Authors
+#
+# Licensed under the Apache License, Version 2.0 (the "License");
+# you may not use this file except in compliance with the License.
+# You may obtain a copy of the License at
+#
+# http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS,
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+# See the License for the specific language governing permissions and
+# limitations under the License.
+
+import("//build_overrides/build.gni")
+import("//build_overrides/pigweed.gni")
+
+import("$dir_pigweed/targets/host/target_toolchains.gni")
+import("${build_root}/toolchain/gcc_toolchain.gni")
+
+# creating a secondary toolchain to be used with pw_fuzzer FuzzTests
+# This toolchain is downstreamed from pigweed's pw_target_toolchain_host.clang_fuzz
+# it allows us to specifically use googletest for fuzzing (instead of the lighter version of googletest used for unit testing)
+
+gcc_toolchain("chip_pw_fuzztest") {
+ forward_variables_from(pw_target_toolchain_host.clang_fuzz, "*", [ "name" ])
+
+ toolchain_args = {
+ # This is needed to have the defaults passed from pw_target_toolchain_host.clang_fuzz to the current scope
+ forward_variables_from(defaults, "*")
+
+ pw_unit_test_MAIN = "$dir_pw_fuzzer:fuzztest_main"
+ pw_unit_test_BACKEND = "$dir_pw_fuzzer:gtest"
+
+ # The next three lines are needed by the gcc_toolchain template
+ current_os = host_os
+ current_cpu = host_cpu
+ is_clang = true
+
+ # the upstream pigweed host_clang toolchain defines a default sysroot, which results in build errors
+ # since it does not include SSL lib and is supposed to be minimal by design.
+ # by removing this default config, we will use the system's libs. Otherwise we can define our own sysroot.
+ # discussion on: https://discord.com/channels/691686718377558037/1275092695764959232
+ remove_default_configs = [ "$dir_pw_toolchain/host_clang:linux_sysroot" ]
+
+ # when is_debug = true, we pass -O0 to cflags and ldflags, while upstream pw_fuzzer toolchain defines "optimize_speed" config that passes -O2.
+ # This condition was added to prevent mixing the flags
+ if (is_debug) {
+ remove_default_configs += [ "$dir_pw_build:optimize_speed" ]
+ }
+
+ # removing pigweed downstreamed configs related to warnings
+ # These are triggering an error related to -Wcast-qual in third_party/nlio
+ remove_default_configs += [
+ "$dir_pw_build:strict_warnings",
+ "$dir_pw_build:extra_strict_warnings",
+ ]
+
+ # the third_party abseil-cpp triggers warnings related to [-Wformat-nonliteral]
+ treat_warnings_as_errors = false
+
+ dir_pw_third_party_abseil_cpp = "//third_party/abseil-cpp/src"
+ dir_pw_third_party_fuzztest = "//third_party/fuzztest"
+ dir_pw_third_party_googletest = "//third_party/googletest"
+
+ # TODO: Seems that re2 support within FuzzTest was deprecated, keeping it defined is triggering warning
+ # Remove if re2 is indeed not needed
+ # dir_pw_third_party_re2 = "//third_party/re2/src"
+ }
+}
diff --git a/docs/guides/BUILDING.md b/docs/guides/BUILDING.md
index 7cd6602..941b532 100644
--- a/docs/guides/BUILDING.md
+++ b/docs/guides/BUILDING.md
@@ -382,6 +382,26 @@
You likely want `libfuzzer` + `asan` builds instead for local testing.
+### `pw_fuzzer` `FuzzTests`
+
+An Alternative way for writing and running Fuzz Tests is Google's `FuzzTest`
+framework, integrated through `pw_fuzzer`. The Tests will have to be built and
+executed manually.
+
+```
+./scripts/build/build_examples.py --target linux-x64-tests-clang-pw-fuzztest build
+```
+
+NOTE: `asan` is enabled by default in FuzzTest, so please do not add it in
+build_examples.py invocation.
+
+Tests will be located in:
+`out/linux-x64-tests-clang-pw-fuzztest/chip_pw_fuzztest/tests/` where
+`chip_pw_fuzztest` is the name of the toolchain used.
+
+- Details on How To Run Fuzz Tests in
+ [Running FuzzTests](https://github.com/project-chip/connectedhomeip/blob/master/docs/testing/fuzz_testing.md)
+
## Build custom configuration
The build is configured by setting build arguments. These you can set in one of
diff --git a/docs/testing/fuzz_testing.md b/docs/testing/fuzz_testing.md
new file mode 100644
index 0000000..b7faeae
--- /dev/null
+++ b/docs/testing/fuzz_testing.md
@@ -0,0 +1,157 @@
+# Fuzz testing
+
+- Fuzz Testing involves providing random and unexpected data as input to
+ functions and methods to uncover bugs, security vulnerabilities, or to
+ determine if the software crashes.
+- it is often continuous; the function under test is called in iteration with
+ thousands of different inputs.
+- Fuzz testing is often done with sanitizers enabled; to catch memory errors
+ and undefined behavior.
+- The most commonly used fuzz testing frameworks for C/C++ are LibFuzzer and
+ AFL.
+- [Google's FuzzTest](https://github.com/google/fuzztest) is a newer framework
+ that simplifies writing fuzz tests with user-friendly APIs and offers more
+ control over input generation. It also integrates seamlessly with Google
+ Test (GTest).
+
+## `Google's FuzzTest`
+
+- Google FuzzTest is integrated through Pigweed
+ [pw_fuzzer](https://pigweed.dev/pw_fuzzer/concepts.html).
+
+### Use cases
+
+1. Finding Undefined Behavior with Sanitizers:
+
+ - Running fuzz tests while checking if a crash or other sanitizer-detected
+ error occurs, allowing detection of subtle memory issues like buffer
+ overflows and use-after-free errors.
+
+2. Find Correctness Bugs using Assertions:
+ - For example, in Round trip Fuzzing, fuzzed input is encoded, decoded, and
+ then verified to match the original input. An example of this can be found
+ in src/setup_payload/tests/FuzzBase38PW.cpp.
+
+- More information can be found in the
+ [FuzzTest Use Cases](https://github.com/google/fuzztest/blob/main/doc/use-cases.md)
+ documentation.
+
+### Writing FuzzTests
+
+Keywords: Property Function, Input Domain
+
+- FuzzTests are instantiated through the macro call of `FUZZ_TEST`:
+
+```cpp
+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
+
+```cpp
+// 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
+
+#### Input Domains
+
+- FuzzTest Offers many Input Domains, all of which are part of the
+ `fuzztest::` namespace.
+- All native C++ types can be used through `Arbitrary<T>()`:
+
+```cpp
+FUZZ_TEST(Base38Decoder, FuzzQRCodeSetupPayloadParser).WithDomains(Arbitrary<std::string>());
+```
+
+- using vector domains is one of the most common. It is possible to limit the
+ size of the input vectors (or any container domain) using `.WithMaxSize()`
+ or `.WithMinSize()`, as shown below:
+
+```cpp
+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:
+
+```cpp
+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>());
+```
+
+- A detailed reference for input domains can be found here:
+ [FuzzTest Domain Reference](https://github.com/google/fuzztest/blob/main/doc/domains-reference.md#elementof-domains-element-of).
+
+### Running FuzzTests
+
+There are several ways to run the tests:
+
+1. Unit-test mode (where the inputs are only fuzzed for a second):
+
+```bash
+./fuzz-chip-cert-pw
+```
+
+2. Continuous fuzzing mode; we need to first list the tests, then specify the
+ FuzzTestCase to run:
+
+```bash
+$ ./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
+```
+
+3. Running all Tests in a TestSuite for a specific time, e.g for 10 minutes
+
+```bash
+#both Fuzz Tests will be run for 10 minutes each
+./fuzz-chip-cert-pw --fuzz_for=10m
+```
+
+4. For Help
+
+```bash
+# FuzzTest related help
+./fuzz-chip-cert-pw --helpfull
+
+# gtest related help
+./fuzz-chip-cert-pw --help
+
+```
+
+#### TO ADD:
+
+- More Information on Test Fixtures (After issues are resolved)
+- How to add FuzzTests to the Build System
+- More Information on OSS-FUZZ
diff --git a/scripts/build/build/targets.py b/scripts/build/build/targets.py
index efb6612..378a286 100755
--- a/scripts/build/build/targets.py
+++ b/scripts/build/build/targets.py
@@ -77,6 +77,8 @@
"-clang").ExceptIfRe('-ossfuzz')
target.AppendModifier("ossfuzz", fuzzing_type=HostFuzzingType.OSS_FUZZ).OnlyIfRe(
"-clang").ExceptIfRe('-libfuzzer')
+ target.AppendModifier("pw-fuzztest", fuzzing_type=HostFuzzingType.PW_FUZZTEST).OnlyIfRe(
+ "-clang").ExceptIfRe('-(libfuzzer|ossfuzz|asan)')
target.AppendModifier('coverage', use_coverage=True).OnlyIfRe(
'-(chip-tool|all-clusters)')
target.AppendModifier('dmalloc', use_dmalloc=True)
@@ -178,6 +180,8 @@
"-clang").ExceptIfRe('-ossfuzz')
target.AppendModifier("ossfuzz", fuzzing_type=HostFuzzingType.OSS_FUZZ).OnlyIfRe(
"-clang").ExceptIfRe('-libfuzzer')
+ target.AppendModifier("pw-fuzztest", fuzzing_type=HostFuzzingType.PW_FUZZTEST).OnlyIfRe(
+ "-clang").ExceptIfRe('-(libfuzzer|ossfuzz|asan)')
target.AppendModifier('coverage', use_coverage=True).OnlyIfRe(
'-(chip-tool|all-clusters|tests)')
target.AppendModifier('dmalloc', use_dmalloc=True)
diff --git a/scripts/build/builders/host.py b/scripts/build/builders/host.py
index c51a8ee..b69421a 100644
--- a/scripts/build/builders/host.py
+++ b/scripts/build/builders/host.py
@@ -42,6 +42,7 @@
NONE = auto()
LIB_FUZZER = auto()
OSS_FUZZ = auto()
+ PW_FUZZTEST = auto()
class HostApp(Enum):
@@ -379,6 +380,8 @@
self.extra_gn_options.append('is_libfuzzer=true')
elif fuzzing_type == HostFuzzingType.OSS_FUZZ:
self.extra_gn_options.append('oss_fuzz=true')
+ elif fuzzing_type == HostFuzzingType.PW_FUZZTEST:
+ self.extra_gn_options.append('pw_enable_fuzz_test_targets=true')
if imgui_ui:
self.extra_gn_options.append('chip_examples_enable_imgui_ui=true')
@@ -468,6 +471,9 @@
if self.app == HostApp.TESTS and fuzzing_type != HostFuzzingType.NONE:
self.build_command = 'fuzz_tests'
+ if self.app == HostApp.TESTS and fuzzing_type == HostFuzzingType.PW_FUZZTEST:
+ self.build_command = 'pw_fuzz_tests'
+
def GnBuildArgs(self):
if self.board == HostBoard.NATIVE:
return self.extra_gn_options
diff --git a/scripts/build/testdata/all_targets_linux_x64.txt b/scripts/build/testdata/all_targets_linux_x64.txt
index 3c5b290..07b409c 100644
--- a/scripts/build/testdata/all_targets_linux_x64.txt
+++ b/scripts/build/testdata/all_targets_linux_x64.txt
@@ -8,8 +8,8 @@
efr32-{brd2704b,brd4316a,brd4317a,brd4318a,brd4319a,brd4186a,brd4187a,brd2601b,brd4187c,brd4186c,brd2703a,brd4338a}-{window-covering,switch,unit-test,light,lock,thermostat,pump}[-rpc][-with-ota-requestor][-icd][-low-power][-shell][-no-logging][-openthread-mtd][-heap-monitoring][-no-openthread-cli][-show-qr-code][-wifi][-rs9116][-wf200][-siwx917][-ipv4][-additional-data-advertising][-use-ot-lib][-use-ot-coap-lib][-no-version][-skip-rps-generation]
esp32-{m5stack,c3devkit,devkitc,qemu}-{all-clusters,all-clusters-minimal,energy-management,ota-provider,ota-requestor,shell,light,lock,bridge,temperature-measurement,ota-requestor,tests}[-rpc][-ipv6only][-tracing]
genio-lighting-app
-linux-fake-tests[-mbedtls][-boringssl][-asan][-tsan][-ubsan][-libfuzzer][-ossfuzz][-coverage][-dmalloc][-clang]
-linux-{x64,arm64}-{rpc-console,all-clusters,all-clusters-minimal,chip-tool,thermostat,java-matter-controller,kotlin-matter-controller,minmdns,light,light-data-model-no-unique-id,lock,shell,ota-provider,ota-requestor,simulated-app1,simulated-app2,python-bindings,tv-app,tv-casting-app,bridge,fabric-admin,fabric-bridge,tests,chip-cert,address-resolve-tool,contact-sensor,dishwasher,microwave-oven,refrigerator,rvc,air-purifier,lit-icd,air-quality-sensor,network-manager,energy-management}[-nodeps][-nlfaultinject][-platform-mdns][-minmdns-verbose][-libnl][-same-event-loop][-no-interactive][-ipv6only][-no-ble][-no-wifi][-no-thread][-mbedtls][-boringssl][-asan][-tsan][-ubsan][-libfuzzer][-ossfuzz][-coverage][-dmalloc][-clang][-test][-rpc][-with-ui][-evse-test-event][-enable-dnssd-tests][-disable-dnssd-tests][-chip-casting-simplified][-data-model-check][-data-model-disabled][-data-model-enabled]
+linux-fake-tests[-mbedtls][-boringssl][-asan][-tsan][-ubsan][-libfuzzer][-ossfuzz][-pw-fuzztest][-coverage][-dmalloc][-clang]
+linux-{x64,arm64}-{rpc-console,all-clusters,all-clusters-minimal,chip-tool,thermostat,java-matter-controller,kotlin-matter-controller,minmdns,light,light-data-model-no-unique-id,lock,shell,ota-provider,ota-requestor,simulated-app1,simulated-app2,python-bindings,tv-app,tv-casting-app,bridge,fabric-admin,fabric-bridge,tests,chip-cert,address-resolve-tool,contact-sensor,dishwasher,microwave-oven,refrigerator,rvc,air-purifier,lit-icd,air-quality-sensor,network-manager,energy-management}[-nodeps][-nlfaultinject][-platform-mdns][-minmdns-verbose][-libnl][-same-event-loop][-no-interactive][-ipv6only][-no-ble][-no-wifi][-no-thread][-mbedtls][-boringssl][-asan][-tsan][-ubsan][-libfuzzer][-ossfuzz][-pw-fuzztest][-coverage][-dmalloc][-clang][-test][-rpc][-with-ui][-evse-test-event][-enable-dnssd-tests][-disable-dnssd-tests][-chip-casting-simplified][-data-model-check][-data-model-disabled][-data-model-enabled]
linux-x64-efr32-test-runner[-clang]
imx-{chip-tool,lighting-app,thermostat,all-clusters-app,all-clusters-minimal-app,ota-provider-app}[-release]
infineon-psoc6-{lock,light,all-clusters,all-clusters-minimal}[-ota][-updateimage][-trustm]
diff --git a/src/credentials/tests/BUILD.gn b/src/credentials/tests/BUILD.gn
index 393b246..46e1f72 100644
--- a/src/credentials/tests/BUILD.gn
+++ b/src/credentials/tests/BUILD.gn
@@ -84,3 +84,13 @@
]
}
}
+
+if (pw_enable_fuzz_test_targets) {
+ chip_pw_fuzz_target("fuzz-chip-cert-pw") {
+ test_source = [ "FuzzChipCertPW.cpp" ]
+ public_deps = [
+ "${chip_root}/src/credentials",
+ "${chip_root}/src/platform/logging:default",
+ ]
+ }
+}
diff --git a/src/credentials/tests/FuzzChipCertPW.cpp b/src/credentials/tests/FuzzChipCertPW.cpp
new file mode 100644
index 0000000..bb929b3
--- /dev/null
+++ b/src/credentials/tests/FuzzChipCertPW.cpp
@@ -0,0 +1,95 @@
+#include <cstddef>
+#include <cstdint>
+
+#include <pw_fuzzer/fuzztest.h>
+#include <pw_unit_test/framework.h>
+
+#include "credentials/CHIPCert.h"
+
+namespace {
+
+using namespace chip;
+using namespace chip::Credentials;
+
+using namespace fuzztest;
+
+void ChipCertFuzzer(const std::vector<std::uint8_t> & bytes)
+{
+ ByteSpan span(bytes.data(), bytes.size());
+
+ {
+ NodeId nodeId;
+ FabricId fabricId;
+ (void) ExtractFabricIdFromCert(span, &fabricId);
+ (void) ExtractNodeIdFabricIdFromOpCert(span, &nodeId, &fabricId);
+ }
+
+ {
+ CATValues cats;
+ (void) ExtractCATsFromOpCert(span, cats);
+ }
+
+ {
+ Credentials::P256PublicKeySpan key;
+ (void) ExtractPublicKeyFromChipCert(span, key);
+ }
+
+ {
+ chip::System::Clock::Seconds32 rcacNotBefore;
+ (void) ExtractNotBeforeFromChipCert(span, rcacNotBefore);
+ }
+
+ {
+ Credentials::CertificateKeyId skid;
+ (void) ExtractSKIDFromChipCert(span, skid);
+ }
+
+ {
+ ChipDN subjectDN;
+ (void) ExtractSubjectDNFromChipCert(span, subjectDN);
+ }
+
+ {
+ uint8_t outCertBuf[kMaxDERCertLength];
+ MutableByteSpan outCert(outCertBuf);
+ (void) ConvertChipCertToX509Cert(span, outCert);
+ }
+
+ {
+ // TODO: #35369 Move this to a Fixture once Errors related to FuzzTest Fixtures are resolved
+ ASSERT_EQ(chip::Platform::MemoryInit(), CHIP_NO_ERROR);
+ ValidateChipRCAC(span);
+ chip::Platform::MemoryShutdown();
+ }
+}
+
+FUZZ_TEST(FuzzChipCert, ChipCertFuzzer).WithDomains(Arbitrary<std::vector<std::uint8_t>>());
+
+// The Property function for DecodeChipCertFuzzer, The FUZZ_TEST Macro will call this function.
+void DecodeChipCertFuzzer(const std::vector<std::uint8_t> & bytes, BitFlags<CertDecodeFlags> aDecodeFlag)
+{
+ ByteSpan span(bytes.data(), bytes.size());
+
+ // TODO: #34352 To Move this to a Fixture once Errors related to FuzzTest Fixtures are resolved
+ ASSERT_EQ(chip::Platform::MemoryInit(), CHIP_NO_ERROR);
+
+ ChipCertificateData certData;
+ (void) DecodeChipCert(span, certData, aDecodeFlag);
+
+ chip::Platform::MemoryShutdown();
+}
+
+// This function allows us to fuzz using one of three CertDecodeFlags flags; by using FuzzTests's `ElementOf` API, we define an
+// input domain by explicitly enumerating the set of values in it More Info:
+// https://github.com/google/fuzztest/blob/main/doc/domains-reference.md#elementof-domains-element-of
+auto AnyCertDecodeFlag()
+{
+ constexpr BitFlags<CertDecodeFlags> NullDecodeFlag;
+ constexpr BitFlags<CertDecodeFlags> GenTBSHashFlag(CertDecodeFlags::kGenerateTBSHash);
+ constexpr BitFlags<CertDecodeFlags> TrustAnchorFlag(CertDecodeFlags::kIsTrustAnchor);
+
+ return ElementOf<CertDecodeFlags>({ NullDecodeFlag, GenTBSHashFlag, TrustAnchorFlag });
+}
+
+FUZZ_TEST(FuzzChipCert, DecodeChipCertFuzzer).WithDomains(Arbitrary<std::vector<std::uint8_t>>(), AnyCertDecodeFlag());
+} // namespace
diff --git a/src/lib/core/tests/BUILD.gn b/src/lib/core/tests/BUILD.gn
index eb17707..264de2c 100644
--- a/src/lib/core/tests/BUILD.gn
+++ b/src/lib/core/tests/BUILD.gn
@@ -59,3 +59,13 @@
]
}
}
+
+if (pw_enable_fuzz_test_targets) {
+ chip_pw_fuzz_target("fuzz-tlv-reader-pw") {
+ test_source = [ "FuzzTlvReaderPW.cpp" ]
+ public_deps = [
+ "${chip_root}/src/lib/core",
+ "${chip_root}/src/platform/logging:default",
+ ]
+ }
+}
diff --git a/src/lib/core/tests/FuzzTlvReaderPW.cpp b/src/lib/core/tests/FuzzTlvReaderPW.cpp
new file mode 100644
index 0000000..4296cac
--- /dev/null
+++ b/src/lib/core/tests/FuzzTlvReaderPW.cpp
@@ -0,0 +1,35 @@
+
+#include <cstddef>
+#include <cstdint>
+
+#include <pw_fuzzer/fuzztest.h>
+#include <pw_unit_test/framework.h>
+
+#include "lib/core/TLV.h"
+#include "lib/core/TLVUtilities.h"
+
+namespace {
+
+using chip::TLV::TLVReader;
+
+using namespace fuzztest;
+
+static CHIP_ERROR FuzzIterator(const TLVReader & aReader, size_t aDepth, void * aContext)
+{
+ aReader.GetLength();
+ aReader.GetTag();
+ aReader.GetType();
+ return CHIP_NO_ERROR;
+}
+
+// The Property Function
+void FuzzTlvReader(const std::vector<std::uint8_t> & bytes)
+{
+ TLVReader reader;
+ reader.Init(bytes.data(), bytes.size());
+ chip::TLV::Utilities::Iterate(reader, FuzzIterator, nullptr);
+}
+// Fuzz tests are instantiated with the FUZZ_TEST macro
+FUZZ_TEST(TLVReader, FuzzTlvReader).WithDomains(Arbitrary<std::vector<std::uint8_t>>());
+
+} // namespace
diff --git a/src/lib/dnssd/minimal_mdns/tests/BUILD.gn b/src/lib/dnssd/minimal_mdns/tests/BUILD.gn
index 47e8365..457c11e 100644
--- a/src/lib/dnssd/minimal_mdns/tests/BUILD.gn
+++ b/src/lib/dnssd/minimal_mdns/tests/BUILD.gn
@@ -53,3 +53,13 @@
]
}
}
+
+if (pw_enable_fuzz_test_targets) {
+ chip_pw_fuzz_target("fuzz-minmdns-packet-parsing-pw") {
+ test_source = [ "FuzzPacketParsingPW.cpp" ]
+ public_deps = [
+ "${chip_root}/src/lib/dnssd/minimal_mdns",
+ "${chip_root}/src/platform/logging:default",
+ ]
+ }
+}
diff --git a/src/lib/dnssd/minimal_mdns/tests/FuzzPacketParsingPW.cpp b/src/lib/dnssd/minimal_mdns/tests/FuzzPacketParsingPW.cpp
new file mode 100644
index 0000000..aec550b
--- /dev/null
+++ b/src/lib/dnssd/minimal_mdns/tests/FuzzPacketParsingPW.cpp
@@ -0,0 +1,132 @@
+#include <cstddef>
+#include <cstdint>
+
+#include <pw_fuzzer/fuzztest.h>
+#include <pw_unit_test/framework.h>
+
+#include <lib/dnssd/minimal_mdns/Parser.h>
+#include <lib/dnssd/minimal_mdns/RecordData.h>
+
+namespace {
+
+using namespace fuzztest;
+using namespace std;
+
+using namespace chip;
+using namespace mdns::Minimal;
+
+class FuzzDelegate : public ParserDelegate
+{
+public:
+ FuzzDelegate(const mdns::Minimal::BytesRange & packet) : mPacketRange(packet) {}
+ virtual ~FuzzDelegate() {}
+
+ void OnHeader(ConstHeaderRef & header) override {}
+ void OnQuery(const QueryData & data) override {}
+ void OnResource(ResourceType type, const ResourceData & data) override
+ {
+ switch (data.GetType())
+ {
+ case QType::SRV: {
+ mdns::Minimal::SrvRecord srv;
+ (void) srv.Parse(data.GetData(), mPacketRange);
+ break;
+ }
+ case QType::A: {
+ chip::Inet::IPAddress addr;
+ (void) mdns::Minimal::ParseARecord(data.GetData(), &addr);
+ break;
+ }
+ case QType::AAAA: {
+ chip::Inet::IPAddress addr;
+ (void) mdns::Minimal::ParseAAAARecord(data.GetData(), &addr);
+ break;
+ }
+ case QType::PTR: {
+ mdns::Minimal::SerializedQNameIterator name;
+ (void) mdns::Minimal::ParsePtrRecord(data.GetData(), mPacketRange, &name);
+ break;
+ }
+ default:
+ // nothing to do
+ break;
+ }
+ }
+
+private:
+ mdns::Minimal::BytesRange mPacketRange;
+};
+
+void PacketParserFuzz(const std::vector<std::uint8_t> & bytes)
+{
+ BytesRange packet(bytes.data(), bytes.data() + bytes.size());
+ FuzzDelegate delegate(packet);
+
+ mdns::Minimal::ParsePacket(packet, &delegate);
+}
+
+FUZZ_TEST(MinimalmDNS, PacketParserFuzz).WithDomains(Arbitrary<vector<uint8_t>>());
+
+class TxtRecordAccumulator : public TxtRecordDelegate
+{
+public:
+ using DataType = vector<pair<string, string>>;
+
+ void OnRecord(const BytesRange & name, const BytesRange & value) override
+ {
+ mData.push_back(make_pair(AsString(name), AsString(value)));
+ }
+
+ DataType & Data() { return mData; }
+ const DataType & Data() const { return mData; }
+
+private:
+ DataType mData;
+
+ static string AsString(const BytesRange & range)
+ {
+ return string(reinterpret_cast<const char *>(range.Start()), reinterpret_cast<const char *>(range.End()));
+ }
+};
+
+// The Property Function
+void TxtResponderFuzz(const std::vector<std::uint8_t> & aRecord)
+{
+
+ bool equal_sign_present = false;
+ auto equal_sign_pos = aRecord.end();
+
+ // This test is only giving a set of values, it can be gives more
+ vector<uint8_t> prefixedRecord{ static_cast<uint8_t>(aRecord.size()) };
+
+ prefixedRecord.insert(prefixedRecord.end(), aRecord.begin(), aRecord.end());
+
+ TxtRecordAccumulator accumulator;
+
+ // The Function under Test, Check that the function does not Crash
+ ParseTxtRecord(BytesRange(prefixedRecord.data(), (&prefixedRecord.back() + 1)), &accumulator);
+
+ for (auto it = aRecord.begin(); it != aRecord.end(); it++)
+ {
+ // if this is first `=` found in the fuzzed record
+ if ('=' == static_cast<char>(*it) && false == equal_sign_present)
+ {
+ equal_sign_present = true;
+ equal_sign_pos = it;
+ }
+ }
+
+ // The Fuzzed Input (record) needs to have at least two characters in order for ParseTxtRecord to do something
+ if (aRecord.size() > 1)
+ {
+ if (true == equal_sign_present)
+ {
+ std::string input_record_value(equal_sign_pos + 1, aRecord.end());
+ EXPECT_EQ(accumulator.Data().at(0).second, input_record_value);
+ }
+ }
+}
+
+FUZZ_TEST(MinimalmDNS, TxtResponderFuzz).WithDomains(Arbitrary<vector<uint8_t>>().WithMaxSize(254));
+
+} // namespace
diff --git a/src/lib/format/tests/BUILD.gn b/src/lib/format/tests/BUILD.gn
index 840a2ee..1ce417e 100644
--- a/src/lib/format/tests/BUILD.gn
+++ b/src/lib/format/tests/BUILD.gn
@@ -57,3 +57,18 @@
]
}
}
+
+if (pw_enable_fuzz_test_targets) {
+ chip_pw_fuzz_target("fuzz-payload-decoder-pw") {
+ test_source = [ "FuzzPayloadDecoderPW.cpp" ]
+ public_deps = [
+ "${chip_root}/src/controller/data_model:cluster-tlv-metadata",
+ "${chip_root}/src/lib/core",
+ "${chip_root}/src/lib/format:flat-tree",
+ "${chip_root}/src/lib/format:protocol-decoder",
+ "${chip_root}/src/lib/format:protocol-tlv-metadata",
+ "${chip_root}/src/lib/support",
+ "${chip_root}/src/platform/logging:stdio",
+ ]
+ }
+}
diff --git a/src/lib/format/tests/FuzzPayloadDecoderPW.cpp b/src/lib/format/tests/FuzzPayloadDecoderPW.cpp
new file mode 100644
index 0000000..52b51b3
--- /dev/null
+++ b/src/lib/format/tests/FuzzPayloadDecoderPW.cpp
@@ -0,0 +1,73 @@
+/*
+ *
+ * Copyright (c) 2020-2021 Project CHIP Authors
+ * All rights reserved.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+#include <cstddef>
+#include <cstdint>
+#include <lib/format/protocol_decoder.h>
+#include <lib/support/StringBuilder.h>
+
+#include <tlv/meta/clusters_meta.h>
+#include <tlv/meta/protocols_meta.h>
+
+#include <pw_fuzzer/fuzztest.h>
+#include <pw_unit_test/framework.h>
+
+namespace {
+
+using namespace chip::Decoders;
+using namespace chip::FlatTree;
+using namespace chip::TLV;
+using namespace chip::TLVMeta;
+using namespace fuzztest;
+
+// The Property Function; The FUZZ_TEST macro will call this function, with the fuzzed input domains
+void RunDecodeFuzz(const std::vector<std::uint8_t> & bytes, chip::Protocols::Id mProtocol, uint8_t mMessageType)
+{
+
+ PayloadDecoderInitParams params;
+ params.SetProtocolDecodeTree(chip::TLVMeta::protocols_meta).SetClusterDecodeTree(chip::TLVMeta::clusters_meta);
+
+ // Fuzzing with different Protocols
+ params.SetProtocol(mProtocol);
+
+ // Fuzzing with different MessageTypes
+ params.SetMessageType(mMessageType);
+ chip::Decoders::PayloadDecoder<64, 128> decoder(params);
+
+ chip::ByteSpan payload(bytes.data(), bytes.size());
+
+ decoder.StartDecoding(payload);
+
+ PayloadEntry entry;
+ while (decoder.Next(entry))
+ {
+ // Nothing to do ...
+ }
+}
+
+// This function allows us to fuzz using one of four protocols; by using FuzzTests's `ElementOf` API, we define an
+// input domain by explicitly enumerating the set of values in it More Info:
+// https://github.com/google/fuzztest/blob/main/doc/domains-reference.md#elementof-domains-element-of
+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>());
+
+} // namespace
diff --git a/src/setup_payload/tests/BUILD.gn b/src/setup_payload/tests/BUILD.gn
index 75cdb8a..fc7bd6f 100644
--- a/src/setup_payload/tests/BUILD.gn
+++ b/src/setup_payload/tests/BUILD.gn
@@ -56,3 +56,13 @@
]
}
}
+
+if (pw_enable_fuzz_test_targets) {
+ chip_pw_fuzz_target("fuzz-setup-payload-base38-pw") {
+ test_source = [ "FuzzBase38PW.cpp" ]
+ public_deps = [
+ "${chip_root}/src/platform/logging:stdio",
+ "${chip_root}/src/setup_payload",
+ ]
+ }
+}
diff --git a/src/setup_payload/tests/FuzzBase38PW.cpp b/src/setup_payload/tests/FuzzBase38PW.cpp
new file mode 100644
index 0000000..b39db09
--- /dev/null
+++ b/src/setup_payload/tests/FuzzBase38PW.cpp
@@ -0,0 +1,77 @@
+#include <cstddef>
+#include <cstdint>
+#include <iostream>
+
+#include <pw_fuzzer/fuzztest.h>
+#include <pw_unit_test/framework.h>
+
+#include "setup_payload/QRCodeSetupPayloadParser.h"
+#include <setup_payload/Base38Decode.h>
+#include <setup_payload/Base38Encode.h>
+
+namespace {
+
+using namespace fuzztest;
+using namespace chip;
+
+// The property Function
+void Base38DecodeFuzz(const std::vector<uint8_t> & bytes)
+{
+ std::string base38EncodedString(reinterpret_cast<const char *>(bytes.data()), bytes.size());
+ std::vector<uint8_t> decodedData;
+
+ // Ignoring return value, because in general the data is garbage and won't decode properly.
+ // We're just testing that the decoder does not crash on the fuzzer-generated inputs.
+ chip::base38Decode(base38EncodedString, decodedData);
+}
+
+// The invocation of the FuzzTest
+FUZZ_TEST(Base38Decoder, Base38DecodeFuzz).WithDomains(Arbitrary<std::vector<uint8_t>>());
+
+/* The property function for a base38 roundtrip Fuzzer.
+ * It starts by encoding the fuzzing value passed
+ * into Base38. The encoded value will then be decoded.
+ *
+ * The fuzzer verifies that the decoded value is the same
+ * as the one in input.*/
+void Base38RoundTripFuzz(const std::vector<uint8_t> & bytes)
+{
+
+ size_t outputSizeNeeded = base38EncodedLength(bytes.size());
+ const size_t kMaxOutputSize = 512;
+
+ ASSERT_LT(outputSizeNeeded, kMaxOutputSize);
+
+ ByteSpan span(bytes.data(), bytes.size());
+
+ char encodedBuf[kMaxOutputSize];
+ MutableCharSpan encodedSpan(encodedBuf);
+ CHIP_ERROR encodingError = base38Encode(span, encodedSpan);
+ ASSERT_EQ(encodingError, CHIP_NO_ERROR);
+
+ std::string base38EncodedString(encodedSpan.data(), encodedSpan.size());
+
+ std::vector<uint8_t> decodedData;
+ CHIP_ERROR decodingError = base38Decode(base38EncodedString, decodedData);
+
+ ASSERT_EQ(decodingError, CHIP_NO_ERROR);
+
+ // Make sure that decoded data is equal to the original fuzzed input; the bytes vector
+ ASSERT_EQ(decodedData, bytes);
+}
+
+// Max size of the vector is defined as 306 since that will give an outputSizeNeeded of 511 which is less than the required
+// kMaxOutputSize
+FUZZ_TEST(Base38Decoder, Base38RoundTripFuzz).WithDomains(Arbitrary<std::vector<uint8_t>>().WithMaxSize(306));
+
+void FuzzQRCodeSetupPayloadParser(const std::string & s)
+{
+ chip::Platform::MemoryInit();
+
+ SetupPayload payload;
+ QRCodeSetupPayloadParser(s).populatePayload(payload);
+}
+
+FUZZ_TEST(Base38Decoder, FuzzQRCodeSetupPayloadParser).WithDomains(Arbitrary<std::string>());
+
+} // namespace
diff --git a/third_party/abseil-cpp/src b/third_party/abseil-cpp/src
new file mode 160000
index 0000000..3ab97e7
--- /dev/null
+++ b/third_party/abseil-cpp/src
@@ -0,0 +1 @@
+Subproject commit 3ab97e7212bff931a201c794fa1331960158bbfa
diff --git a/third_party/fuzztest b/third_party/fuzztest
new file mode 160000
index 0000000..6eb010c
--- /dev/null
+++ b/third_party/fuzztest
@@ -0,0 +1 @@
+Subproject commit 6eb010c7223a6aa609b94d49bfc06ac88f922961
diff --git a/third_party/googletest b/third_party/googletest
new file mode 160000
index 0000000..1d17ea1
--- /dev/null
+++ b/third_party/googletest
@@ -0,0 +1 @@
+Subproject commit 1d17ea141d2c11b8917d2c7d029f1c4e2b9769b2
diff --git a/third_party/re2/src b/third_party/re2/src
new file mode 160000
index 0000000..85dd7ad
--- /dev/null
+++ b/third_party/re2/src
@@ -0,0 +1 @@
+Subproject commit 85dd7ad833a73095ecf3e3baea608ba051bbe2c7