From d9b77e908fe1836e1f8b9f6f8c9819c43e9f3c0c Mon Sep 17 00:00:00 2001 From: Boris Zbarsky Date: Thu, 20 Jan 2022 17:56:39 -0500 Subject: [PATCH] Add workflow for doing fuzzing builds. (#13670) * Add workflow for doing fuzzing builds. Summary of changes: 1) examples/all-clusters-app/all-clusters-common/BUILD.gn: Makes all-clusters-app compile when compiled on its own, without any client apps (like chip-tool) as part of the same build. It used to be sneaking in a use_default_client_callbacks from someone else's configuration, as best I can tell. 2) build/config/compiler/BUILD.gn: Add a define in the fuzzer config so we can compile slightly different main() code. 3) examples/all-clusters-app/linux/main.cpp: Add a fuzzer hook that tries to process the fuzzer-provided data as a message. 4) scripts/build: Add support for a "libfuzzer" variant. * Addressing review comments. * Add clean shutdown when the fuzzer decides to stop. --- .github/workflows/fuzzing-build.yaml | 131 ++++++++++++++++++ build/config/compiler/BUILD.gn | 3 - build/config/compiler/compiler.gni | 3 + .../all-clusters-common/BUILD.gn | 1 + examples/all-clusters-app/linux/BUILD.gn | 43 +++++- .../all-clusters-app/linux/fuzzing-main.cpp | 73 ++++++++++ .../all-clusters-app/linux/main-common.cpp | 118 ++++++++++++++++ examples/all-clusters-app/linux/main.cpp | 95 ------------- scripts/build/build/targets.py | 1 + scripts/build/builders/host.py | 8 +- 10 files changed, 373 insertions(+), 103 deletions(-) create mode 100644 .github/workflows/fuzzing-build.yaml create mode 100644 examples/all-clusters-app/linux/fuzzing-main.cpp create mode 100644 examples/all-clusters-app/linux/main-common.cpp diff --git a/.github/workflows/fuzzing-build.yaml b/.github/workflows/fuzzing-build.yaml new file mode 100644 index 00000000000000..ea96c4d94413e1 --- /dev/null +++ b/.github/workflows/fuzzing-build.yaml @@ -0,0 +1,131 @@ +# Copyright (c) 2021 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. + +name: Fuzzing Builds + +on: + # For now, only manual triggers. + workflow_dispatch: + +concurrency: + group: ${{ github.ref }}-${{ github.workflow }}-${{ (github.event_name == 'pull_request' && github.event.number) || (github.event_name == 'workflow_dispatch' && github.run_number) || github.sha }} + cancel-in-progress: true + +jobs: + build_linux_fuzzing: + name: Build on Linux + timeout-minutes: 90 + + runs-on: ubuntu-latest + if: github.actor != 'restyled-io[bot]' + + container: + image: connectedhomeip/chip-build:0.5.48 + volumes: + - "/tmp/log_output:/tmp/test_logs" + + steps: + - name: Checkout + uses: actions/checkout@v2 + with: + submodules: true + - run: apt-get update + - run: apt-get install --fix-missing llvm-10 clang-10 + - name: + Try to ensure the objdir-clone dir exists + run: | + mkdir objdir-clone || true + - name: Bootstrap + timeout-minutes: 10 + run: scripts/build/gn_bootstrap.sh + - name: Uploading bootstrap logs + uses: actions/upload-artifact@v2 + if: ${{ always() }} && ${{ !env.ACT }} + with: + name: bootstrap-logs + path: | + .environment/gn_out/.ninja_log + .environment/pigweed-venv/*.log + - name: Build all-clusters-app + timeout-minutes: 20 + run: | + ./scripts/run_in_build_env.sh \ + "./scripts/build/build_examples.py \ + --target linux-x64-all-clusters-no-ble-libfuzzer-test-group \ + build \ + --copy-artifacts-to objdir-clone \ + " + - name: Uploading binaries + uses: actions/upload-artifact@v2 + if: ${{ !env.ACT }} + with: + name: + objdir-linux + path: objdir-clone/ + # objdirs are big; don't hold on to them too long. + retention-days: 5 + + build_darwin_fuzzing: + name: Build on Darwin + timeout-minutes: 90 + runs-on: macos-latest + if: github.actor != 'restyled-io[bot]' + + steps: + - name: Checkout + uses: actions/checkout@v2 + with: + submodules: true + - name: Setup Environment + run: brew install openssl pkg-config llvm + - name: Try to ensure the objdir-clone dir exists + run: | + mkdir objdir-clone || true + - name: Fix pkgconfig link + working-directory: /usr/local/lib/pkgconfig + run: | + pwd + ls -la /usr/local/Cellar/ + ls -la /usr/local/Cellar/openssl@1.1 + OPEN_SSL_VERSION=`ls -la /usr/local/Cellar/openssl@1.1 | cat | tail -n1 | awk '{print $NF}'` + ln -s /usr/local/Cellar/openssl@1.1/$OPEN_SSL_VERSION/lib/pkgconfig/* . + - name: Bootstrap + timeout-minutes: 25 + run: scripts/build/gn_bootstrap.sh + - name: Uploading bootstrap logs + uses: actions/upload-artifact@v2 + if: ${{ always() }} && ${{ !env.ACT }} + with: + name: bootstrap-logs + path: | + .environment/gn_out/.ninja_log + .environment/pigweed-venv/*.log + - name: Build all-clusters-app + timeout-minutes: 20 + run: | + ./scripts/run_in_build_env.sh \ + "./scripts/build/build_examples.py \ + --target darwin-x64-all-clusters-no-ble-asan-libfuzzer \ + build \ + --copy-artifacts-to objdir-clone \ + " + - name: Uploading binaries + uses: actions/upload-artifact@v2 + if: ${{ !env.ACT }} + with: + name: + crash-darwin + path: objdir-clone/ + # objdirs are big; don't hold on to them too long. + retention-days: 5 diff --git a/build/config/compiler/BUILD.gn b/build/config/compiler/BUILD.gn index 9df167b4f83234..f08497920fa488 100644 --- a/build/config/compiler/BUILD.gn +++ b/build/config/compiler/BUILD.gn @@ -307,9 +307,6 @@ declare_args() { # enable undefined behavior sanitizer is_ubsan = false - # enable libfuzzer - is_libfuzzer = false - # Exit on sanitize error. Generally standard libraries may get errors # so not stopping on the first error is often useful is_sanitize_fatal = true diff --git a/build/config/compiler/compiler.gni b/build/config/compiler/compiler.gni index 00bb1317576a26..f5b6414eca8ee4 100644 --- a/build/config/compiler/compiler.gni +++ b/build/config/compiler/compiler.gni @@ -41,4 +41,7 @@ declare_args() { # C++ standard level (value for -std flag). cpp_standard = "gnu++14" + + # enable libfuzzer + is_libfuzzer = false } diff --git a/examples/all-clusters-app/all-clusters-common/BUILD.gn b/examples/all-clusters-app/all-clusters-common/BUILD.gn index 0210fcedb2c646..90751a8106cea9 100644 --- a/examples/all-clusters-app/all-clusters-common/BUILD.gn +++ b/examples/all-clusters-app/all-clusters-common/BUILD.gn @@ -22,4 +22,5 @@ chip_data_model("all-clusters-common") { zap_pregenerated_dir = "${chip_root}/zzz_generated/all-clusters-app/zap-generated" is_server = true + use_default_client_callbacks = true } diff --git a/examples/all-clusters-app/linux/BUILD.gn b/examples/all-clusters-app/linux/BUILD.gn index 563c101cb91e7b..36d3d0aa3c10a9 100644 --- a/examples/all-clusters-app/linux/BUILD.gn +++ b/examples/all-clusters-app/linux/BUILD.gn @@ -14,17 +14,18 @@ import("//build_overrides/build.gni") import("//build_overrides/chip.gni") +import("${build_root}/config/compiler/compiler.gni") import("${chip_root}/src/lib/lib.gni") -executable("chip-all-clusters-app") { +source_set("chip-all-clusters-common") { sources = [ "${chip_root}/examples/all-clusters-app/all-clusters-common/src/binding-handler.cpp", "${chip_root}/examples/all-clusters-app/all-clusters-common/src/bridged-actions-stub.cpp", "${chip_root}/examples/all-clusters-app/all-clusters-common/src/static-supported-modes-manager.cpp", "include/tv-callbacks.cpp", "include/tv-callbacks.h", - "main.cpp", + "main-common.cpp", ] deps = [ @@ -41,10 +42,44 @@ executable("chip-all-clusters-app") { if (chip_build_libshell) { defines = [ "ENABLE_CHIP_SHELL" ] } +} + +if (is_libfuzzer) { + executable("chip-all-clusters-app-fuzzing") { + sources = [ "fuzzing-main.cpp" ] + + deps = [ + ":chip-all-clusters-common", + "${chip_root}/examples/platform/linux:app-main", + ] + + cflags = [ "-Wconversion" ] + + output_dir = root_out_dir + } +} else { + executable("chip-all-clusters-app") { + sources = [ "main.cpp" ] - output_dir = root_out_dir + deps = [ + ":chip-all-clusters-common", + "${chip_root}/examples/platform/linux:app-main", + ] + + cflags = [ "-Wconversion" ] + + include_dirs = + [ "${chip_root}/examples/all-clusters-app/all-clusters-common/include" ] + + output_dir = root_out_dir + } } group("linux") { - deps = [ ":chip-all-clusters-app" ] + deps = [] + if (is_libfuzzer) { + deps += [ ":chip-all-clusters-app-fuzzing" ] + } else { + deps += [ ":chip-all-clusters-app" ] + } } diff --git a/examples/all-clusters-app/linux/fuzzing-main.cpp b/examples/all-clusters-app/linux/fuzzing-main.cpp new file mode 100644 index 00000000000000..f7fef1bb214fb1 --- /dev/null +++ b/examples/all-clusters-app/linux/fuzzing-main.cpp @@ -0,0 +1,73 @@ +/* + * Copyright (c) 2022 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 "AppMain.h" +#include + +using namespace chip; +using namespace chip::DeviceLayer; + +void CleanShutdown() +{ + Server::GetInstance().Shutdown(); + PlatformMgr().Shutdown(); + // TODO: We don't Platform::MemoryShutdown because ~CASESessionManager calls + // Dnssd::ResolverProxy::Shutdown, which starts doing Platform::Delete. + // Platform::MemoryShutdown(); +} + +extern "C" int LLVMFuzzerTestOneInput(const uint8_t * aData, size_t aSize) +{ + static bool matterStackInitialized = false; + if (!matterStackInitialized) + { + // Might be simpler to do ChipLinuxAppInit() with argc == 0, argv set to + // just a fake executable name? + VerifyOrDie(Platform::MemoryInit() == CHIP_NO_ERROR); + VerifyOrDie(PlatformMgr().InitChipStack() == CHIP_NO_ERROR); + + // ChipLinuxAppMainLoop blocks, and we don't want that here. + VerifyOrDie(Server::GetInstance().Init() == CHIP_NO_ERROR); + + ApplicationInit(); + + // We don't start the event loop task, because we don't plan to deliver + // data on a separate thread. + + matterStackInitialized = true; + + // The fuzzer does not have a way to tell us when it's done, so just + // shut down things on exit. + atexit(CleanShutdown); + } + + // For now, just dump the data as a UDP payload into the session manager. + // But maybe we should try to separately extract a PeerAddress and data from + // the incoming data? + Transport::PeerAddress peerAddr; + System::PacketBufferHandle buf = + System::PacketBufferHandle::NewWithData(aData, aSize, /* aAdditionalSize = */ 0, /* aReservedSize = */ 0); + + // Ignoring the return value from OnMessageReceived, because we might be + // passing it all sorts of garbage that will cause it to fail. + Server::GetInstance().GetSecureSessionManager().OnMessageReceived(peerAddr, std::move(buf)); + + // Now process pending events until our sentinel is reached. + PlatformMgr().ScheduleWork([](intptr_t) { PlatformMgr().StopEventLoopTask(); }); + PlatformMgr().RunEventLoop(); + return 0; +} diff --git a/examples/all-clusters-app/linux/main-common.cpp b/examples/all-clusters-app/linux/main-common.cpp new file mode 100644 index 00000000000000..9497d1d0f5f072 --- /dev/null +++ b/examples/all-clusters-app/linux/main-common.cpp @@ -0,0 +1,118 @@ +/* + * + * Copyright (c) 2020 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/tv-callbacks.h" +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +using namespace chip; +using namespace chip::app; +using namespace chip::DeviceLayer; + +namespace { +static LowPowerManager lowPowerManager; +} // namespace + +bool emberAfBasicClusterMfgSpecificPingCallback(chip::app::CommandHandler * commandObj) +{ + emberAfSendDefaultResponse(emberAfCurrentCommand(), EMBER_ZCL_STATUS_SUCCESS); + return true; +} + +void OnIdentifyStart(::Identify *) +{ + ChipLogProgress(Zcl, "OnIdentifyStart"); +} + +void OnIdentifyStop(::Identify *) +{ + ChipLogProgress(Zcl, "OnIdentifyStop"); +} + +void OnTriggerEffect(::Identify * identify) +{ + switch (identify->mCurrentEffectIdentifier) + { + case EMBER_ZCL_IDENTIFY_EFFECT_IDENTIFIER_BLINK: + ChipLogProgress(Zcl, "EMBER_ZCL_IDENTIFY_EFFECT_IDENTIFIER_BLINK"); + break; + case EMBER_ZCL_IDENTIFY_EFFECT_IDENTIFIER_BREATHE: + ChipLogProgress(Zcl, "EMBER_ZCL_IDENTIFY_EFFECT_IDENTIFIER_BREATHE"); + break; + case EMBER_ZCL_IDENTIFY_EFFECT_IDENTIFIER_OKAY: + ChipLogProgress(Zcl, "EMBER_ZCL_IDENTIFY_EFFECT_IDENTIFIER_OKAY"); + break; + case EMBER_ZCL_IDENTIFY_EFFECT_IDENTIFIER_CHANNEL_CHANGE: + ChipLogProgress(Zcl, "EMBER_ZCL_IDENTIFY_EFFECT_IDENTIFIER_CHANNEL_CHANGE"); + break; + default: + ChipLogProgress(Zcl, "No identifier effect"); + return; + } +} + +static Identify gIdentify0 = { + chip::EndpointId{ 0 }, OnIdentifyStart, OnIdentifyStop, EMBER_ZCL_IDENTIFY_IDENTIFY_TYPE_VISIBLE_LED, OnTriggerEffect, +}; + +static Identify gIdentify1 = { + chip::EndpointId{ 1 }, OnIdentifyStart, OnIdentifyStop, EMBER_ZCL_IDENTIFY_IDENTIFY_TYPE_VISIBLE_LED, OnTriggerEffect, +}; + +// Network commissioning +namespace { +// This file is being used by platforms other than Linux, so we need this check to disable related features since we only +// implemented them on linux. +#if CHIP_DEVICE_LAYER_TARGET_LINUX +#if CHIP_DEVICE_CONFIG_ENABLE_THREAD +NetworkCommissioning::LinuxThreadDriver sLinuxThreadDriver; +Clusters::NetworkCommissioning::Instance sThreadNetworkCommissioningInstance(0 /* Endpoint Id */, &sLinuxThreadDriver); +#endif +#if CHIP_DEVICE_CONFIG_ENABLE_WPA +NetworkCommissioning::LinuxWiFiDriver sLinuxWiFiDriver; +Clusters::NetworkCommissioning::Instance sWiFiNetworkCommissioningInstance(1 /* Endpoint Id */, &sLinuxWiFiDriver); +#endif +#endif +} // namespace + +void ApplicationInit() +{ +#if CHIP_DEVICE_LAYER_TARGET_LINUX && defined(ZCL_USING_LEVEL_CONTROL_CLUSTER_SERVER) +#if CHIP_DEVICE_CONFIG_ENABLE_THREAD + sThreadNetworkCommissioningInstance.Init(); +#endif +#if CHIP_DEVICE_CONFIG_ENABLE_WPA + sWiFiNetworkCommissioningInstance.Init(); +#endif +#endif +} + +void emberAfLowPowerClusterInitCallback(EndpointId endpoint) +{ + ChipLogProgress(Zcl, "TV Linux App: LowPower::SetDefaultDelegate"); + chip::app::Clusters::LowPower::SetDefaultDelegate(endpoint, &lowPowerManager); +} diff --git a/examples/all-clusters-app/linux/main.cpp b/examples/all-clusters-app/linux/main.cpp index 2c02d212ac84f9..3ae4959c2a64fd 100644 --- a/examples/all-clusters-app/linux/main.cpp +++ b/examples/all-clusters-app/linux/main.cpp @@ -16,98 +16,9 @@ * limitations under the License. */ -#include "include/tv-callbacks.h" -#include -#include -#include -#include -#include - #include "AppMain.h" #include "binding-handler.h" -using namespace chip; -using namespace chip::app; -using namespace chip::DeviceLayer; - -namespace { -static LowPowerManager lowPowerManager; -} // namespace - -bool emberAfBasicClusterMfgSpecificPingCallback(chip::app::CommandHandler * commandObj) -{ - emberAfSendDefaultResponse(emberAfCurrentCommand(), EMBER_ZCL_STATUS_SUCCESS); - return true; -} - -void OnIdentifyStart(::Identify *) -{ - ChipLogProgress(Zcl, "OnIdentifyStart"); -} - -void OnIdentifyStop(::Identify *) -{ - ChipLogProgress(Zcl, "OnIdentifyStop"); -} - -void OnTriggerEffect(::Identify * identify) -{ - switch (identify->mCurrentEffectIdentifier) - { - case EMBER_ZCL_IDENTIFY_EFFECT_IDENTIFIER_BLINK: - ChipLogProgress(Zcl, "EMBER_ZCL_IDENTIFY_EFFECT_IDENTIFIER_BLINK"); - break; - case EMBER_ZCL_IDENTIFY_EFFECT_IDENTIFIER_BREATHE: - ChipLogProgress(Zcl, "EMBER_ZCL_IDENTIFY_EFFECT_IDENTIFIER_BREATHE"); - break; - case EMBER_ZCL_IDENTIFY_EFFECT_IDENTIFIER_OKAY: - ChipLogProgress(Zcl, "EMBER_ZCL_IDENTIFY_EFFECT_IDENTIFIER_OKAY"); - break; - case EMBER_ZCL_IDENTIFY_EFFECT_IDENTIFIER_CHANNEL_CHANGE: - ChipLogProgress(Zcl, "EMBER_ZCL_IDENTIFY_EFFECT_IDENTIFIER_CHANNEL_CHANGE"); - break; - default: - ChipLogProgress(Zcl, "No identifier effect"); - return; - } -} - -static Identify gIdentify0 = { - chip::EndpointId{ 0 }, OnIdentifyStart, OnIdentifyStop, EMBER_ZCL_IDENTIFY_IDENTIFY_TYPE_VISIBLE_LED, OnTriggerEffect, -}; - -static Identify gIdentify1 = { - chip::EndpointId{ 1 }, OnIdentifyStart, OnIdentifyStop, EMBER_ZCL_IDENTIFY_IDENTIFY_TYPE_VISIBLE_LED, OnTriggerEffect, -}; - -// Network commissioning -namespace { -// This file is being used by platforms other than Linux, so we need this check to disable related features since we only -// implemented them on linux. -#if CHIP_DEVICE_LAYER_TARGET_LINUX -#if CHIP_DEVICE_CONFIG_ENABLE_THREAD -NetworkCommissioning::LinuxThreadDriver sLinuxThreadDriver; -Clusters::NetworkCommissioning::Instance sThreadNetworkCommissioningInstance(0 /* Endpoint Id */, &sLinuxThreadDriver); -#endif -#if CHIP_DEVICE_CONFIG_ENABLE_WPA -NetworkCommissioning::LinuxWiFiDriver sLinuxWiFiDriver; -Clusters::NetworkCommissioning::Instance sWiFiNetworkCommissioningInstance(1 /* Endpoint Id */, &sLinuxWiFiDriver); -#endif -#endif -} // namespace - -void ApplicationInit() -{ -#if CHIP_DEVICE_LAYER_TARGET_LINUX && defined(ZCL_USING_LEVEL_CONTROL_CLUSTER_SERVER) -#if CHIP_DEVICE_CONFIG_ENABLE_THREAD - sThreadNetworkCommissioningInstance.Init(); -#endif -#if CHIP_DEVICE_CONFIG_ENABLE_WPA - sWiFiNetworkCommissioningInstance.Init(); -#endif -#endif -} - int main(int argc, char * argv[]) { VerifyOrDie(ChipLinuxAppInit(argc, argv) == 0); @@ -115,9 +26,3 @@ int main(int argc, char * argv[]) ChipLinuxAppMainLoop(); return 0; } - -void emberAfLowPowerClusterInitCallback(EndpointId endpoint) -{ - ChipLogProgress(Zcl, "TV Linux App: LowPower::SetDefaultDelegate"); - chip::app::Clusters::LowPower::SetDefaultDelegate(endpoint, &lowPowerManager); -} diff --git a/scripts/build/build/targets.py b/scripts/build/build/targets.py index 8d38fbb20f8bf4..5e729c86d4fb23 100644 --- a/scripts/build/build/targets.py +++ b/scripts/build/build/targets.py @@ -161,6 +161,7 @@ def HostTargets(): HostBuildVariant(name="no-ble", enable_ble=False), HostBuildVariant(name="tsan", conflicts=['asan'], use_tsan=True), HostBuildVariant(name="asan", conflicts=['tsan'], use_asan=True), + HostBuildVariant(name="libfuzzer", use_libfuzzer=True, use_clang=True), HostBuildVariant(name="test-group", validator=AcceptNameWithSubstrings(['-all-clusters', '-chip-tool']), test_group=True), HostBuildVariant(name="same-event-loop", diff --git a/scripts/build/builders/host.py b/scripts/build/builders/host.py index 047d7a2a959fef..58685b62092f5a 100644 --- a/scripts/build/builders/host.py +++ b/scripts/build/builders/host.py @@ -131,7 +131,7 @@ class HostBuilder(GnBuilder): def __init__(self, root, runner, app: HostApp, board=HostBoard.NATIVE, enable_ipv4=True, enable_ble=True, use_tsan=False, use_asan=False, separate_event_loop=True, - test_group=False): + test_group=False, use_libfuzzer=False, use_clang=False): super(HostBuilder, self).__init__( root=os.path.join(root, 'examples', app.ExamplePath()), runner=runner) @@ -159,6 +159,12 @@ def __init__(self, root, runner, app: HostApp, board=HostBoard.NATIVE, enable_ip self.extra_gn_options.append( 'chip_enable_group_messaging_tests=true') + if use_libfuzzer: + self.extra_gn_options.append('is_libfuzzer=true') + + if use_clang: + self.extra_gn_options.append('is_clang=true') + if app == HostApp.TESTS: self.extra_gn_options.append('chip_build_tests=true') self.build_command = 'check'