libxml2: patch CVE-2026-0989

Pick patch from [1] linked from [2].

[1] https://gitlab.gnome.org/GNOME/libxml2/-/merge_requests/374
[2] https://gitlab.gnome.org/GNOME/libxml2/-/issues/998

(From OE-Core rev: d201a09eee8efca8a889f0b7a60133e850256369)

Signed-off-by: Peter Marko <peter.marko@siemens.com>
Signed-off-by: Yoann Congal <yoann.congal@smile.fr>
Signed-off-by: Paul Barker <paul@pbarker.dev>
Signed-off-by: Richard Purdie <richard.purdie@linuxfoundation.org>
This commit is contained in:
Peter Marko
2026-01-25 19:35:04 +01:00
committed by Richard Purdie
parent 308c5dd2c9
commit 35fca9ec35
2 changed files with 310 additions and 0 deletions

View File

@@ -0,0 +1,309 @@
From 19549c61590c1873468c53e0026a2fbffae428ef Mon Sep 17 00:00:00 2001
From: Daniel Garcia Moreno <daniel.garcia@suse.com>
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
CVE: CVE-2026-0989
Upstream-Status: Backport [https://gitlab.gnome.org/GNOME/libxml2/-/commit/19549c61590c1873468c53e0026a2fbffae428ef]
Signed-off-by: Peter Marko <peter.marko@siemens.com>
---
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
diff --git a/include/libxml/relaxng.h b/include/libxml/relaxng.h
index eafc6604..099dacd8 100644
--- a/include/libxml/relaxng.h
+++ b/include/libxml/relaxng.h
@@ -138,6 +138,10 @@ XMLPUBFUN int
xmlRelaxParserSetFlag (xmlRelaxNGParserCtxtPtr ctxt,
int flag);
+XMLPUBFUN int
+ xmlRelaxParserSetIncLImit (xmlRelaxNGParserCtxt *ctxt,
+ int limit);
+
XMLPUBFUN void
xmlRelaxNGFreeParserCtxt (xmlRelaxNGParserCtxtPtr ctxt);
XMLPUBFUN void
diff --git a/relaxng.c b/relaxng.c
index 1d74ba9f..c0e94a3c 100644
--- a/relaxng.c
+++ b/relaxng.c
@@ -18,6 +18,8 @@
#ifdef LIBXML_SCHEMAS_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) && \
@@ -225,6 +233,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 */
@@ -1410,6 +1419,23 @@ xmlRelaxParserSetFlag(xmlRelaxNGParserCtxtPtr 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 *
@@ -1425,7 +1451,7 @@ static xmlDocPtr xmlRelaxNGCleanupDoc(xmlRelaxNGParserCtxtPtr ctxt,
*
* 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,
@@ -1439,9 +1465,15 @@ xmlRelaxNGIncludePush(xmlRelaxNGParserCtxtPtr ctxt,
sizeof(ctxt->incTab[0]));
if (ctxt->incTab == NULL) {
xmlRngPErrMemory(ctxt, "allocating include\n");
- 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 =
@@ -1450,7 +1482,7 @@ xmlRelaxNGIncludePush(xmlRelaxNGParserCtxtPtr ctxt,
sizeof(ctxt->incTab[0]));
if (ctxt->incTab == NULL) {
xmlRngPErrMemory(ctxt, "allocating include\n");
- return (0);
+ return (-1);
}
}
ctxt->incTab[ctxt->incNr] = value;
@@ -1620,7 +1652,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
@@ -7357,11 +7391,32 @@ xmlRelaxNGParse(xmlRelaxNGParserCtxtPtr 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
*/
diff --git a/runtest.c b/runtest.c
index 49519aef..45109f0a 100644
--- a/runtest.c
+++ b/runtest.c
@@ -3781,6 +3781,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:
@@ -5112,6 +5176,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,
diff --git a/test/relaxng/include/include-limit.rng b/test/relaxng/include/include-limit.rng
new file mode 100644
index 00000000..51f03942
--- /dev/null
+++ b/test/relaxng/include/include-limit.rng
@@ -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>
diff --git a/test/relaxng/include/include-limit_1.rng b/test/relaxng/include/include-limit_1.rng
new file mode 100644
index 00000000..4672da38
--- /dev/null
+++ b/test/relaxng/include/include-limit_1.rng
@@ -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>
diff --git a/test/relaxng/include/include-limit_2.rng b/test/relaxng/include/include-limit_2.rng
new file mode 100644
index 00000000..b35ecaa8
--- /dev/null
+++ b/test/relaxng/include/include-limit_2.rng
@@ -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>
diff --git a/test/relaxng/include/include-limit_3.rng b/test/relaxng/include/include-limit_3.rng
new file mode 100644
index 00000000..86213c62
--- /dev/null
+++ b/test/relaxng/include/include-limit_3.rng
@@ -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>

View File

@@ -25,6 +25,7 @@ SRC_URI += "http://www.w3.org/XML/Test/xmlts20130923.tar;subdir=${BP};name=testt
file://CVE-2025-49795.patch \
file://CVE-2025-6170.patch \
file://CVE-2025-7425.patch \
file://CVE-2026-0989.patch \
"
SRC_URI[archive.sha256sum] = "c3d8c0c34aa39098f66576fe51969db12a5100b956233dc56506f7a8679be995"