diff --git a/CVE-2018-1000632-pre.patch b/CVE-2018-1000632-pre.patch new file mode 100644 index 0000000..e7c8be6 --- /dev/null +++ b/CVE-2018-1000632-pre.patch @@ -0,0 +1,152 @@ +From 92d87957c4c4948d048ff7729c77ba10474f73ae Mon Sep 17 00:00:00 2001 +From: =?UTF-8?q?Filip=20Jirs=C3=A1k?= +Date: Sun, 1 Jul 2018 13:06:18 +0200 +Subject: [PATCH] Fix tests with invalid QNames. + +--- + .../java/org/dom4j/datatype/SchemaParser.java | 29 +++++++++++-------- + src/test/java/org/dom4j/IteratorTest.java | 20 ++++++------- + src/test/java/org/dom4j/dom/DOMTest.java | 2 +- + 3 files changed, 28 insertions(+), 23 deletions(-) + +diff --git a/src/main/java/org/dom4j/datatype/SchemaParser.java b/src/main/java/org/dom4j/datatype/SchemaParser.java +index c35806ba..ab299d82 100644 +--- a/src/main/java/org/dom4j/datatype/SchemaParser.java ++++ b/src/main/java/org/dom4j/datatype/SchemaParser.java +@@ -180,15 +180,19 @@ private void onDatatypeElement(Element xsdElement, + DocumentFactory parentFactory) { + String name = xsdElement.attributeValue("name"); + String type = xsdElement.attributeValue("type"); +- QName qname = getQName(name); + +- DatatypeElementFactory factory = getDatatypeElementFactory(qname); ++ QName qname = null; ++ DatatypeElementFactory factory = null; ++ if (name != null) { ++ qname = getQName(name); ++ factory = getDatatypeElementFactory(qname); ++ } + + if (type != null) { + // register type with this element name + XSDatatype dataType = getTypeByName(type); + +- if (dataType != null) { ++ if (dataType != null && factory != null) { + factory.setChildElementXSDatatype(qname, dataType); + } else { + QName typeQName = getQName(type); +@@ -205,24 +209,25 @@ private void onDatatypeElement(Element xsdElement, + if (xsdSimpleType != null) { + XSDatatype dataType = loadXSDatatypeFromSimpleType(xsdSimpleType); + +- if (dataType != null) { ++ if (dataType != null && factory != null) { + factory.setChildElementXSDatatype(qname, dataType); + } + } + + Element schemaComplexType = xsdElement.element(XSD_COMPLEXTYPE); + +- if (schemaComplexType != null) { ++ if (schemaComplexType != null && factory != null) { + onSchemaComplexType(schemaComplexType, factory); + } + +- Iterator iter = xsdElement.elementIterator(XSD_ATTRIBUTE); +- +- if (iter.hasNext()) { +- do { +- onDatatypeAttribute(xsdElement, factory, iter +- .next()); +- } while (iter.hasNext()); ++ if (factory != null) { ++ Iterator iter = xsdElement.elementIterator(XSD_ATTRIBUTE); ++ if (iter.hasNext()) { ++ do { ++ onDatatypeAttribute(xsdElement, factory, iter ++ .next()); ++ } while (iter.hasNext()); ++ } + } + } + +diff --git a/src/test/java/org/dom4j/IteratorTest.java b/src/test/java/org/dom4j/IteratorTest.java +index 76a2eef8..53091ae9 100644 +--- a/src/test/java/org/dom4j/IteratorTest.java ++++ b/src/test/java/org/dom4j/IteratorTest.java +@@ -31,7 +31,7 @@ public void setUp() throws Exception { + Element root = iterDocument.addElement("root"); + + for (int i = 0; i < NUMELE; i++) { +- root.addElement("iterator test").addAttribute("instance", ++ root.addElement("iterator-test").addAttribute("instance", + Integer.toString(i)); + } + } +@@ -42,7 +42,7 @@ public void testElementCount() throws Exception { + Element root = iterDocument.getRootElement(); + assertTrue("Has root element", root != null); + +- List elements = root.elements("iterator test"); ++ List elements = root.elements("iterator-test"); + int elementSize = elements.size(); + assertTrue("Root has " + elementSize + " children", (elements != null) + && (elementSize == NUMELE)); +@@ -50,8 +50,8 @@ public void testElementCount() throws Exception { + + public void testPlainIteration() throws Exception { + Element root = iterDocument.getRootElement(); +- List elements = root.elements("iterator test"); +- Iterator iter = root.elementIterator("iterator test"); ++ List elements = root.elements("iterator-test"); ++ Iterator iter = root.elementIterator("iterator-test"); + int elementSize = elements.size(); + + int count = 0; +@@ -69,8 +69,8 @@ public void testPlainIteration() throws Exception { + + public void testSkipAlternates() throws Exception { + Element root = iterDocument.getRootElement(); +- List elements = root.elements("iterator test"); +- Iterator iter = root.elementIterator("iterator test"); ++ List elements = root.elements("iterator-test"); ++ Iterator iter = root.elementIterator("iterator-test"); + int elementSize = elements.size(); + int count = 0; + +@@ -89,8 +89,8 @@ public void testSkipAlternates() throws Exception { + + public void testNoHasNext() throws Exception { + Element root = iterDocument.getRootElement(); +- List elements = root.elements("iterator test"); +- Iterator iter = root.elementIterator("iterator test"); ++ List elements = root.elements("iterator-test"); ++ Iterator iter = root.elementIterator("iterator-test"); + int elementSize = elements.size(); + int count = 0; + Element e = null; +@@ -121,8 +121,8 @@ public void testNoHasNext() throws Exception { + + public void testExtraHasNexts() throws Exception { + Element root = iterDocument.getRootElement(); +- List elements = root.elements("iterator test"); +- Iterator iter = root.elementIterator("iterator test"); ++ List elements = root.elements("iterator-test"); ++ Iterator iter = root.elementIterator("iterator-test"); + int elementSize = elements.size(); + int count = 0; + +diff --git a/src/test/java/org/dom4j/dom/DOMTest.java b/src/test/java/org/dom4j/dom/DOMTest.java +index f44d3e80..4b1f9c85 100644 +--- a/src/test/java/org/dom4j/dom/DOMTest.java ++++ b/src/test/java/org/dom4j/dom/DOMTest.java +@@ -109,7 +109,7 @@ public void testReplaceChild() throws Exception { + assertEquals(newFirst, firstChild); + + /* try to replace a node that doesn't exist */ +- org.w3c.dom.Element badNode = document.createElement("No Child"); ++ org.w3c.dom.Element badNode = document.createElement("No-Child"); + + try { + parent.replaceChild(newFirst, badNode); diff --git a/CVE-2018-1000632.patch b/CVE-2018-1000632.patch new file mode 100644 index 0000000..2f09b85 --- /dev/null +++ b/CVE-2018-1000632.patch @@ -0,0 +1,257 @@ +diff --git a/src/main/java/org/dom4j/Namespace.java b/src/main/java/org/dom4j/Namespace.java +index fd123b93..8f948ad8 100644 +--- a/src/main/java/org/dom4j/Namespace.java ++++ b/src/main/java/org/dom4j/Namespace.java +@@ -49,6 +49,10 @@ + public Namespace(String prefix, String uri) { + this.prefix = (prefix != null) ? prefix : ""; + this.uri = (uri != null) ? uri : ""; ++ ++ if (!this.prefix.isEmpty()) { ++ QName.validateNCName(this.prefix); ++ } + } + + /** +diff --git a/src/main/java/org/dom4j/QName.java b/src/main/java/org/dom4j/QName.java +index 9ac0d4d8..e9b2170e 100644 +--- a/src/main/java/org/dom4j/QName.java ++++ b/src/main/java/org/dom4j/QName.java +@@ -11,6 +11,7 @@ + import java.io.ObjectInputStream; + import java.io.ObjectOutputStream; + import java.io.Serializable; ++import java.util.regex.Pattern; + + import org.dom4j.tree.QNameCache; + import org.dom4j.util.SingletonStrategy; +@@ -21,11 +22,86 @@ + * object is immutable. + * + * @author James Strachan ++ * @author Filip Jirsák + */ + public class QName implements Serializable { + /** The Singleton instance */ + private static SingletonStrategy singleton = null; + ++ /** ++ * {@code NameStartChar} without colon. ++ * ++ *
NameStartChar      ::=     ":" | [A-Z] | "_" | [a-z] | [#xC0-#xD6] | [#xD8-#xF6] | [#xF8-#x2FF] | [#x370-#x37D] | [#x37F-#x1FFF] | [#x200C-#x200D] | [#x2070-#x218F] | [#x2C00-#x2FEF] | [#x3001-#xD7FF] | [#xF900-#xFDCF] | [#xFDF0-#xFFFD] | [#x10000-#xEFFFF]
++ * ++ * @see XML 1.0 – 2.3 Common Syntactic Constructs ++ * @see XML 1.1 – 2.3 Common Syntactic Constructs ++ */ ++ private static final String NAME_START_CHAR = "_A-Za-z\u00C0-\u00D6\u00D8-\u00F6\u00F8-\u02FF\u0370-\u037D\u037F-\u1FFF\u200C-\u200D\u2070-\u218F\u2C00-\u2FEF\u3001-\uD7FF\uF900-\uFDCF\uFDF0-\uFFFD"; ++ ++ /** ++ * {@code NameChar} without colon. ++ * ++ *
NameChar   ::=     NameStartChar | "-" | "." | [0-9] | #xB7 | [#x0300-#x036F] | [#x203F-#x2040]
++ * ++ * @see XML 1.0 – 2.3 Common Syntactic Constructs ++ * @see XML 1.1 – 2.3 Common Syntactic Constructs ++ */ ++ private static final String NAME_CHAR = NAME_START_CHAR + "-.0-9\u00B7\u0300-\u036F\u203F-\u2040"; ++ ++ /** ++ * {@code NCName} ++ * ++ *
++     * NCName          ::=     NCNameStartChar NCNameChar*     (An XML Name, minus the ":")
++     * NCNameChar      ::=     NameChar -':'
++     * NCNameStartChar ::=     NameStartChar -':'
++     * 
++ * ++ * @see Namespaces in XML 1.0 – 4 Qualified Names ++ * @see Namespaces in XML 1.1 – 4 Qualified Names ++ */ ++ private static final String NCNAME = "["+NAME_START_CHAR+"]["+NAME_CHAR+"]*"; ++ ++ /** ++ * Regular expression for {@code Name} (with colon). ++ * ++ *
Name       ::=     NameStartChar (NameChar)*
++ * ++ * @see XML 1.0 – 2.3 Common Syntactic Constructs ++ * @see XML 1.1 – 2.3 Common Syntactic Constructs ++ */ ++ private static final Pattern RE_NAME = Pattern.compile("[:"+NAME_START_CHAR+"][:"+NAME_CHAR+"]*"); ++ ++ /** ++ * Regular expression for {@code NCName}. ++ * ++ *
++     * NCName          ::=     NCNameStartChar NCNameChar*     (An XML Name, minus the ":")
++     * NCNameChar      ::=     NameChar -':'
++     * NCNameStartChar ::=     NameStartChar -':'
++     * 
++ * ++ * @see Namespaces in XML 1.0 – 4 Qualified Names ++ * @see Namespaces in XML 1.1 – 4 Qualified Names ++ */ ++ private static final Pattern RE_NCNAME = Pattern.compile(NCNAME); ++ ++ /** ++ * Regular expression for {@code QName}. ++ * ++ *
++     * QName           ::=     PrefixedName | UnprefixedName
++     * PrefixedName    ::=     Prefix ':' LocalPart
++     * UnprefixedName  ::=     LocalPart
++     * Prefix          ::=     NCName
++     * LocalPart       ::=     NCName
++     * 
++ * ++ * @see Namespaces in XML 1.0 – 4 Qualified Names ++ * @see Namespaces in XML 1.1 – 4 Qualified Names ++ */ ++ private static final Pattern RE_QNAME = Pattern.compile("(?:"+NCNAME+":)?"+NCNAME); ++ + static { + try { + String defaultSingletonClass = "org.dom4j.util.SimpleSingleton"; +@@ -71,6 +147,11 @@ public QName(String name, Namespace namespace) { + this.name = (name == null) ? "" : name; + this.namespace = (namespace == null) ? Namespace.NO_NAMESPACE + : namespace; ++ if (this.namespace.equals(Namespace.NO_NAMESPACE)) { ++ validateName(this.name); ++ } else { ++ validateNCName(this.name); ++ } + } + + public QName(String name, Namespace namespace, String qualifiedName) { +@@ -78,6 +159,8 @@ public QName(String name, Namespace namespace, String qualifiedName) { + this.qualifiedName = qualifiedName; + this.namespace = (namespace == null) ? Namespace.NO_NAMESPACE + : namespace; ++ validateNCName(this.name); ++ validateQName(this.qualifiedName); + } + + public static QName get(String name) { +@@ -251,6 +334,24 @@ private static QNameCache getCache() { + QNameCache cache = singleton.instance(); + return cache; + } ++ ++ private static void validateName(String name) { ++ if (!RE_NAME.matcher(name).matches()) { ++ throw new IllegalArgumentException(String.format("Illegal character in name: '%s'.", name)); ++ } ++ } ++ ++ protected static void validateNCName(String ncname) { ++ if (!RE_NCNAME.matcher(ncname).matches()) { ++ throw new IllegalArgumentException(String.format("Illegal character in local name: '%s'.", ncname)); ++ } ++ } ++ ++ private static void validateQName(String qname) { ++ if (!RE_QNAME.matcher(qname).matches()) { ++ throw new IllegalArgumentException(String.format("Illegal character in qualified name: '%s'.", qname)); ++ } ++ } + } + +diff --git a/src/main/java/org/dom4j/tree/QNameCache.java b/src/main/java/org/dom4j/tree/QNameCache.java +index 330f3794..d37e8aaa 100644 +--- a/src/main/java/org/dom4j/tree/QNameCache.java ++++ b/src/main/java/org/dom4j/tree/QNameCache.java +@@ -152,6 +152,8 @@ public QName get(String qualifiedName, String uri) { + + if (index < 0) { + return get(qualifiedName, Namespace.get(uri)); ++ } else if (index == 0){ ++ throw new IllegalArgumentException("Qualified name cannot start with ':'."); + } else { + String name = qualifiedName.substring(index + 1); + String prefix = qualifiedName.substring(0, index); +diff --git a/src/test/java/org/dom4j/AllowedCharsTest.java b/src/test/java/org/dom4j/AllowedCharsTest.java +new file mode 100644 +index 00000000..20c1de0b +--- /dev/null ++++ b/src/test/java/org/dom4j/AllowedCharsTest.java +@@ -0,0 +1,78 @@ ++package org.dom4j; ++ ++import org.testng.annotations.Test; ++ ++/** ++ * @author Filip Jirsák ++ */ ++public class AllowedCharsTest { ++ @Test ++ public void localName() { ++ QName.get("element"); ++ QName.get(":element"); ++ QName.get("elem:ent"); ++ } ++ ++ @Test(expectedExceptions = IllegalArgumentException.class) ++ public void localNameFail() { ++ QName.get("!element"); ++ } ++ ++ @Test ++ public void qname() { ++ QName.get("element", "http://example.com/namespace"); ++ QName.get("ns:element", "http://example.com/namespace"); ++ } ++ ++ @Test(expectedExceptions = IllegalArgumentException.class) ++ public void qnameFail1() { ++ QName.get("ns:elem:ent", "http://example.com/namespace"); ++ } ++ ++ @Test(expectedExceptions = IllegalArgumentException.class) ++ public void qnameFail2() { ++ QName.get(":nselement", "http://example.com/namespace"); ++ } ++ ++ @Test(expectedExceptions = IllegalArgumentException.class) ++ public void createElementLT() { ++ DocumentHelper.createElement("elementname"); ++ } ++ ++ @Test(expectedExceptions = IllegalArgumentException.class) ++ public void createElementAmpersand() { ++ DocumentHelper.createElement("element&name"); ++ } ++ ++ @Test(expectedExceptions = IllegalArgumentException.class) ++ public void addElement() { ++ Element root = DocumentHelper.createElement("root"); ++ root.addElement("element>name"); ++ } ++ ++ @Test(expectedExceptions = IllegalArgumentException.class) ++ public void addElementQualified() { ++ Element root = DocumentHelper.createElement("root"); ++ root.addElement("element>name", "http://example.com/namespace"); ++ } ++ ++ @Test(expectedExceptions = IllegalArgumentException.class) ++ public void addElementQualifiedPrefix() { ++ Element root = DocumentHelper.createElement("root"); ++ root.addElement("ns:element>name", "http://example.com/namespace"); ++ } ++ ++ @Test(expectedExceptions = IllegalArgumentException.class) ++ public void addElementPrefix() { ++ Element root = DocumentHelper.createElement("root"); ++ root.addElement("ns>:element", "http://example.com/namespace"); ++ } ++ ++ //TODO It is illegal to create element or attribute with namespace prefix and empty namespace IRI. ++ //See https://www.w3.org/TR/2006/REC-xml-names11-20060816/#scoping ++} + diff --git a/dom4j-2.0.0-6.fc29.src.rpm b/dom4j-2.0.0-6.fc29.src.rpm new file mode 100644 index 0000000..a9d9630 Binary files /dev/null and b/dom4j-2.0.0-6.fc29.src.rpm differ