diff --git a/cmake/fuzzing_build.cmake b/cmake/fuzzing_build.cmake index 43f367001..354287121 100644 --- a/cmake/fuzzing_build.cmake +++ b/cmake/fuzzing_build.cmake @@ -106,7 +106,7 @@ 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) + set(FUZZERS bt_fuzzer script_fuzzer bb_fuzzer bt_tree_exec_fuzzer) foreach(fuzzer ${FUZZERS}) add_executable(${fuzzer} fuzzing/${fuzzer}.cpp) diff --git a/fuzzing/bt_tree_exec_fuzzer.cpp b/fuzzing/bt_tree_exec_fuzzer.cpp new file mode 100644 index 000000000..edf9b434d --- /dev/null +++ b/fuzzing/bt_tree_exec_fuzzer.cpp @@ -0,0 +1,418 @@ +// Fuzz target: bt_tree_exec_fuzzer +// +// Threat model (see fuzzing/README.md and the coverage-pipeline report): +// Behavior trees are frequently loaded from untrusted sources (files, network +// APIs, editor payloads). The existing bt_fuzzer covers XML *parsing* only: +// it never ticks a tree, so the whole execution engine (control nodes, +// decorators, tree_node/behavior_tree state machines, ports at runtime, +// substitution rules, wake-up/timer machinery) is dark in production coverage. +// +// This harness generates *structurally valid* XML over the builtin node set, +// creates the tree, and actually drives it: repeated tickRoot() calls until a +// terminal status, halt() while RUNNING, re-tick after halt, SubTree +// references, scripting enums, substitution rules (incl. wildcard filters) and +// a typed blackboard. All generated values are bounded (depth, children, +// Repeat/Retry counts never -1, Sleep/Timeout/Delay/Delayed-Test in the low +// milliseconds) so that no input can make the harness hang or blow memory. +// +// Expected bug classes: state-machine logic violations, use-after-free/dangling +// node handling on halt-while-RUNNING, out-of-bounds child indexing in control +// nodes, numeric parsing of attributes, scripting runtime errors. + +#include "behaviortree_cpp/blackboard.h" +#include "behaviortree_cpp/bt_factory.h" +#include "behaviortree_cpp/xml_parsing.h" + +#include +#include +#include +#include + +#include + +namespace +{ + +constexpr int kMaxDepth = 3; +constexpr unsigned kMaxTicks = 8; + +const char* kNames[] = { "n1", "act", "cond", "root_task", "aux" }; + +// Scripts used by Script/ScriptCondition/Precondition/TestNode. All short, +// terminating and side-effect limited to the blackboard. +const char* kConditionScripts[] = { "var > 0", "var < 5", "flag", "!flag", + "dbl < 10.0", "str == 'abc'", "var == MODE_A" }; +const char* kActionScripts[] = { "var = var + 1", "var = 3", "var = var - 1", + "str = 'fuzz'", "dbl = var * 0.5", "flag = !flag", + "var = MODE_A", "flag = var > 2" }; +const char* kLiterals[] = { "1", "2", "true", "false", "1.5", "hello", "{var}" }; +const char* kStatuses[] = { "SUCCESS", "FAILURE", "SKIPPED" }; + +std::string pickName(FuzzedDataProvider& fdp) +{ + return fdp.PickValueInArray(kNames); +} + +std::string pickScript(FuzzedDataProvider& fdp) +{ + return fdp.PickValueInArray(kActionScripts); +} + +std::string pickCondition(FuzzedDataProvider& fdp) +{ + return fdp.PickValueInArray(kConditionScripts); +} + +// A condition-like leaf: ScriptCondition / AlwaysSuccess / AlwaysFailure. +std::string genConditionLeaf(FuzzedDataProvider& fdp) +{ + switch(fdp.ConsumeIntegralInRange(0, 2)) + { + case 0: + return ""; + case 1: + return ""; + default: + return ""; + } +} + +// Leaf actions. Occasionally blackboard-remap a port ("{key}" syntax). +std::string genLeaf(FuzzedDataProvider& fdp) +{ + switch(fdp.ConsumeIntegralInRange(0, 8)) + { + case 0: + return ""; + case 1: + return ""; + case 2: + return "