From f8399e62a31095bf1ced01827c33f9b29494046f Mon Sep 17 00:00:00 2001 From: Daniel Garcia Moreno Date: Fri, 19 Dec 2025 12:27:54 +0100 Subject: [PATCH] testcatalog: Add new tests for catalog.c Adds a new test program to run specific tests related to catalog parsing. This initial version includes a couple of tests, the first one to check the infinite recursion detection related to: https://gitlab.gnome.org/GNOME/libxml2/-/issues/1018. The second one tests the nextCatalog element repeated parsing, related to: https://gitlab.gnome.org/GNOME/libxml2/-/issues/1019 https://gitlab.gnome.org/GNOME/libxml2/-/issues/1040 CVE: CVE-2026-0992 Upstream-Status: Backport [https://gitlab.gnome.org/GNOME/libxml2/-/commit/f8399e62a31095bf1ced01827c33f9b29494046f] Signed-off-by: Peter Marko --- CMakeLists.txt | 2 + Makefile.am | 8 ++- catalog.c | 63 +++++++++++----- include/libxml/catalog.h | 2 + test/catalogs/catalog-recursive.xml | 3 + test/catalogs/repeated-next-catalog.xml | 10 +++ testcatalog.c | 96 +++++++++++++++++++++++++ 7 files changed, 164 insertions(+), 20 deletions(-) create mode 100644 test/catalogs/catalog-recursive.xml create mode 100644 test/catalogs/repeated-next-catalog.xml create mode 100644 testcatalog.c diff --git a/CMakeLists.txt b/CMakeLists.txt index 163661f8..7d5702df 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -555,6 +555,7 @@ if(LIBXML2_WITH_TESTS) testapi testAutomata testC14N + testcatalog testchar testdict testHTML @@ -579,6 +580,7 @@ if(LIBXML2_WITH_TESTS) if(NOT WIN32) add_test(NAME testapi COMMAND testapi) endif() + add_test(NAME testcatalog COMMAND testcatalog) add_test(NAME testchar COMMAND testchar) add_test(NAME testdict COMMAND testdict) add_test(NAME testrecurse COMMAND testrecurse WORKING_DIRECTORY ${CMAKE_CURRENT_SOURCE_DIR}) diff --git a/Makefile.am b/Makefile.am index c51dfd8e..c794eac8 100644 --- a/Makefile.am +++ b/Makefile.am @@ -12,7 +12,7 @@ AM_CFLAGS = $(EXTRA_CFLAGS) $(THREAD_CFLAGS) $(Z_CFLAGS) $(LZMA_CFLAGS) check_PROGRAMS=testSchemas testRelax testSAX testHTML testXPath testURI \ testThreads testC14N testAutomata testRegexp \ - testReader testapi testModule runtest runsuite testchar \ + testReader testapi testModule runtest runsuite testcatalog testchar \ testdict runxmlconf testrecurse testlimits bin_PROGRAMS = xmllint xmlcatalog @@ -81,6 +81,11 @@ testlimits_LDFLAGS = testlimits_DEPENDENCIES = $(DEPS) testlimits_LDADD= $(BASE_THREAD_LIBS) $(RDL_LIBS) $(LDADDS) +testcatalog_SOURCES=testcatalog.c +testcatalog_LDFLAGS = +testcatalog_DEPENDENCIES = $(DEPS) +testcatalog_LDADD= $(LDADDS) + testchar_SOURCES=testchar.c testchar_LDFLAGS = testchar_DEPENDENCIES = $(DEPS) @@ -213,6 +218,7 @@ runtests: $(CHECKER) ./runtest$(EXEEXT) && \ $(CHECKER) ./testrecurse$(EXEEXT) && \ ASAN_OPTIONS="$$ASAN_OPTIONS:detect_leaks=0" $(CHECKER) ./testapi$(EXEEXT) && \ + $(CHECKER) ./testcatalog$(EXEEXT) \ $(CHECKER) ./testchar$(EXEEXT) && \ $(CHECKER) ./testdict$(EXEEXT) && \ $(CHECKER) ./runxmlconf$(EXEEXT) diff --git a/catalog.c b/catalog.c index 401dbc14..eb889162 100644 --- a/catalog.c +++ b/catalog.c @@ -658,43 +658,54 @@ static void xmlDumpXMLCatalogNode(xmlCatalogEntryPtr catal, xmlNodePtr catalog, } } -static int -xmlDumpXMLCatalog(FILE *out, xmlCatalogEntryPtr catal) { - int ret; - xmlDocPtr doc; +static xmlDocPtr +xmlDumpXMLCatalogToDoc(xmlCatalogEntryPtr catal) { xmlNsPtr ns; xmlDtdPtr dtd; xmlNodePtr catalog; - xmlOutputBufferPtr buf; + xmlDocPtr doc = xmlNewDoc(NULL); + if (doc == NULL) { + return(NULL); + } - /* - * Rebuild a catalog - */ - doc = xmlNewDoc(NULL); - if (doc == NULL) - return(-1); dtd = xmlNewDtd(doc, BAD_CAST "catalog", - BAD_CAST "-//OASIS//DTD Entity Resolution XML Catalog V1.0//EN", -BAD_CAST "http://www.oasis-open.org/committees/entity/release/1.0/catalog.dtd"); + BAD_CAST "-//OASIS//DTD Entity Resolution XML Catalog V1.0//EN", + BAD_CAST "http://www.oasis-open.org/committees/entity/release/1.0/catalog.dtd"); xmlAddChild((xmlNodePtr) doc, (xmlNodePtr) dtd); ns = xmlNewNs(NULL, XML_CATALOGS_NAMESPACE, NULL); if (ns == NULL) { - xmlFreeDoc(doc); - return(-1); + xmlFreeDoc(doc); + return(NULL); } catalog = xmlNewDocNode(doc, ns, BAD_CAST "catalog", NULL); if (catalog == NULL) { - xmlFreeNs(ns); - xmlFreeDoc(doc); - return(-1); + xmlFreeDoc(doc); + xmlFreeNs(ns); + return(NULL); } catalog->nsDef = ns; xmlAddChild((xmlNodePtr) doc, catalog); - xmlDumpXMLCatalogNode(catal, catalog, doc, ns, NULL); + return(doc); +} + +static int +xmlDumpXMLCatalog(FILE *out, xmlCatalogEntryPtr catal) { + int ret; + xmlDocPtr doc; + xmlOutputBufferPtr buf; + + /* + * Rebuild a catalog + */ + doc = xmlDumpXMLCatalogToDoc(catal); + if (doc == NULL) { + return(-1); + } + /* * reserialize it */ @@ -3430,6 +3441,20 @@ xmlCatalogDump(FILE *out) { xmlACatalogDump(xmlDefaultCatalog, out); } + +/** + * Dump all the global catalog content as a xmlDoc + * This function is just for testing/debugging purposes + * + * @returns The catalog as xmlDoc or NULL if failed, it must be freed by the caller. + */ +xmlDocPtr +xmlCatalogDumpDoc(void) { + if (!xmlCatalogInitialized) + xmlInitializeCatalog(); + + return xmlDumpXMLCatalogToDoc(xmlDefaultCatalog->xml); +} #endif /* LIBXML_OUTPUT_ENABLED */ /** diff --git a/include/libxml/catalog.h b/include/libxml/catalog.h index 88a7483c..e1bc5feb 100644 --- a/include/libxml/catalog.h +++ b/include/libxml/catalog.h @@ -119,6 +119,8 @@ XMLPUBFUN void XMLCALL #ifdef LIBXML_OUTPUT_ENABLED XMLPUBFUN void XMLCALL xmlCatalogDump (FILE *out); +XMLPUBFUN xmlDocPtr + xmlCatalogDumpDoc (void); #endif /* LIBXML_OUTPUT_ENABLED */ XMLPUBFUN xmlChar * XMLCALL xmlCatalogResolve (const xmlChar *pubID, diff --git a/test/catalogs/catalog-recursive.xml b/test/catalogs/catalog-recursive.xml new file mode 100644 index 00000000..3b3d03f9 --- /dev/null +++ b/test/catalogs/catalog-recursive.xml @@ -0,0 +1,3 @@ + + + diff --git a/test/catalogs/repeated-next-catalog.xml b/test/catalogs/repeated-next-catalog.xml new file mode 100644 index 00000000..76d34c3c --- /dev/null +++ b/test/catalogs/repeated-next-catalog.xml @@ -0,0 +1,10 @@ + + + + + + + + + + diff --git a/testcatalog.c b/testcatalog.c new file mode 100644 index 00000000..86d33bd0 --- /dev/null +++ b/testcatalog.c @@ -0,0 +1,96 @@ +/* + * testcatalog.c: C program to run libxml2 catalog.c unit tests + * + * To compile on Unixes: + * cc -o testcatalog `xml2-config --cflags` testcatalog.c `xml2-config --libs` -lpthread + * + * See Copyright for the status of this software. + * + * Author: Daniel Garcia + */ + + +#include "libxml.h" +#include + +#ifdef LIBXML_CATALOG_ENABLED +#include + +/* Test catalog resolve uri with recursive catalog */ +static int +testRecursiveDelegateUri(void) { + int ret = 0; + const char *cat = "test/catalogs/catalog-recursive.xml"; + const char *entity = "/foo.ent"; + xmlChar *resolved = NULL; + + xmlInitParser(); + xmlLoadCatalog(cat); + + /* This should trigger recursive error */ + resolved = xmlCatalogResolveURI(BAD_CAST entity); + if (resolved != NULL) { + fprintf(stderr, "CATALOG-FAILURE: Catalog %s entity should fail to resolve\n", entity); + ret = 1; + } + xmlCatalogCleanup(); + + return ret; +} + +/* Test parsing repeated NextCatalog */ +static int +testRepeatedNextCatalog(void) { + int ret = 0; + int i = 0; + const char *cat = "test/catalogs/repeated-next-catalog.xml"; + const char *entity = "/foo.ent"; + xmlDocPtr doc = NULL; + xmlNodePtr node = NULL; + + xmlInitParser(); + + xmlLoadCatalog(cat); + /* To force the complete recursive load */ + xmlCatalogResolveURI(BAD_CAST entity); + /** + * Ensure that the doc doesn't contain the same nextCatalog + */ + doc = xmlCatalogDumpDoc(); + xmlCatalogCleanup(); + + if (doc == NULL) { + fprintf(stderr, "CATALOG-FAILURE: Failed to dump the catalog\n"); + return 1; + } + + /* Just the root "catalog" node with a series of nextCatalog */ + node = xmlDocGetRootElement(doc); + node = node->children; + for (i=0; node != NULL; node=node->next, i++) {} + if (i > 1) { + fprintf(stderr, "CATALOG-FAILURE: Found %d nextCatalog entries and should be 1\n", i); + ret = 1; + } + + xmlFreeDoc(doc); + + return ret; +} + +int +main(void) { + int err = 0; + + err |= testRecursiveDelegateUri(); + err |= testRepeatedNextCatalog(); + + return err; +} +#else +/* No catalog, so everything okay */ +int +main(void) { + return 0; +} +#endif