diff mbox series

libxml2: Fix CVE-2026-0989

Message ID 20260128055616.3324710-1-mingli.yu@windriver.com
State New
Headers show
Series libxml2: Fix CVE-2026-0989 | expand

Commit Message

Yu, Mingli Jan. 28, 2026, 5:56 a.m. UTC
From: Mingli Yu <mingli.yu@windriver.com>

Backport a patch [1] to fix CVE-2026-0989.

[1] https://gitlab.gnome.org/GNOME/libxml2/-/commit/19549c61590c1873468c53e0026a2fbffae428ef

Signed-off-by: Mingli Yu <mingli.yu@windriver.com>
---
 .../libxml/libxml2/CVE-2026-0989.patch        | 314 ++++++++++++++++++
 meta/recipes-core/libxml/libxml2_2.15.1.bb    |   1 +
 2 files changed, 315 insertions(+)
 create mode 100644 meta/recipes-core/libxml/libxml2/CVE-2026-0989.patch

Comments

Marko, Peter Jan. 28, 2026, 6:46 a.m. UTC | #1
Patch for this CVE has been already submitted under https://lists.openembedded.org/g/openembedded-core/message/229940
Peter

> -----Original Message-----
> From: openembedded-core@lists.openembedded.org <openembedded-
> core@lists.openembedded.org> On Behalf Of Yu, Mingli via
> lists.openembedded.org
> Sent: Wednesday, January 28, 2026 6:56
> To: openembedded-core@lists.openembedded.org
> Subject: [OE-core] [PATCH] libxml2: Fix CVE-2026-0989
> 
> From: Mingli Yu <mingli.yu@windriver.com>
> 
> Backport a patch [1] to fix CVE-2026-0989.
> 
> [1] https://gitlab.gnome.org/GNOME/libxml2/-
> /commit/19549c61590c1873468c53e0026a2fbffae428ef
> 
> Signed-off-by: Mingli Yu <mingli.yu@windriver.com>
> ---
>  .../libxml/libxml2/CVE-2026-0989.patch        | 314 ++++++++++++++++++
>  meta/recipes-core/libxml/libxml2_2.15.1.bb    |   1 +
>  2 files changed, 315 insertions(+)
>  create mode 100644 meta/recipes-core/libxml/libxml2/CVE-2026-0989.patch
> 
> diff --git a/meta/recipes-core/libxml/libxml2/CVE-2026-0989.patch b/meta/recipes-
> core/libxml/libxml2/CVE-2026-0989.patch
> new file mode 100644
> index 0000000000..800c8cf845
> --- /dev/null
> +++ b/meta/recipes-core/libxml/libxml2/CVE-2026-0989.patch
> @@ -0,0 +1,314 @@
> +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: Mingli Yu <mingli.yu@windriver.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
> +@@ -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
> +diff --git a/relaxng.c b/relaxng.c
> +index 1d74ba9f..c0e94a3c 100644
> +--- a/relaxng.c
> ++++ b/relaxng.c
> +@@ -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
> +      */
> +diff --git a/runtest.c b/runtest.c
> +index 49519aef..45109f0a 100644
> +--- a/runtest.c
> ++++ b/runtest.c
> +@@ -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,
> +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>
> +--
> +2.34.1
> +
> diff --git a/meta/recipes-core/libxml/libxml2_2.15.1.bb b/meta/recipes-
> core/libxml/libxml2_2.15.1.bb
> index a64ed8098e..26fe27e933 100644
> --- a/meta/recipes-core/libxml/libxml2_2.15.1.bb
> +++ b/meta/recipes-core/libxml/libxml2_2.15.1.bb
> @@ -17,6 +17,7 @@ inherit gnomebase
>  SRC_URI +=
> "http://www.w3.org/XML/Test/xmlts20130923.tar;subdir=${BP};name=testtar \
>             file://CVE-2026-0990.patch \
>             file://CVE-2026-0992.patch \
> +           file://CVE-2026-0989.patch \
>             file://run-ptest \
>             file://install-tests.patch \
>             file://0001-Revert-cmake-Fix-installation-directories-in-libxml2.patch \
> --
> 2.34.1
diff mbox series

Patch

diff --git a/meta/recipes-core/libxml/libxml2/CVE-2026-0989.patch b/meta/recipes-core/libxml/libxml2/CVE-2026-0989.patch
new file mode 100644
index 0000000000..800c8cf845
--- /dev/null
+++ b/meta/recipes-core/libxml/libxml2/CVE-2026-0989.patch
@@ -0,0 +1,314 @@ 
+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: Mingli Yu <mingli.yu@windriver.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
+@@ -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
+diff --git a/relaxng.c b/relaxng.c
+index 1d74ba9f..c0e94a3c 100644
+--- a/relaxng.c
++++ b/relaxng.c
+@@ -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
+      */
+diff --git a/runtest.c b/runtest.c
+index 49519aef..45109f0a 100644
+--- a/runtest.c
++++ b/runtest.c
+@@ -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,
+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>
+-- 
+2.34.1
+
diff --git a/meta/recipes-core/libxml/libxml2_2.15.1.bb b/meta/recipes-core/libxml/libxml2_2.15.1.bb
index a64ed8098e..26fe27e933 100644
--- a/meta/recipes-core/libxml/libxml2_2.15.1.bb
+++ b/meta/recipes-core/libxml/libxml2_2.15.1.bb
@@ -17,6 +17,7 @@  inherit gnomebase
 SRC_URI += "http://www.w3.org/XML/Test/xmlts20130923.tar;subdir=${BP};name=testtar \
            file://CVE-2026-0990.patch \
            file://CVE-2026-0992.patch \
+           file://CVE-2026-0989.patch \
            file://run-ptest \
            file://install-tests.patch \
            file://0001-Revert-cmake-Fix-installation-directories-in-libxml2.patch \