From 3be39335ced799eb1ec312b7b542c84dac4ebd57 Mon Sep 17 00:00:00 2001
From: Nick Wellnhofer <wellnhofer@aevum.de>
Date: Fri, 4 Oct 2019 14:42:59 +0200
Subject: [PATCH] Make xmlDumpElementContent non-recursive

Avoid call stack overflow when dumping deeply nested element
declarations.

Found by OSS-Fuzz.

backport from https://gitlab.gnome.org/GNOME/libxml2/commit/24e3973bc03c15d534b2eac6e70fc2b2bef2b6c0
---
 valid.c | 157 ++++++++++++++++++++++++++++++++------------------------
 1 file changed, 89 insertions(+), 68 deletions(-)

diff --git a/valid.c b/valid.c
index b1cfede..632c5e7 100644
--- a/valid.c
+++ b/valid.c
@@ -1148,83 +1148,104 @@ xmlFreeElementContent(xmlElementContentPtr cur) {
 
 #ifdef LIBXML_OUTPUT_ENABLED
 /**
- * xmlDumpElementContent:
+ * xmlDumpElementOccur:
  * @buf:  An XML buffer
- * @content:  An element table
- * @glob: 1 if one must print the englobing parenthesis, 0 otherwise
+ * @cur:  An element table
  *
- * This will dump the content of the element table as an XML DTD definition
+ * Dump the occurence operator of an element.
  */
 static void
-xmlDumpElementContent(xmlBufferPtr buf, xmlElementContentPtr content, int glob) {
-    if (content == NULL) return;
-
-    if (glob) xmlBufferWriteChar(buf, "(");
-    switch (content->type) {
-        case XML_ELEMENT_CONTENT_PCDATA:
-            xmlBufferWriteChar(buf, "#PCDATA");
-	    break;
-	case XML_ELEMENT_CONTENT_ELEMENT:
-	    if (content->prefix != NULL) {
-		xmlBufferWriteCHAR(buf, content->prefix);
-		xmlBufferWriteChar(buf, ":");
-	    }
-	    xmlBufferWriteCHAR(buf, content->name);
-	    break;
-	case XML_ELEMENT_CONTENT_SEQ:
-	    if ((content->c1 != NULL) &&
-	        ((content->c1->type == XML_ELEMENT_CONTENT_OR) ||
-	         (content->c1->type == XML_ELEMENT_CONTENT_SEQ)))
-		xmlDumpElementContent(buf, content->c1, 1);
-	    else
-		xmlDumpElementContent(buf, content->c1, 0);
-            xmlBufferWriteChar(buf, " , ");
-	    if ((content->c2 != NULL) &&
-	        ((content->c2->type == XML_ELEMENT_CONTENT_OR) ||
-	         ((content->c2->type == XML_ELEMENT_CONTENT_SEQ) &&
-		  (content->c2->ocur != XML_ELEMENT_CONTENT_ONCE))))
-		xmlDumpElementContent(buf, content->c2, 1);
-	    else
-		xmlDumpElementContent(buf, content->c2, 0);
-	    break;
-	case XML_ELEMENT_CONTENT_OR:
-	    if ((content->c1 != NULL) &&
-	        ((content->c1->type == XML_ELEMENT_CONTENT_OR) ||
-	         (content->c1->type == XML_ELEMENT_CONTENT_SEQ)))
-		xmlDumpElementContent(buf, content->c1, 1);
-	    else
-		xmlDumpElementContent(buf, content->c1, 0);
-            xmlBufferWriteChar(buf, " | ");
-	    if ((content->c2 != NULL) &&
-	        ((content->c2->type == XML_ELEMENT_CONTENT_SEQ) ||
-	         ((content->c2->type == XML_ELEMENT_CONTENT_OR) &&
-		  (content->c2->ocur != XML_ELEMENT_CONTENT_ONCE))))
-		xmlDumpElementContent(buf, content->c2, 1);
-	    else
-		xmlDumpElementContent(buf, content->c2, 0);
-	    break;
-	default:
-	    xmlErrValid(NULL, XML_ERR_INTERNAL_ERROR,
-		    "Internal: ELEMENT content corrupted invalid type\n",
-		    NULL);
-    }
-    if (glob)
-        xmlBufferWriteChar(buf, ")");
-    switch (content->ocur) {
+xmlDumpElementOccur(xmlBufferPtr buf, xmlElementContentPtr cur) {
+    switch (cur->ocur) {
         case XML_ELEMENT_CONTENT_ONCE:
-	    break;
+            break;
         case XML_ELEMENT_CONTENT_OPT:
-	    xmlBufferWriteChar(buf, "?");
-	    break;
+            xmlBufferWriteChar(buf, "?");
+            break;
         case XML_ELEMENT_CONTENT_MULT:
-	    xmlBufferWriteChar(buf, "*");
-	    break;
+            xmlBufferWriteChar(buf, "*");
+            break;
         case XML_ELEMENT_CONTENT_PLUS:
-	    xmlBufferWriteChar(buf, "+");
-	    break;
+            xmlBufferWriteChar(buf, "+");
+            break;
     }
 }
 
+/**
+ * xmlDumpElementContent:
+ * @buf:  An XML buffer
+ * @content:  An element table
+ *
+ * This will dump the content of the element table as an XML DTD definition
+ */
+static void
+xmlDumpElementContent(xmlBufferPtr buf, xmlElementContentPtr content) {
+    xmlElementContentPtr cur;
+
+    if (content == NULL) return;
+
+    xmlBufferWriteChar(buf, "(");
+    cur = content;
+
+    do {
+        if (cur == NULL) return;
+
+        switch (cur->type) {
+            case XML_ELEMENT_CONTENT_PCDATA:
+                xmlBufferWriteChar(buf, "#PCDATA");
+                break;
+            case XML_ELEMENT_CONTENT_ELEMENT:
+                if (cur->prefix != NULL) {
+                    xmlBufferWriteCHAR(buf, cur->prefix);
+                    xmlBufferWriteChar(buf, ":");
+                }
+                xmlBufferWriteCHAR(buf, cur->name);
+                break;
+            case XML_ELEMENT_CONTENT_SEQ:
+            case XML_ELEMENT_CONTENT_OR:
+                if ((cur != content) &&
+                    (cur->parent != NULL) &&
+                    ((cur->type != cur->parent->type) ||
+                     (cur->ocur != XML_ELEMENT_CONTENT_ONCE)))
+                    xmlBufferWriteChar(buf, "(");
+                cur = cur->c1;
+                continue;
+            default:
+                xmlErrValid(NULL, XML_ERR_INTERNAL_ERROR,
+                        "Internal: ELEMENT cur corrupted invalid type\n",
+                        NULL);
+        }
+
+        while (cur != content) {
+            xmlElementContentPtr parent = cur->parent;
+
+            if (parent == NULL) return;
+
+            if (((cur->type == XML_ELEMENT_CONTENT_OR) ||
+                 (cur->type == XML_ELEMENT_CONTENT_SEQ)) &&
+                ((cur->type != parent->type) ||
+                 (cur->ocur != XML_ELEMENT_CONTENT_ONCE)))
+                xmlBufferWriteChar(buf, ")");
+            xmlDumpElementOccur(buf, cur);
+
+            if (cur == parent->c1) {
+                if (parent->type == XML_ELEMENT_CONTENT_SEQ)
+                    xmlBufferWriteChar(buf, " , ");
+                else if (parent->type == XML_ELEMENT_CONTENT_OR)
+                    xmlBufferWriteChar(buf, " | ");
+
+                cur = parent->c2;
+                break;
+            }
+
+            cur = parent;
+        }
+    } while (cur != content);
+
+    xmlBufferWriteChar(buf, ")");
+    xmlDumpElementOccur(buf, content);
+}
+
 /**
  * xmlSprintfElementContent:
  * @buf:  an output buffer
@@ -1703,7 +1724,7 @@ xmlDumpElementDecl(xmlBufferPtr buf, xmlElementPtr elem) {
 	    }
 	    xmlBufferWriteCHAR(buf, elem->name);
 	    xmlBufferWriteChar(buf, " ");
-	    xmlDumpElementContent(buf, elem->content, 1);
+	    xmlDumpElementContent(buf, elem->content);
 	    xmlBufferWriteChar(buf, ">\n");
 	    break;
 	case XML_ELEMENT_TYPE_ELEMENT:
@@ -1714,7 +1735,7 @@ xmlDumpElementDecl(xmlBufferPtr buf, xmlElementPtr elem) {
 	    }
 	    xmlBufferWriteCHAR(buf, elem->name);
 	    xmlBufferWriteChar(buf, " ");
-	    xmlDumpElementContent(buf, elem->content, 1);
+	    xmlDumpElementContent(buf, elem->content);
 	    xmlBufferWriteChar(buf, ">\n");
 	    break;
 	default:
-- 
2.18.1

