diff --git a/api/src/main/java/javax/jdo/JDOHelper.java b/api/src/main/java/javax/jdo/JDOHelper.java index aa1d0601..f35a485f 100644 --- a/api/src/main/java/javax/jdo/JDOHelper.java +++ b/api/src/main/java/javax/jdo/JDOHelper.java @@ -1138,6 +1138,18 @@ protected static DocumentBuilderFactory getDocumentBuilderFactory() { DocumentBuilderFactory factory = IMPL_HELPER.getRegisteredDocumentBuilderFactory(); if (factory == null) { factory = getDefaultDocumentBuilderFactory(); + } else { + // Re-apply the secure defaults to the registered factory before every parse. + // Registration is an SPI open to any code in the process; without this, a factory + // registered with default settings would re-enable DOCTYPE processing (external + // entities / XXE) for every jdoconfig.xml on the classpath. + try { + factory.setFeature("http://apache.org/xml/features/disallow-doctype-decl", true); + } catch (ParserConfigurationException e) { + // fail closed: do not parse with a factory that cannot disable DOCTYPEs + throw new JDOFatalUserException(e.getMessage()); + } + factory.setExpandEntityReferences(false); } return factory; } diff --git a/api/src/main/java/javax/jdo/spi/JDOImplHelper.java b/api/src/main/java/javax/jdo/spi/JDOImplHelper.java index 10fd95c4..95993cdf 100644 --- a/api/src/main/java/javax/jdo/spi/JDOImplHelper.java +++ b/api/src/main/java/javax/jdo/spi/JDOImplHelper.java @@ -604,10 +604,21 @@ public static void registerAuthorizedStateManagerClasses(Collection> smClasses * META-INF/jdoconfig.xml. The default is governed by the semantics of * DocumentBuilderFactory.newInstance(). * + *
Note: secure XML parsing defaults (DOCTYPE declarations disallowed, entity references not
+ * expanded) are re-applied to the registered factory before each use. When running with a legacy
+ * SecurityManager, the caller must be authorized for
+ * JDOPermission("manageMetadata").
+ *
* @param factory the DocumentBuilderFactory instance to use
+ * @throws SecurityException if the caller is not authorized for JDOPermission("manageMetadata").
* @since 2.1
*/
public synchronized void registerDocumentBuilderFactory(DocumentBuilderFactory factory) {
+ SecurityManager sec = LegacyJava.getSecurityManager();
+ if (sec != null) {
+ // throws exception if caller is not authorized
+ sec.checkPermission(JDOPermission.MANAGE_METADATA);
+ }
documentBuilderFactory = factory;
}
@@ -623,12 +634,20 @@ public static DocumentBuilderFactory getRegisteredDocumentBuilderFactory() {
/**
* Register an ErrorHandler instance for use in parsing the resource(s) META-INF/jdoconfig.xml.
- * The default is an ErrorHandler that throws on error or fatalError and ignores warnings.
+ * The default is an ErrorHandler that throws on error or fatalError and ignores warnings. When
+ * running with a legacy SecurityManager, the caller must be authorized for
+ * JDOPermission("manageMetadata").
*
* @param handler the ErrorHandler instance to use
+ * @throws SecurityException if the caller is not authorized for JDOPermission("manageMetadata").
* @since 2.1
*/
public synchronized void registerErrorHandler(ErrorHandler handler) {
+ SecurityManager sec = LegacyJava.getSecurityManager();
+ if (sec != null) {
+ // throws exception if caller is not authorized
+ sec.checkPermission(JDOPermission.MANAGE_METADATA);
+ }
errorHandler = handler;
}
diff --git a/api/src/test/java/javax/jdo/JDOHelperDocumentBuilderFactoryTest.java b/api/src/test/java/javax/jdo/JDOHelperDocumentBuilderFactoryTest.java
new file mode 100644
index 00000000..96a1703b
--- /dev/null
+++ b/api/src/test/java/javax/jdo/JDOHelperDocumentBuilderFactoryTest.java
@@ -0,0 +1,72 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License. You may obtain a copy of the License at
+ *
+ * https://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package javax.jdo;
+
+import javax.jdo.spi.JDOImplHelper;
+import javax.jdo.util.AbstractTest;
+import javax.xml.parsers.DocumentBuilderFactory;
+import javax.xml.parsers.ParserConfigurationException;
+import org.junit.jupiter.api.AfterEach;
+import org.junit.jupiter.api.Assertions;
+import org.junit.jupiter.api.Test;
+
+/**
+ * Tests that JDOHelper re-applies the secure XML parsing defaults to a DocumentBuilderFactory
+ * registered via JDOImplHelper before it is used for jdoconfig.xml parsing.
+ */
+class JDOHelperDocumentBuilderFactoryTest extends AbstractTest {
+
+ private static final String DISALLOW_DOCTYPE_DECL =
+ "http://apache.org/xml/features/disallow-doctype-decl";
+
+ @AfterEach
+ void cleanup() {
+ JDOImplHelper.getInstance().registerDocumentBuilderFactory(null);
+ }
+
+ /** The default factory is hardened. */
+ @Test
+ void testDefaultFactoryIsHardened() throws ParserConfigurationException {
+ DocumentBuilderFactory factory = JDOHelper.getDocumentBuilderFactory();
+ Assertions.assertTrue(
+ factory.getFeature(DISALLOW_DOCTYPE_DECL),
+ "Default DocumentBuilderFactory must disallow DOCTYPE declarations");
+ Assertions.assertFalse(
+ factory.isExpandEntityReferences(),
+ "Default DocumentBuilderFactory must not expand entity references");
+ }
+
+ /** A registered, unhardened factory is re-hardened before use. */
+ @Test
+ void testRegisteredFactoryIsRehardened() throws ParserConfigurationException {
+ DocumentBuilderFactory unhardened = DocumentBuilderFactory.newInstance();
+ Assertions.assertFalse(
+ unhardened.getFeature(DISALLOW_DOCTYPE_DECL),
+ "Precondition: a factory from newInstance() allows DOCTYPE declarations");
+ JDOImplHelper.getInstance().registerDocumentBuilderFactory(unhardened);
+
+ DocumentBuilderFactory factory = JDOHelper.getDocumentBuilderFactory();
+ Assertions.assertSame(unhardened, factory, "The registered factory must be preferred");
+ Assertions.assertTrue(
+ factory.getFeature(DISALLOW_DOCTYPE_DECL),
+ "The registered DocumentBuilderFactory must have DOCTYPE declarations re-disabled");
+ Assertions.assertFalse(
+ factory.isExpandEntityReferences(),
+ "The registered DocumentBuilderFactory must not expand entity references");
+ }
+}