diff --git a/.github/.wordlist.txt b/.github/.wordlist.txt index 153345827ee086..4444d8299d26cb 100644 --- a/.github/.wordlist.txt +++ b/.github/.wordlist.txt @@ -37,6 +37,7 @@ AdvSendAdvert AE aef AES +AFL AIDL algs alloc @@ -570,6 +571,7 @@ fsync ftd fullclean fuzzer +fuzztest FW gbl gcloud @@ -1008,6 +1010,7 @@ optionOverride optionsMask optionsOverride orgs +OSS OTA OTADownloader otaDownloadPath diff --git a/.gitmodules b/.gitmodules index 78a6cabb940d05..40801ec9742517 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 c8e41976599173..4efa25007aa523 100644 --- a/BUILD.gn +++ b/BUILD.gn @@ -62,6 +62,18 @@ if (current_toolchain != "${dir_pw_toolchain}/default:default") { } } + 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 @@ if (current_toolchain != "${dir_pw_toolchain}/default:default") { 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 784ed60273b02a..98def1dab0463d 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 @@ template("chip_fuzz_target") { } } } + +# 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 00000000000000..385e57cc81be39 --- /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 7cd660227ed165..941b5328887e31 100644 --- a/docs/guides/BUILDING.md +++ b/docs/guides/BUILDING.md @@ -382,6 +382,26 @@ They pick up environment variables such as `$CFLAGS`, `$CXXFLAGS` and 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 00000000000000..b7faeae463a10e --- /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>()); +``` + +- 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 & 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()`: + +```cpp +FUZZ_TEST(Base38Decoder, FuzzQRCodeSetupPayloadParser).WithDomains(Arbitrary()); +``` + +- 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>().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>(), AnyProtocolID(), Arbitrary()); +``` + +- 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 77e55b8874946c..4e657778d964ee 100755 --- a/scripts/build/build/targets.py +++ b/scripts/build/build/targets.py @@ -77,6 +77,8 @@ def BuildHostFakeTarget(): "-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 @@ def BuildHostTarget(): "-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 c51a8ee88cb0ee..b69421a782e948 100644 --- a/scripts/build/builders/host.py +++ b/scripts/build/builders/host.py @@ -42,6 +42,7 @@ class HostFuzzingType(Enum): NONE = auto() LIB_FUZZER = auto() OSS_FUZZ = auto() + PW_FUZZTEST = auto() class HostApp(Enum): @@ -379,6 +380,8 @@ def __init__(self, root, runner, app: HostApp, board=HostBoard.NATIVE, 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 @@ def __init__(self, root, runner, app: HostApp, board=HostBoard.NATIVE, 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 85246d9a88a9d1..194f4cbebc4cb6 100644 --- a/scripts/build/testdata/all_targets_linux_x64.txt +++ b/scripts/build/testdata/all_targets_linux_x64.txt @@ -8,8 +8,8 @@ cyw30739-{cyw30739b2_p5_evk_01,cyw30739b2_p5_evk_02,cyw30739b2_p5_evk_03,cyw9307 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 393b246ef20ee3..46e1f724349102 100644 --- a/src/credentials/tests/BUILD.gn +++ b/src/credentials/tests/BUILD.gn @@ -84,3 +84,13 @@ if (enable_fuzz_test_targets) { ] } } + +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 00000000000000..bb929b392500d7 --- /dev/null +++ b/src/credentials/tests/FuzzChipCertPW.cpp @@ -0,0 +1,95 @@ +#include +#include + +#include +#include + +#include "credentials/CHIPCert.h" + +namespace { + +using namespace chip; +using namespace chip::Credentials; + +using namespace fuzztest; + +void ChipCertFuzzer(const std::vector & 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>()); + +// The Property function for DecodeChipCertFuzzer, The FUZZ_TEST Macro will call this function. +void DecodeChipCertFuzzer(const std::vector & bytes, BitFlags 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 NullDecodeFlag; + constexpr BitFlags GenTBSHashFlag(CertDecodeFlags::kGenerateTBSHash); + constexpr BitFlags TrustAnchorFlag(CertDecodeFlags::kIsTrustAnchor); + + return ElementOf({ NullDecodeFlag, GenTBSHashFlag, TrustAnchorFlag }); +} + +FUZZ_TEST(FuzzChipCert, DecodeChipCertFuzzer).WithDomains(Arbitrary>(), AnyCertDecodeFlag()); +} // namespace diff --git a/src/lib/core/tests/BUILD.gn b/src/lib/core/tests/BUILD.gn index eb17707dec1755..264de2c8aafe6b 100644 --- a/src/lib/core/tests/BUILD.gn +++ b/src/lib/core/tests/BUILD.gn @@ -59,3 +59,13 @@ if (enable_fuzz_test_targets) { ] } } + +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 00000000000000..4296cac3cffb02 --- /dev/null +++ b/src/lib/core/tests/FuzzTlvReaderPW.cpp @@ -0,0 +1,35 @@ + +#include +#include + +#include +#include + +#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 & 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>()); + +} // namespace diff --git a/src/lib/dnssd/minimal_mdns/tests/BUILD.gn b/src/lib/dnssd/minimal_mdns/tests/BUILD.gn index 47e83650d41acc..457c11ee688b94 100644 --- a/src/lib/dnssd/minimal_mdns/tests/BUILD.gn +++ b/src/lib/dnssd/minimal_mdns/tests/BUILD.gn @@ -53,3 +53,13 @@ if (enable_fuzz_test_targets) { ] } } + +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 00000000000000..aec550b26e026a --- /dev/null +++ b/src/lib/dnssd/minimal_mdns/tests/FuzzPacketParsingPW.cpp @@ -0,0 +1,132 @@ +#include +#include + +#include +#include + +#include +#include + +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 & bytes) +{ + BytesRange packet(bytes.data(), bytes.data() + bytes.size()); + FuzzDelegate delegate(packet); + + mdns::Minimal::ParsePacket(packet, &delegate); +} + +FUZZ_TEST(MinimalmDNS, PacketParserFuzz).WithDomains(Arbitrary>()); + +class TxtRecordAccumulator : public TxtRecordDelegate +{ +public: + using DataType = vector>; + + 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(range.Start()), reinterpret_cast(range.End())); + } +}; + +// The Property Function +void TxtResponderFuzz(const std::vector & 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 prefixedRecord{ static_cast(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(*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>().WithMaxSize(254)); + +} // namespace diff --git a/src/lib/format/tests/BUILD.gn b/src/lib/format/tests/BUILD.gn index 840a2eede60c78..1ce417ea91cdc3 100644 --- a/src/lib/format/tests/BUILD.gn +++ b/src/lib/format/tests/BUILD.gn @@ -57,3 +57,18 @@ if (enable_fuzz_test_targets) { ] } } + +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 00000000000000..52b51b308d0c2a --- /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 +#include +#include +#include + +#include +#include + +#include +#include + +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 & 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>(), AnyProtocolID(), Arbitrary()); + +} // namespace diff --git a/src/setup_payload/tests/BUILD.gn b/src/setup_payload/tests/BUILD.gn index 75cdb8a71b0ad3..fc7bd6fe13c83c 100644 --- a/src/setup_payload/tests/BUILD.gn +++ b/src/setup_payload/tests/BUILD.gn @@ -56,3 +56,13 @@ if (enable_fuzz_test_targets) { ] } } + +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 00000000000000..b39db0905dfea0 --- /dev/null +++ b/src/setup_payload/tests/FuzzBase38PW.cpp @@ -0,0 +1,77 @@ +#include +#include +#include + +#include +#include + +#include "setup_payload/QRCodeSetupPayloadParser.h" +#include +#include + +namespace { + +using namespace fuzztest; +using namespace chip; + +// The property Function +void Base38DecodeFuzz(const std::vector & bytes) +{ + std::string base38EncodedString(reinterpret_cast(bytes.data()), bytes.size()); + std::vector 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>()); + +/* 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 & 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 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>().WithMaxSize(306)); + +void FuzzQRCodeSetupPayloadParser(const std::string & s) +{ + chip::Platform::MemoryInit(); + + SetupPayload payload; + QRCodeSetupPayloadParser(s).populatePayload(payload); +} + +FUZZ_TEST(Base38Decoder, FuzzQRCodeSetupPayloadParser).WithDomains(Arbitrary()); + +} // namespace diff --git a/third_party/abseil-cpp/src b/third_party/abseil-cpp/src new file mode 160000 index 00000000000000..3ab97e7212bff9 --- /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 00000000000000..6eb010c7223a6a --- /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 00000000000000..1d17ea141d2c11 --- /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 00000000000000..85dd7ad833a730 --- /dev/null +++ b/third_party/re2/src @@ -0,0 +1 @@ +Subproject commit 85dd7ad833a73095ecf3e3baea608ba051bbe2c7