From 19549c61590c1873468c53e0026a2fbffae428ef Mon Sep 17 00:00:00 2001 From: Daniel Garcia Moreno Date: Fri, 10 Oct 2025 09:38:31 +0200 Subject: [PATCH] Add RelaxNG include limit This patch adds a default xmlRelaxNGIncludeLimit of 1.000, and that limit can be modified at runtime with the env variable RNG_INCLUDE_LIMIT. Fix https://gitlab.gnome.org/GNOME/libxml2/-/issues/998 --- include/libxml/relaxng.h | 4 ++ relaxng.c | 63 ++++++++++++++++++++-- runtest.c | 67 ++++++++++++++++++++++++ test/relaxng/include/include-limit.rng | 4 ++ test/relaxng/include/include-limit_1.rng | 4 ++ test/relaxng/include/include-limit_2.rng | 4 ++ test/relaxng/include/include-limit_3.rng | 8 +++ 7 files changed, 150 insertions(+), 4 deletions(-) create mode 100644 test/relaxng/include/include-limit.rng create mode 100644 test/relaxng/include/include-limit_1.rng create mode 100644 test/relaxng/include/include-limit_2.rng create mode 100644 test/relaxng/include/include-limit_3.rng Index: libxml2-2.14.5/include/libxml/relaxng.h =================================================================== --- libxml2-2.14.5.orig/include/libxml/relaxng.h +++ libxml2-2.14.5/include/libxml/relaxng.h @@ -139,6 +139,10 @@ XMLPUBFUN int xmlRelaxParserSetFlag (xmlRelaxNGParserCtxtPtr ctxt, int flag); +XMLPUBFUN int + xmlRelaxParserSetIncLImit (xmlRelaxNGParserCtxt *ctxt, + int limit); + XMLPUBFUN void xmlRelaxNGFreeParserCtxt (xmlRelaxNGParserCtxtPtr ctxt); XMLPUBFUN void Index: libxml2-2.14.5/relaxng.c =================================================================== --- libxml2-2.14.5.orig/relaxng.c +++ libxml2-2.14.5/relaxng.c @@ -18,6 +18,8 @@ #ifdef LIBXML_RELAXNG_ENABLED +#include +#include #include #include #include @@ -43,6 +45,12 @@ static const xmlChar *xmlRelaxNGNs = (const xmlChar *) "http://relaxng.org/ns/structure/1.0"; +/* + * Default include limit, this can be override with RNG_INCLUDE_LIMIT + * env variable + */ +static const int _xmlRelaxNGIncludeLimit = 1000; + #define IS_RELAXNG(node, typ) \ ((node != NULL) && (node->ns != NULL) && \ (node->type == XML_ELEMENT_NODE) && \ @@ -219,6 +227,7 @@ struct _xmlRelaxNGParserCtxt { int incNr; /* Depth of the include parsing stack */ int incMax; /* Max depth of the parsing stack */ xmlRelaxNGIncludePtr *incTab; /* array of incs */ + int incLimit; /* Include limit, to avoid stack-overflow on parse */ int idref; /* requires idref checking */ @@ -1405,6 +1414,23 @@ xmlRelaxParserSetFlag(xmlRelaxNGParserCt return(0); } +/** + * Semi private function used to set the include recursion limit to a + * parser context. Set to 0 to use the default value. + * + * @param ctxt a RelaxNG parser context + * @param limit the new include depth limit + * @returns 0 if success and -1 in case of error + */ +int +xmlRelaxParserSetIncLImit(xmlRelaxNGParserCtxt *ctxt, int limit) +{ + if (ctxt == NULL) return(-1); + if (limit < 0) return(-1); + ctxt->incLimit = limit; + return(0); +} + /************************************************************************ * * * Document functions * @@ -1462,7 +1488,7 @@ xmlRelaxReadMemory(xmlRelaxNGParserCtxtP * * Pushes a new include on top of the include stack * - * Returns 0 in case of error, the index in the stack otherwise + * @returns -1 in case of error, the index in the stack otherwise */ static int xmlRelaxNGIncludePush(xmlRelaxNGParserCtxtPtr ctxt, @@ -1476,9 +1502,15 @@ xmlRelaxNGIncludePush(xmlRelaxNGParserCt sizeof(ctxt->incTab[0])); if (ctxt->incTab == NULL) { xmlRngPErrMemory(ctxt); - return (0); + return (-1); } } + if (ctxt->incNr >= ctxt->incLimit) { + xmlRngPErr(ctxt, (xmlNodePtr)value->doc, XML_RNGP_PARSE_ERROR, + "xmlRelaxNG: inclusion recursion limit reached\n", NULL, NULL); + return(-1); + } + if (ctxt->incNr >= ctxt->incMax) { ctxt->incMax *= 2; ctxt->incTab = @@ -1487,7 +1519,7 @@ xmlRelaxNGIncludePush(xmlRelaxNGParserCt sizeof(ctxt->incTab[0])); if (ctxt->incTab == NULL) { xmlRngPErrMemory(ctxt); - return (0); + return (-1); } } ctxt->incTab[ctxt->incNr] = value; @@ -1657,7 +1689,9 @@ xmlRelaxNGLoadInclude(xmlRelaxNGParserCt /* * push it on the stack */ - xmlRelaxNGIncludePush(ctxt, ret); + if (xmlRelaxNGIncludePush(ctxt, ret) < 0) { + return (NULL); + } /* * Some preprocessing of the document content, this include recursing @@ -7381,11 +7415,32 @@ xmlRelaxNGParse(xmlRelaxNGParserCtxtPtr xmlDocPtr doc; xmlNodePtr root; + const char *include_limit_env = getenv("RNG_INCLUDE_LIMIT"); + xmlRelaxNGInitTypes(); if (ctxt == NULL) return (NULL); + if (ctxt->incLimit == 0) { + ctxt->incLimit = _xmlRelaxNGIncludeLimit; + if (include_limit_env != NULL) { + char *strEnd; + unsigned long val = 0; + errno = 0; + val = strtoul(include_limit_env, &strEnd, 10); + if (errno != 0 || *strEnd != 0 || val > INT_MAX) { + xmlRngPErr(ctxt, NULL, XML_RNGP_PARSE_ERROR, + "xmlRelaxNGParse: invalid RNG_INCLUDE_LIMIT %s\n", + (const xmlChar*)include_limit_env, + NULL); + return(NULL); + } + if (val) + ctxt->incLimit = val; + } + } + /* * First step is to parse the input document into an DOM/Infoset */ Index: libxml2-2.14.5/runtest.c =================================================================== --- libxml2-2.14.5.orig/runtest.c +++ libxml2-2.14.5/runtest.c @@ -3832,6 +3832,70 @@ rngTest(const char *filename, return(ret); } +/** + * Parse an RNG schemas with a custom RNG_INCLUDE_LIMIT + * + * @param filename the schemas file + * @param result the file with expected result + * @param err the file with error messages + * @returns 0 in case of success, an error code otherwise + */ +static int +rngIncludeTest(const char *filename, + const char *resul ATTRIBUTE_UNUSED, + const char *errr ATTRIBUTE_UNUSED, + int options ATTRIBUTE_UNUSED) { + xmlRelaxNGParserCtxtPtr ctxt; + xmlRelaxNGPtr schemas; + int ret = 0; + + /* first compile the schemas if possible */ + ctxt = xmlRelaxNGNewParserCtxt(filename); + xmlRelaxNGSetParserStructuredErrors(ctxt, testStructuredErrorHandler, + NULL); + + /* Should work */ + schemas = xmlRelaxNGParse(ctxt); + if (schemas == NULL) { + testErrorHandler(NULL, "Relax-NG schema %s failed to compile\n", + filename); + ret = -1; + goto done; + } + xmlRelaxNGFree(schemas); + xmlRelaxNGFreeParserCtxt(ctxt); + + ctxt = xmlRelaxNGNewParserCtxt(filename); + /* Should fail */ + xmlRelaxParserSetIncLImit(ctxt, 2); + xmlRelaxNGSetParserStructuredErrors(ctxt, testStructuredErrorHandler, + NULL); + schemas = xmlRelaxNGParse(ctxt); + if (schemas != NULL) { + ret = -1; + xmlRelaxNGFree(schemas); + } + xmlRelaxNGFreeParserCtxt(ctxt); + + ctxt = xmlRelaxNGNewParserCtxt(filename); + /* Should work */ + xmlRelaxParserSetIncLImit(ctxt, 3); + xmlRelaxNGSetParserStructuredErrors(ctxt, testStructuredErrorHandler, + NULL); + schemas = xmlRelaxNGParse(ctxt); + if (schemas == NULL) { + testErrorHandler(NULL, "Relax-NG schema %s failed to compile\n", + filename); + ret = -1; + goto done; + } + xmlRelaxNGFree(schemas); + +done: + xmlRelaxNGFreeParserCtxt(ctxt); + return(ret); +} + #ifdef LIBXML_READER_ENABLED /** * rngStreamTest: @@ -5299,6 +5363,9 @@ testDesc testDescriptions[] = { { "Relax-NG regression tests" , rngTest, "./test/relaxng/*.rng", NULL, NULL, NULL, XML_PARSE_DTDATTR | XML_PARSE_NOENT }, + { "Relax-NG include limit tests" , + rngIncludeTest, "./test/relaxng/include/include-limit.rng", NULL, NULL, NULL, + 0 }, #ifdef LIBXML_READER_ENABLED { "Relax-NG streaming regression tests" , rngStreamTest, "./test/relaxng/*.rng", NULL, NULL, NULL, Index: libxml2-2.14.5/test/relaxng/include/include-limit.rng =================================================================== --- /dev/null +++ libxml2-2.14.5/test/relaxng/include/include-limit.rng @@ -0,0 +1,4 @@ + + + + Index: libxml2-2.14.5/test/relaxng/include/include-limit_1.rng =================================================================== --- /dev/null +++ libxml2-2.14.5/test/relaxng/include/include-limit_1.rng @@ -0,0 +1,4 @@ + + + + Index: libxml2-2.14.5/test/relaxng/include/include-limit_2.rng =================================================================== --- /dev/null +++ libxml2-2.14.5/test/relaxng/include/include-limit_2.rng @@ -0,0 +1,4 @@ + + + + Index: libxml2-2.14.5/test/relaxng/include/include-limit_3.rng =================================================================== --- /dev/null +++ libxml2-2.14.5/test/relaxng/include/include-limit_3.rng @@ -0,0 +1,8 @@ + + + + + + + +