From 153f2c39b2cd8f99c48e039a1bd996d238b4c789 Mon Sep 17 00:00:00 2001 From: cktii Date: Mon, 3 Feb 2025 20:21:30 +0400 Subject: [PATCH 1/6] feat: add fuzzing harnesses --- .gitignore | 2 + CMakeLists.txt | 136 ++++++++++++- fuzzing/README.md | 19 ++ fuzzing/bb_fuzzer.cpp | 270 +++++++++++++++++++++++++ fuzzing/bt_fuzzer.cpp | 125 ++++++++++++ fuzzing/corpus/bb_corpus/basic.json | 6 + fuzzing/corpus/bb_corpus/edge.json | 7 + fuzzing/corpus/bb_corpus/multiple.json | 8 + fuzzing/corpus/bb_corpus/nested.json | 11 + fuzzing/corpus/bb_corpus/special.json | 6 + fuzzing/corpus/bt_corpus/corpus1.xml | 7 + fuzzing/corpus/bt_corpus/corpus2.xml | 14 ++ fuzzing/corpus/bt_corpus/corpus3.xml | 8 + fuzzing/corpus/bt_corpus/corpus4.xml | 9 + fuzzing/corpus/bt_corpus/sample1 | 1 + fuzzing/corpus/script_corpus/sample1 | 1 + fuzzing/corpus/script_corpus/sample2 | 1 + fuzzing/corpus/script_corpus/sample3 | 1 + fuzzing/script_fuzzer.cpp | 70 +++++++ 19 files changed, 692 insertions(+), 10 deletions(-) create mode 100644 fuzzing/README.md create mode 100644 fuzzing/bb_fuzzer.cpp create mode 100644 fuzzing/bt_fuzzer.cpp create mode 100644 fuzzing/corpus/bb_corpus/basic.json create mode 100644 fuzzing/corpus/bb_corpus/edge.json create mode 100644 fuzzing/corpus/bb_corpus/multiple.json create mode 100644 fuzzing/corpus/bb_corpus/nested.json create mode 100644 fuzzing/corpus/bb_corpus/special.json create mode 100644 fuzzing/corpus/bt_corpus/corpus1.xml create mode 100644 fuzzing/corpus/bt_corpus/corpus2.xml create mode 100644 fuzzing/corpus/bt_corpus/corpus3.xml create mode 100644 fuzzing/corpus/bt_corpus/corpus4.xml create mode 100644 fuzzing/corpus/bt_corpus/sample1 create mode 100644 fuzzing/corpus/script_corpus/sample1 create mode 100644 fuzzing/corpus/script_corpus/sample2 create mode 100644 fuzzing/corpus/script_corpus/sample3 create mode 100644 fuzzing/script_fuzzer.cpp diff --git a/.gitignore b/.gitignore index cba22ead7..9d5bd4326 100644 --- a/.gitignore +++ b/.gitignore @@ -16,3 +16,5 @@ CMakeSettings.json .pixi CMakeUserPresets.json + +tags diff --git a/CMakeLists.txt b/CMakeLists.txt index 722e102b7..09c4d2d5a 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -2,18 +2,95 @@ cmake_minimum_required(VERSION 3.16.3) # version on Ubuntu Focal project(behaviortree_cpp VERSION 4.6.2 LANGUAGES C CXX) -set(CMAKE_CONFIG_PATH ${CMAKE_MODULE_PATH} "${CMAKE_CURRENT_LIST_DIR}/cmake") +# Build configuration options +option(ENABLE_FUZZING "Enable fuzzing builds" OFF) +option(USE_AFLPLUSPLUS "Use AFL++ instead of libFuzzer" OFF) +option(ENABLE_DEBUG "Enable debug build with full symbols" OFF) + +set(BASE_FLAGS "") + +# Debug build configuration +if(ENABLE_DEBUG) + list(APPEND BASE_FLAGS + -g3 + -ggdb3 + -O0 + -fno-omit-frame-pointer + ) +endif() + +# Fuzzing configuration +if(ENABLE_FUZZING) + if(USE_AFLPLUSPLUS) + list(APPEND BASE_FLAGS -O3) + else() + list(APPEND BASE_FLAGS -O2) + endif() + + if(USE_AFLPLUSPLUS) + set(SANITIZER_FLAGS + -fsanitize=address,undefined + ) + else() + # For libFuzzer, use fuzzer-no-link for the library + set(SANITIZER_FLAGS + -fsanitize=address,undefined,fuzzer-no-link + ) + endif() + + # Apply sanitizer flags to the base library + list(APPEND BASE_FLAGS ${SANITIZER_FLAGS}) + + # Apply base flags globally + add_compile_options(${BASE_FLAGS}) + add_link_options(${BASE_FLAGS}) + + function(apply_fuzzing_flags target) + if(USE_AFLPLUSPLUS) + # AFL++ specific flags + target_compile_options(${target} PRIVATE + ${BASE_FLAGS} + ${SANITIZER_FLAGS} + ) + target_link_options(${target} PRIVATE + ${BASE_FLAGS} + -fsanitize=fuzzer,address,undefined + ) + else() + # libFuzzer specific flags + target_compile_options(${target} PRIVATE + ${BASE_FLAGS} + -fsanitize=fuzzer + ${SANITIZER_FLAGS} + ) + target_link_options(${target} PRIVATE + ${BASE_FLAGS} + -fsanitize=fuzzer + ${SANITIZER_FLAGS} + ) + endif() + endfunction() + + set(BTCPP_EXAMPLES OFF CACHE BOOL "Disable examples during fuzzing" FORCE) + set(BTCPP_BUILD_TOOLS OFF CACHE BOOL "Disable tools during fuzzing" FORCE) + set(BTCPP_UNIT_TESTS OFF CACHE BOOL "Disable tests during fuzzing" FORCE) + set(BTCPP_SHARED_LIBS OFF CACHE BOOL "Build static library for fuzzing" FORCE) +else() + # Apply base flags for non-fuzzing builds + add_compile_options(${BASE_FLAGS}) + add_link_options(${BASE_FLAGS}) +endif() + +set(CMAKE_CONFIG_PATH ${CMAKE_MODULE_PATH} "${CMAKE_CURRENT_LIST_DIR}/cmake") list(APPEND CMAKE_MODULE_PATH "${CMAKE_CONFIG_PATH}") set(BTCPP_LIBRARY ${PROJECT_NAME}) if(NOT CMAKE_BUILD_TYPE AND NOT CMAKE_CONFIGURATION_TYPES) - message(STATUS "Setting build type to 'Release' as none was specified.") - set(CMAKE_BUILD_TYPE "Release" CACHE - STRING "Choose the type of build." FORCE) - # Set the possible values of build type for cmake-gui - set_property(CACHE CMAKE_BUILD_TYPE PROPERTY STRINGS - "Debug" "Release" "MinSizeRel" "RelWithDebInfo") + message(STATUS "Setting build type to 'Release' as none was specified.") + set(CMAKE_BUILD_TYPE "Release" CACHE STRING "Choose the type of build." FORCE) + set_property(CACHE CMAKE_BUILD_TYPE PROPERTY STRINGS + "Debug" "Release" "MinSizeRel" "RelWithDebInfo") endif() if(MSVC) @@ -186,20 +263,59 @@ target_compile_definitions(${BTCPP_LIBRARY} PUBLIC BTCPP_LIBRARY_VERSION="${CMAK target_compile_features(${BTCPP_LIBRARY} PUBLIC cxx_std_17) if(MSVC) - target_compile_options(${BTCPP_LIBRARY} PRIVATE "/source-charset:utf-8") + target_compile_options(${BTCPP_LIBRARY} PRIVATE "/source-charset:utf-8") else() - target_compile_options(${BTCPP_LIBRARY} PRIVATE -Wall -Wextra) + if(ENABLE_DEBUG) + target_compile_options(${BTCPP_LIBRARY} PRIVATE -Wall -Wextra -g3 -ggdb3 -O0 -fno-omit-frame-pointer) + else() + target_compile_options(${BTCPP_LIBRARY} PRIVATE -Wall -Wextra) + endif() endif() add_library(BT::${BTCPP_LIBRARY} ALIAS ${BTCPP_LIBRARY}) + +# Add fuzzing targets +if(ENABLE_FUZZING) + add_executable(bt_fuzzer fuzzing/bt_fuzzer.cpp) + apply_fuzzing_flags(bt_fuzzer) + target_link_libraries(bt_fuzzer PRIVATE ${BTCPP_LIBRARY} ${BTCPP_EXTRA_LIBRARIES}) + + add_executable(script_fuzzer fuzzing/script_fuzzer.cpp) + apply_fuzzing_flags(script_fuzzer) + target_link_libraries(script_fuzzer PRIVATE ${BTCPP_LIBRARY} ${BTCPP_EXTRA_LIBRARIES}) + + add_executable(bb_fuzzer fuzzing/bb_fuzzer.cpp) + apply_fuzzing_flags(bb_fuzzer) + target_link_libraries(bb_fuzzer PRIVATE ${BTCPP_LIBRARY} ${BTCPP_EXTRA_LIBRARIES}) + + foreach(fuzzer bt_fuzzer script_fuzzer bb_fuzzer) + set(CORPUS_DIR ${CMAKE_BINARY_DIR}/corpus/${fuzzer}) + file(MAKE_DIRECTORY ${CORPUS_DIR}) + endforeach() + + file(GLOB BT_CORPUS_FILES "fuzzing/corpus/bt_fuzzer/*") + file(GLOB SCRIPT_CORPUS_FILES "fuzzing/corpus/script_fuzzer/*") + file(GLOB BB_CORPUS_FILES "fuzzing/corpus/bb_fuzzer/*") + + if(BT_CORPUS_FILES) + file(COPY ${BT_CORPUS_FILES} DESTINATION ${CMAKE_BINARY_DIR}/corpus/bt_fuzzer) + endif() + if(SCRIPT_CORPUS_FILES) + file(COPY ${SCRIPT_CORPUS_FILES} DESTINATION ${CMAKE_BINARY_DIR}/corpus/script_fuzzer) + endif() + if(BB_CORPUS_FILES) + file(COPY ${BB_CORPUS_FILES} DESTINATION ${CMAKE_BINARY_DIR}/corpus/bb_fuzzer) + endif() +endif() + ############################################################# message( STATUS "BTCPP_LIB_DESTINATION: ${BTCPP_LIB_DESTINATION} " ) message( STATUS "BTCPP_INCLUDE_DESTINATION: ${BTCPP_INCLUDE_DESTINATION} " ) message( STATUS "BTCPP_UNIT_TESTS: ${BTCPP_UNIT_TESTS} " ) if (BTCPP_UNIT_TESTS OR BTCPP_EXAMPLES) -add_subdirectory(sample_nodes) + add_subdirectory(sample_nodes) endif() ###################################################### diff --git a/fuzzing/README.md b/fuzzing/README.md new file mode 100644 index 000000000..45c48db90 --- /dev/null +++ b/fuzzing/README.md @@ -0,0 +1,19 @@ +# Fuzzing BehaviorTree.CPP + +You can build the existing harnesses either for libfuzzer or AFL++: + +## libfuzzer + +```bash +mkdir build_libfuzzer && cd build_libfuzzer +cmake -DENABLE_FUZZING .. +``` + +## AFL++ + +```bash +export CC=afl-clang-fast +export CXX=afl-clang-fast++ +mkdir build_afl && cd build_afl +cmake -DENABLE_FUZZING -DUSE_AFLPLUSPLUS .. +``` diff --git a/fuzzing/bb_fuzzer.cpp b/fuzzing/bb_fuzzer.cpp new file mode 100644 index 000000000..5667d8ffb --- /dev/null +++ b/fuzzing/bb_fuzzer.cpp @@ -0,0 +1,270 @@ +#include "behaviortree_cpp/blackboard.h" +#include +#include +#include +#include +#include + +#include + +class ExceptionFilter +{ +public: + static bool isExpectedException(const std::exception& e) + { + const std::string what = e.what(); + const std::vector expected_patterns = { "Blackboard::set", + "once declared, the type of a " + "port shall not change", + "Missing key", + "hasn't been initialized", + "Missing parent blackboard", + "Floating point truncated", + "Value outside the max " + "numerical limit", + "Value outside the lovest " + "numerical limit", + "Value is negative and can't be " + "converted to unsigned", + "Implicit casting to bool is " + "not allowed" }; + + for(const auto& pattern : expected_patterns) + { + if(what.find(pattern) != std::string::npos) + { + return true; + } + } + return false; + } +}; + +class BlackboardFuzzer +{ +private: + std::vector blackboards_; + std::vector generated_keys_; + FuzzedDataProvider& fuzz_data_; + + std::string generateKey() + { + const std::string key_chars = "abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ01" + "23456789_@"; + size_t length = fuzz_data_.ConsumeIntegralInRange(1, 32); + std::string key; + for(size_t i = 0; i < length; ++i) + { + key += + key_chars[fuzz_data_.ConsumeIntegralInRange(0, key_chars.length() - 1)]; + } + generated_keys_.push_back(key); + return key; + } + + void fuzzSingleBB(BT::Blackboard::Ptr bb) + { + if(!bb) + return; + + try + { + // Create random entry + std::string key = generateKey(); + switch(fuzz_data_.ConsumeIntegralInRange(0, 6)) + { + case 0: + bb->set(key, fuzz_data_.ConsumeIntegral()); + break; + case 1: + bb->set(key, fuzz_data_.ConsumeFloatingPoint()); + break; + case 2: + bb->set(key, fuzz_data_.ConsumeRandomLengthString()); + break; + case 3: + bb->set(key, fuzz_data_.ConsumeBool()); + break; + case 4: + bb->set(key, fuzz_data_.ConsumeIntegral()); + break; + case 5: + bb->set(key, fuzz_data_.ConsumeFloatingPoint()); + break; + case 6: { + // Try to get non-existent key + bb->get(generateKey()); + break; + } + } + + // Random operations on existing keys + if(!generated_keys_.empty()) + { + const auto& existing_key = + generated_keys_[fuzz_data_.ConsumeIntegralInRange( + 0, generated_keys_.size() - 1)]; + + switch(fuzz_data_.ConsumeIntegralInRange(0, 4)) + { + case 0: + bb->unset(existing_key); + break; + case 1: + bb->getEntry(existing_key); + break; + case 2: + bb->get(existing_key); + break; + case 3: + bb->get(existing_key); + break; + case 4: + bb->get(existing_key); + break; + } + } + + // Random remapping operations + if(generated_keys_.size() >= 2) + { + size_t idx1 = + fuzz_data_.ConsumeIntegralInRange(0, generated_keys_.size() - 1); + size_t idx2 = + fuzz_data_.ConsumeIntegralInRange(0, generated_keys_.size() - 1); + bb->addSubtreeRemapping(generated_keys_[idx1], generated_keys_[idx2]); + } + } + catch(const std::exception& e) + { + if(!ExceptionFilter::isExpectedException(e)) + { + throw; + } + } + } + + void createBlackboardHierarchy() + { + if(blackboards_.empty()) + return; + + auto parent = blackboards_[fuzz_data_.ConsumeIntegralInRange( + 0, blackboards_.size() - 1)]; + + auto child = BT::Blackboard::create(parent); + if(fuzz_data_.ConsumeBool()) + { + child->enableAutoRemapping(true); + } + + blackboards_.push_back(child); + } + + void fuzzJsonOperations(BT::Blackboard::Ptr bb) + { + try + { + auto json = BT::ExportBlackboardToJSON(*bb); + if(fuzz_data_.ConsumeBool()) + { + std::string json_str = json.dump(); + size_t pos = fuzz_data_.ConsumeIntegralInRange(0, json_str.length()); + json_str.insert(pos, fuzz_data_.ConsumeRandomLengthString()); + json = nlohmann::json::parse(json_str); + } + BT::ImportBlackboardFromJSON(json, *bb); + } + catch(const std::exception& e) + { + if(!ExceptionFilter::isExpectedException(e)) + { + throw; + } + } + } + +public: + explicit BlackboardFuzzer(FuzzedDataProvider& provider) : fuzz_data_(provider) + { + blackboards_.push_back(BT::Blackboard::create()); + } + + void fuzz() + { + size_t num_operations = fuzz_data_.ConsumeIntegralInRange(50, 200); + + for(size_t i = 0; i < num_operations && !blackboards_.empty(); ++i) + { + try + { + // Randomly select a blackboard to operate on + size_t bb_idx = + fuzz_data_.ConsumeIntegralInRange(0, blackboards_.size() - 1); + auto bb = blackboards_[bb_idx]; + + switch(fuzz_data_.ConsumeIntegralInRange(0, 3)) + { + case 0: + // Fuzz single blackboard operations + fuzzSingleBB(bb); + break; + + case 1: + // Create new blackboards in hierarchy + if(fuzz_data_.ConsumeBool()) + { + createBlackboardHierarchy(); + } + break; + + case 2: + // JSON operations + fuzzJsonOperations(bb); + break; + + case 3: + // Cleanup operations + if(fuzz_data_.ConsumeBool() && blackboards_.size() > 1) + { + size_t remove_idx = + fuzz_data_.ConsumeIntegralInRange(0, blackboards_.size() - 1); + blackboards_.erase(blackboards_.begin() + remove_idx); + } + break; + } + } + catch(const std::exception& e) + { + if(!ExceptionFilter::isExpectedException(e)) + { + std::cerr << "Unexpected exception: " << e.what() << std::endl; + throw; + } + } + } + } +}; + +extern "C" int LLVMFuzzerTestOneInput(const uint8_t* data, size_t size) +{ + if(size < 64) + return 0; + + try + { + FuzzedDataProvider fuzz_data(data, size); + BlackboardFuzzer fuzzer(fuzz_data); + fuzzer.fuzz(); + } + catch(const std::exception& e) + { + if(!ExceptionFilter::isExpectedException(e)) + { + std::cerr << "Unexpected top-level exception: " << e.what() << std::endl; + return 1; + } + } + + return 0; +} diff --git a/fuzzing/bt_fuzzer.cpp b/fuzzing/bt_fuzzer.cpp new file mode 100644 index 000000000..7411109cc --- /dev/null +++ b/fuzzing/bt_fuzzer.cpp @@ -0,0 +1,125 @@ +#include +#include "behaviortree_cpp/bt_factory.h" +#include "behaviortree_cpp/xml_parsing.h" +#include + +// List of valid node types we can use to construct valid-ish XML +constexpr const char* NODE_TYPES[] = { + "Sequence", "Fallback", "ParallelAll", + "ReactiveSequence", "ReactiveFallback", "IfThenElse", + "WhileDoElse", "Inverter", "RetryUntilSuccessful", + "Repeat", "Timeout", "Delay", + "ForceSuccess", "ForceFailure", "AlwaysSuccess", + "AlwaysFailure", "SetBlackboard", "SubTree" +}; + +// Attributes that can be added to nodes +constexpr const char* NODE_ATTRIBUTES[] = { "name", "ID", "port_1", + "port_2", "timeout_ms", "delay_ms", + "threshold", "max_repeats" }; + +std::string generateFuzzedNodeXML(FuzzedDataProvider& fdp, int depth = 0) +{ + // Prevent stack overflow with max depth + if(depth > 6) + { // Reasonable limit for XML tree depth + return ""; + } + + std::string xml; + const std::string node_type = fdp.PickValueInArray(NODE_TYPES); + + xml += "<" + node_type; + + size_t num_attributes = fdp.ConsumeIntegralInRange(0, 3); + for(size_t i = 0; i < num_attributes; i++) + { + const std::string attr = fdp.PickValueInArray(NODE_ATTRIBUTES); + std::string value = fdp.ConsumeRandomLengthString(10); + xml += " " + attr + "=\"" + value + "\""; + } + + if(depth > 3 || fdp.ConsumeBool()) + { + xml += "/>"; + } + else + { + xml += ">"; + // Add some child nodes recursively with depth limit + size_t num_children = fdp.ConsumeIntegralInRange(0, 2); + for(size_t i = 0; i < num_children; i++) + { + xml += generateFuzzedNodeXML(fdp, depth + 1); + } + xml += ""; + } + + return xml; +} + +extern "C" int LLVMFuzzerTestOneInput(const uint8_t* data, size_t size) +{ + if(size < 4) + { + return 0; + } + + FuzzedDataProvider fdp(data, size); + BT::BehaviorTreeFactory factory; + + try + { + // Strategy 1: Test with completely random data + if(fdp.ConsumeBool()) + { + std::string random_xml = fdp.ConsumeRandomLengthString(size - 1); + try + { + factory.createTreeFromText(random_xml); + } + catch(const std::exception&) + {} + } + // Strategy 2: Generate semi-valid XML + else + { + std::string xml = R"( + + )"; + + size_t num_nodes = fdp.ConsumeIntegralInRange(1, 5); + for(size_t i = 0; i < num_nodes; i++) + { + xml += generateFuzzedNodeXML(fdp); + } + + xml += R"( + + )"; + + auto blackboard = BT::Blackboard::create(); + + switch(fdp.ConsumeIntegralInRange(0, 2)) + { + case 0: + factory.createTreeFromText(xml, blackboard); + break; + case 1: + BT::VerifyXML(xml, {}); + break; + case 2: + factory.registerBehaviorTreeFromText(xml); + if(!factory.registeredBehaviorTrees().empty()) + { + factory.createTree(factory.registeredBehaviorTrees().front(), blackboard); + } + break; + } + } + } + catch(const std::exception&) + {} + + return 0; +} diff --git a/fuzzing/corpus/bb_corpus/basic.json b/fuzzing/corpus/bb_corpus/basic.json new file mode 100644 index 000000000..9121b1350 --- /dev/null +++ b/fuzzing/corpus/bb_corpus/basic.json @@ -0,0 +1,6 @@ +{ + "int_key": 42, + "double_key": 3.14159, + "string_key": "test_string", + "bool_key": true +} diff --git a/fuzzing/corpus/bb_corpus/edge.json b/fuzzing/corpus/bb_corpus/edge.json new file mode 100644 index 000000000..8008ef811 --- /dev/null +++ b/fuzzing/corpus/bb_corpus/edge.json @@ -0,0 +1,7 @@ +{ + "max_int": 2147483647, + "min_int": -2147483648, + "zero": 0, + "empty_string": "", + "null_value": null +} diff --git a/fuzzing/corpus/bb_corpus/multiple.json b/fuzzing/corpus/bb_corpus/multiple.json new file mode 100644 index 000000000..cf5cfa76b --- /dev/null +++ b/fuzzing/corpus/bb_corpus/multiple.json @@ -0,0 +1,8 @@ +{ + "key1": 42, + "key2": "string", + "key3": 3.14, + "key4": true, + "key5": -123, + "key6": 99.99 +} diff --git a/fuzzing/corpus/bb_corpus/nested.json b/fuzzing/corpus/bb_corpus/nested.json new file mode 100644 index 000000000..39c4b000e --- /dev/null +++ b/fuzzing/corpus/bb_corpus/nested.json @@ -0,0 +1,11 @@ +{ + "outer_key": { + "inner_int": 100, + "inner_string": "nested" + }, + "array_key": [ + 1, + 2, + 3 + ] +} diff --git a/fuzzing/corpus/bb_corpus/special.json b/fuzzing/corpus/bb_corpus/special.json new file mode 100644 index 000000000..76a6a4b70 --- /dev/null +++ b/fuzzing/corpus/bb_corpus/special.json @@ -0,0 +1,6 @@ +{ + "special@key": 123, + "key_with_underscore": "value", + "KeyWithCaps": true, + "123numeric_start": 456 +} diff --git a/fuzzing/corpus/bt_corpus/corpus1.xml b/fuzzing/corpus/bt_corpus/corpus1.xml new file mode 100644 index 000000000..e0a36705b --- /dev/null +++ b/fuzzing/corpus/bt_corpus/corpus1.xml @@ -0,0 +1,7 @@ + + + + + + + \ No newline at end of file diff --git a/fuzzing/corpus/bt_corpus/corpus2.xml b/fuzzing/corpus/bt_corpus/corpus2.xml new file mode 100644 index 000000000..0313b11d1 --- /dev/null +++ b/fuzzing/corpus/bt_corpus/corpus2.xml @@ -0,0 +1,14 @@ + + + + + + + + + + + + + + \ No newline at end of file diff --git a/fuzzing/corpus/bt_corpus/corpus3.xml b/fuzzing/corpus/bt_corpus/corpus3.xml new file mode 100644 index 000000000..0d0715a01 --- /dev/null +++ b/fuzzing/corpus/bt_corpus/corpus3.xml @@ -0,0 +1,8 @@ + + + + + + + + \ No newline at end of file diff --git a/fuzzing/corpus/bt_corpus/corpus4.xml b/fuzzing/corpus/bt_corpus/corpus4.xml new file mode 100644 index 000000000..18dd1456a --- /dev/null +++ b/fuzzing/corpus/bt_corpus/corpus4.xml @@ -0,0 +1,9 @@ + + + + + + + + + \ No newline at end of file diff --git a/fuzzing/corpus/bt_corpus/sample1 b/fuzzing/corpus/bt_corpus/sample1 new file mode 100644 index 000000000..4528b2741 --- /dev/null +++ b/fuzzing/corpus/bt_corpus/sample1 @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/fuzzing/corpus/script_corpus/sample1 b/fuzzing/corpus/script_corpus/sample1 new file mode 100644 index 000000000..d9bf10e00 --- /dev/null +++ b/fuzzing/corpus/script_corpus/sample1 @@ -0,0 +1 @@ +a:=1; b:=2; a+b \ No newline at end of file diff --git a/fuzzing/corpus/script_corpus/sample2 b/fuzzing/corpus/script_corpus/sample2 new file mode 100644 index 000000000..2ee60bbcb --- /dev/null +++ b/fuzzing/corpus/script_corpus/sample2 @@ -0,0 +1 @@ +x:=true; y:=false; x and y \ No newline at end of file diff --git a/fuzzing/corpus/script_corpus/sample3 b/fuzzing/corpus/script_corpus/sample3 new file mode 100644 index 000000000..9bca5c152 --- /dev/null +++ b/fuzzing/corpus/script_corpus/sample3 @@ -0,0 +1 @@ +str:='hello'; len(str) \ No newline at end of file diff --git a/fuzzing/script_fuzzer.cpp b/fuzzing/script_fuzzer.cpp new file mode 100644 index 000000000..b507dcf17 --- /dev/null +++ b/fuzzing/script_fuzzer.cpp @@ -0,0 +1,70 @@ +#include +#include "behaviortree_cpp/scripting/script_parser.hpp" +#include "behaviortree_cpp/blackboard.h" +#include "behaviortree_cpp/basic_types.h" +#include +#include +#include +#include + +extern "C" int LLVMFuzzerTestOneInput(const uint8_t* data, size_t size) +{ + if(size < 4) + { + return 0; + } + + FuzzedDataProvider fuzz_data(data, size); + + try + { + BT::Ast::Environment env; + env.vars = BT::Blackboard::create(); + env.enums = std::make_shared(); + + // Add some test variables to the blackboard + env.vars->set("test_int", 42); + env.vars->set("test_double", 3.14); + env.vars->set("test_bool", true); + env.vars->set("test_string", std::string("test")); + + // Add some test enums + (*env.enums)["RUNNING"] = 0; + (*env.enums)["SUCCESS"] = 1; + (*env.enums)["FAILURE"] = 2; + + std::string script = fuzz_data.ConsumeRandomLengthString(); + + auto validation_result = BT::ValidateScript(script); + + if(!validation_result) + { + auto parsed_script = BT::ParseScript(script); + if(parsed_script) + { + try + { + auto result = parsed_script.value()(env); + + if(result.isNumber()) + { + volatile auto num = result.cast(); + } + + env.vars->set("result", result); + + BT::Any read_back; + env.vars->get("result", read_back); + } + catch(const BT::RuntimeError&) + {} + } + } + + BT::ParseScriptAndExecute(env, script); + } + catch(const std::exception&) + {} + + return 0; +} From 03e9c405aad5e41df0731d255fc03ec06e080ec1 Mon Sep 17 00:00:00 2001 From: cktii Date: Tue, 4 Feb 2025 09:28:58 +0400 Subject: [PATCH 2/6] chore: make pre-commit happy --- fuzzing/corpus/bt_corpus/corpus1.xml | 3 ++- fuzzing/corpus/bt_corpus/corpus2.xml | 3 ++- fuzzing/corpus/bt_corpus/corpus3.xml | 3 ++- fuzzing/corpus/bt_corpus/corpus4.xml | 3 ++- fuzzing/corpus/script_corpus/sample1 | 4 +++- fuzzing/corpus/script_corpus/sample2 | 4 +++- fuzzing/corpus/script_corpus/sample3 | 3 ++- 7 files changed, 16 insertions(+), 7 deletions(-) diff --git a/fuzzing/corpus/bt_corpus/corpus1.xml b/fuzzing/corpus/bt_corpus/corpus1.xml index e0a36705b..218ef90d5 100644 --- a/fuzzing/corpus/bt_corpus/corpus1.xml +++ b/fuzzing/corpus/bt_corpus/corpus1.xml @@ -4,4 +4,5 @@ - \ No newline at end of file + + diff --git a/fuzzing/corpus/bt_corpus/corpus2.xml b/fuzzing/corpus/bt_corpus/corpus2.xml index 0313b11d1..fbee7f2d7 100644 --- a/fuzzing/corpus/bt_corpus/corpus2.xml +++ b/fuzzing/corpus/bt_corpus/corpus2.xml @@ -11,4 +11,5 @@ - \ No newline at end of file + + diff --git a/fuzzing/corpus/bt_corpus/corpus3.xml b/fuzzing/corpus/bt_corpus/corpus3.xml index 0d0715a01..f0df8a575 100644 --- a/fuzzing/corpus/bt_corpus/corpus3.xml +++ b/fuzzing/corpus/bt_corpus/corpus3.xml @@ -5,4 +5,5 @@ - \ No newline at end of file + + diff --git a/fuzzing/corpus/bt_corpus/corpus4.xml b/fuzzing/corpus/bt_corpus/corpus4.xml index 18dd1456a..d825419a8 100644 --- a/fuzzing/corpus/bt_corpus/corpus4.xml +++ b/fuzzing/corpus/bt_corpus/corpus4.xml @@ -6,4 +6,5 @@ - \ No newline at end of file + + diff --git a/fuzzing/corpus/script_corpus/sample1 b/fuzzing/corpus/script_corpus/sample1 index d9bf10e00..ccdfe3aa5 100644 --- a/fuzzing/corpus/script_corpus/sample1 +++ b/fuzzing/corpus/script_corpus/sample1 @@ -1 +1,3 @@ -a:=1; b:=2; a+b \ No newline at end of file +a : = 1; +b : = 2; +a + b \ No newline at end of file diff --git a/fuzzing/corpus/script_corpus/sample2 b/fuzzing/corpus/script_corpus/sample2 index 2ee60bbcb..8452030f3 100644 --- a/fuzzing/corpus/script_corpus/sample2 +++ b/fuzzing/corpus/script_corpus/sample2 @@ -1 +1,3 @@ -x:=true; y:=false; x and y \ No newline at end of file +x : = true; +y : = false; +x and y \ No newline at end of file diff --git a/fuzzing/corpus/script_corpus/sample3 b/fuzzing/corpus/script_corpus/sample3 index 9bca5c152..71d366857 100644 --- a/fuzzing/corpus/script_corpus/sample3 +++ b/fuzzing/corpus/script_corpus/sample3 @@ -1 +1,2 @@ -str:='hello'; len(str) \ No newline at end of file +str : = 'hello'; +len(str) \ No newline at end of file From 0700e52e083996f8ec5db3a9cd66b5a52696d135 Mon Sep 17 00:00:00 2001 From: cktii Date: Tue, 4 Feb 2025 09:55:09 +0400 Subject: [PATCH 3/6] fix: incomplete command in fuzzing README.md --- fuzzing/README.md | 8 +++++--- 1 file changed, 5 insertions(+), 3 deletions(-) diff --git a/fuzzing/README.md b/fuzzing/README.md index 45c48db90..e42a75bd0 100644 --- a/fuzzing/README.md +++ b/fuzzing/README.md @@ -1,12 +1,14 @@ # Fuzzing BehaviorTree.CPP -You can build the existing harnesses either for libfuzzer or AFL++: +You can build the existing harnesses either for libfuzzer or AFL++. +Building the fuzzers requires `clang` (libfuzzer) or an installed version +of [AFL++](https://github.com/AFLplusplus/AFLplusplus). ## libfuzzer ```bash mkdir build_libfuzzer && cd build_libfuzzer -cmake -DENABLE_FUZZING .. +cmake -DENABLE_FUZZING=ON .. ``` ## AFL++ @@ -15,5 +17,5 @@ cmake -DENABLE_FUZZING .. export CC=afl-clang-fast export CXX=afl-clang-fast++ mkdir build_afl && cd build_afl -cmake -DENABLE_FUZZING -DUSE_AFLPLUSPLUS .. +cmake -DENABLE_FUZZING=ON -DUSE_AFLPLUSPLUS=ON .. ``` From 9df4125a4fc9440d908de4f0caab8edb1cd44715 Mon Sep 17 00:00:00 2001 From: cktii Date: Tue, 4 Feb 2025 11:14:07 +0400 Subject: [PATCH 4/6] fix: prepare static harnesses --- CMakeLists.txt | 121 +++++++++++++++++++++++++++++++------------------ 1 file changed, 78 insertions(+), 43 deletions(-) diff --git a/CMakeLists.txt b/CMakeLists.txt index 09c4d2d5a..9f2f0df3c 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -2,14 +2,22 @@ cmake_minimum_required(VERSION 3.16.3) # version on Ubuntu Focal project(behaviortree_cpp VERSION 4.6.2 LANGUAGES C CXX) -# Build configuration options +#---- project configuration ---- +option(BTCPP_SHARED_LIBS "Build shared libraries" ON) +option(BTCPP_BUILD_TOOLS "Build commandline tools" ON) +option(BTCPP_EXAMPLES "Build tutorials and examples" ON) +option(BTCPP_UNIT_TESTS "Build the unit tests" ON) +option(BTCPP_GROOT_INTERFACE "Add Groot2 connection. Requires ZeroMQ" ON) +option(BTCPP_SQLITE_LOGGING "Add SQLite logging." ON) + +option(USE_V3_COMPATIBLE_NAMES "Use some alias to compile more easily old 3.x code" OFF) option(ENABLE_FUZZING "Enable fuzzing builds" OFF) option(USE_AFLPLUSPLUS "Use AFL++ instead of libFuzzer" OFF) option(ENABLE_DEBUG "Enable debug build with full symbols" OFF) +option(FORCE_STATIC_LINKING "Force static linking of all dependencies" OFF) set(BASE_FLAGS "") -# Debug build configuration if(ENABLE_DEBUG) list(APPEND BASE_FLAGS -g3 @@ -21,12 +29,32 @@ endif() # Fuzzing configuration if(ENABLE_FUZZING) - if(USE_AFLPLUSPLUS) - list(APPEND BASE_FLAGS -O3) - else() - list(APPEND BASE_FLAGS -O2) + if(CMAKE_C_COMPILER MATCHES ".*afl-.*" OR CMAKE_CXX_COMPILER MATCHES ".*afl-.*") + set(USE_AFLPLUSPLUS ON CACHE BOOL "Use AFL++ instead of libFuzzer" FORCE) + message(STATUS "AFL++ compiler detected - automatically enabling AFL++ mode") + endif() + + # When building for fuzzing, we still want static library by default + set(BTCPP_SHARED_LIBS OFF CACHE BOOL "Build static library for fuzzing" FORCE) + + # Only apply static linking settings if explicitly requested + if(FORCE_STATIC_LINKING) + set(CMAKE_FIND_LIBRARY_SUFFIXES .a ${CMAKE_FIND_LIBRARY_SUFFIXES}) + set(BUILD_SHARED_LIBS OFF) + + # Force static linking for dependencies + if(BTCPP_GROOT_INTERFACE) + set(ZeroMQ_USE_STATIC_LIBS ON) + set(ZEROMQ_STATIC_LIBRARY ON) + endif() + + if(BTCPP_SQLITE_LOGGING) + set(SQLite3_USE_STATIC_LIBS ON) + endif() endif() + list(APPEND BASE_FLAGS -O2) + if(USE_AFLPLUSPLUS) set(SANITIZER_FLAGS -fsanitize=address,undefined @@ -41,33 +69,47 @@ if(ENABLE_FUZZING) # Apply sanitizer flags to the base library list(APPEND BASE_FLAGS ${SANITIZER_FLAGS}) - # Apply base flags globally add_compile_options(${BASE_FLAGS}) add_link_options(${BASE_FLAGS}) function(apply_fuzzing_flags target) - if(USE_AFLPLUSPLUS) - # AFL++ specific flags - target_compile_options(${target} PRIVATE + target_compile_options(${target} PRIVATE + ${BASE_FLAGS} + ${SANITIZER_FLAGS} + ) + + if(FORCE_STATIC_LINKING) + if(USE_AFLPLUSPLUS) + target_link_options(${target} PRIVATE ${BASE_FLAGS} ${SANITIZER_FLAGS} + -static-libstdc++ + -static-libgcc + -fsanitize=fuzzer ) - target_link_options(${target} PRIVATE + else() + target_link_options(${target} PRIVATE ${BASE_FLAGS} - -fsanitize=fuzzer,address,undefined + -fsanitize=fuzzer + ${SANITIZER_FLAGS} + -static-libstdc++ + -static-libgcc ) + endif() else() - # libFuzzer specific flags - target_compile_options(${target} PRIVATE + if(USE_AFLPLUSPLUS) + target_link_options(${target} PRIVATE ${BASE_FLAGS} - -fsanitize=fuzzer ${SANITIZER_FLAGS} + -fsanitize=fuzzer ) - target_link_options(${target} PRIVATE + else() + target_link_options(${target} PRIVATE ${BASE_FLAGS} -fsanitize=fuzzer ${SANITIZER_FLAGS} ) + endif() endif() endfunction() @@ -99,17 +141,6 @@ else() add_definitions(-Wpedantic -fno-omit-frame-pointer) endif() - -#---- project configuration ---- -option(BTCPP_SHARED_LIBS "Build shared libraries" ON) -option(BTCPP_BUILD_TOOLS "Build commandline tools" ON) -option(BTCPP_EXAMPLES "Build tutorials and examples" ON) -option(BTCPP_UNIT_TESTS "Build the unit tests" ON) -option(BTCPP_GROOT_INTERFACE "Add Groot2 connection. Requires ZeroMQ" ON) -option(BTCPP_SQLITE_LOGGING "Add SQLite logging." ON) - -option(USE_V3_COMPATIBLE_NAMES "Use some alias to compile more easily old 3.x code" OFF) - if(USE_V3_COMPATIBLE_NAMES) add_definitions(-DUSE_BTCPP3_OLD_NAMES) endif() @@ -277,27 +308,31 @@ add_library(BT::${BTCPP_LIBRARY} ALIAS ${BTCPP_LIBRARY}) # Add fuzzing targets if(ENABLE_FUZZING) - add_executable(bt_fuzzer fuzzing/bt_fuzzer.cpp) - apply_fuzzing_flags(bt_fuzzer) - target_link_libraries(bt_fuzzer PRIVATE ${BTCPP_LIBRARY} ${BTCPP_EXTRA_LIBRARIES}) - - add_executable(script_fuzzer fuzzing/script_fuzzer.cpp) - apply_fuzzing_flags(script_fuzzer) - target_link_libraries(script_fuzzer PRIVATE ${BTCPP_LIBRARY} ${BTCPP_EXTRA_LIBRARIES}) - - add_executable(bb_fuzzer fuzzing/bb_fuzzer.cpp) - apply_fuzzing_flags(bb_fuzzer) - target_link_libraries(bb_fuzzer PRIVATE ${BTCPP_LIBRARY} ${BTCPP_EXTRA_LIBRARIES}) - foreach(fuzzer bt_fuzzer script_fuzzer bb_fuzzer) + add_executable(${fuzzer} fuzzing/${fuzzer}.cpp) + apply_fuzzing_flags(${fuzzer}) + + if(FORCE_STATIC_LINKING) + target_link_libraries(${fuzzer} PRIVATE + -static-libstdc++ + -static-libgcc + ${BTCPP_LIBRARY} + ${BTCPP_EXTRA_LIBRARIES} + ) + else() + target_link_libraries(${fuzzer} PRIVATE + ${BTCPP_LIBRARY} + ${BTCPP_EXTRA_LIBRARIES} + ) + endif() + set(CORPUS_DIR ${CMAKE_BINARY_DIR}/corpus/${fuzzer}) file(MAKE_DIRECTORY ${CORPUS_DIR}) endforeach() - file(GLOB BT_CORPUS_FILES "fuzzing/corpus/bt_fuzzer/*") - file(GLOB SCRIPT_CORPUS_FILES "fuzzing/corpus/script_fuzzer/*") - file(GLOB BB_CORPUS_FILES "fuzzing/corpus/bb_fuzzer/*") - + file(GLOB BT_CORPUS_FILES "${CMAKE_SOURCE_DIR}/fuzzing/corpus/bt_corpus/*") + file(GLOB SCRIPT_CORPUS_FILES "${CMAKE_SOURCE_DIR}/fuzzing/corpus/script_corpus/*") + file(GLOB BB_CORPUS_FILES "${CMAKE_SOURCE_DIR}/fuzzing/corpus/bb_corpus/*") if(BT_CORPUS_FILES) file(COPY ${BT_CORPUS_FILES} DESTINATION ${CMAKE_BINARY_DIR}/corpus/bt_fuzzer) endif() From cc4cc9bbe7a333919425237a0312a4461334451c Mon Sep 17 00:00:00 2001 From: cktii Date: Mon, 10 Feb 2025 09:41:03 +0400 Subject: [PATCH 5/6] fix: pre-commit --- fuzzing/corpus/bt_corpus/corpus1.xml | 1 - fuzzing/corpus/bt_corpus/corpus2.xml | 1 - fuzzing/corpus/bt_corpus/corpus3.xml | 1 - fuzzing/corpus/bt_corpus/corpus4.xml | 1 - fuzzing/corpus/bt_corpus/sample1 | 2 +- fuzzing/corpus/script_corpus/sample1 | 2 +- fuzzing/corpus/script_corpus/sample2 | 2 +- fuzzing/corpus/script_corpus/sample3 | 2 +- 8 files changed, 4 insertions(+), 8 deletions(-) diff --git a/fuzzing/corpus/bt_corpus/corpus1.xml b/fuzzing/corpus/bt_corpus/corpus1.xml index 218ef90d5..e625cb41b 100644 --- a/fuzzing/corpus/bt_corpus/corpus1.xml +++ b/fuzzing/corpus/bt_corpus/corpus1.xml @@ -5,4 +5,3 @@ - diff --git a/fuzzing/corpus/bt_corpus/corpus2.xml b/fuzzing/corpus/bt_corpus/corpus2.xml index fbee7f2d7..461e53c64 100644 --- a/fuzzing/corpus/bt_corpus/corpus2.xml +++ b/fuzzing/corpus/bt_corpus/corpus2.xml @@ -12,4 +12,3 @@ - diff --git a/fuzzing/corpus/bt_corpus/corpus3.xml b/fuzzing/corpus/bt_corpus/corpus3.xml index f0df8a575..e21ea8adf 100644 --- a/fuzzing/corpus/bt_corpus/corpus3.xml +++ b/fuzzing/corpus/bt_corpus/corpus3.xml @@ -6,4 +6,3 @@ - diff --git a/fuzzing/corpus/bt_corpus/corpus4.xml b/fuzzing/corpus/bt_corpus/corpus4.xml index d825419a8..3111d8be4 100644 --- a/fuzzing/corpus/bt_corpus/corpus4.xml +++ b/fuzzing/corpus/bt_corpus/corpus4.xml @@ -7,4 +7,3 @@ - diff --git a/fuzzing/corpus/bt_corpus/sample1 b/fuzzing/corpus/bt_corpus/sample1 index 4528b2741..932d7e835 100644 --- a/fuzzing/corpus/bt_corpus/sample1 +++ b/fuzzing/corpus/bt_corpus/sample1 @@ -1 +1 @@ - \ No newline at end of file + diff --git a/fuzzing/corpus/script_corpus/sample1 b/fuzzing/corpus/script_corpus/sample1 index ccdfe3aa5..36c81d57f 100644 --- a/fuzzing/corpus/script_corpus/sample1 +++ b/fuzzing/corpus/script_corpus/sample1 @@ -1,3 +1,3 @@ a : = 1; b : = 2; -a + b \ No newline at end of file +a + b diff --git a/fuzzing/corpus/script_corpus/sample2 b/fuzzing/corpus/script_corpus/sample2 index 8452030f3..dc1d11e2c 100644 --- a/fuzzing/corpus/script_corpus/sample2 +++ b/fuzzing/corpus/script_corpus/sample2 @@ -1,3 +1,3 @@ x : = true; y : = false; -x and y \ No newline at end of file +x and y diff --git a/fuzzing/corpus/script_corpus/sample3 b/fuzzing/corpus/script_corpus/sample3 index 71d366857..39ce18d0c 100644 --- a/fuzzing/corpus/script_corpus/sample3 +++ b/fuzzing/corpus/script_corpus/sample3 @@ -1,2 +1,2 @@ str : = 'hello'; -len(str) \ No newline at end of file +len(str) From c4be5cb983ceeb65e2a2c3eb52c1abc831bfbab1 Mon Sep 17 00:00:00 2001 From: cktii Date: Tue, 25 Feb 2025 15:44:56 +0400 Subject: [PATCH 6/6] refactor: split root-level CMakeLists.txt to isolate fuzzing setup --- CMakeLists.txt | 127 +------------------------------ cmake/fuzzing_build.cmake | 153 ++++++++++++++++++++++++++++++++++++++ 2 files changed, 156 insertions(+), 124 deletions(-) create mode 100644 cmake/fuzzing_build.cmake diff --git a/CMakeLists.txt b/CMakeLists.txt index 4e57e85d0..f4e63c131 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -30,96 +30,9 @@ if(ENABLE_DEBUG) ) endif() -# Fuzzing configuration +# Include fuzzing configuration if enabled if(ENABLE_FUZZING) - if(CMAKE_C_COMPILER MATCHES ".*afl-.*" OR CMAKE_CXX_COMPILER MATCHES ".*afl-.*") - set(USE_AFLPLUSPLUS ON CACHE BOOL "Use AFL++ instead of libFuzzer" FORCE) - message(STATUS "AFL++ compiler detected - automatically enabling AFL++ mode") - endif() - - # When building for fuzzing, we still want static library by default - set(BTCPP_SHARED_LIBS OFF CACHE BOOL "Build static library for fuzzing" FORCE) - - # Only apply static linking settings if explicitly requested - if(FORCE_STATIC_LINKING) - set(CMAKE_FIND_LIBRARY_SUFFIXES .a ${CMAKE_FIND_LIBRARY_SUFFIXES}) - set(BUILD_SHARED_LIBS OFF) - - # Force static linking for dependencies - if(BTCPP_GROOT_INTERFACE) - set(ZeroMQ_USE_STATIC_LIBS ON) - set(ZEROMQ_STATIC_LIBRARY ON) - endif() - - if(BTCPP_SQLITE_LOGGING) - set(SQLite3_USE_STATIC_LIBS ON) - endif() - endif() - - list(APPEND BASE_FLAGS -O2) - - if(USE_AFLPLUSPLUS) - set(SANITIZER_FLAGS - -fsanitize=address,undefined - ) - else() - # For libFuzzer, use fuzzer-no-link for the library - set(SANITIZER_FLAGS - -fsanitize=address,undefined,fuzzer-no-link - ) - endif() - - # Apply sanitizer flags to the base library - list(APPEND BASE_FLAGS ${SANITIZER_FLAGS}) - - add_compile_options(${BASE_FLAGS}) - add_link_options(${BASE_FLAGS}) - - function(apply_fuzzing_flags target) - target_compile_options(${target} PRIVATE - ${BASE_FLAGS} - ${SANITIZER_FLAGS} - ) - - if(FORCE_STATIC_LINKING) - if(USE_AFLPLUSPLUS) - target_link_options(${target} PRIVATE - ${BASE_FLAGS} - ${SANITIZER_FLAGS} - -static-libstdc++ - -static-libgcc - -fsanitize=fuzzer - ) - else() - target_link_options(${target} PRIVATE - ${BASE_FLAGS} - -fsanitize=fuzzer - ${SANITIZER_FLAGS} - -static-libstdc++ - -static-libgcc - ) - endif() - else() - if(USE_AFLPLUSPLUS) - target_link_options(${target} PRIVATE - ${BASE_FLAGS} - ${SANITIZER_FLAGS} - -fsanitize=fuzzer - ) - else() - target_link_options(${target} PRIVATE - ${BASE_FLAGS} - -fsanitize=fuzzer - ${SANITIZER_FLAGS} - ) - endif() - endif() - endfunction() - - set(BTCPP_EXAMPLES OFF CACHE BOOL "Disable examples during fuzzing" FORCE) - set(BTCPP_BUILD_TOOLS OFF CACHE BOOL "Disable tools during fuzzing" FORCE) - set(BTCPP_UNIT_TESTS OFF CACHE BOOL "Disable tests during fuzzing" FORCE) - set(BTCPP_SHARED_LIBS OFF CACHE BOOL "Build static library for fuzzing" FORCE) + include(${CMAKE_CURRENT_SOURCE_DIR}/cmake/fuzzing_build.cmake) else() # Apply base flags for non-fuzzing builds add_compile_options(${BASE_FLAGS}) @@ -308,43 +221,9 @@ endif() add_library(BT::${BTCPP_LIBRARY} ALIAS ${BTCPP_LIBRARY}) - # Add fuzzing targets if(ENABLE_FUZZING) - foreach(fuzzer bt_fuzzer script_fuzzer bb_fuzzer) - add_executable(${fuzzer} fuzzing/${fuzzer}.cpp) - apply_fuzzing_flags(${fuzzer}) - - if(FORCE_STATIC_LINKING) - target_link_libraries(${fuzzer} PRIVATE - -static-libstdc++ - -static-libgcc - ${BTCPP_LIBRARY} - ${BTCPP_EXTRA_LIBRARIES} - ) - else() - target_link_libraries(${fuzzer} PRIVATE - ${BTCPP_LIBRARY} - ${BTCPP_EXTRA_LIBRARIES} - ) - endif() - - set(CORPUS_DIR ${CMAKE_BINARY_DIR}/corpus/${fuzzer}) - file(MAKE_DIRECTORY ${CORPUS_DIR}) - endforeach() - - file(GLOB BT_CORPUS_FILES "${CMAKE_SOURCE_DIR}/fuzzing/corpus/bt_corpus/*") - file(GLOB SCRIPT_CORPUS_FILES "${CMAKE_SOURCE_DIR}/fuzzing/corpus/script_corpus/*") - file(GLOB BB_CORPUS_FILES "${CMAKE_SOURCE_DIR}/fuzzing/corpus/bb_corpus/*") - if(BT_CORPUS_FILES) - file(COPY ${BT_CORPUS_FILES} DESTINATION ${CMAKE_BINARY_DIR}/corpus/bt_fuzzer) - endif() - if(SCRIPT_CORPUS_FILES) - file(COPY ${SCRIPT_CORPUS_FILES} DESTINATION ${CMAKE_BINARY_DIR}/corpus/script_fuzzer) - endif() - if(BB_CORPUS_FILES) - file(COPY ${BB_CORPUS_FILES} DESTINATION ${CMAKE_BINARY_DIR}/corpus/bb_fuzzer) - endif() + add_fuzzing_targets() endif() ############################################################# diff --git a/cmake/fuzzing_build.cmake b/cmake/fuzzing_build.cmake new file mode 100644 index 000000000..43f367001 --- /dev/null +++ b/cmake/fuzzing_build.cmake @@ -0,0 +1,153 @@ +# Fuzzing configuration +# Supports both local fuzzing and OSS-Fuzz integration + +# Detect if we're running in OSS-Fuzz environment +if(DEFINED ENV{LIB_FUZZING_ENGINE}) + set(OSS_FUZZ ON) + message(STATUS "OSS-Fuzz environment detected") +else() + set(OSS_FUZZ OFF) +endif() + +# Auto-detect AFL++ compiler if not in OSS-Fuzz mode +if(NOT OSS_FUZZ AND (CMAKE_C_COMPILER MATCHES ".*afl-.*" OR CMAKE_CXX_COMPILER MATCHES ".*afl-.*")) + set(USE_AFLPLUSPLUS ON CACHE BOOL "Use AFL++ instead of libFuzzer" FORCE) + message(STATUS "AFL++ compiler detected - automatically enabling AFL++ mode") +endif() + +# When building for fuzzing, we want static library by default +set(BTCPP_SHARED_LIBS OFF CACHE BOOL "Build static library for fuzzing" FORCE) + +# Only apply static linking settings if explicitly requested +if(FORCE_STATIC_LINKING) + set(CMAKE_FIND_LIBRARY_SUFFIXES .a ${CMAKE_FIND_LIBRARY_SUFFIXES}) + set(BUILD_SHARED_LIBS OFF) + + # Force static linking for dependencies + if(BTCPP_GROOT_INTERFACE) + set(ZeroMQ_USE_STATIC_LIBS ON) + set(ZEROMQ_STATIC_LIBRARY ON) + endif() + + if(BTCPP_SQLITE_LOGGING) + set(SQLite3_USE_STATIC_LIBS ON) + endif() +endif() + +# Set up flags for local fuzzing (not used for OSS-Fuzz) +if(NOT OSS_FUZZ) + list(APPEND BASE_FLAGS -O2) + + if(USE_AFLPLUSPLUS) + set(SANITIZER_FLAGS + -fsanitize=address,undefined + ) + else() + # For libFuzzer, use fuzzer-no-link for the library + set(SANITIZER_FLAGS + -fsanitize=address,undefined,fuzzer-no-link + ) + endif() + + # Apply sanitizer flags to the base library + list(APPEND BASE_FLAGS ${SANITIZER_FLAGS}) + + add_compile_options(${BASE_FLAGS}) + add_link_options(${BASE_FLAGS}) +endif() + +# Disable certain features during fuzzing +set(BTCPP_EXAMPLES OFF CACHE BOOL "Disable examples during fuzzing" FORCE) +set(BTCPP_BUILD_TOOLS OFF CACHE BOOL "Disable tools during fuzzing" FORCE) +set(BTCPP_UNIT_TESTS OFF CACHE BOOL "Disable tests during fuzzing" FORCE) +set(BTCPP_SHARED_LIBS OFF CACHE BOOL "Build static library for fuzzing" FORCE) + +# Function to apply fuzzing flags for local development builds +function(apply_local_fuzzing_flags target) + target_compile_options(${target} PRIVATE + ${BASE_FLAGS} + ${SANITIZER_FLAGS} + ) + + if(FORCE_STATIC_LINKING) + if(USE_AFLPLUSPLUS) + target_link_options(${target} PRIVATE + ${BASE_FLAGS} + ${SANITIZER_FLAGS} + -static-libstdc++ + -static-libgcc + -fsanitize=fuzzer + ) + else() + target_link_options(${target} PRIVATE + ${BASE_FLAGS} + -fsanitize=fuzzer + ${SANITIZER_FLAGS} + -static-libstdc++ + -static-libgcc + ) + endif() + else() + if(USE_AFLPLUSPLUS) + target_link_options(${target} PRIVATE + ${BASE_FLAGS} + ${SANITIZER_FLAGS} + -fsanitize=fuzzer + ) + else() + target_link_options(${target} PRIVATE + ${BASE_FLAGS} + -fsanitize=fuzzer + ${SANITIZER_FLAGS} + ) + endif() + endif() +endfunction() + +# Function to add fuzzing targets - compatible with both local and OSS-Fuzz builds +function(add_fuzzing_targets) + set(FUZZERS bt_fuzzer script_fuzzer bb_fuzzer) + + foreach(fuzzer ${FUZZERS}) + add_executable(${fuzzer} fuzzing/${fuzzer}.cpp) + + if(OSS_FUZZ) + # For OSS-Fuzz environment, we rely on environment variables + # like $CC, $CXX, $CFLAGS, $CXXFLAGS, and $LIB_FUZZING_ENGINE + target_link_libraries(${fuzzer} PRIVATE + ${BTCPP_LIBRARY} + ${BTCPP_EXTRA_LIBRARIES} + $ENV{LIB_FUZZING_ENGINE} + ) + else() + # For local development, use our own flags + apply_local_fuzzing_flags(${fuzzer}) + target_link_libraries(${fuzzer} PRIVATE + ${BTCPP_LIBRARY} + ${BTCPP_EXTRA_LIBRARIES} + ) + endif() + + # Setup corpus directories (useful for both environments) + set(CORPUS_DIR ${CMAKE_BINARY_DIR}/corpus/${fuzzer}) + file(MAKE_DIRECTORY ${CORPUS_DIR}) + endforeach() + + # Copy corpus files if they exist (useful for local testing) + # OSS-Fuzz provides its own corpus handling + if(NOT OSS_FUZZ) + file(GLOB BT_CORPUS_FILES "${CMAKE_SOURCE_DIR}/fuzzing/corpus/bt_corpus/*") + file(GLOB SCRIPT_CORPUS_FILES "${CMAKE_SOURCE_DIR}/fuzzing/corpus/script_corpus/*") + file(GLOB BB_CORPUS_FILES "${CMAKE_SOURCE_DIR}/fuzzing/corpus/bb_corpus/*") + + if(BT_CORPUS_FILES) + file(COPY ${BT_CORPUS_FILES} DESTINATION ${CMAKE_BINARY_DIR}/corpus/bt_fuzzer) + endif() + if(SCRIPT_CORPUS_FILES) + file(COPY ${SCRIPT_CORPUS_FILES} DESTINATION ${CMAKE_BINARY_DIR}/corpus/script_fuzzer) + endif() + if(BB_CORPUS_FILES) + file(COPY ${BB_CORPUS_FILES} DESTINATION ${CMAKE_BINARY_DIR}/corpus/bb_fuzzer) + endif() + endif() +endfunction()