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