src/lib/VSDXMLHelper.cpp      |    6 ++-
 src/test/Makefile.am          |    5 ++
 src/test/VSDXMLHelperTest.cpp |   73 ++++++++++++++++++++++++++++++++++++++++++
 3 files changed, 82 insertions(+), 2 deletions(-)

New commits:
commit 8a326b11a267bd6daa0d1d5b9cbe027cbc08a1bd
Author:     Caolán McNamara <[email protected]>
AuthorDate: Fri May 22 10:00:46 2026 +0100
Commit:     Caolán McNamara <[email protected]>
CommitDate: Fri May 22 11:10:31 2026 +0200

    guard against a malformed Target with excess .. segments
    
    Change-Id: I04cbe356e4263b74d882932751d72a9c607c1ec6
    Reviewed-on: https://gerrit.libreoffice.org/c/libvisio/+/205519
    Reviewed-by: Caolán McNamara <[email protected]>
    Tested-by: Caolán McNamara <[email protected]>

diff --git a/src/lib/VSDXMLHelper.cpp b/src/lib/VSDXMLHelper.cpp
index 24045b6..9cc198b 100644
--- a/src/lib/VSDXMLHelper.cpp
+++ b/src/lib/VSDXMLHelper.cpp
@@ -66,7 +66,11 @@ void libvisio::VSDXRelationship::rebaseTarget(const char 
*baseDir)
   for (auto &segment : segments)
   {
     if (segment == "..")
-      normalizedSegments.pop_back();
+    {
+      // guard against a malformed Target with excess ".." segments
+      if (!normalizedSegments.empty())
+        normalizedSegments.pop_back();
+    }
     else if (segment != "." && !segment.empty())
       normalizedSegments.push_back(segment);
   }
diff --git a/src/test/Makefile.am b/src/test/Makefile.am
index 97b7e83..6f0928c 100644
--- a/src/test/Makefile.am
+++ b/src/test/Makefile.am
@@ -33,6 +33,7 @@ importtest_SOURCES = \
 unittest_CPPFLAGS = \
        -I$(top_srcdir)/src/lib \
        $(LIBVISIO_CXXFLAGS) \
+       $(REVENGE_STREAM_CFLAGS) \
        $(CPPUNIT_CFLAGS) \
        $(DEBUG_CXXFLAGS)
 
@@ -41,10 +42,12 @@ unittest_LDADD = \
        $(top_builddir)/src/lib/libvisio-internal.la \
        libtest_driver.la \
        $(LIBVISIO_LIBS) \
+       $(REVENGE_STREAM_LIBS) \
        $(CPPUNIT_LIBS)
 
 unittest_SOURCES = \
-       VSDInternalStreamTest.cpp
+       VSDInternalStreamTest.cpp \
+       VSDXMLHelperTest.cpp
 
 EXTRA_DIST = \
        data/Visio11FormatLine.vsd \
diff --git a/src/test/VSDXMLHelperTest.cpp b/src/test/VSDXMLHelperTest.cpp
new file mode 100644
index 0000000..5adfe9c
--- /dev/null
+++ b/src/test/VSDXMLHelperTest.cpp
@@ -0,0 +1,73 @@
+/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/*
+ * This file is part of the libvisio project.
+ *
+ * This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/.
+ */
+
+#include <cppunit/TestFixture.h>
+#include <cppunit/extensions/HelperMacros.h>
+
+#include <librevenge-stream/librevenge-stream.h>
+
+#include "VSDXMLHelper.h"
+
+namespace test
+{
+
+class VSDXMLHelperTest : public CPPUNIT_NS::TestFixture
+{
+public:
+  virtual void setUp();
+  virtual void tearDown();
+
+private:
+  CPPUNIT_TEST_SUITE(VSDXMLHelperTest);
+  CPPUNIT_TEST(testRebaseTargetOverPop);
+  CPPUNIT_TEST_SUITE_END();
+
+private:
+  void testRebaseTargetOverPop();
+};
+
+void VSDXMLHelperTest::setUp()
+{
+}
+
+void VSDXMLHelperTest::tearDown()
+{
+}
+
+// A Target whose ".." segments would walk past the base directory must not
+// crash. Previously the segment walk called pop_back() on an empty vector,
+// which is undefined behaviour and triggered a heap-use-after-free.
+void VSDXMLHelperTest::testRebaseTargetOverPop()
+{
+  static const char rels[] =
+    "<?xml version=\"1.0\" encoding=\"UTF-8\" standalone=\"yes\"?>"
+    "<Relationships 
xmlns=\"http://schemas.openxmlformats.org/package/2006/relationships\";>"
+    "<Relationship Id=\"rId1\""
+    " Type=\"http://schemas.microsoft.com/visio/2010/relationships/page\"";
+    " Target=\"../../trigger\"/>"
+    "</Relationships>";
+
+  librevenge::RVNGStringStream input(reinterpret_cast<const unsigned char 
*>(rels),
+                                     sizeof(rels) - 1);
+  libvisio::VSDXRelationships parsed(&input);
+  parsed.rebaseTargets("visio");
+
+  const libvisio::VSDXRelationship *r = parsed.getRelationshipById("rId1");
+  CPPUNIT_ASSERT(r != nullptr);
+  // The over-deep ".." segments are silently dropped; what survives is the
+  // tail of the path. The exact result is not the point of the test - the
+  // point is that the rebase did not crash.
+  CPPUNIT_ASSERT_EQUAL(std::string("trigger"), r->getTarget());
+}
+
+CPPUNIT_TEST_SUITE_REGISTRATION(VSDXMLHelperTest);
+
+}
+
+/* vim:set shiftwidth=2 softtabstop=2 expandtab: */

Reply via email to