diff --git a/src/main/java/org/apache/commons/xml/HardeningSchema.java b/src/main/java/org/apache/commons/xml/HardeningSchema.java index 3dcc1093..5f0ad158 100644 --- a/src/main/java/org/apache/commons/xml/HardeningSchema.java +++ b/src/main/java/org/apache/commons/xml/HardeningSchema.java @@ -17,36 +17,33 @@ package org.apache.commons.xml; -import java.util.function.UnaryOperator; - import javax.xml.validation.Schema; import javax.xml.validation.Validator; import javax.xml.validation.ValidatorHandler; /** - * {@link Schema} wrapper that applies provider-specific decoration to every {@link Validator} and {@link ValidatorHandler} the inner Schema produces, then - * wraps each {@link Validator} in {@link HardeningValidator} so {@link Validator#validate(javax.xml.transform.Source)} runs through - * {@link XmlFactories#harden(javax.xml.transform.Source)}. + * {@link Schema} wrapper that hardens every {@link Validator} and {@link ValidatorHandler} the inner Schema produces: each {@link Validator} is wrapped in + * {@link HardeningValidator} (which rewrites the Source through {@link XmlFactories#harden(javax.xml.transform.Source)} and installs the deny-all resolver), and + * each {@link ValidatorHandler} gets the same deny-all {@link Resolvers.DenyAll#LS_RESOURCE} so {@code xsi:schemaLocation} is not resolved during SAX-driven + * validation. */ final class HardeningSchema extends Schema { private final Schema delegate; - private final UnaryOperator validatorHardener; - private final UnaryOperator handlerHardener; - HardeningSchema(final Schema delegate, final UnaryOperator validatorHardener, final UnaryOperator handlerHardener) { + HardeningSchema(final Schema delegate) { this.delegate = delegate; - this.validatorHardener = validatorHardener; - this.handlerHardener = handlerHardener; } @Override public Validator newValidator() { - return new HardeningValidator(validatorHardener.apply(delegate.newValidator())); + return new HardeningValidator(delegate.newValidator()); } @Override public ValidatorHandler newValidatorHandler() { - return handlerHardener.apply(delegate.newValidatorHandler()); + final ValidatorHandler handler = delegate.newValidatorHandler(); + handler.setResourceResolver(Resolvers.DenyAll.LS_RESOURCE); + return handler; } } diff --git a/src/main/java/org/apache/commons/xml/HardeningSchemaFactory.java b/src/main/java/org/apache/commons/xml/HardeningSchemaFactory.java index ece94942..e28f65ee 100644 --- a/src/main/java/org/apache/commons/xml/HardeningSchemaFactory.java +++ b/src/main/java/org/apache/commons/xml/HardeningSchemaFactory.java @@ -17,53 +17,51 @@ package org.apache.commons.xml; -import java.util.function.UnaryOperator; - import javax.xml.transform.Source; import javax.xml.transform.TransformerConfigurationException; import javax.xml.validation.Schema; import javax.xml.validation.SchemaFactory; import javax.xml.validation.Validator; -import javax.xml.validation.ValidatorHandler; import org.xml.sax.SAXException; /** - * {@link SchemaFactory} wrapper that rewrites every Source-taking entry point through {@link XmlFactories#harden(Source)} and applies provider-specific - * decoration to every {@link Validator} and {@link ValidatorHandler} that the produced {@link Schema} hands out. + * Capability-driven hardening wrapper for any {@link SchemaFactory} on the classpath, the same recipe for every implementation. It is the entry point reached + * by {@link XmlFactories#newSchemaFactory()}; there is no per-implementation branching, no {@code FEATURE_SECURE_PROCESSING} and no limit configuration on the + * factory itself. * *

Three layers cooperate:

*
    - *
  1. {@link HardeningSchemaFactory} rewrites the Source on every {@code newSchema(Source[])} entry point.
  2. - *
  3. {@link HardeningSchema} applies the provider-specific hardener to every Validator/ValidatorHandler the inner Schema produces (e.g. Xerces re-installs - * its {@code LSResourceResolver} and {@code SecurityManager} since it does not propagate them through Schema).
  4. + *
  5. {@link HardeningSchemaFactory} installs a deny-all {@link Resolvers.DenyAll#LS_RESOURCE} on the factory (blocking + * {@code xs:import}/{@code xs:include}/{@code xs:redefine} at compile time) and rewrites the Source on every {@code newSchema(Source[])} entry point + * through {@link XmlFactories#harden(Source)}.
  6. + *
  7. {@link HardeningSchema} wraps every Validator/ValidatorHandler the inner Schema produces and re-installs the deny-all resolver on each (blocking + * {@code xsi:schemaLocation} at validation time), since neither the JDK nor Xerces reliably propagates it through {@code Schema}.
  8. *
  9. {@link HardeningValidator} rewrites the Source on every {@link Validator#validate(Source)} call.
  10. *
+ * + *

The hardened reader supplied by {@link XmlFactories#harden(Source)} already carries {@code FEATURE_SECURE_PROCESSING} and the processing limits, so a + * DOCTYPE, external entity or Billion Laughs payload in the schema or instance document is bounded there rather than on this factory. The JAXP 1.5 + * {@code ACCESS_EXTERNAL_*} properties are deliberately not set: the deny-all resolver already blocks the same fetches on every implementation, and the JDK 8 + * {@code SchemaFactory} has a bug whereby those properties keep blocking even when a caller's own resolver would grant the access, so leaving them unset lets a + * caller re-enable specific lookups by swapping the resolver.

*/ final class HardeningSchemaFactory extends DelegatingSchemaFactory { - private final UnaryOperator validatorHardener; - private final UnaryOperator handlerHardener; - HardeningSchemaFactory(final SchemaFactory delegate) { - this(delegate, UnaryOperator.identity(), UnaryOperator.identity()); - } - - HardeningSchemaFactory(final SchemaFactory delegate, final UnaryOperator validatorHardener, - final UnaryOperator handlerHardener) { super(delegate); - this.validatorHardener = validatorHardener; - this.handlerHardener = handlerHardener; + // Compile-time block for xs:import/include/redefine; the wrappers carry the rest (per-product resolver, source rewriting, limits via the reader). + delegate.setResourceResolver(Resolvers.DenyAll.LS_RESOURCE); } @Override public Schema newSchema() throws SAXException { - return new HardeningSchema(super.newSchema(), validatorHardener, handlerHardener); + return new HardeningSchema(super.newSchema()); } @Override public Schema newSchema(final Source[] schemas) throws SAXException { - return new HardeningSchema(super.newSchema(harden(schemas)), validatorHardener, handlerHardener); + return new HardeningSchema(super.newSchema(harden(schemas))); } private static Source[] harden(final Source[] schemas) throws SAXException { diff --git a/src/main/java/org/apache/commons/xml/HardeningValidator.java b/src/main/java/org/apache/commons/xml/HardeningValidator.java index 3df95962..86dc0639 100644 --- a/src/main/java/org/apache/commons/xml/HardeningValidator.java +++ b/src/main/java/org/apache/commons/xml/HardeningValidator.java @@ -32,7 +32,8 @@ /** * {@link Validator} wrapper that rewrites the Source on every {@link Validator#validate(Source)} and {@link Validator#validate(Source, Result)} call through - * {@link XmlFactories#harden(Source)} before delegating. + * {@link XmlFactories#harden(Source)} before delegating, and installs a deny-all {@link LSResourceResolver} so {@code xsi:schemaLocation} is not resolved at + * validation time. */ final class HardeningValidator extends Validator { @@ -40,6 +41,9 @@ final class HardeningValidator extends Validator { HardeningValidator(final Validator delegate) { this.delegate = delegate; + // Block xsi:schemaLocation resolution; neither the JDK nor Xerces reliably propagates the factory's resolver to its Validators. A caller may re-enable + // specific lookups by setting their own resolver afterwards. + delegate.setResourceResolver(Resolvers.DenyAll.LS_RESOURCE); } @Override diff --git a/src/main/java/org/apache/commons/xml/JaxpSetters.java b/src/main/java/org/apache/commons/xml/JaxpSetters.java index 6f24c5c0..b73a6de4 100644 --- a/src/main/java/org/apache/commons/xml/JaxpSetters.java +++ b/src/main/java/org/apache/commons/xml/JaxpSetters.java @@ -18,7 +18,6 @@ package org.apache.commons.xml; import javax.xml.parsers.DocumentBuilderFactory; -import javax.xml.parsers.SAXParser; import javax.xml.parsers.SAXParserFactory; import javax.xml.stream.XMLInputFactory; import javax.xml.transform.TransformerFactory; @@ -118,59 +117,35 @@ static void setOptionalProperty(final XMLInputFactory factory, final String prop trySetProperty(factory, property, value); } - static void setProperty(final XMLInputFactory factory, final String property, final Object value) { - apply(factory, KIND_PROPERTY, property, () -> factory.setProperty(property, value)); - } - - static void setProperty(final SAXParser parser, final String property, final Object value) { - apply(parser, KIND_PROPERTY, property, () -> parser.setProperty(property, value)); - } - - static void setProperty(final XMLReader reader, final String property, final Object value) { - apply(reader, KIND_PROPERTY, property, () -> reader.setProperty(property, value)); - } - /** - * Sets a property on an {@link XMLReader} and returns whether the implementation accepted it. + * Sets an attribute on a {@link DocumentBuilderFactory} and returns whether the implementation accepted it. Some implementations may reject certain + * attributes, in which case this method will return {@code false}. * - * @param reader The target reader on which to set the property. - * @param property The name of the property to set. - * @param value The value of the property to set. - * @return {@code true} if the property was applied, {@code false} if the implementation rejected it. + * @param factory The target factory on which to set the attribute. + * @param attribute The name of the attribute to set. + * @param value The value of the attribute to set. + * @return {@code true} if the attribute was applied, {@code false} if the implementation rejected it. */ - static boolean trySetProperty(final XMLReader reader, final String property, final Object value) { + static boolean trySetAttribute(final DocumentBuilderFactory factory, final String attribute, final Object value) { try { - reader.setProperty(property, value); + factory.setAttribute(attribute, value); return true; } catch (final Exception e) { return false; } } - static void setProperty(final SchemaFactory factory, final String property, final Object value) { - apply(factory, KIND_PROPERTY, property, () -> factory.setProperty(property, value)); - } - - static void setProperty(final Validator validator, final String property, final Object value) { - apply(validator, KIND_PROPERTY, property, () -> validator.setProperty(property, value)); - } - - static void setProperty(final ValidatorHandler handler, final String property, final Object value) { - apply(handler, KIND_PROPERTY, property, () -> handler.setProperty(property, value)); - } - /** - * Sets an attribute on a {@link DocumentBuilderFactory} and returns whether the implementation accepted it. Some implementations may reject certain - * attributes, in which case this method will return {@code false}. + * Sets a property on an {@link XMLReader} and returns whether the implementation accepted it. * - * @param factory The target factory on which to set the attribute. - * @param attribute The name of the attribute to set. - * @param value The value of the attribute to set. - * @return {@code true} if the attribute was applied, {@code false} if the implementation rejected it. + * @param reader The target reader on which to set the property. + * @param property The name of the property to set. + * @param value The value of the property to set. + * @return {@code true} if the property was applied, {@code false} if the implementation rejected it. */ - static boolean trySetAttribute(final DocumentBuilderFactory factory, final String attribute, final Object value) { + static boolean trySetProperty(final XMLReader reader, final String property, final Object value) { try { - factory.setAttribute(attribute, value); + reader.setProperty(property, value); return true; } catch (final Exception e) { return false; diff --git a/src/main/java/org/apache/commons/xml/Limits.java b/src/main/java/org/apache/commons/xml/Limits.java index 216f604c..8145a0f2 100644 --- a/src/main/java/org/apache/commons/xml/Limits.java +++ b/src/main/java/org/apache/commons/xml/Limits.java @@ -18,7 +18,6 @@ import static org.apache.commons.xml.JaxpSetters.setAttribute; import static org.apache.commons.xml.JaxpSetters.setOptionalAttribute; -import static org.apache.commons.xml.JaxpSetters.setProperty; import static org.apache.commons.xml.JaxpSetters.trySetProperty; import java.util.Collections; @@ -29,7 +28,6 @@ import javax.xml.parsers.DocumentBuilderFactory; import javax.xml.stream.XMLInputFactory; import javax.xml.transform.TransformerFactory; -import javax.xml.validation.SchemaFactory; import org.xml.sax.SAXNotRecognizedException; import org.xml.sax.SAXNotSupportedException; @@ -135,6 +133,14 @@ final class Limits { *

Applied to: stock JDK only. External Xerces' {@code SecurityManager} and Woodstox have no equivalent.

*/ private static final int DEFAULT_TOTAL_ENTITY_SIZE_LIMIT = 100000; + /** + * Class name of the external Apache Xerces {@link DocumentBuilderFactory}, whose limits live on a {@code SecurityManager} rather than JDK attributes. + */ + private static final String EXTERNAL_XERCES_DOCUMENT_BUILDER_FACTORY = "org.apache.xerces.jaxp.DocumentBuilderFactoryImpl"; + /** + * Class name of the external Apache Xerces {@link XMLReader}, whose limits live on a {@code SecurityManager} rather than JDK properties. + */ + private static final String EXTERNAL_XERCES_SAX_READER = "org.apache.xerces.jaxp.SAXParserImpl$JAXPSAXParser"; /** * URL-form property name to current value, in a stable order, for every limit that the stock JDK's parsers accept. Iterated by every {@code applyToJdk*} * method so each JDK factory or parser instance ends up with the same set of limits applied. @@ -221,13 +227,9 @@ final class Limits { */ private static final String WSTX_MAX_ENTITY_COUNT = "com.ctc.wstx.maxEntityCount"; /** - * Class name of the external Apache Xerces {@link DocumentBuilderFactory}, whose limits live on a {@code SecurityManager} rather than JDK attributes. - */ - private static final String EXTERNAL_XERCES_DOCUMENT_BUILDER_FACTORY = "org.apache.xerces.jaxp.DocumentBuilderFactoryImpl"; - /** - * Class name of the external Apache Xerces {@link XMLReader}, whose limits live on a {@code SecurityManager} rather than JDK properties. + * Xerces-specific property whose value is an {@code org.apache.xerces.util.SecurityManager} instance carrying processing-limit thresholds */ - private static final String EXTERNAL_XERCES_SAX_READER = "org.apache.xerces.jaxp.SAXParserImpl$JAXPSAXParser"; + private static final String XERCES_SECURITY_MANAGER_PROPERTY = "http://apache.org/xml/properties/security-manager"; static { final Map map = new LinkedHashMap<>(); @@ -242,15 +244,6 @@ final class Limits { JDK_LIMITS = Collections.unmodifiableMap(map); } - /** - * Sets every JDK-supported limit on a stock JDK {@link SchemaFactory}. - * - * @param factory The target factory to modify. - */ - static void applyToJdkSchema(final SchemaFactory factory) { - JDK_LIMITS.forEach((name, supplier) -> setProperty(factory, name, Integer.toString(supplier.getAsInt()))); - } - /** * Sets every JDK-supported limit on a stock JDK {@link TransformerFactory}. * @@ -260,30 +253,6 @@ static void applyToJdkTransformer(final TransformerFactory factory) { JDK_LIMITS.forEach((name, supplier) -> setAttribute(factory, name, Integer.toString(supplier.getAsInt()))); } - /** - * Best-effort application of the processing limits to an {@link XMLReader}, dispatched on the implementation. - * - *

External Xerces carries its limits on an {@code org.apache.xerces.util.SecurityManager} instance reachable through the - * {@value XercesProvider#XERCES_SECURITY_MANAGER_PROPERTY} property. The stock JDK reader is itself a Xerces fork that exposes the same property, so a probe - * cannot tell the two apart; the external distribution is therefore matched by class name, and every other reader (the stock JDK, a Saxon-picked JDK reader, - * any future attribute-based parser) takes the JDK limit properties. Neither path throws if the reader declines a limit.

- * - * @param reader The target reader to modify. - */ - static void tryApply(final XMLReader reader) { - if (EXTERNAL_XERCES_SAX_READER.equals(reader.getClass().getName())) { - try { - // External Xerces: tighten the SecurityManager it already installed under FSP to JDK 25 limits. - applyToXerces(reader.getProperty(XercesProvider.XERCES_SECURITY_MANAGER_PROPERTY)); - } catch (final SAXNotRecognizedException | SAXNotSupportedException e) { - throw new HardeningException("Failed to read Xerces security manager from XMLReader", e); - } - return; - } - // Pin the JDK limit properties to JDK 25 secure values; skip silently any property the reader does not recognize. - JDK_LIMITS.forEach((name, supplier) -> trySetProperty(reader, name, Integer.toString(supplier.getAsInt()))); - } - /** * Sets every JDK-supported limit on a Xerces {@code org.apache.xerces.util.SecurityManager}. * @@ -302,14 +271,6 @@ static void applyToXerces(final Object securityManager) { } } - private static Object newSecurityManager() { - try { - return Class.forName("org.apache.xerces.util.SecurityManager").getDeclaredConstructor().newInstance(); - } catch (final ReflectiveOperationException e) { - throw new HardeningException("Failed to instantiate org.apache.xerces.util.SecurityManager; expected Xerces to be on the classpath", e); - } - } - private static int getElementAttributeLimit() { return read(SP_ELEMENT_ATTRIBUTE_LIMIT, DEFAULT_ELEMENT_ATTRIBUTE_LIMIT); } @@ -346,6 +307,14 @@ private static int getTotalEntitySizeLimit() { return read(SP_TOTAL_ENTITY_SIZE_LIMIT, DEFAULT_TOTAL_ENTITY_SIZE_LIMIT); } + private static Object newSecurityManager() { + try { + return Class.forName("org.apache.xerces.util.SecurityManager").getDeclaredConstructor().newInstance(); + } catch (final ReflectiveOperationException e) { + throw new HardeningException("Failed to instantiate org.apache.xerces.util.SecurityManager; expected Xerces to be on the classpath", e); + } + } + private static int read(final String systemPropertyName, final int defaultValue) { final String raw = System.getProperty(systemPropertyName); if (raw == null || raw.isEmpty()) { @@ -371,7 +340,7 @@ static void tryApply(final DocumentBuilderFactory factory) { // Install a fresh SecurityManager pinned to JDK 25 limits, replacing Xerces' built-in caps which are looser than even JDK 8. final Object securityManager = newSecurityManager(); applyToXerces(securityManager); - setAttribute(factory, XercesProvider.XERCES_SECURITY_MANAGER_PROPERTY, securityManager); + setAttribute(factory, XERCES_SECURITY_MANAGER_PROPERTY, securityManager); return; } // Pin the JDK attribute limits to JDK 25 secure values; skip silently any attribute the implementation does not recognize. @@ -393,6 +362,30 @@ static void tryApply(final XMLInputFactory factory) { trySetProperty(factory, WSTX_MAX_ELEMENT_DEPTH, getMaxElementDepth()); } + /** + * Best-effort application of the processing limits to an {@link XMLReader}, dispatched on the implementation. + * + *

External Xerces carries its limits on an {@code org.apache.xerces.util.SecurityManager} instance reachable through the + * {@value #XERCES_SECURITY_MANAGER_PROPERTY} property. The stock JDK reader is itself a Xerces fork that exposes the same property, so a probe + * cannot tell the two apart; the external distribution is therefore matched by class name, and every other reader (the stock JDK, a Saxon-picked JDK reader, + * any future attribute-based parser) takes the JDK limit properties. Neither path throws if the reader declines a limit.

+ * + * @param reader The target reader to modify. + */ + static void tryApply(final XMLReader reader) { + if (EXTERNAL_XERCES_SAX_READER.equals(reader.getClass().getName())) { + try { + // External Xerces: tighten the SecurityManager it already installed under FSP to JDK 25 limits. + applyToXerces(reader.getProperty(XERCES_SECURITY_MANAGER_PROPERTY)); + } catch (final SAXNotRecognizedException | SAXNotSupportedException e) { + throw new HardeningException("Failed to read Xerces security manager from XMLReader", e); + } + return; + } + // Pin the JDK limit properties to JDK 25 secure values; skip silently any property the reader does not recognize. + JDK_LIMITS.forEach((name, supplier) -> trySetProperty(reader, name, Integer.toString(supplier.getAsInt()))); + } + private Limits() { } } diff --git a/src/main/java/org/apache/commons/xml/StockJdkProvider.java b/src/main/java/org/apache/commons/xml/StockJdkProvider.java index 7bd4cedf..efb88397 100644 --- a/src/main/java/org/apache/commons/xml/StockJdkProvider.java +++ b/src/main/java/org/apache/commons/xml/StockJdkProvider.java @@ -19,13 +19,11 @@ import static org.apache.commons.xml.JaxpSetters.setAttribute; import static org.apache.commons.xml.JaxpSetters.setFeature; -import static org.apache.commons.xml.JaxpSetters.setProperty; import javax.xml.XMLConstants; import javax.xml.parsers.SAXParserFactory; import javax.xml.transform.TransformerFactory; import javax.xml.transform.sax.SAXTransformerFactory; -import javax.xml.validation.SchemaFactory; import javax.xml.xpath.XPathFactory; import org.xml.sax.XMLReader; @@ -75,19 +73,6 @@ static XPathFactory configure(final XPathFactory factory) { return factory; } - static SchemaFactory configure(final SchemaFactory factory) { - // Required: enables the JDK XMLSecurityManager limits. - setFeature(factory, XMLConstants.FEATURE_SECURE_PROCESSING, true); - // Defense-in-depth: pin to JDK 25 limits so older JDKs do not fall back to looser secure values. - Limits.applyToJdkSchema(factory); - // Required: XMLSchemaLoader propagates this onto its inner SAX reader, otherwise it is overrideable by system properties - setProperty(factory, XMLConstants.ACCESS_EXTERNAL_DTD, ""); - // Required: gates xs:import/include/redefine fetches and xsi:schemaLocation. - setProperty(factory, XMLConstants.ACCESS_EXTERNAL_SCHEMA, ""); - // Required: routes every newSchema(Source[]) parse through an XmlFactories-hardened reader. - return new HardeningSchemaFactory(factory); - } - private StockJdkProvider() { } } diff --git a/src/main/java/org/apache/commons/xml/XercesProvider.java b/src/main/java/org/apache/commons/xml/XercesProvider.java deleted file mode 100644 index f5ad9c36..00000000 --- a/src/main/java/org/apache/commons/xml/XercesProvider.java +++ /dev/null @@ -1,99 +0,0 @@ -/* - * 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 org.apache.commons.xml; - -import static org.apache.commons.xml.JaxpSetters.setFeature; - -import javax.xml.XMLConstants; -import javax.xml.validation.Schema; -import javax.xml.validation.SchemaFactory; -import javax.xml.validation.Validator; -import javax.xml.validation.ValidatorHandler; - -import org.xml.sax.SAXNotRecognizedException; -import org.xml.sax.SAXNotSupportedException; - -/** - * Hardening recipes for the external Apache Xerces distribution (the {@code xerces:xercesImpl} artifact). - * - *

Factory classes live in the {@code org.apache.xerces.*} package. External Xerces does not ship a {@code TransformerFactory}, {@code XMLInputFactory} or - * {@code XPathFactory}, so this class only handles Schema factories; SAX hardening lives in {@link SAXParserHardener} and DOM hardening in - * {@link DocumentBuilderHardener}.

- * - *

Hardening recipe applied to every factory below uses the same building blocks:

- *
    - *
  • FSP ({@link XMLConstants#FEATURE_SECURE_PROCESSING}, set to {@code true}): enables Xerces' built-in {@code SecurityManager}, which - * is what carries the processing limits. Required.
  • - *
  • {@link Limits#applyToXerces}: defense-in-depth. Xerces' {@code SecurityManager} ships its own caps, but they are looser than even - * JDK 8's secure values; this call pins them to the JDK 25 secure values (entity-expansion limit and {@code maxOccurs} node limit, the only two its - * API exposes setters for).
  • - *
  • - *

    {@code HardeningXxx} wrappers + {@link Resolvers.DenyAll}: required. Xerces does not implement the JAXP 1.5 - * {@code ACCESS_EXTERNAL_*} properties, so an explicit resolver installed on every validator is the best way to block external - * schema fetching, without disabling those features altogether. The wrapper exists because Xerces' {@link Schema} does not propagate the - * {@link SchemaFactory}'s resolver or security manager to its {@link Validator} / {@link ValidatorHandler} products, so it re-installs both on - * every product.

    - *
  • - *
- */ -final class XercesProvider { - - private static Validator hardenValidator(final Validator validator) { - try { - Limits.applyToXerces(validator.getProperty(XERCES_SECURITY_MANAGER_PROPERTY)); - } catch (final SAXNotRecognizedException | SAXNotSupportedException e) { - throw new HardeningException("Failed to read Xerces security manager from Validator", e); - } - validator.setResourceResolver(Resolvers.DenyAll.LS_RESOURCE); - return validator; - } - - private static ValidatorHandler hardenValidatorHandler(final ValidatorHandler handler) { - try { - Limits.applyToXerces(handler.getProperty(XERCES_SECURITY_MANAGER_PROPERTY)); - } catch (final SAXNotRecognizedException | SAXNotSupportedException e) { - throw new HardeningException("Failed to read Xerces security manager from ValidatorHandler", e); - } - handler.setResourceResolver(Resolvers.DenyAll.LS_RESOURCE); - return handler; - } - - /** - * Xerces-specific property whose value is an {@code org.apache.xerces.util.SecurityManager} instance carrying processing-limit thresholds - */ - static final String XERCES_SECURITY_MANAGER_PROPERTY = "http://apache.org/xml/properties/security-manager"; - - static SchemaFactory configure(final SchemaFactory factory) { - // Required: enables Xerces' built-in SecurityManager. - setFeature(factory, XMLConstants.FEATURE_SECURE_PROCESSING, true); - try { - // Required: pins limits to JDK 25 secure values, otherwise Xerces' own caps are looser than JDK 8. - Limits.applyToXerces(factory.getProperty(XERCES_SECURITY_MANAGER_PROPERTY)); - } catch (final SAXNotRecognizedException | SAXNotSupportedException e) { - throw new HardeningException("Failed to read Xerces security manager from SchemaFactory", e); - } - // Required: Xerces ignores ACCESS_EXTERNAL_*; the deny-all resolver blocks xs:import/include/redefine fetches. - factory.setResourceResolver(Resolvers.DenyAll.LS_RESOURCE); - // Required: routes every newSchema(Source[]) parse through an XmlFactories-hardened reader, and re-installs limits + resolver on each Validator and - // ValidatorHandler since Xerces' Schema does not propagate factory state through. - return new HardeningSchemaFactory(factory, XercesProvider::hardenValidator, XercesProvider::hardenValidatorHandler); - } - - private XercesProvider() { - } -} diff --git a/src/main/java/org/apache/commons/xml/XmlFactories.java b/src/main/java/org/apache/commons/xml/XmlFactories.java index 53bcfd2b..ca1a9c65 100644 --- a/src/main/java/org/apache/commons/xml/XmlFactories.java +++ b/src/main/java/org/apache/commons/xml/XmlFactories.java @@ -102,17 +102,6 @@ private static XPathFactory dispatch(final XPathFactory factory) { } } - private static SchemaFactory dispatch(final SchemaFactory factory) { - switch (factory.getClass().getName()) { - case "com.sun.org.apache.xerces.internal.jaxp.validation.XMLSchemaFactory": - return StockJdkProvider.configure(factory); - case "org.apache.xerces.jaxp.validation.XMLSchemaFactory": - return XercesProvider.configure(factory); - default: - throw noProvider(factory); - } - } - /** * Rewrites a {@link Source} so that any SAX parsing it triggers runs through an {@link XmlFactories}-hardened {@link XMLReader}. * @@ -192,11 +181,9 @@ public static SAXParserFactory newSAXParserFactory() { * resulting {@link javax.xml.validation.Schema}.

* * @return a hardened factory. - * @throws IllegalStateException if the underlying Schema implementation is not recognized by any bundled hardening recipe, or if the matching recipe - * cannot apply its settings to it. */ public static SchemaFactory newSchemaFactory() { - return dispatch(SchemaFactory.newInstance(XMLConstants.W3C_XML_SCHEMA_NS_URI)); + return new HardeningSchemaFactory(SchemaFactory.newInstance(XMLConstants.W3C_XML_SCHEMA_NS_URI)); } /**