From b345db464bbf97521f46b0eba4ae55d4f54d0688 Mon Sep 17 00:00:00 2001 From: Arthur Chan Date: Tue, 3 Nov 2026 08:23:29 +0000 Subject: [PATCH 1/2] OSS-Fuzz: Create new fuzzer/corpus/dict targets xml processing Signed-off-by: Arthur Chan --- cmake/fuzzing_build.cmake | 2 +- fuzzing/bt_xml_roundtrip_fuzzer.cpp | 309 ++++++++++++++++++ .../bt_xml_roundtrip_fuzzer/raw-malformed-xml | Bin 0 -> 56 bytes .../bt_xml_roundtrip_fuzzer/raw-model-doc | Bin 0 -> 153 bytes .../bt_xml_roundtrip_fuzzer/raw-valid-xml | Bin 0 -> 388 bytes .../roundtrip-model-section | Bin 0 -> 14 bytes .../roundtrip-single-tree | Bin 0 -> 16 bytes .../roundtrip-subtree-model | Bin 0 -> 15 bytes .../dictionaries/bt_xml_roundtrip_fuzzer.dict | 36 ++ 9 files changed, 346 insertions(+), 1 deletion(-) create mode 100644 fuzzing/bt_xml_roundtrip_fuzzer.cpp create mode 100644 fuzzing/corpus/bt_xml_roundtrip_fuzzer/raw-malformed-xml create mode 100644 fuzzing/corpus/bt_xml_roundtrip_fuzzer/raw-model-doc create mode 100644 fuzzing/corpus/bt_xml_roundtrip_fuzzer/raw-valid-xml create mode 100644 fuzzing/corpus/bt_xml_roundtrip_fuzzer/roundtrip-model-section create mode 100644 fuzzing/corpus/bt_xml_roundtrip_fuzzer/roundtrip-single-tree create mode 100644 fuzzing/corpus/bt_xml_roundtrip_fuzzer/roundtrip-subtree-model create mode 100644 fuzzing/dictionaries/bt_xml_roundtrip_fuzzer.dict diff --git a/cmake/fuzzing_build.cmake b/cmake/fuzzing_build.cmake index 43f367001..45f21c282 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_xml_roundtrip_fuzzer) foreach(fuzzer ${FUZZERS}) add_executable(${fuzzer} fuzzing/${fuzzer}.cpp) diff --git a/fuzzing/bt_xml_roundtrip_fuzzer.cpp b/fuzzing/bt_xml_roundtrip_fuzzer.cpp new file mode 100644 index 000000000..c37bce5d1 --- /dev/null +++ b/fuzzing/bt_xml_roundtrip_fuzzer.cpp @@ -0,0 +1,309 @@ +// Fuzz target: bt_xml_roundtrip_fuzzer +// +// Threat model: BehaviorTree.CPP both *reads* XML from untrusted sources and +// *writes* XML that other tools re-read (Groot2 project files, exported +// trees, persisted models). A divergence between writer and reader is a bug +// on its own; the writer path (WriteTreeToXML, writeTreeNodesModelXML, +// writeTreeXSD) and the model/manifest reading paths are dark in production +// coverage (xml_parsing.cpp 50.6%, tinyxml2 writer paths unexercised). +// +// Strategy: +// mode 0: raw fuzz bytes into VerifyXML / registerBehaviorTreeFromText +// (parser error and recovery paths), +// modes 1-3: generate a *valid* document (optionally with two trees or a +// section), build the tree, export it with +// WriteTreeToXML (both metadata/builtin-model flags), re-parse the +// export in a fresh factory, export again, and additionally +// generate + re-verify the nodes-model XML and the XSD writer. +// +// All generation is bounded exactly as in bt_tree_exec_fuzzer (depth, child +// counts, no infinite loops, no real sleeps). No tree is ticked here: the +// tick engine belongs to bt_tree_exec_fuzzer; this target owns the parse and +// serialize surface. +// +// Expected bug classes: memory-safety in tinyxml2 writer/reader paths, +// XML attribute escaping/unescaping mismatches, logic divergence between +// export and re-import (lost or duplicated attributes/ports). + +#include "behaviortree_cpp/bt_factory.h" +#include "behaviortree_cpp/blackboard.h" +#include "behaviortree_cpp/xml_parsing.h" + +#include + +#include +#include +#include + +namespace +{ + +constexpr int kMaxDepth = 3; + +const char* kConditionScripts[] = { + "var > 0", "var < 5", "flag", "!flag", "str == 'abc'", "var == MODE_A" +}; +const char* kLiterals[] = { "1", "2", "true", "false", "1.5", "hello", "{var}" }; +const char* kStatuses[] = { "SUCCESS", "FAILURE", "SKIPPED" }; + +std::string pickCondition(FuzzedDataProvider& fdp) +{ + return fdp.PickValueInArray(kConditionScripts); +} + +std::string genConditionLeaf(FuzzedDataProvider& fdp) +{ + switch(fdp.ConsumeIntegralInRange(0, 2)) + { + case 0: + return ""; + case 1: + return ""; + default: + return ""; + } +} + +std::string genLeaf(FuzzedDataProvider& fdp) +{ + switch(fdp.ConsumeIntegralInRange(0, 8)) + { + case 0: + return ""; + case 1: + return ""; + case 2: + return "