50b54c
From 3b2a73ad85da069637a73beca432950204535979 Mon Sep 17 00:00:00 2001
50b54c
From: Ondrej Dubaj <odubaj@redhat.com>
50b54c
Date: Wed, 22 Jul 2020 11:39:42 +0200
50b54c
Subject: [PATCH] Fix for XXE vulnerability
50b54c
50b54c
by defaulting to disabling external access and doc types. The
50b54c
legacy insecure behavior can be restored via the new connection property xmlFactoryFactory
50b54c
with a value of LEGACY_INSECURE. Alternatively, a custom class name can be specified that
50b54c
implements org.postgresql.xml.PGXmlFactoryFactory and takes a no argument constructor.
50b54c
50b54c
* refactor: Clean up whitespace in existing PgSQLXMLTest
50b54c
* fix: Fix XXE vulnerability in PgSQLXML by disabling external access and doctypes
50b54c
* fix: Add missing getter and setter for XML_FACTORY_FACTORY to BasicDataSource
50b54c
---
50b54c
 .../main/java/org/postgresql/PGProperty.java  |  11 ++
50b54c
 .../org/postgresql/core/BaseConnection.java   |   9 ++
50b54c
 .../postgresql/ds/common/BaseDataSource.java  |   8 +
50b54c
 .../org/postgresql/jdbc/PgConnection.java     |  41 +++++
50b54c
 .../java/org/postgresql/jdbc/PgSQLXML.java    |  44 +++---
50b54c
 .../xml/DefaultPGXmlFactoryFactory.java       | 141 ++++++++++++++++++
50b54c
 .../xml/EmptyStringEntityResolver.java        |  23 +++
50b54c
 .../LegacyInsecurePGXmlFactoryFactory.java    |  57 +++++++
50b54c
 .../org/postgresql/xml/NullErrorHandler.java  |  25 ++++
50b54c
 .../postgresql/xml/PGXmlFactoryFactory.java   |  30 ++++
50b54c
 .../org/postgresql/jdbc/PgSQLXMLTest.java     | 124 +++++++++++++++
50b54c
 .../postgresql/test/jdbc2/Jdbc2TestSuite.java |   2 +
50b54c
 12 files changed, 489 insertions(+), 26 deletions(-)
50b54c
 create mode 100644 pgjdbc/src/main/java/org/postgresql/xml/DefaultPGXmlFactoryFactory.java
50b54c
 create mode 100644 pgjdbc/src/main/java/org/postgresql/xml/EmptyStringEntityResolver.java
50b54c
 create mode 100644 pgjdbc/src/main/java/org/postgresql/xml/LegacyInsecurePGXmlFactoryFactory.java
50b54c
 create mode 100644 pgjdbc/src/main/java/org/postgresql/xml/NullErrorHandler.java
50b54c
 create mode 100644 pgjdbc/src/main/java/org/postgresql/xml/PGXmlFactoryFactory.java
50b54c
 create mode 100644 pgjdbc/src/test/java/org/postgresql/jdbc/PgSQLXMLTest.java
50b54c
50b54c
diff --git a/pgjdbc/src/main/java/org/postgresql/PGProperty.java b/pgjdbc/src/main/java/org/postgresql/PGProperty.java
50b54c
index e56e05e..7c2eed8 100644
50b54c
--- a/pgjdbc/src/main/java/org/postgresql/PGProperty.java
50b54c
+++ b/pgjdbc/src/main/java/org/postgresql/PGProperty.java
50b54c
@@ -331,6 +331,17 @@ public enum PGProperty {
50b54c
    */
50b54c
   USE_SPNEGO("useSpnego", "false", "Use SPNEGO in SSPI authentication requests"),
50b54c
 
50b54c
+  /**
50b54c
+   * Factory class to instantiate factories for XML processing.
50b54c
+   * The default factory disables external entity processing.
50b54c
+   * Legacy behavior with external entity processing can be enabled by specifying a value of LEGACY_INSECURE.
50b54c
+   * Or specify a custom class that implements {@code org.postgresql.xml.PGXmlFactoryFactory}.
50b54c
+   */
50b54c
+  XML_FACTORY_FACTORY(
50b54c
+    "xmlFactoryFactory",
50b54c
+    "",
50b54c
+    "Factory class to instantiate factories for XML processing"),
50b54c
+
50b54c
   /**
50b54c
    * Force one of
50b54c
    * 
    50b54c
    diff --git a/pgjdbc/src/main/java/org/postgresql/core/BaseConnection.java b/pgjdbc/src/main/java/org/postgresql/core/BaseConnection.java
    50b54c
    index 1d316a0..5f85964 100644
    50b54c
    --- a/pgjdbc/src/main/java/org/postgresql/core/BaseConnection.java
    50b54c
    +++ b/pgjdbc/src/main/java/org/postgresql/core/BaseConnection.java
    50b54c
    @@ -9,6 +9,7 @@ import org.postgresql.PGConnection;
    50b54c
     import org.postgresql.jdbc.FieldMetadata;
    50b54c
     import org.postgresql.jdbc.TimestampUtils;
    50b54c
     import org.postgresql.util.LruCache;
    50b54c
    +import org.postgresql.xml.PGXmlFactoryFactory;
    50b54c
     
    50b54c
     import java.sql.Connection;
    50b54c
     import java.sql.ResultSet;
    50b54c
    @@ -202,4 +203,12 @@ public interface BaseConnection extends PGConnection, Connection {
    50b54c
        * @param flushCacheOnDeallocate true if statement cache should be reset when "deallocate/discard" message observed
    50b54c
        */
    50b54c
       void setFlushCacheOnDeallocate(boolean flushCacheOnDeallocate);
    50b54c
    +
    50b54c
    +  /**
    50b54c
    +   * Retrieve the factory to instantiate XML processing factories.
    50b54c
    +   *
    50b54c
    +   * @return The factory to use to instantiate XML processing factories
    50b54c
    +   * @throws SQLException if the class cannot be found or instantiated.
    50b54c
    +   */
    50b54c
    +  PGXmlFactoryFactory getXmlFactoryFactory() throws SQLException;
    50b54c
     }
    50b54c
    diff --git a/pgjdbc/src/main/java/org/postgresql/ds/common/BaseDataSource.java b/pgjdbc/src/main/java/org/postgresql/ds/common/BaseDataSource.java
    50b54c
    index 268d936..2fb4e06 100644
    50b54c
    --- a/pgjdbc/src/main/java/org/postgresql/ds/common/BaseDataSource.java
    50b54c
    +++ b/pgjdbc/src/main/java/org/postgresql/ds/common/BaseDataSource.java
    50b54c
    @@ -1313,4 +1313,12 @@ public abstract class BaseDataSource implements CommonDataSource, Referenceable
    50b54c
         return Logger.getLogger("org.postgresql");
    50b54c
       }
    50b54c
       //#endif
    50b54c
    +
    50b54c
    +  public String getXmlFactoryFactory() {
    50b54c
    +    return PGProperty.XML_FACTORY_FACTORY.get(properties);
    50b54c
    +  }
    50b54c
    +
    50b54c
    +  public void setXmlFactoryFactory(String xmlFactoryFactory) {
    50b54c
    +    PGProperty.XML_FACTORY_FACTORY.set(properties, xmlFactoryFactory);
    50b54c
    +  }
    50b54c
     }
    50b54c
    diff --git a/pgjdbc/src/main/java/org/postgresql/jdbc/PgConnection.java b/pgjdbc/src/main/java/org/postgresql/jdbc/PgConnection.java
    50b54c
    index 7140ab4..c9c4ada 100644
    50b54c
    --- a/pgjdbc/src/main/java/org/postgresql/jdbc/PgConnection.java
    50b54c
    +++ b/pgjdbc/src/main/java/org/postgresql/jdbc/PgConnection.java
    50b54c
    @@ -37,6 +37,9 @@ import org.postgresql.util.PGBinaryObject;
    50b54c
     import org.postgresql.util.PGobject;
    50b54c
     import org.postgresql.util.PSQLException;
    50b54c
     import org.postgresql.util.PSQLState;
    50b54c
    +import org.postgresql.xml.DefaultPGXmlFactoryFactory;
    50b54c
    +import org.postgresql.xml.LegacyInsecurePGXmlFactoryFactory;
    50b54c
    +import org.postgresql.xml.PGXmlFactoryFactory;
    50b54c
     
    50b54c
     import java.io.IOException;
    50b54c
     import java.sql.Array;
    50b54c
    @@ -142,6 +145,9 @@ public class PgConnection implements BaseConnection {
    50b54c
     
    50b54c
       private final LruCache<FieldMetadata.Key, FieldMetadata> fieldMetadataCache;
    50b54c
     
    50b54c
    +  private final String xmlFactoryFactoryClass;
    50b54c
    +  private PGXmlFactoryFactory xmlFactoryFactory;
    50b54c
    +
    50b54c
       final CachedQuery borrowQuery(String sql) throws SQLException {
    50b54c
         return queryExecutor.borrowQuery(sql);
    50b54c
       }
    50b54c
    @@ -290,6 +296,8 @@ public class PgConnection implements BaseConnection {
    50b54c
             false);
    50b54c
     
    50b54c
         replicationConnection = PGProperty.REPLICATION.get(info) != null;
    50b54c
    +
    50b54c
    +    xmlFactoryFactoryClass = PGProperty.XML_FACTORY_FACTORY.get(info);
    50b54c
       }
    50b54c
     
    50b54c
       private static Set<Integer> getBinaryOids(Properties info) throws PSQLException {
    50b54c
    @@ -1729,4 +1737,37 @@ public class PgConnection implements BaseConnection {
    50b54c
         }
    50b54c
         return ps;
    50b54c
       }
    50b54c
    +
    50b54c
    +  @Override
    50b54c
    +  public PGXmlFactoryFactory getXmlFactoryFactory() throws SQLException {
    50b54c
    +    if (xmlFactoryFactory == null) {
    50b54c
    +      if (xmlFactoryFactoryClass == null || xmlFactoryFactoryClass.equals("")) {
    50b54c
    +        xmlFactoryFactory = DefaultPGXmlFactoryFactory.INSTANCE;
    50b54c
    +      } else if (xmlFactoryFactoryClass.equals("LEGACY_INSECURE")) {
    50b54c
    +        xmlFactoryFactory = LegacyInsecurePGXmlFactoryFactory.INSTANCE;
    50b54c
    +      } else {
    50b54c
    +        Class clazz;
    50b54c
    +        try {
    50b54c
    +          clazz = Class.forName(xmlFactoryFactoryClass);
    50b54c
    +        } catch (ClassNotFoundException ex) {
    50b54c
    +          throw new PSQLException(
    50b54c
    +              GT.tr("Could not instantiate xmlFactoryFactory: {0}", xmlFactoryFactoryClass),
    50b54c
    +              PSQLState.INVALID_PARAMETER_VALUE, ex);
    50b54c
    +        }
    50b54c
    +        if (!clazz.isAssignableFrom(PGXmlFactoryFactory.class)) {
    50b54c
    +          throw new PSQLException(
    50b54c
    +              GT.tr("Connection property xmlFactoryFactory must implement PGXmlFactoryFactory: {0}", xmlFactoryFactoryClass),
    50b54c
    +              PSQLState.INVALID_PARAMETER_VALUE);
    50b54c
    +        }
    50b54c
    +        try {
    50b54c
    +          xmlFactoryFactory = (PGXmlFactoryFactory) clazz.newInstance();
    50b54c
    +        } catch (Exception ex) {
    50b54c
    +          throw new PSQLException(
    50b54c
    +              GT.tr("Could not instantiate xmlFactoryFactory: {0}", xmlFactoryFactoryClass),
    50b54c
    +              PSQLState.INVALID_PARAMETER_VALUE, ex);
    50b54c
    +        }
    50b54c
    +      }
    50b54c
    +    }
    50b54c
    +    return xmlFactoryFactory;
    50b54c
    +  }
    50b54c
     }
    50b54c
    diff --git a/pgjdbc/src/main/java/org/postgresql/jdbc/PgSQLXML.java b/pgjdbc/src/main/java/org/postgresql/jdbc/PgSQLXML.java
    50b54c
    index 9fb0eed..dd7d5ac 100644
    50b54c
    --- a/pgjdbc/src/main/java/org/postgresql/jdbc/PgSQLXML.java
    50b54c
    +++ b/pgjdbc/src/main/java/org/postgresql/jdbc/PgSQLXML.java
    50b54c
    @@ -9,10 +9,11 @@ import org.postgresql.core.BaseConnection;
    50b54c
     import org.postgresql.util.GT;
    50b54c
     import org.postgresql.util.PSQLException;
    50b54c
     import org.postgresql.util.PSQLState;
    50b54c
    +import org.postgresql.xml.DefaultPGXmlFactoryFactory;
    50b54c
    +import org.postgresql.xml.PGXmlFactoryFactory;
    50b54c
     
    50b54c
    -import org.xml.sax.ErrorHandler;
    50b54c
     import org.xml.sax.InputSource;
    50b54c
    -import org.xml.sax.SAXParseException;
    50b54c
    +import org.xml.sax.XMLReader;
    50b54c
     
    50b54c
     import java.io.ByteArrayInputStream;
    50b54c
     import java.io.ByteArrayOutputStream;
    50b54c
    @@ -27,7 +28,6 @@ import java.sql.SQLException;
    50b54c
     import java.sql.SQLXML;
    50b54c
     
    50b54c
     import javax.xml.parsers.DocumentBuilder;
    50b54c
    -import javax.xml.parsers.DocumentBuilderFactory;
    50b54c
     import javax.xml.stream.XMLInputFactory;
    50b54c
     import javax.xml.stream.XMLOutputFactory;
    50b54c
     import javax.xml.stream.XMLStreamException;
    50b54c
    @@ -77,6 +77,13 @@ public class PgSQLXML implements SQLXML {
    50b54c
         _freed = false;
    50b54c
       }
    50b54c
     
    50b54c
    +  private PGXmlFactoryFactory getXmlFactoryFactory() throws SQLException {
    50b54c
    +    if (_conn != null) {
    50b54c
    +      return _conn.getXmlFactoryFactory();
    50b54c
    +    }
    50b54c
    +    return DefaultPGXmlFactoryFactory.INSTANCE;
    50b54c
    +  }
    50b54c
    +
    50b54c
       public synchronized void free() {
    50b54c
         _freed = true;
    50b54c
         _data = null;
    50b54c
    @@ -128,18 +135,17 @@ public class PgSQLXML implements SQLXML {
    50b54c
     
    50b54c
         try {
    50b54c
           if (sourceClass == null || DOMSource.class.equals(sourceClass)) {
    50b54c
    -        DocumentBuilderFactory factory = DocumentBuilderFactory.newInstance();
    50b54c
    -        DocumentBuilder builder = factory.newDocumentBuilder();
    50b54c
    -        builder.setErrorHandler(new NonPrintingErrorHandler());
    50b54c
    +        DocumentBuilder builder = getXmlFactoryFactory().newDocumentBuilder();
    50b54c
             InputSource input = new InputSource(new StringReader(_data));
    50b54c
             return (T) new DOMSource(builder.parse(input));
    50b54c
           } else if (SAXSource.class.equals(sourceClass)) {
    50b54c
    +        XMLReader reader = getXmlFactoryFactory().createXMLReader();
    50b54c
             InputSource is = new InputSource(new StringReader(_data));
    50b54c
    -        return (T) new SAXSource(is);
    50b54c
    +        return (T) new SAXSource(reader, is);
    50b54c
           } else if (StreamSource.class.equals(sourceClass)) {
    50b54c
             return (T) new StreamSource(new StringReader(_data));
    50b54c
           } else if (StAXSource.class.equals(sourceClass)) {
    50b54c
    -        XMLInputFactory xif = XMLInputFactory.newInstance();
    50b54c
    +        XMLInputFactory xif = getXmlFactoryFactory().newXMLInputFactory();
    50b54c
             XMLStreamReader xsr = xif.createXMLStreamReader(new StringReader(_data));
    50b54c
             return (T) new StAXSource(xsr);
    50b54c
           }
    50b54c
    @@ -168,6 +174,7 @@ public class PgSQLXML implements SQLXML {
    50b54c
       public synchronized Writer setCharacterStream() throws SQLException {
    50b54c
         checkFreed();
    50b54c
         initialize();
    50b54c
    +    _active = true;
    50b54c
         _stringWriter = new StringWriter();
    50b54c
         return _stringWriter;
    50b54c
       }
    50b54c
    @@ -182,8 +189,7 @@ public class PgSQLXML implements SQLXML {
    50b54c
           return (T) _domResult;
    50b54c
         } else if (SAXResult.class.equals(resultClass)) {
    50b54c
           try {
    50b54c
    -        SAXTransformerFactory transformerFactory =
    50b54c
    -            (SAXTransformerFactory) SAXTransformerFactory.newInstance();
    50b54c
    +        SAXTransformerFactory transformerFactory = getXmlFactoryFactory().newSAXTransformerFactory();
    50b54c
             TransformerHandler transformerHandler = transformerFactory.newTransformerHandler();
    50b54c
             _stringWriter = new StringWriter();
    50b54c
             transformerHandler.setResult(new StreamResult(_stringWriter));
    50b54c
    @@ -200,7 +206,7 @@ public class PgSQLXML implements SQLXML {
    50b54c
         } else if (StAXResult.class.equals(resultClass)) {
    50b54c
           _stringWriter = new StringWriter();
    50b54c
           try {
    50b54c
    -        XMLOutputFactory xof = XMLOutputFactory.newInstance();
    50b54c
    +        XMLOutputFactory xof = getXmlFactoryFactory().newXMLOutputFactory();
    50b54c
             XMLStreamWriter xsw = xof.createXMLStreamWriter(_stringWriter);
    50b54c
             _active = true;
    50b54c
             return (T) new StAXResult(xsw);
    50b54c
    @@ -262,7 +268,7 @@ public class PgSQLXML implements SQLXML {
    50b54c
           // and use the identify transform to get it into a
    50b54c
           // friendlier result format.
    50b54c
           try {
    50b54c
    -        TransformerFactory factory = TransformerFactory.newInstance();
    50b54c
    +        TransformerFactory factory = getXmlFactoryFactory().newTransformerFactory();
    50b54c
             Transformer transformer = factory.newTransformer();
    50b54c
             DOMSource domSource = new DOMSource(_domResult.getNode());
    50b54c
             StringWriter stringWriter = new StringWriter();
    50b54c
    @@ -289,19 +295,5 @@ public class PgSQLXML implements SQLXML {
    50b54c
         }
    50b54c
         _initialized = true;
    50b54c
       }
    50b54c
    -
    50b54c
    -  // Don't clutter System.err with errors the user can't silence.
    50b54c
    -  // If something bad really happens an exception will be thrown.
    50b54c
    -  static class NonPrintingErrorHandler implements ErrorHandler {
    50b54c
    -    public void error(SAXParseException e) {
    50b54c
    -    }
    50b54c
    -
    50b54c
    -    public void fatalError(SAXParseException e) {
    50b54c
    -    }
    50b54c
    -
    50b54c
    -    public void warning(SAXParseException e) {
    50b54c
    -    }
    50b54c
    -  }
    50b54c
    -
    50b54c
     }
    50b54c
     
    50b54c
    diff --git a/pgjdbc/src/main/java/org/postgresql/xml/DefaultPGXmlFactoryFactory.java b/pgjdbc/src/main/java/org/postgresql/xml/DefaultPGXmlFactoryFactory.java
    50b54c
    new file mode 100644
    50b54c
    index 0000000..b6a381d
    50b54c
    --- /dev/null
    50b54c
    +++ b/pgjdbc/src/main/java/org/postgresql/xml/DefaultPGXmlFactoryFactory.java
    50b54c
    @@ -0,0 +1,141 @@
    50b54c
    +
    50b54c
    +/*
    50b54c
    + * Copyright (c) 2020, PostgreSQL Global Development Group
    50b54c
    + * See the LICENSE file in the project root for more information.
    50b54c
    + */
    50b54c
    +
    50b54c
    +package org.postgresql.xml;
    50b54c
    +
    50b54c
    +import org.xml.sax.SAXException;
    50b54c
    +import org.xml.sax.XMLReader;
    50b54c
    +import org.xml.sax.helpers.XMLReaderFactory;
    50b54c
    +
    50b54c
    +import javax.xml.XMLConstants;
    50b54c
    +import javax.xml.parsers.DocumentBuilder;
    50b54c
    +import javax.xml.parsers.DocumentBuilderFactory;
    50b54c
    +import javax.xml.parsers.ParserConfigurationException;
    50b54c
    +import javax.xml.stream.XMLInputFactory;
    50b54c
    +import javax.xml.stream.XMLOutputFactory;
    50b54c
    +import javax.xml.transform.TransformerFactory;
    50b54c
    +import javax.xml.transform.sax.SAXTransformerFactory;
    50b54c
    +
    50b54c
    +/**
    50b54c
    + * Default implementation of PGXmlFactoryFactory that configures each factory per OWASP recommendations.
    50b54c
    + *
    50b54c
    + * @see https://cheatsheetseries.owasp.org/cheatsheets/XML_External_Entity_Prevention_Cheat_Sheet.html
    50b54c
    + */
    50b54c
    +public class DefaultPGXmlFactoryFactory implements PGXmlFactoryFactory {
    50b54c
    +  public static final DefaultPGXmlFactoryFactory INSTANCE = new DefaultPGXmlFactoryFactory();
    50b54c
    +
    50b54c
    +  private DefaultPGXmlFactoryFactory() {
    50b54c
    +  }
    50b54c
    +
    50b54c
    +  private DocumentBuilderFactory getDocumentBuilderFactory() {
    50b54c
    +    DocumentBuilderFactory factory = DocumentBuilderFactory.newInstance();
    50b54c
    +    setFactoryProperties(factory);
    50b54c
    +    factory.setXIncludeAware(false);
    50b54c
    +    factory.setExpandEntityReferences(false);
    50b54c
    +    return factory;
    50b54c
    +  }
    50b54c
    +
    50b54c
    +  @Override
    50b54c
    +  public DocumentBuilder newDocumentBuilder() throws ParserConfigurationException {
    50b54c
    +    DocumentBuilder builder = getDocumentBuilderFactory().newDocumentBuilder();
    50b54c
    +    builder.setEntityResolver(EmptyStringEntityResolver.INSTANCE);
    50b54c
    +    builder.setErrorHandler(NullErrorHandler.INSTANCE);
    50b54c
    +    return builder;
    50b54c
    +  }
    50b54c
    +
    50b54c
    +  @Override
    50b54c
    +  public TransformerFactory newTransformerFactory() {
    50b54c
    +    TransformerFactory factory = TransformerFactory.newInstance();
    50b54c
    +    setFactoryProperties(factory);
    50b54c
    +    return factory;
    50b54c
    +  }
    50b54c
    +
    50b54c
    +  @Override
    50b54c
    +  public SAXTransformerFactory newSAXTransformerFactory() {
    50b54c
    +    SAXTransformerFactory factory = (SAXTransformerFactory) SAXTransformerFactory.newInstance();
    50b54c
    +    setFactoryProperties(factory);
    50b54c
    +    return factory;
    50b54c
    +  }
    50b54c
    +
    50b54c
    +  @Override
    50b54c
    +  public XMLInputFactory newXMLInputFactory() {
    50b54c
    +    XMLInputFactory factory = XMLInputFactory.newInstance();
    50b54c
    +    setPropertyQuietly(factory, XMLInputFactory.SUPPORT_DTD, false);
    50b54c
    +    setPropertyQuietly(factory, XMLInputFactory.IS_SUPPORTING_EXTERNAL_ENTITIES, false);
    50b54c
    +    return factory;
    50b54c
    +  }
    50b54c
    +
    50b54c
    +  @Override
    50b54c
    +  public XMLOutputFactory newXMLOutputFactory() {
    50b54c
    +    XMLOutputFactory factory = XMLOutputFactory.newInstance();
    50b54c
    +    return factory;
    50b54c
    +  }
    50b54c
    +
    50b54c
    +  @Override
    50b54c
    +  public XMLReader createXMLReader() throws SAXException {
    50b54c
    +    XMLReader factory = XMLReaderFactory.createXMLReader();
    50b54c
    +    setFeatureQuietly(factory, "http://apache.org/xml/features/disallow-doctype-decl", true);
    50b54c
    +    setFeatureQuietly(factory, "http://apache.org/xml/features/nonvalidating/load-external-dtd", false);
    50b54c
    +    setFeatureQuietly(factory, "http://xml.org/sax/features/external-general-entities", false);
    50b54c
    +    setFeatureQuietly(factory, "http://xml.org/sax/features/external-parameter-entities", false);
    50b54c
    +    factory.setErrorHandler(NullErrorHandler.INSTANCE);
    50b54c
    +    return factory;
    50b54c
    +  }
    50b54c
    +
    50b54c
    +  private static void setFeatureQuietly(Object factory, String name, boolean value) {
    50b54c
    +    try {
    50b54c
    +      if (factory instanceof DocumentBuilderFactory) {
    50b54c
    +        ((DocumentBuilderFactory) factory).setFeature(name, value);
    50b54c
    +      } else if (factory instanceof TransformerFactory) {
    50b54c
    +        ((TransformerFactory) factory).setFeature(name, value);
    50b54c
    +      } else if (factory instanceof XMLReader) {
    50b54c
    +        ((XMLReader) factory).setFeature(name, value);
    50b54c
    +      } else {
    50b54c
    +        throw new Error("Invalid factory class: " + factory.getClass());
    50b54c
    +      }
    50b54c
    +      return;
    50b54c
    +    } catch (Exception ignore) {
    50b54c
    +    }
    50b54c
    +  }
    50b54c
    +
    50b54c
    +  private static void setAttributeQuietly(Object factory, String name, Object value) {
    50b54c
    +    try {
    50b54c
    +      if (factory instanceof DocumentBuilderFactory) {
    50b54c
    +        ((DocumentBuilderFactory) factory).setAttribute(name, value);
    50b54c
    +      } else if (factory instanceof TransformerFactory) {
    50b54c
    +        ((TransformerFactory) factory).setAttribute(name, value);
    50b54c
    +      } else {
    50b54c
    +        throw new Error("Invalid factory class: " + factory.getClass());
    50b54c
    +      }
    50b54c
    +    } catch (Exception ignore) {
    50b54c
    +    }
    50b54c
    +  }
    50b54c
    +
    50b54c
    +  private static void setFactoryProperties(Object factory) {
    50b54c
    +    setFeatureQuietly(factory, XMLConstants.FEATURE_SECURE_PROCESSING, true);
    50b54c
    +    setFeatureQuietly(factory, "http://apache.org/xml/features/disallow-doctype-decl", true);
    50b54c
    +    setFeatureQuietly(factory, "http://apache.org/xml/features/nonvalidating/load-external-dtd", false);
    50b54c
    +    setFeatureQuietly(factory, "http://xml.org/sax/features/external-general-entities", false);
    50b54c
    +    setFeatureQuietly(factory, "http://xml.org/sax/features/external-parameter-entities", false);
    50b54c
    +    // Values from XMLConstants inlined for JDK 1.6 compatibility
    50b54c
    +    setAttributeQuietly(factory, "http://javax.xml.XMLConstants/property/accessExternalDTD", "");
    50b54c
    +    setAttributeQuietly(factory, "http://javax.xml.XMLConstants/property/accessExternalSchema", "");
    50b54c
    +    setAttributeQuietly(factory, "http://javax.xml.XMLConstants/property/accessExternalStylesheet", "");
    50b54c
    +  }
    50b54c
    +
    50b54c
    +  private static void setPropertyQuietly(Object factory, String name, Object value) {
    50b54c
    +    try {
    50b54c
    +      if (factory instanceof XMLReader) {
    50b54c
    +        ((XMLReader) factory).setProperty(name, value);
    50b54c
    +      } else if (factory instanceof XMLInputFactory) {
    50b54c
    +        ((XMLInputFactory) factory).setProperty(name, value);
    50b54c
    +      } else {
    50b54c
    +        throw new Error("Invalid factory class: " + factory.getClass());
    50b54c
    +      }
    50b54c
    +    } catch (Exception ignore) {
    50b54c
    +    }
    50b54c
    +  }
    50b54c
    +}
    50b54c
    \ No newline at end of file
    50b54c
    diff --git a/pgjdbc/src/main/java/org/postgresql/xml/EmptyStringEntityResolver.java b/pgjdbc/src/main/java/org/postgresql/xml/EmptyStringEntityResolver.java
    50b54c
    new file mode 100644
    50b54c
    index 0000000..39227e0
    50b54c
    --- /dev/null
    50b54c
    +++ b/pgjdbc/src/main/java/org/postgresql/xml/EmptyStringEntityResolver.java
    50b54c
    @@ -0,0 +1,23 @@
    50b54c
    +/*
    50b54c
    + * Copyright (c) 2020, PostgreSQL Global Development Group
    50b54c
    + * See the LICENSE file in the project root for more information.
    50b54c
    + */
    50b54c
    +
    50b54c
    +package org.postgresql.xml;
    50b54c
    +
    50b54c
    +import org.xml.sax.EntityResolver;
    50b54c
    +import org.xml.sax.InputSource;
    50b54c
    +import org.xml.sax.SAXException;
    50b54c
    +
    50b54c
    +import java.io.IOException;
    50b54c
    +import java.io.StringReader;
    50b54c
    +
    50b54c
    +public class EmptyStringEntityResolver implements EntityResolver {
    50b54c
    +  public static final EmptyStringEntityResolver INSTANCE = new EmptyStringEntityResolver();
    50b54c
    +
    50b54c
    +  @Override
    50b54c
    +  public InputSource resolveEntity(String publicId, String systemId)
    50b54c
    +      throws SAXException, IOException {
    50b54c
    +    return new InputSource(new StringReader(""));
    50b54c
    +  }
    50b54c
    +}
    50b54c
    \ No newline at end of file
    50b54c
    diff --git a/pgjdbc/src/main/java/org/postgresql/xml/LegacyInsecurePGXmlFactoryFactory.java b/pgjdbc/src/main/java/org/postgresql/xml/LegacyInsecurePGXmlFactoryFactory.java
    50b54c
    new file mode 100644
    50b54c
    index 0000000..ed7a66b
    50b54c
    --- /dev/null
    50b54c
    +++ b/pgjdbc/src/main/java/org/postgresql/xml/LegacyInsecurePGXmlFactoryFactory.java
    50b54c
    @@ -0,0 +1,57 @@
    50b54c
    +/*
    50b54c
    + * Copyright (c) 2020, PostgreSQL Global Development Group
    50b54c
    + * See the LICENSE file in the project root for more information.
    50b54c
    + */
    50b54c
    +
    50b54c
    +package org.postgresql.xml;
    50b54c
    +
    50b54c
    +import org.xml.sax.SAXException;
    50b54c
    +import org.xml.sax.XMLReader;
    50b54c
    +import org.xml.sax.helpers.XMLReaderFactory;
    50b54c
    +
    50b54c
    +import javax.xml.parsers.DocumentBuilder;
    50b54c
    +import javax.xml.parsers.DocumentBuilderFactory;
    50b54c
    +import javax.xml.parsers.ParserConfigurationException;
    50b54c
    +import javax.xml.stream.XMLInputFactory;
    50b54c
    +import javax.xml.stream.XMLOutputFactory;
    50b54c
    +import javax.xml.transform.TransformerFactory;
    50b54c
    +import javax.xml.transform.sax.SAXTransformerFactory;
    50b54c
    +
    50b54c
    +public class LegacyInsecurePGXmlFactoryFactory implements PGXmlFactoryFactory {
    50b54c
    +  public static final LegacyInsecurePGXmlFactoryFactory INSTANCE = new LegacyInsecurePGXmlFactoryFactory();
    50b54c
    +
    50b54c
    +  private LegacyInsecurePGXmlFactoryFactory() {
    50b54c
    +  }
    50b54c
    +
    50b54c
    +  @Override
    50b54c
    +  public DocumentBuilder newDocumentBuilder() throws ParserConfigurationException {
    50b54c
    +    DocumentBuilder builder = DocumentBuilderFactory.newInstance().newDocumentBuilder();
    50b54c
    +    builder.setErrorHandler(NullErrorHandler.INSTANCE);
    50b54c
    +    return builder;
    50b54c
    +  }
    50b54c
    +
    50b54c
    +  @Override
    50b54c
    +  public TransformerFactory newTransformerFactory() {
    50b54c
    +    return TransformerFactory.newInstance();
    50b54c
    +  }
    50b54c
    +
    50b54c
    +  @Override
    50b54c
    +  public SAXTransformerFactory newSAXTransformerFactory() {
    50b54c
    +    return (SAXTransformerFactory) SAXTransformerFactory.newInstance();
    50b54c
    +  }
    50b54c
    +
    50b54c
    +  @Override
    50b54c
    +  public XMLInputFactory newXMLInputFactory() {
    50b54c
    +    return XMLInputFactory.newInstance();
    50b54c
    +  }
    50b54c
    +
    50b54c
    +  @Override
    50b54c
    +  public XMLOutputFactory newXMLOutputFactory() {
    50b54c
    +    return XMLOutputFactory.newInstance();
    50b54c
    +  }
    50b54c
    +
    50b54c
    +  @Override
    50b54c
    +  public XMLReader createXMLReader() throws SAXException {
    50b54c
    +    return XMLReaderFactory.createXMLReader();
    50b54c
    +  }
    50b54c
    +}
    50b54c
    \ No newline at end of file
    50b54c
    diff --git a/pgjdbc/src/main/java/org/postgresql/xml/NullErrorHandler.java b/pgjdbc/src/main/java/org/postgresql/xml/NullErrorHandler.java
    50b54c
    new file mode 100644
    50b54c
    index 0000000..ad486c7
    50b54c
    --- /dev/null
    50b54c
    +++ b/pgjdbc/src/main/java/org/postgresql/xml/NullErrorHandler.java
    50b54c
    @@ -0,0 +1,25 @@
    50b54c
    +/*
    50b54c
    + * Copyright (c) 2020, PostgreSQL Global Development Group
    50b54c
    + * See the LICENSE file in the project root for more information.
    50b54c
    + */
    50b54c
    +
    50b54c
    +package org.postgresql.xml;
    50b54c
    +
    50b54c
    +import org.xml.sax.ErrorHandler;
    50b54c
    +import org.xml.sax.SAXParseException;
    50b54c
    +
    50b54c
    +/**
    50b54c
    + * Error handler that silently suppresses all errors.
    50b54c
    + */
    50b54c
    +public class NullErrorHandler implements ErrorHandler {
    50b54c
    +  public static final NullErrorHandler INSTANCE = new NullErrorHandler();
    50b54c
    +
    50b54c
    +  public void error(SAXParseException e) {
    50b54c
    +  }
    50b54c
    +
    50b54c
    +  public void fatalError(SAXParseException e) {
    50b54c
    +  }
    50b54c
    +
    50b54c
    +  public void warning(SAXParseException e) {
    50b54c
    +  }
    50b54c
    +}
    50b54c
    \ No newline at end of file
    50b54c
    diff --git a/pgjdbc/src/main/java/org/postgresql/xml/PGXmlFactoryFactory.java b/pgjdbc/src/main/java/org/postgresql/xml/PGXmlFactoryFactory.java
    50b54c
    new file mode 100644
    50b54c
    index 0000000..4bb98e4
    50b54c
    --- /dev/null
    50b54c
    +++ b/pgjdbc/src/main/java/org/postgresql/xml/PGXmlFactoryFactory.java
    50b54c
    @@ -0,0 +1,30 @@
    50b54c
    +/*
    50b54c
    + * Copyright (c) 2020, PostgreSQL Global Development Group
    50b54c
    + * See the LICENSE file in the project root for more information.
    50b54c
    + */
    50b54c
    +
    50b54c
    +package org.postgresql.xml;
    50b54c
    +
    50b54c
    +import org.xml.sax.SAXException;
    50b54c
    +import org.xml.sax.XMLReader;
    50b54c
    +
    50b54c
    +import javax.xml.parsers.DocumentBuilder;
    50b54c
    +import javax.xml.parsers.ParserConfigurationException;
    50b54c
    +import javax.xml.stream.XMLInputFactory;
    50b54c
    +import javax.xml.stream.XMLOutputFactory;
    50b54c
    +import javax.xml.transform.TransformerFactory;
    50b54c
    +import javax.xml.transform.sax.SAXTransformerFactory;
    50b54c
    +
    50b54c
    +public interface PGXmlFactoryFactory {
    50b54c
    +  DocumentBuilder newDocumentBuilder() throws ParserConfigurationException;
    50b54c
    +
    50b54c
    +  TransformerFactory newTransformerFactory();
    50b54c
    +
    50b54c
    +  SAXTransformerFactory newSAXTransformerFactory();
    50b54c
    +
    50b54c
    +  XMLInputFactory newXMLInputFactory();
    50b54c
    +
    50b54c
    +  XMLOutputFactory newXMLOutputFactory();
    50b54c
    +
    50b54c
    +  XMLReader createXMLReader() throws SAXException;
    50b54c
    +}
    50b54c
    \ No newline at end of file
    50b54c
    diff --git a/pgjdbc/src/test/java/org/postgresql/jdbc/PgSQLXMLTest.java b/pgjdbc/src/test/java/org/postgresql/jdbc/PgSQLXMLTest.java
    50b54c
    new file mode 100644
    50b54c
    index 0000000..49e389c
    50b54c
    --- /dev/null
    50b54c
    +++ b/pgjdbc/src/test/java/org/postgresql/jdbc/PgSQLXMLTest.java
    50b54c
    @@ -0,0 +1,124 @@
    50b54c
    +/*
    50b54c
    + * Copyright (c) 2019, PostgreSQL Global Development Group
    50b54c
    + * See the LICENSE file in the project root for more information.
    50b54c
    + */
    50b54c
    +
    50b54c
    +package org.postgresql.jdbc;
    50b54c
    +
    50b54c
    +import static org.junit.Assert.assertEquals;
    50b54c
    +import static org.junit.Assert.assertNotNull;
    50b54c
    +import static org.junit.Assert.assertTrue;
    50b54c
    +import static org.junit.Assert.fail;
    50b54c
    +
    50b54c
    +import org.postgresql.PGProperty;
    50b54c
    +import org.postgresql.core.BaseConnection;
    50b54c
    +import org.postgresql.test.TestUtil;
    50b54c
    +import org.postgresql.test.jdbc2.BaseTest4;
    50b54c
    +
    50b54c
    +import org.junit.Before;
    50b54c
    +import org.junit.Test;
    50b54c
    +
    50b54c
    +import java.io.StringWriter;
    50b54c
    +import java.io.Writer;
    50b54c
    +import java.sql.Connection;
    50b54c
    +import java.sql.PreparedStatement;
    50b54c
    +import java.sql.ResultSet;
    50b54c
    +import java.sql.SQLException;
    50b54c
    +import java.sql.SQLXML;
    50b54c
    +import java.sql.Statement;
    50b54c
    +import java.util.Properties;
    50b54c
    +
    50b54c
    +import javax.xml.stream.XMLStreamException;
    50b54c
    +import javax.xml.stream.XMLStreamReader;
    50b54c
    +import javax.xml.transform.Source;
    50b54c
    +import javax.xml.transform.Transformer;
    50b54c
    +import javax.xml.transform.TransformerException;
    50b54c
    +import javax.xml.transform.TransformerFactory;
    50b54c
    +import javax.xml.transform.dom.DOMSource;
    50b54c
    +import javax.xml.transform.sax.SAXSource;
    50b54c
    +import javax.xml.transform.stax.StAXSource;
    50b54c
    +import javax.xml.transform.stream.StreamResult;
    50b54c
    +
    50b54c
    +public class PgSQLXMLTest extends BaseTest4 {
    50b54c
    +
    50b54c
    +  @Override
    50b54c
    +  @Before
    50b54c
    +  public void setUp() throws Exception {
    50b54c
    +    super.setUp();
    50b54c
    +    TestUtil.createTempTable(con, "xmltab", "x xml");
    50b54c
    +  }
    50b54c
    +
    50b54c
    +  @Test
    50b54c
    +  public void setCharacterStream() throws Exception {
    50b54c
    +    String exmplar = "<x>value</x>";
    50b54c
    +    SQLXML pgSQLXML = con.createSQLXML();
    50b54c
    +    Writer writer = pgSQLXML.setCharacterStream();
    50b54c
    +    writer.write(exmplar);
    50b54c
    +    PreparedStatement preparedStatement = con.prepareStatement("insert into xmltab values (?)");
    50b54c
    +    preparedStatement.setSQLXML(1, pgSQLXML);
    50b54c
    +    preparedStatement.execute();
    50b54c
    +
    50b54c
    +    Statement statement = con.createStatement();
    50b54c
    +    ResultSet rs = statement.executeQuery("select * from xmltab");
    50b54c
    +    assertTrue(rs.next());
    50b54c
    +    SQLXML result = rs.getSQLXML(1);
    50b54c
    +    assertNotNull(result);
    50b54c
    +    assertEquals(exmplar, result.getString());
    50b54c
    +  }
    50b54c
    +
    50b54c
    +  private static final String LICENSE_URL =
    50b54c
    +      PgSQLXMLTest.class.getClassLoader().getResource("META-INF/LICENSE").toString();
    50b54c
    +  private static final String XXE_EXAMPLE =
    50b54c
    +      "\n"
    50b54c
    +      + "]>"
    50b54c
    +      + "<foo>&xx;;</foo>";
    50b54c
    +
    50b54c
    +  @Test
    50b54c
    +  public void testLegacyXxe() throws Exception {
    50b54c
    +    Properties props = new Properties();
    50b54c
    +    props.setProperty(PGProperty.XML_FACTORY_FACTORY.getName(), "LEGACY_INSECURE");
    50b54c
    +    try (Connection conn = TestUtil.openDB(props)) {
    50b54c
    +      BaseConnection baseConn = conn.unwrap(BaseConnection.class);
    50b54c
    +      PgSQLXML xml = new PgSQLXML(baseConn, XXE_EXAMPLE);
    50b54c
    +      xml.getSource(null);
    50b54c
    +    }
    50b54c
    +  }
    50b54c
    +
    50b54c
    +  private static String sourceToString(Source source) throws TransformerException {
    50b54c
    +    StringWriter sw = new StringWriter();
    50b54c
    +    Transformer transformer = TransformerFactory.newInstance().newTransformer();
    50b54c
    +    transformer.transform(source, new StreamResult(sw));
    50b54c
    +    return sw.toString();
    50b54c
    +  }
    50b54c
    +
    50b54c
    +  @Test(expected = SQLException.class)
    50b54c
    +  public void testGetSourceXxeNull() throws Exception {
    50b54c
    +    PgSQLXML xml = new PgSQLXML(null, XXE_EXAMPLE);
    50b54c
    +      xml.getSource(null);
    50b54c
    +  }
    50b54c
    +
    50b54c
    +  @Test(expected = SQLException.class)
    50b54c
    +  public void testGetSourceXxeDOMSource() throws Exception {
    50b54c
    +    PgSQLXML xml = new PgSQLXML(null, XXE_EXAMPLE);
    50b54c
    +      xml.getSource(DOMSource.class);
    50b54c
    +  }
    50b54c
    +
    50b54c
    +  @Test(expected = TransformerException.class)
    50b54c
    +  public void testGetSourceXxeSAXSource() throws Exception {
    50b54c
    +    PgSQLXML xml = new PgSQLXML(null, XXE_EXAMPLE);
    50b54c
    +    SAXSource source = xml.getSource(SAXSource.class);
    50b54c
    +    sourceToString(source);
    50b54c
    +    
    50b54c
    +  }
    50b54c
    +
    50b54c
    +  @Test(expected = XMLStreamException.class)
    50b54c
    +  public void testGetSourceXxeStAXSource() throws Exception {
    50b54c
    +    PgSQLXML xml = new PgSQLXML(null, XXE_EXAMPLE);
    50b54c
    +    StAXSource source = xml.getSource(StAXSource.class);
    50b54c
    +    XMLStreamReader reader = source.getXMLStreamReader();
    50b54c
    +    // STAX will not throw XXE error until we actually read the element
    50b54c
    +    while (reader.hasNext()) {
    50b54c
    +      reader.next();
    50b54c
    +    }
    50b54c
    +  }
    50b54c
    +}
    50b54c
    diff --git a/pgjdbc/src/test/java/org/postgresql/test/jdbc2/Jdbc2TestSuite.java b/pgjdbc/src/test/java/org/postgresql/test/jdbc2/Jdbc2TestSuite.java
    50b54c
    index 6314d21..814288c 100644
    50b54c
    --- a/pgjdbc/src/test/java/org/postgresql/test/jdbc2/Jdbc2TestSuite.java
    50b54c
    +++ b/pgjdbc/src/test/java/org/postgresql/test/jdbc2/Jdbc2TestSuite.java
    50b54c
    @@ -13,6 +13,7 @@ import org.postgresql.core.ParserTest;
    50b54c
     import org.postgresql.core.ReturningParserTest;
    50b54c
     import org.postgresql.core.v3.V3ParameterListTests;
    50b54c
     import org.postgresql.jdbc.DeepBatchedInsertStatementTest;
    50b54c
    +import org.postgresql.jdbc.PgSQLXMLTest;
    50b54c
     import org.postgresql.jdbc.PrimitiveArraySupportTest;
    50b54c
     import org.postgresql.test.core.JavaVersionTest;
    50b54c
     import org.postgresql.test.core.NativeQueryBindLengthTest;
    50b54c
    @@ -76,6 +77,7 @@ import org.junit.runners.Suite;
    50b54c
             TimestampTest.class,
    50b54c
             TimezoneTest.class,
    50b54c
             PGTimeTest.class,
    50b54c
    +        PgSQLXMLTest.class,
    50b54c
             PGTimestampTest.class,
    50b54c
             TimezoneCachingTest.class,
    50b54c
             ParserTest.class,
    50b54c
    -- 
    50b54c
    2.24.1
    50b54c