From ffaec75809a315457891a0e54f8828bc6e056067 Mon Sep 17 00:00:00 2001
From: Nick Wellnhofer <wellnhofer@aevum.de>
Date: Thu, 25 Aug 2022 17:43:08 +0200
Subject: [PATCH] Fix integer overflows with XML_PARSE_HUGE

Also impose size limits when XML_PARSE_HUGE is set. Limit size of names
to XML_MAX_TEXT_LENGTH (10 million bytes) and other content to
XML_MAX_HUGE_LENGTH (1 billion bytes).

Move some the length checks to the end of the respective loop to make
them strict.

xmlParseEntityValue didn't have a length limitation at all. But without
XML_PARSE_HUGE, this should eventually trigger an error in xmlGROW.

Thanks to Maddie Stone working with Google Project Zero for the report!
---
 include/libxml/parserInternals.h |   12 ++
 include/libxml/xmlerror.h        |    1 
 parser.c                         |  188 +++++++++++++++++++++++++++++++++++----
 3 files changed, 186 insertions(+), 15 deletions(-)

Index: b/parser.c
===================================================================
--- a/parser.c
+++ b/parser.c
@@ -100,6 +100,8 @@ xmlCreateEntityParserCtxtInternal(const
  *									*
  ************************************************************************/
 
+#define XML_MAX_HUGE_LENGTH 1000000000
+
 #define XML_PARSER_BIG_ENTITY 1000
 #define XML_PARSER_LOT_ENTITY 5000
 
@@ -536,6 +538,9 @@ xmlFatalErr(xmlParserCtxtPtr ctxt, xmlPa
         case XML_ERR_VERSION_MISSING:
             errmsg = "Malformed declaration expecting version\n";
             break;
+        case XML_ERR_NAME_TOO_LONG:
+            errmsg = "Name too long";
+            break;
 #if 0
         case:
             errmsg = "\n";
@@ -1884,6 +1889,7 @@ static int spacePop(xmlParserCtxtPtr ctx
 #define CUR (*ctxt->input->cur)
 #define NXT(val) ctxt->input->cur[(val)]
 #define CUR_PTR ctxt->input->cur
+#define BASE_PTR ctxt->input->base
 
 #define CMP4( s, c1, c2, c3, c4 ) \
   ( ((unsigned char *) s)[ 0 ] == c1 && ((unsigned char *) s)[ 1 ] == c2 && \
@@ -3180,6 +3186,9 @@ xmlParseNameComplex(xmlParserCtxtPtr ctx
     int len = 0, l;
     int c;
     int count = 0;
+    int maxLength = (ctxt->options & XML_PARSE_HUGE) ?
+                    XML_MAX_TEXT_LENGTH :
+                    XML_MAX_NAME_LENGTH;
 
 #ifdef DEBUG
     nbParseNameComplex++;
@@ -3245,7 +3254,8 @@ xmlParseNameComplex(xmlParserCtxtPtr ctx
                 if (ctxt->instate == XML_PARSER_EOF)
                     return(NULL);
 	    }
-	    len += l;
+            if (len <= INT_MAX - l)
+	        len += l;
 	    NEXTL(l);
 	    c = CUR_CHAR(l);
 	}
@@ -3271,11 +3281,16 @@ xmlParseNameComplex(xmlParserCtxtPtr ctx
                 if (ctxt->instate == XML_PARSER_EOF)
                     return(NULL);
 	    }
-	    len += l;
+      if (len <= INT_MAX - l)
+	      len += l;
 	    NEXTL(l);
 	    c = CUR_CHAR(l);
 	}
     }
+    if (len > maxLength) {
+        xmlFatalErr(ctxt, XML_ERR_NAME_TOO_LONG, "Name");
+        return(NULL);
+    }
     if (ctxt->input->cur - ctxt->input->base < len) {
         /*
          * There were a couple of bugs where PERefs lead to to a change
@@ -3311,7 +3326,10 @@ const xmlChar *
 xmlParseName(xmlParserCtxtPtr ctxt) {
     const xmlChar *in;
     const xmlChar *ret;
-    int count = 0;
+    size_t count = 0;
+    size_t maxLength = (ctxt->options & XML_PARSE_HUGE) ?
+                       XML_MAX_TEXT_LENGTH :
+                       XML_MAX_NAME_LENGTH;
 
     GROW;
 
@@ -3335,6 +3353,10 @@ xmlParseName(xmlParserCtxtPtr ctxt) {
 	    in++;
 	if ((*in > 0) && (*in < 0x80)) {
 	    count = in - ctxt->input->cur;
+      if (count > maxLength) {
+        xmlFatalErr(ctxt, XML_ERR_NAME_TOO_LONG, "Name");
+        return(NULL);
+      }
 	    ret = xmlDictLookup(ctxt->dict, ctxt->input->cur, count);
 	    ctxt->input->cur = in;
 	    ctxt->nbChars += count;
@@ -3354,6 +3376,9 @@ xmlParseNCNameComplex(xmlParserCtxtPtr c
     int c;
     int count = 0;
 
+    int maxLength = (ctxt->options & XML_PARSE_HUGE) ?
+                    XML_MAX_TEXT_LENGTH :
+                    XML_MAX_NAME_LENGTH;
 #ifdef DEBUG
     nbParseNCNameComplex++;
 #endif
@@ -3376,10 +3401,15 @@ xmlParseNCNameComplex(xmlParserCtxtPtr c
             if (ctxt->instate == XML_PARSER_EOF)
                 return(NULL);
 	}
-	len += l;
+	if (len <= INT_MAX - l)
+		len += l;
 	NEXTL(l);
 	c = CUR_CHAR(l);
     }
+		if (len > maxLength) {
+        xmlFatalErr(ctxt, XML_ERR_NAME_TOO_LONG, "NCName");
+        return(NULL);
+    }
     return(xmlDictLookup(ctxt->dict, ctxt->input->cur - len, len));
 }
 
@@ -3402,7 +3432,11 @@ static const xmlChar *
 xmlParseNCName(xmlParserCtxtPtr ctxt) {
     const xmlChar *in;
     const xmlChar *ret;
-    int count = 0;
+    size_t count = 0;
+	size_t maxLength = (ctxt->options & XML_PARSE_HUGE) ?
+                       XML_MAX_TEXT_LENGTH :
+                       XML_MAX_NAME_LENGTH;
+
 
 #ifdef DEBUG
     nbParseNCName++;
@@ -3424,6 +3458,10 @@ xmlParseNCName(xmlParserCtxtPtr ctxt) {
 	    in++;
 	if ((*in > 0) && (*in < 0x80)) {
 	    count = in - ctxt->input->cur;
+	    if (count > maxLength) {
+		    xmlFatalErr(ctxt, XML_ERR_NAME_TOO_LONG, "NCName");
+		    return(NULL);
+	    }
 	    ret = xmlDictLookup(ctxt->dict, ctxt->input->cur, count);
 	    ctxt->input->cur = in;
 	    ctxt->nbChars += count;
@@ -3502,6 +3540,9 @@ xmlParseStringName(xmlParserCtxtPtr ctxt
     const xmlChar *cur = *str;
     int len = 0, l;
     int c;
+    int maxLength = (ctxt->options & XML_PARSE_HUGE) ?
+                    XML_MAX_TEXT_LENGTH :
+                    XML_MAX_NAME_LENGTH;
 
 #ifdef DEBUG
     nbParseStringName++;
@@ -3549,12 +3590,21 @@ xmlParseStringName(xmlParserCtxtPtr ctxt
 		COPY_BUF(l,buffer,len,c);
 		cur += l;
 		c = CUR_SCHAR(cur, l);
+		if (len > maxLength) {
+                    xmlFatalErr(ctxt, XML_ERR_NAME_TOO_LONG, "NCName");
+                    xmlFree(buffer);
+                    return(NULL);
+                }
 	    }
 	    buffer[len] = 0;
 	    *str = cur;
 	    return(buffer);
 	}
     }
+    if (len > maxLength) {
+        xmlFatalErr(ctxt, XML_ERR_NAME_TOO_LONG, "NCName");
+        return(NULL);
+    }
     *str = cur;
     return(xmlStrndup(buf, len));
 }
@@ -3578,6 +3628,9 @@ xmlParseNmtoken(xmlParserCtxtPtr ctxt) {
     int len = 0, l;
     int c;
     int count = 0;
+    int maxLength = (ctxt->options & XML_PARSE_HUGE) ?
+                    XML_MAX_TEXT_LENGTH :
+                    XML_MAX_NAME_LENGTH;
 
 #ifdef DEBUG
     nbParseNmToken++;
@@ -3635,6 +3688,11 @@ xmlParseNmtoken(xmlParserCtxtPtr ctxt) {
 		COPY_BUF(l,buffer,len,c);
 		NEXTL(l);
 		c = CUR_CHAR(l);
+                if (len > maxLength) {
+                    xmlFatalErr(ctxt, XML_ERR_NAME_TOO_LONG, "NmToken");
+                    xmlFree(buffer);
+                    return(NULL);
+		}
 	    }
 	    buffer[len] = 0;
 	    return(buffer);
@@ -3642,6 +3700,10 @@ xmlParseNmtoken(xmlParserCtxtPtr ctxt) {
     }
     if (len == 0)
         return(NULL);
+    if (len > maxLength) {
+        xmlFatalErr(ctxt, XML_ERR_NAME_TOO_LONG, "NmToken");
+        return(NULL);
+    }
     return(xmlStrndup(buf, len));
 }
 
@@ -3664,6 +3726,9 @@ xmlParseEntityValue(xmlParserCtxtPtr ctx
     int len = 0;
     int size = XML_PARSER_BUFFER_SIZE;
     int c, l;
+    int maxLength = (ctxt->options & XML_PARSE_HUGE) ?
+                    XML_MAX_HUGE_LENGTH :
+                    XML_MAX_TEXT_LENGTH;
     xmlChar stop;
     xmlChar *ret = NULL;
     const xmlChar *cur = NULL;
@@ -3731,6 +3796,13 @@ xmlParseEntityValue(xmlParserCtxtPtr ctx
 	    GROW;
 	    c = CUR_CHAR(l);
 	}
+        if (len > maxLength) {
+		xmlFatalErrMsg(ctxt, XML_ERR_ENTITY_NOT_FINISHED,
+				"entity value too long\n");
+		if (buf != NULL)
+			xmlFree(buf);
+        	return(NULL);
+        }
     }
     buf[len] = 0;
     if (ctxt->instate == XML_PARSER_EOF) {
@@ -3814,6 +3886,9 @@ xmlParseAttValueComplex(xmlParserCtxtPtr
     xmlChar *rep = NULL;
     size_t len = 0;
     size_t buf_size = 0;
+    size_t maxLength = (ctxt->options & XML_PARSE_HUGE) ?
+                       XML_MAX_HUGE_LENGTH :
+                       XML_MAX_TEXT_LENGTH;
     int c, l, in_space = 0;
     xmlChar *current = NULL;
     xmlEntityPtr ent;
@@ -3982,6 +4057,11 @@ xmlParseAttValueComplex(xmlParserCtxtPtr
 	}
 	GROW;
 	c = CUR_CHAR(l);
+        if (len > maxLength) {
+            xmlFatalErrMsg(ctxt, XML_ERR_ATTRIBUTE_NOT_FINISHED,
+                           "AttValue length too long\n");
+            goto mem_error;
+        }
     }
     if (ctxt->instate == XML_PARSER_EOF)
         goto error;
@@ -4003,16 +4083,6 @@ xmlParseAttValueComplex(xmlParserCtxtPtr
     } else
 	NEXT;
 
-    /*
-     * There we potentially risk an overflow, don't allow attribute value of
-     * lenght more than INT_MAX it is a very reasonnable assumption !
-     */
-    if (len >= INT_MAX) {
-        xmlFatalErrMsg(ctxt, XML_ERR_ATTRIBUTE_NOT_FINISHED,
-                       "AttValue lenght too long\n");
-        goto mem_error;
-    }
-
     if (attlen != NULL) *attlen = (int) len;
     return(buf);
 
@@ -4083,6 +4153,9 @@ xmlParseSystemLiteral(xmlParserCtxtPtr c
     int len = 0;
     int size = XML_PARSER_BUFFER_SIZE;
     int cur, l;
+    int maxLength = (ctxt->options & XML_PARSE_HUGE) ?
+                    XML_MAX_TEXT_LENGTH :
+                    XML_MAX_NAME_LENGTH;
     xmlChar stop;
     int state = ctxt->instate;
     int count = 0;
@@ -4137,6 +4210,12 @@ xmlParseSystemLiteral(xmlParserCtxtPtr c
 	    SHRINK;
 	    cur = CUR_CHAR(l);
 	}
+        if (len > maxLength) {
+            xmlFatalErr(ctxt, XML_ERR_NAME_TOO_LONG, "SystemLiteral");
+            xmlFree(buf);
+            ctxt->instate = (xmlParserInputState) state;
+            return(NULL);
+        }
     }
     buf[len] = 0;
     ctxt->instate = (xmlParserInputState) state;
@@ -4164,6 +4243,9 @@ xmlParsePubidLiteral(xmlParserCtxtPtr ct
     xmlChar *buf = NULL;
     int len = 0;
     int size = XML_PARSER_BUFFER_SIZE;
+    int maxLength = (ctxt->options & XML_PARSE_HUGE) ?
+                    XML_MAX_TEXT_LENGTH :
+                    XML_MAX_NAME_LENGTH;
     xmlChar cur;
     xmlChar stop;
     int count = 0;
@@ -4217,6 +4299,11 @@ xmlParsePubidLiteral(xmlParserCtxtPtr ct
 	    SHRINK;
 	    cur = CUR;
 	}
+        if (len > maxLength) {
+            xmlFatalErr(ctxt, XML_ERR_NAME_TOO_LONG, "Public ID");
+            xmlFree(buf);
+            return(NULL);
+        }
     }
     buf[len] = 0;
     if (cur != stop) {
@@ -4621,6 +4708,9 @@ xmlParseCommentComplex(xmlParserCtxtPtr
     int r, rl;
     int cur, l;
     int count = 0;
+    size_t maxLength = (ctxt->options & XML_PARSE_HUGE) ?
+                       XML_MAX_HUGE_LENGTH :
+                       XML_MAX_TEXT_LENGTH;
     int inputid;
 
     inputid = ctxt->input->id;
@@ -4699,6 +4789,13 @@ xmlParseCommentComplex(xmlParserCtxtPtr
 	    GROW;
 	    cur = CUR_CHAR(l);
 	}
+
+        if (len > maxLength) {
+            xmlFatalErrMsgStr(ctxt, XML_ERR_COMMENT_NOT_FINISHED,
+                         "Comment too big found", NULL);
+            xmlFree (buf);
+            return;
+        }
     }
     buf[len] = 0;
     if (cur == 0) {
@@ -4742,6 +4839,9 @@ xmlParseComment(xmlParserCtxtPtr ctxt) {
     xmlChar *buf = NULL;
     int size = XML_PARSER_BUFFER_SIZE;
     int len = 0;
+    size_t maxLength = (ctxt->options & XML_PARSE_HUGE) ?
+                       XML_MAX_HUGE_LENGTH :
+                       XML_MAX_TEXT_LENGTH;
     xmlParserInputState state;
     const xmlChar *in;
     int nbchar = 0, ccol;
@@ -4824,6 +4924,12 @@ get_more:
 		buf[len] = 0;
 	    }
 	}
+        if (len > maxLength) {
+            xmlFatalErrMsgStr(ctxt, XML_ERR_COMMENT_NOT_FINISHED,
+                         "Comment too big found", NULL);
+            xmlFree (buf);
+            return;
+        }
 	ctxt->input->cur = in;
 	if (*in == 0xA) {
 	    in++;
@@ -5012,6 +5118,9 @@ xmlParsePI(xmlParserCtxtPtr ctxt) {
     xmlChar *buf = NULL;
     int len = 0;
     int size = XML_PARSER_BUFFER_SIZE;
+    size_t maxLength = (ctxt->options & XML_PARSE_HUGE) ?
+                       XML_MAX_HUGE_LENGTH :
+                       XML_MAX_TEXT_LENGTH;
     int cur, l;
     const xmlChar *target;
     xmlParserInputState state;
@@ -5096,6 +5205,13 @@ xmlParsePI(xmlParserCtxtPtr ctxt) {
 		    GROW;
 		    cur = CUR_CHAR(l);
 		}
+                if (len > maxLength) {
+                    xmlFatalErrMsgStr(ctxt, XML_ERR_PI_NOT_FINISHED,
+                                      "PI %s too big found", target);
+                    xmlFree(buf);
+                    ctxt->instate = state;
+                    return;
+                }
 	    }
 	    buf[len] = 0;
 	    if (cur != '?') {
@@ -8790,6 +8906,9 @@ xmlParseAttValueInternal(xmlParserCtxtPt
     xmlChar limit = 0;
     const xmlChar *in = NULL, *start, *end, *last;
     xmlChar *ret = NULL;
+    int maxLength = (ctxt->options & XML_PARSE_HUGE) ?
+                    XML_MAX_HUGE_LENGTH :
+                    XML_MAX_TEXT_LENGTH;
 
     GROW;
     in = (xmlChar *) CUR_PTR;
@@ -8837,6 +8956,11 @@ xmlParseAttValueInternal(xmlParserCtxtPt
 		    in = in + delta;
 		}
 		end = ctxt->input->end;
+                if ((in - start) > maxLength) {
+                    xmlFatalErrMsg(ctxt, XML_ERR_ATTRIBUTE_NOT_FINISHED,
+                                   "AttValue length too long\n");
+                    return(NULL);
+                }
 	    }
 	}
 	while ((in < end) && (*in != limit) && (*in >= 0x20) &&
@@ -8853,6 +8977,11 @@ xmlParseAttValueInternal(xmlParserCtxtPt
 		    in = in + delta;
 		}
 		end = ctxt->input->end;
+                if ((in - start) > maxLength) {
+                    xmlFatalErrMsg(ctxt, XML_ERR_ATTRIBUTE_NOT_FINISHED,
+                                   "AttValue length too long\n");
+                    return(NULL);
+                }
 	    }
 	}
 	last = in;
@@ -8876,8 +9005,18 @@ xmlParseAttValueInternal(xmlParserCtxtPt
 		    last = last + delta;
 		}
 		end = ctxt->input->end;
+                if ((in - start) > maxLength) {
+                    xmlFatalErrMsg(ctxt, XML_ERR_ATTRIBUTE_NOT_FINISHED,
+                                   "AttValue length too long\n");
+                    return(NULL);
+                }
 	    }
 	}
+        if ((in - start) > maxLength) {
+            xmlFatalErrMsg(ctxt, XML_ERR_ATTRIBUTE_NOT_FINISHED,
+                           "AttValue length too long\n");
+            return(NULL);
+        }
 	if (*in != limit) goto need_complex;
     } else {
 	while ((in < end) && (*in != limit) && (*in >= 0x20) &&
@@ -8894,9 +9033,19 @@ xmlParseAttValueInternal(xmlParserCtxtPt
 		    in = in + delta;
 		}
 		end = ctxt->input->end;
+                if ((in - start) > maxLength) {
+                    xmlFatalErrMsg(ctxt, XML_ERR_ATTRIBUTE_NOT_FINISHED,
+                                   "AttValue length too long\n");
+                    return(NULL);
+                }
 	    }
 	}
 	last = in;
+        if ((in - start) > maxLength) {
+            xmlFatalErrMsg(ctxt, XML_ERR_ATTRIBUTE_NOT_FINISHED,
+                           "AttValue length too long\n");
+            return(NULL);
+        }
 	if (*in != limit) goto need_complex;
     }
     in++;
@@ -9619,6 +9768,9 @@ xmlParseCDSect(xmlParserCtxtPtr ctxt) {
     int	s, sl;
     int cur, l;
     int count = 0;
+    int maxLength = (ctxt->options & XML_PARSE_HUGE) ?
+                    XML_MAX_HUGE_LENGTH :
+                    XML_MAX_TEXT_LENGTH;
 
     /* Check 2.6.0 was NXT(0) not RAW */
     if (CMP9(CUR_PTR, '<', '!', '[', 'C', 'D', 'A', 'T', 'A', '[')) {
@@ -9677,6 +9829,12 @@ xmlParseCDSect(xmlParserCtxtPtr ctxt) {
 	}
 	NEXTL(l);
 	cur = CUR_CHAR(l);
+        if (len > maxLength) {
+            xmlFatalErrMsg(ctxt, XML_ERR_CDATA_NOT_FINISHED,
+                           "CData section too big found\n");
+            xmlFree(buf);
+            return;
+        }
     }
     buf[len] = 0;
     ctxt->instate = XML_PARSER_CONTENT;
Index: b/include/libxml/parserInternals.h
===================================================================
--- a/include/libxml/parserInternals.h
+++ b/include/libxml/parserInternals.h
@@ -40,6 +40,18 @@ XMLPUBVAR unsigned int xmlParserMaxDepth
 #define XML_MAX_TEXT_LENGTH 10000000
 
 /**
+ * XML_MAX_NAME_LENGTH:
+ *
+ * Maximum size allowed for a markup identitier
+ * This is not a limitation of the parser but a safety boundary feature,
+ * use XML_PARSE_HUGE option to override it.
+ * Note that with the use of parsing dictionaries overriding the limit
+ * may result in more runtime memory usage in face of "unfriendly' content
+ * Introduced in 2.9.0
+ */
+#define XML_MAX_NAME_LENGTH 50000
+
+/**
  * XML_MAX_LOOKUP_LIMIT:
  *
  * Maximum size allowed by the parser for ahead lookup
Index: b/include/libxml/xmlerror.h
===================================================================
--- a/include/libxml/xmlerror.h
+++ b/include/libxml/xmlerror.h
@@ -205,6 +205,7 @@ typedef enum {
     XML_WAR_ENTITY_REDEFINED, /* 107 */
     XML_ERR_UNKNOWN_VERSION, /* 108 */
     XML_ERR_VERSION_MISMATCH, /* 109 */
+    XML_ERR_NAME_TOO_LONG, /* 110 */
     XML_ERR_USER_STOP, /* 111 */
     XML_NS_ERR_XML_NAMESPACE = 200,
     XML_NS_ERR_UNDEFINED_NAMESPACE, /* 201 */
