Skip to content

SAXBuilder.build() throws an undeclared unchecked exception on valid XML 1.1 #223

Description

@alex9849

SAXBuilder.build() throws an undeclared unchecked exception on valid XML 1.1

SAXBuilder.build() is declared as

public Document build(Reader characterStream) throws JDOMException, IOException

so a caller catching JDOMException and IOException has handled every failure the
signature announces. On a valid XML 1.1 document it can still throw
org.jdom2.IllegalDataException, which extends IllegalArgumentException — unchecked,
undeclared, and past the caller's catch.

XML 1.1 permits , XML 1.0 does not, and JDOM does not fully implement XML 1.1:
the parser accepts the character under XML 1.1 rules and passes it to JDOM, whose verifier applies XML 1.0 rules and rejects it afterwards. (From inside a SAX callback, after build() has already begun.)

Affects the released 2.0.6.1.

Reproduce

Download and unzip Reproduce.zip

Poc.java, attached, parses the same content twice (once declared version="1.0", once version="1.1") inside the catch a caller would write from the signature:

curl -LO https://repo1.maven.org/maven2/org/jdom/jdom2/2.0.6.1/jdom2-2.0.6.1.jar
javac -cp jdom2-2.0.6.1.jar Poc.java
java -cp jdom2-2.0.6.1.jar:. Poc
static final String XML_10 = "<?xml version=\"1.0\" encoding=\"UTF-8\"?>\n<r>char:&#x05;</r>";
static final String XML_11 = "<?xml version=\"1.1\" encoding=\"UTF-8\"?>\n<r>char:&#x05;</r>";

static void parse(String label, String xml) throws JDOMException, IOException {
    try {
        Document doc = new SAXBuilder().build(new StringReader(xml));
        System.out.println(label + ": parsed <" + doc.getRootElement().getName() + ">");
    }
    catch (JDOMException | IOException e) {
        System.out.println(label + ": handled " + e.getClass().getName());
    }
}

Output:

XML 1.0: handled org.jdom2.input.JDOMParseException
Exception in thread "main" org.jdom2.IllegalDataException: The data "char:" is not legal for a JDOM character content: 0x0005 is not a legal XML character.
	at org.jdom2.Text.setText(Text.java:182)
	at org.jdom2.Text.<init>(Text.java:112)
	at org.jdom2.DefaultJDOMFactory.text(DefaultJDOMFactory.java:131)
	at org.jdom2.input.sax.SAXHandler.flushCharacters(SAXHandler.java:840)
	at org.jdom2.input.sax.SAXHandler.flushCharacters(SAXHandler.java:806)
	at org.jdom2.input.sax.SAXHandler.endElement(SAXHandler.java:869)
	at java.xml/com.sun.org.apache.xerces.internal.impl.XML11NSDocumentScannerImpl.scanEndElement(XML11NSDocumentScannerImpl.java:782)
	...
	at org.jdom2.input.sax.SAXBuilderEngine.build(SAXBuilderEngine.java:217)
	at org.jdom2.input.sax.SAXBuilderEngine.build(SAXBuilderEngine.java:303)
	at org.jdom2.input.SAXBuilder.build(SAXBuilder.java:1184)
	at Poc.parse(Poc.java:16)

The XML 1.0 line is the same rejection reported the way the signature promises: the parser refuses the character itself and build() turns that into a JDOMParseException.
Only the XML 1.1 line escapes the catch.

Suggested direction

This is not a request to support XML 1.1. Rejecting content JDOM cannot represent is fine, but the rejection should reach the caller as the parse error it is, through JDOMException, like every other unusable input.

Found by the CISPA Fandango-Team while triaging OSS-Fuzz findings for jdom.

Activity

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Projects

    No projects

      Milestone

      No milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions