CVE-2022-2309 (published 2022-07-05T10:15Z)

NULL Pointer Dereference allows attackers to cause a denial of service (or application crash). This only applies when lxml is used together with libxml2 2.9.10 through 2.9.14. libxml2 2.9.9 and earlier are not affected. It allows triggering crashes through forged input data, given a vulnerable code sequence in the application. The vulnerability is caused by the iterwalk function (also used by the canonicalize function). Such code shouldn't be in wide-spread use, given that parsing + iterwalk would usually be replaced with the more efficient iterparse function. However, an XML converter that serialises to C14N would also be vulnerable, for example, and there are legitimate use cases for this code sequence. If untrusted input is received (also remotely) and processed via iterwalk function, a crash can be triggered.

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

Weakness information:
cwe_id: CWE-476
cwe_name: NULL Pointer Dereference
description: The product dereferences a pointer that it expects to be valid but is NULL.
extended_description: 
            
            
            
         
         

Fix information:
repo_url: https://github.com/lxml/lxml
commit_details: repo_url: https://github.com/lxml/lxml
author: Stefan Behnel
author_date: 2022-07-01 21:06:10+02:00
msg: Fix a crash when incorrect parser input occurs together with usages of iterwalk() on trees generated by the same parser.
repository: repo_url: https://github.com/lxml/lxml
repo_name: lxml/lxml
description: The lxml XML toolkit for Python
repo_language: Python
owner: lxml
file_changes: filename: apihelpers.pxi
change_type: ModificationType.MODIFY
diff: @@ -246,9 +246,10 @@ cdef dict _build_nsmap(xmlNode* c_node):
     while c_node is not NULL and c_node.type == tree.XML_ELEMENT_NODE:
         c_ns = c_node.nsDef
         while c_ns is not NULL:
-            prefix = funicodeOrNone(c_ns.prefix)
-            if prefix not in nsmap:
-                nsmap[prefix] = funicodeOrNone(c_ns.href)
+            if c_ns.prefix or c_ns.href:
+                prefix = funicodeOrNone(c_ns.prefix)
+                if prefix not in nsmap:
+                    nsmap[prefix] = funicodeOrNone(c_ns.href)
             c_ns = c_ns.next
         c_node = c_node.parent
     return nsmap

programming_language: Python
filename: iterparse.pxi
change_type: ModificationType.MODIFY
diff: @@ -420,7 +420,7 @@ cdef int _countNsDefs(xmlNode* c_node):
     count = 0
     c_ns = c_node.nsDef
     while c_ns is not NULL:
-        count += 1
+        count += (c_ns.href is not NULL)
         c_ns = c_ns.next
     return count
 
@@ -431,9 +431,10 @@ cdef int _appendStartNsEvents(xmlNode* c_node, list event_list) except -1:
     count = 0
     c_ns = c_node.nsDef
     while c_ns is not NULL:
-        ns_tuple = (funicode(c_ns.prefix) if c_ns.prefix is not NULL else '',
-                    funicode(c_ns.href))
-        event_list.append( (u"start-ns", ns_tuple) )
-        count += 1
+        if c_ns.href:
+            ns_tuple = (funicodeOrEmpty(c_ns.prefix),
+                        funicode(c_ns.href))
+            event_list.append( (u"start-ns", ns_tuple) )
+            count += 1
         c_ns = c_ns.next
     return count

programming_language: Python
filename: test_etree.py
change_type: ModificationType.MODIFY
diff: @@ -1460,6 +1460,26 @@ class ETreeOnlyTestCase(HelperTestCase):
             [1,2,1,4],
             counts)
 
+    def test_walk_after_parse_failure(self):
+        # This used to be an issue because libxml2 can leak empty namespaces
+        # between failed parser runs.  iterwalk() failed to handle such a tree.
+        try:
+            etree.XML('''<anot xmlns="1">''')
+        except etree.XMLSyntaxError:
+            pass
+        else:
+            assert False, "invalid input did not fail to parse"
+
+        et = etree.XML('''<root>  </root>''')
+        try:
+            ns = next(etree.iterwalk(et, events=('start-ns',)))
+        except StopIteration:
+            # This would be the expected result, because there was no namespace
+            pass
+        else:
+            # This is a bug in libxml2
+            assert not ns, repr(ns)
+
     def test_itertext_comment_pi(self):
         # https://bugs.launchpad.net/lxml/+bug/1844674
         XML = self.etree.XML

programming_language: Python