CVE-2017-16931 (published 2017-11-23T21:29Z)

parser.c in libxml2 before 2.9.5 mishandles parameter-entity references because the NEXTL macro calls the xmlParserHandlePEReference function in the case of a '%' character in a DTD name.

Severity: HIGH
CVSS v2 base score: 7.5 (AV:N/AC:L/Au:N/C:P/I:P/A:P)
CVSS v3 base score: 9.8, CRITICAL (CVSS:3.0/AV:N/AC:L/PR:N/UI:N/S:U/C:H/I:H/A:H)

Weakness information:
cwe_id: CWE-119
cwe_name: Improper Restriction of Operations within the Bounds of a Memory Buffer
description: The product performs operations on a memory buffer, but it reads from or writes to a memory location outside the buffer's intended boundary. This may result in read or write operations on unexpected memory locations that could be linked to other variables, data structures, or internal program data.
extended_description: 
            Certain languages allow direct addressing of memory locations and do not automatically ensure that these locations are valid for the memory buffer that is being referenced. This can cause read or write operations to be performed on memory locations that may be associated with other variables, data structures, or internal program data.
         
         

Fix information:
repo_url: https://github.com/GNOME/libxml2
commit_details: repo_url: https://github.com/GNOME/libxml2
author: Nick Wellnhofer
author_date: 2017-06-05 15:37:17+02:00
msg: Fix handling of parameter-entity references

There were two bugs where parameter-entity references could lead to an
unexpected change of the input buffer in xmlParseNameComplex and
xmlDictLookup being called with an invalid pointer.

Percent sign in DTD Names
=========================

The NEXTL macro used to call xmlParserHandlePEReference. When parsing
"complex" names inside the DTD, this could result in entity expansion
which created a new input buffer. The fix is to simply remove the call
to xmlParserHandlePEReference from the NEXTL macro. This is safe because
no users of the macro require expansion of parameter entities.

- xmlParseNameComplex
- xmlParseNCNameComplex
- xmlParseNmtoken

The percent sign is not allowed in names, which are grammatical tokens.

- xmlParseEntityValue

Parameter-entity references in entity values are expanded but this
happens in a separate step in this function.

- xmlParseSystemLiteral

Parameter-entity references are ignored in the system literal.

- xmlParseAttValueComplex
- xmlParseCharDataComplex
- xmlParseCommentComplex
- xmlParsePI
- xmlParseCDSect

Parameter-entity references are ignored outside the DTD.

- xmlLoadEntityContent

This function is only called from xmlStringLenDecodeEntities and
entities are replaced in a separate step immediately after the function
call.

This bug could also be triggered with an internal subset and double
entity expansion.

This fixes bug 766956 initially reported by Wei Lei and independently by
Chromium's ClusterFuzz, Hanno Böck, and Marco Grassi. Thanks to everyone
involved.

xmlParseNameComplex with XML_PARSE_OLD10
========================================

When parsing Names inside an expanded parameter entity with the
XML_PARSE_OLD10 option, xmlParseNameComplex would call xmlGROW via the
GROW macro if the input buffer was exhausted. At the end of the
parameter entity's replacement text, this function would then call
xmlPopInput which invalidated the input buffer.

There should be no need to invoke GROW in this situation because the
buffer is grown periodically every XML_PARSER_CHUNK_SIZE characters and,
at least for UTF-8, in xmlCurrentChar. This also matches the code path
executed when XML_PARSE_OLD10 is not set.

This fixes bugs 781205 (CVE-2017-9049) and 781361 (CVE-2017-9050).
Thanks to Marcel Böhme and Thuan Pham for the report.

Additional hardening
====================

A separate check was added in xmlParseNameComplex to validate the
buffer size.
repository: repo_url: https://github.com/GNOME/libxml2
repo_name: GNOME/libxml2
description: Read-only mirror of https://gitlab.gnome.org/GNOME/libxml2
repo_language: C
owner: GNOME
file_changes: filename: Makefile.am
change_type: ModificationType.MODIFY
diff: @@ -427,6 +427,24 @@ Errtests : xmllint$(EXEEXT)
 	      if [ -n "$$log" ] ; then echo $$name result ; echo "$$log" ; fi ; \
 	      rm result.$$name error.$$name ; \
 	  fi ; fi ; done)
+	@echo "## Error cases regression tests (old 1.0)"
+	-@(for i in $(srcdir)/test/errors10/*.xml ; do \
+	  name=`basename $$i`; \
+	  if [ ! -d $$i ] ; then \
+	  if [ ! -f $(srcdir)/result/errors10/$$name ] ; then \
+	      echo New test file $$name ; \
+	      $(CHECKER) $(top_builddir)/xmllint --oldxml10 $$i \
+	         2> $(srcdir)/result/errors10/$$name.err \
+		 > $(srcdir)/result/errors10/$$name ; \
+	      grep "MORY ALLO" .memdump  | grep -v "MEMORY ALLOCATED : 0"; \
+	  else \
+	      log=`$(CHECKER) $(top_builddir)/xmllint --oldxml10 $$i 2> error.$$name > result.$$name ; \
+	      grep "MORY ALLO" .memdump  | grep -v "MEMORY ALLOCATED : 0"; \
+	      diff $(srcdir)/result/errors10/$$name result.$$name ; \
+	      diff $(srcdir)/result/errors10/$$name.err error.$$name` ; \
+	      if [ -n "$$log" ] ; then echo $$name result ; echo "$$log" ; fi ; \
+	      rm result.$$name error.$$name ; \
+	  fi ; fi ; done)
 	@echo "## Error cases stream regression tests"
 	-@(for i in $(srcdir)/test/errors/*.xml ; do \
 	  name=`basename $$i`; \

programming_language: Shell
filename: parser.c
change_type: ModificationType.MODIFY
diff: @@ -2121,7 +2121,6 @@ static void xmlGROW (xmlParserCtxtPtr ctxt) {
 	ctxt->input->line++; ctxt->input->col = 1;			\
     } else ctxt->input->col++;						\
     ctxt->input->cur += l;				\
-    if (*ctxt->input->cur == '%') xmlParserHandlePEReference(ctxt);	\
   } while (0)
 
 #define CUR_CHAR(l) xmlCurrentChar(ctxt, &l)
@@ -3412,13 +3411,6 @@ xmlParseNameComplex(xmlParserCtxtPtr ctxt) {
 	    len += l;
 	    NEXTL(l);
 	    c = CUR_CHAR(l);
-	    if (c == 0) {
-		count = 0;
-		GROW;
-                if (ctxt->instate == XML_PARSER_EOF)
-                    return(NULL);
-		c = CUR_CHAR(l);
-	    }
 	}
     }
     if ((len > XML_MAX_NAME_LENGTH) &&
@@ -3426,6 +3418,16 @@ xmlParseNameComplex(xmlParserCtxtPtr ctxt) {
         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
+         * of the buffer. Check the buffer size to avoid passing an invalid
+         * pointer to xmlDictLookup.
+         */
+        xmlFatalErr(ctxt, XML_ERR_INTERNAL_ERROR,
+                    "unexpected change of input buffer");
+        return (NULL);
+    }
     if ((*ctxt->input->cur == '\n') && (ctxt->input->cur[-1] == '\r'))
         return(xmlDictLookup(ctxt->dict, ctxt->input->cur - (len + 1), len));
     return(xmlDictLookup(ctxt->dict, ctxt->input->cur - len, len));

programming_language: C
filename: 781205.xml.err
change_type: ModificationType.ADD
diff: @@ -0,0 +1,21 @@
+Entity: line 1: parser error : internal error: xmlParseInternalSubset: error detected in Markup declaration
+
+ %a; 
+    ^
+Entity: line 1: 
+<:0000
+^
+Entity: line 1: parser error : DOCTYPE improperly terminated
+ %a; 
+    ^
+Entity: line 1: 
+<:0000
+^
+namespace error : Failed to parse QName ':0000'
+ %a; 
+    ^
+<:0000
+      ^
+./test/errors10/781205.xml:4: parser error : Couldn't find end of Start Tag :0000 line 1
+
+^

programming_language: Batchfile
filename: 781361.xml.err
change_type: ModificationType.ADD
diff: @@ -0,0 +1,13 @@
+./test/errors10/781361.xml:4: parser error : xmlParseElementDecl: 'EMPTY', 'ANY' or '(' expected
+
+^
+./test/errors10/781361.xml:4: parser error : internal error: xmlParseInternalSubset: error detected in Markup declaration
+
+
+^
+./test/errors10/781361.xml:4: parser error : DOCTYPE improperly terminated
+
+^
+./test/errors10/781361.xml:4: parser error : Start tag expected, '<' not found
+
+^

programming_language: CoffeeScript
filename: 766956.xml.err
change_type: ModificationType.ADD
diff: @@ -0,0 +1,9 @@
+test/valid/dtds/766956.dtd:2: parser error : PEReference: expecting ';'
+%ä%ent;
+   ^
+Entity: line 1: parser error : Content error in the external subset
+ %ent; 
+      ^
+Entity: line 1: 
+value
+^

programming_language: SQL
filename: 766956.xml.err.rdr
change_type: ModificationType.ADD
diff: @@ -0,0 +1,10 @@
+test/valid/dtds/766956.dtd:2: parser error : PEReference: expecting ';'
+%ä%ent;
+   ^
+Entity: line 1: parser error : Content error in the external subset
+ %ent; 
+      ^
+Entity: line 1: 
+value
+^
+./test/valid/766956.xml : failed to parse

programming_language: SQL
filename: runtest.c
change_type: ModificationType.MODIFY
diff: @@ -4214,6 +4214,9 @@ testDesc testDescriptions[] = {
     { "Error cases regression tests",
       errParseTest, "./test/errors/*.xml", "result/errors/", "", ".err",
       0 },
+    { "Error cases regression tests (old 1.0)",
+      errParseTest, "./test/errors10/*.xml", "result/errors10/", "", ".err",
+      XML_PARSE_OLD10 },
 #ifdef LIBXML_READER_ENABLED
     { "Error cases stream regression tests",
       streamParseTest, "./test/errors/*.xml", "result/errors/", NULL, ".str",

programming_language: C
filename: 781205.xml
change_type: ModificationType.ADD
diff: @@ -0,0 +1,3 @@
+<!DOCTYPE D [
+  <!ENTITY % a "<:0000">
+  %a;

programming_language: SQL
filename: 781361.xml
change_type: ModificationType.ADD
diff: @@ -0,0 +1,3 @@
+<!DOCTYPE doc [
+  <!ENTITY % elem "<!ELEMENT e0000000000">
+  %elem;

programming_language: SQL
filename: 766956.xml
change_type: ModificationType.ADD
diff: @@ -0,0 +1,2 @@
+<!DOCTYPE test SYSTEM "dtds/766956.dtd">
+<test/>

programming_language: SQL
filename: 766956.dtd
change_type: ModificationType.ADD
diff: @@ -0,0 +1,2 @@
+<!ENTITY % ent "value">
+%ä%ent;

programming_language: SQL