src/lib/VSDXParser.cpp             |   40 +++++++++++++++++++++++++++++++------
 src/lib/VSDXParser.h               |    4 +++
 src/test/Makefile.am               |    3 +-
 src/test/data/recursion-cycle.vsdx |binary
 src/test/importtest.cpp            |   22 ++++++++++++++++++++
 5 files changed, 62 insertions(+), 7 deletions(-)

New commits:
commit ec5dc2dcbb97006cd1d2dbbee608934b08c33b04
Author:     Caolán McNamara <[email protected]>
AuthorDate: Fri May 22 09:49:02 2026 +0000
Commit:     Caolán McNamara <[email protected]>
CommitDate: Fri May 22 13:07:09 2026 +0200

    avoid infinite depth page/master relationships parse
    
    Track per-target visited parts on the parsePage / parseMaster
    recursion edge and refuse to re-enter a part already on the stack.
    
    Same shape as the visited-set of:
    
    commit d095eb768180f86df4f939d86f3debf3a7078872
    Date:   Mon May 15 16:39:58 2017 +0200
    
        ofz#1000 avoid stack overflow
    
    Change-Id: I26a864bba701fd03b4e36d6fcea087933428a411
    Reviewed-on: https://gerrit.libreoffice.org/c/libvisio/+/205554
    Reviewed-by: Caolán McNamara <[email protected]>
    Tested-by: Caolán McNamara <[email protected]>

diff --git a/src/lib/VSDXParser.cpp b/src/lib/VSDXParser.cpp
index 4d47f6e..ba977ad 100644
--- a/src/lib/VSDXParser.cpp
+++ b/src/lib/VSDXParser.cpp
@@ -335,15 +335,43 @@ void 
libvisio::VSDXParser::processXmlDocument(librevenge::RVNGInputStream *input
               std::string type = rel->getType();
               if (type == 
"http://schemas.microsoft.com/visio/2010/relationships/master";)
               {
-                m_currentDepth += xmlTextReaderDepth(reader.get());
-                parseMaster(m_input, rel->getTarget().c_str());
-                m_currentDepth -= xmlTextReaderDepth(reader.get());
+                const std::string target = rel->getTarget();
+                const auto inserted = m_visitedParts.insert(target);
+                if (inserted.second)
+                {
+                  m_currentDepth += xmlTextReaderDepth(reader.get());
+                  try
+                  {
+                    parseMaster(m_input, target.c_str());
+                  }
+                  catch (...)
+                  {
+                    m_visitedParts.erase(inserted.first);
+                    throw;
+                  }
+                  m_currentDepth -= xmlTextReaderDepth(reader.get());
+                  m_visitedParts.erase(inserted.first);
+                }
               }
               else if (type == 
"http://schemas.microsoft.com/visio/2010/relationships/page";)
               {
-                m_currentDepth += xmlTextReaderDepth(reader.get());
-                parsePage(m_input, rel->getTarget().c_str());
-                m_currentDepth -= xmlTextReaderDepth(reader.get());
+                const std::string target = rel->getTarget();
+                const auto inserted = m_visitedParts.insert(target);
+                if (inserted.second)
+                {
+                  m_currentDepth += xmlTextReaderDepth(reader.get());
+                  try
+                  {
+                    parsePage(m_input, target.c_str());
+                  }
+                  catch (...)
+                  {
+                    m_visitedParts.erase(inserted.first);
+                    throw;
+                  }
+                  m_currentDepth -= xmlTextReaderDepth(reader.get());
+                  m_visitedParts.erase(inserted.first);
+                }
               }
               else if (type == 
"http://schemas.openxmlformats.org/officeDocument/2006/relationships/image";)
               {
diff --git a/src/lib/VSDXParser.h b/src/lib/VSDXParser.h
index d8d0686..4b48814 100644
--- a/src/lib/VSDXParser.h
+++ b/src/lib/VSDXParser.h
@@ -10,6 +10,8 @@
 #ifndef __VSDXPARSER_H__
 #define __VSDXPARSER_H__
 
+#include <set>
+#include <string>
 #include <librevenge/librevenge.h>
 #include "VSDXTheme.h"
 #include "VSDXMLParserBase.h"
@@ -86,6 +88,8 @@ private:
   int m_currentDepth;
   VSDXRelationships *m_rels;
   VSDXTheme m_currentTheme;
+  // parsePage / parseMaster targets currently on the recursion stack
+  std::set<std::string> m_visitedParts;
 };
 
 } // namespace libvisio
diff --git a/src/test/Makefile.am b/src/test/Makefile.am
index 6f0928c..7889ede 100644
--- a/src/test/Makefile.am
+++ b/src/test/Makefile.am
@@ -89,7 +89,8 @@ EXTRA_DIST = \
        data/testfile3.vsdx \
        data/testfile4.vsdx \
        data/testfile5.vsdx \
-       data/testfile6.vsdx
+       data/testfile6.vsdx \
+       data/recursion-cycle.vsdx
 
 # ImportTest::testVsdMetadataTitleUtf8 checks formatted date string
 AM_TESTS_ENVIRONMENT = TZ=UTC; export TZ;
diff --git a/src/test/data/recursion-cycle.vsdx 
b/src/test/data/recursion-cycle.vsdx
new file mode 100644
index 0000000..fc74677
Binary files /dev/null and b/src/test/data/recursion-cycle.vsdx differ
diff --git a/src/test/importtest.cpp b/src/test/importtest.cpp
index 2d44159..4054929 100644
--- a/src/test/importtest.cpp
+++ b/src/test/importtest.cpp
@@ -235,6 +235,8 @@ class ImportTest : public CPPUNIT_NS::TestFixture
   CPPUNIT_TEST(testVsdxFillStylesFromTheme5);
   CPPUNIT_TEST(testVsdxFillStylesFromTheme6);
 
+  CPPUNIT_TEST(testVsdxPageSelfReferenceCycle);
+
   CPPUNIT_TEST_SUITE_END();
 
   void testVsd6Textfields();
@@ -267,6 +269,8 @@ class ImportTest : public CPPUNIT_NS::TestFixture
   void testVsdxFillStylesFromTheme5();
   void testVsdxFillStylesFromTheme6();
 
+  void testVsdxPageSelfReferenceCycle();
+
   xmlBufferPtr m_buffer;
   xmlDocPtr m_doc;
 
@@ -653,6 +657,24 @@ void ImportTest::testVsdxFillStylesFromTheme6()
   assertXPath(m_doc, "/document/page/layer[3]//setStyle[2]", "fill-color", 
"#feffff");
 }
 
+// pages.xml.rels points at pages.xml - must not recurse forever
+void ImportTest::testVsdxPageSelfReferenceCycle()
+{
+  librevenge::RVNGString path(TDOC "/recursion-cycle.vsdx");
+  librevenge::RVNGFileStream input(path.cstr());
+  CPPUNIT_ASSERT(libvisio::VisioDocument::isSupported(&input));
+
+  xmlTextWriterPtr writer = xmlNewTextWriterMemory(m_buffer, 0);
+  CPPUNIT_ASSERT(writer);
+  xmlTextWriterStartDocument(writer, 0, 0, 0);
+  libvisio::XmlDrawingGenerator painter(writer);
+
+  (void)libvisio::VisioDocument::parse(&input, &painter);
+
+  xmlTextWriterEndDocument(writer);
+  xmlFreeTextWriter(writer);
+}
+
 CPPUNIT_TEST_SUITE_REGISTRATION(ImportTest);
 
 /* vim:set shiftwidth=2 softtabstop=2 expandtab: */

Reply via email to