@@ -563,3 +563,170 @@ TEST(BehaviorTreeFactory, FactoryDestroyedBeforeTick)
563563 // when getInput() looked up the port in the manifest.
564564 EXPECT_EQ (BT::NodeStatus::SUCCESS, tree.tickWhileRunning ());
565565}
566+
567+ // Regression tests for issue #672: stack buffer overflow in
568+ // xml_parsing.cpp when parsing malformed/pathological XML.
569+ // In v3 a fuzz test triggered a stack-buffer-overflow via ASAN in
570+ // the BehaviorTree element iteration loop with recursiveStep.
571+ // In v4 the parser was rewritten, but the recursive validation and
572+ // instantiation paths can still overflow the stack with deeply nested
573+ // input. The fix adds a depth limit.
574+
575+ TEST (BehaviorTreeFactory, MalformedXML_InvalidRoot)
576+ {
577+ // XML that is not valid XML at all
578+ BehaviorTreeFactory factory;
579+ EXPECT_ANY_THROW (factory.createTreeFromText (" <not valid xml!!!" ));
580+ }
581+
582+ TEST (BehaviorTreeFactory, MalformedXML_MissingRootElement)
583+ {
584+ // Well-formed XML but missing <root> element
585+ const char * xml = R"(
586+ <something BTCPP_format="4">
587+ <BehaviorTree ID="Main">
588+ <AlwaysSuccess/>
589+ </BehaviorTree>
590+ </something>)" ;
591+
592+ BehaviorTreeFactory factory;
593+ EXPECT_THROW (factory.createTreeFromText (xml), RuntimeError);
594+ }
595+
596+ TEST (BehaviorTreeFactory, MalformedXML_EmptyBehaviorTree)
597+ {
598+ // BehaviorTree element with no children
599+ const char * xml = R"(
600+ <root BTCPP_format="4">
601+ <BehaviorTree ID="Main">
602+ </BehaviorTree>
603+ </root>)" ;
604+
605+ BehaviorTreeFactory factory;
606+ EXPECT_THROW (factory.createTreeFromText (xml), RuntimeError);
607+ }
608+
609+ TEST (BehaviorTreeFactory, MalformedXML_EmptyBehaviorTreeID)
610+ {
611+ // BehaviorTree element with empty ID when multiple trees exist
612+ const char * xml = R"(
613+ <root BTCPP_format="4">
614+ <BehaviorTree ID="">
615+ <AlwaysSuccess/>
616+ </BehaviorTree>
617+ <BehaviorTree ID="Other">
618+ <AlwaysSuccess/>
619+ </BehaviorTree>
620+ </root>)" ;
621+
622+ BehaviorTreeFactory factory;
623+ EXPECT_THROW (factory.createTreeFromText (xml), RuntimeError);
624+ }
625+
626+ TEST (BehaviorTreeFactory, MalformedXML_MissingBehaviorTreeID)
627+ {
628+ // Multiple BehaviorTree elements without IDs
629+ const char * xml = R"(
630+ <root BTCPP_format="4">
631+ <BehaviorTree>
632+ <AlwaysSuccess/>
633+ </BehaviorTree>
634+ <BehaviorTree>
635+ <AlwaysFailure/>
636+ </BehaviorTree>
637+ </root>)" ;
638+
639+ BehaviorTreeFactory factory;
640+ EXPECT_THROW (factory.createTreeFromText (xml), RuntimeError);
641+ }
642+
643+ TEST (BehaviorTreeFactory, MalformedXML_DeeplyNestedElements)
644+ {
645+ // Generate XML with nesting depth that exceeds the limit (256).
646+ // This should throw a readable exception rather than crash with
647+ // a stack overflow.
648+ std::string xml = R"( <root BTCPP_format="4"><BehaviorTree ID="Main">)" ;
649+ const int depth = 300 ;
650+ for (int i = 0 ; i < depth; i++)
651+ {
652+ xml += " <Sequence>" ;
653+ }
654+ xml += " <AlwaysSuccess/>" ;
655+ for (int i = 0 ; i < depth; i++)
656+ {
657+ xml += " </Sequence>" ;
658+ }
659+ xml += " </BehaviorTree></root>" ;
660+
661+ BehaviorTreeFactory factory;
662+ EXPECT_THROW (factory.createTreeFromText (xml), RuntimeError);
663+ }
664+
665+ TEST (BehaviorTreeFactory, MalformedXML_ModerateNestingIsOK)
666+ {
667+ // Nesting depth well within the limit should succeed.
668+ std::string xml = R"( <root BTCPP_format="4"><BehaviorTree ID="Main">)" ;
669+ const int depth = 50 ;
670+ for (int i = 0 ; i < depth; i++)
671+ {
672+ xml += " <Sequence>" ;
673+ }
674+ xml += " <AlwaysSuccess/>" ;
675+ for (int i = 0 ; i < depth; i++)
676+ {
677+ xml += " </Sequence>" ;
678+ }
679+ xml += " </BehaviorTree></root>" ;
680+
681+ BehaviorTreeFactory factory;
682+ // Should not throw
683+ EXPECT_NO_THROW (factory.createTreeFromText (xml));
684+ }
685+
686+ TEST (BehaviorTreeFactory, MalformedXML_MultipleBTChildElements)
687+ {
688+ // BehaviorTree with more than one child element
689+ const char * xml = R"(
690+ <root BTCPP_format="4">
691+ <BehaviorTree ID="Main">
692+ <AlwaysSuccess/>
693+ <AlwaysFailure/>
694+ </BehaviorTree>
695+ </root>)" ;
696+
697+ BehaviorTreeFactory factory;
698+ EXPECT_THROW (factory.createTreeFromText (xml), RuntimeError);
699+ }
700+
701+ TEST (BehaviorTreeFactory, MalformedXML_CompletelyEmpty)
702+ {
703+ // Completely empty string
704+ BehaviorTreeFactory factory;
705+ EXPECT_ANY_THROW (factory.createTreeFromText (" " ));
706+ }
707+
708+ TEST (BehaviorTreeFactory, MalformedXML_EmptyRoot)
709+ {
710+ // Root element with no children at all
711+ const char * xml = R"( <root BTCPP_format="4"></root>)" ;
712+
713+ BehaviorTreeFactory factory;
714+ // No BehaviorTree elements: registering succeeds but creating
715+ // a tree should fail because there is nothing to instantiate.
716+ factory.registerBehaviorTreeFromText (xml);
717+ EXPECT_ANY_THROW (factory.createTree (" MainTree" ));
718+ }
719+
720+ TEST (BehaviorTreeFactory, MalformedXML_UnknownNodeType)
721+ {
722+ // Reference to a node type that is not registered
723+ const char * xml = R"(
724+ <root BTCPP_format="4">
725+ <BehaviorTree ID="Main">
726+ <NonExistentNodeType/>
727+ </BehaviorTree>
728+ </root>)" ;
729+
730+ BehaviorTreeFactory factory;
731+ EXPECT_THROW (factory.createTreeFromText (xml), RuntimeError);
732+ }
0 commit comments