1
0
mirror of https://gitlab.gnome.org/GNOME/libxml2.git synced 2026-01-26 21:41:34 +03:00

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
This commit is contained in:
Daniel Garcia Moreno
2025-10-10 09:38:31 +02:00
committed by Daniel Garcia Moreno
parent 354f620a45
commit 19549c6159
7 changed files with 150 additions and 4 deletions

View File

@@ -136,6 +136,10 @@ XMLPUBFUN int
xmlRelaxParserSetFlag (xmlRelaxNGParserCtxt *ctxt,
int flag);
XMLPUBFUN int
xmlRelaxParserSetIncLImit (xmlRelaxNGParserCtxt *ctxt,
int limit);
XMLPUBFUN void
xmlRelaxNGFreeParserCtxt (xmlRelaxNGParserCtxt *ctxt);
XMLPUBFUN void

View File

@@ -18,6 +18,8 @@
#ifdef LIBXML_RELAXNG_ENABLED
#include <errno.h>
#include <stdlib.h>
#include <string.h>
#include <stdio.h>
#include <stddef.h>
@@ -44,6 +46,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) && \
@@ -218,6 +226,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 */
@@ -1342,6 +1351,23 @@ xmlRelaxParserSetFlag(xmlRelaxNGParserCtxt *ctxt, int flags)
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 *
@@ -1397,7 +1423,7 @@ xmlRelaxReadMemory(xmlRelaxNGParserCtxtPtr ctxt, const char *buf, int size) {
*
* @param ctxt the parser context
* @param value the element doc
* @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,
@@ -1411,9 +1437,15 @@ xmlRelaxNGIncludePush(xmlRelaxNGParserCtxtPtr ctxt,
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 =
@@ -1422,7 +1454,7 @@ xmlRelaxNGIncludePush(xmlRelaxNGParserCtxtPtr ctxt,
sizeof(ctxt->incTab[0]));
if (ctxt->incTab == NULL) {
xmlRngPErrMemory(ctxt);
return (0);
return (-1);
}
}
ctxt->incTab[ctxt->incNr] = value;
@@ -1586,7 +1618,9 @@ xmlRelaxNGLoadInclude(xmlRelaxNGParserCtxtPtr ctxt, const xmlChar * URL,
/*
* push it on the stack
*/
xmlRelaxNGIncludePush(ctxt, ret);
if (xmlRelaxNGIncludePush(ctxt, ret) < 0) {
return (NULL);
}
/*
* Some preprocessing of the document content, this include recursing
@@ -7261,11 +7295,32 @@ xmlRelaxNGParse(xmlRelaxNGParserCtxt *ctxt)
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
*/

View File

@@ -3741,6 +3741,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
/**
* Parse a set of files with streaming, applying an RNG schemas
@@ -5202,6 +5266,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,

View File

@@ -0,0 +1,4 @@
<?xml version="1.0" encoding="UTF-8"?>
<grammar xmlns="http://relaxng.org/ns/structure/1.0">
<include href="include-limit_1.rng"/>
</grammar>

View File

@@ -0,0 +1,4 @@
<?xml version="1.0" encoding="UTF-8"?>
<grammar xmlns="http://relaxng.org/ns/structure/1.0">
<include href="include-limit_2.rng"/>
</grammar>

View File

@@ -0,0 +1,4 @@
<?xml version="1.0" encoding="UTF-8"?>
<grammar xmlns="http://relaxng.org/ns/structure/1.0">
<include href="include-limit_3.rng"/>
</grammar>

View File

@@ -0,0 +1,8 @@
<?xml version="1.0" encoding="UTF-8"?>
<grammar xmlns="http://relaxng.org/ns/structure/1.0">
<start>
<element name="root">
<empty/>
</element>
</start>
</grammar>