Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
20 changes: 16 additions & 4 deletions src/main/java/org/apache/commons/xml/JaxpSetters.java
Original file line number Diff line number Diff line change
Expand Up @@ -57,10 +57,6 @@ static void setAttribute(final DocumentBuilderFactory factory, final String attr
apply(factory, KIND_ATTRIBUTE, attribute, () -> factory.setAttribute(attribute, value));
}

static void setAttribute(final TransformerFactory factory, final String attribute, final Object value) {
apply(factory, KIND_ATTRIBUTE, attribute, () -> factory.setAttribute(attribute, value));
}

static void setFeature(final DocumentBuilderFactory factory, final String feature, final boolean value) {
apply(factory, KIND_FEATURE, feature, () -> factory.setFeature(feature, value));
}
Expand Down Expand Up @@ -97,6 +93,14 @@ static void setOptionalAttribute(final DocumentBuilderFactory factory, final Str
trySetAttribute(factory, attribute, value);
}

static void setOptionalAttribute(final TransformerFactory factory, final String attribute, final Object value) {
try {
factory.setAttribute(attribute, value);
} catch (final Exception e) {
// Ignored: the implementation does not recognize this attribute.
}
}

static void setOptionalFeature(final DocumentBuilderFactory factory, final String feature, final boolean value) {
try {
factory.setFeature(feature, value);
Expand All @@ -113,6 +117,14 @@ static void setOptionalFeature(final XMLReader reader, final String feature, fin
}
}

static void setOptionalFeature(final XPathFactory factory, final String feature, final boolean value) {
try {
factory.setFeature(feature, value);
} catch (final Exception e) {
// Ignored: the implementation does not recognize this feature.
}
}

static void setOptionalProperty(final XMLInputFactory factory, final String property, final Object value) {
trySetProperty(factory, property, value);
}
Expand Down
22 changes: 13 additions & 9 deletions src/main/java/org/apache/commons/xml/Limits.java
Original file line number Diff line number Diff line change
Expand Up @@ -244,15 +244,6 @@ final class Limits {
JDK_LIMITS = Collections.unmodifiableMap(map);
}

/**
* Sets every JDK-supported limit on a stock JDK {@link TransformerFactory}.
*
* @param factory The target factory to modify.
*/
static void applyToJdkTransformer(final TransformerFactory factory) {
JDK_LIMITS.forEach((name, supplier) -> setAttribute(factory, name, Integer.toString(supplier.getAsInt())));
}

/**
* Sets every JDK-supported limit on a Xerces {@code org.apache.xerces.util.SecurityManager}.
*
Expand Down Expand Up @@ -347,6 +338,19 @@ static void tryApply(final DocumentBuilderFactory factory) {
JDK_LIMITS.forEach((name, supplier) -> setOptionalAttribute(factory, name, Integer.toString(supplier.getAsInt())));
}

/**
* Best-effort application of the JDK processing limits to a {@link TransformerFactory}.
*
* <p>The stock JDK's XSLTC honours the JDK limit attributes, so this pins them to JDK 25 secure values; other implementations (Apache Xalan) reject the
* attributes and their caps come from {@link javax.xml.XMLConstants#FEATURE_SECURE_PROCESSING} instead. Neither path throws if the implementation declines a
* limit.</p>
*
* @param factory The target factory to modify.
*/
static void tryApply(final TransformerFactory factory) {
JDK_LIMITS.forEach((name, supplier) -> setOptionalAttribute(factory, name, Integer.toString(supplier.getAsInt())));
}

/**
* Best-effort application of the processing limits to an {@link XMLInputFactory}, regardless of implementation.
*
Expand Down
78 changes: 0 additions & 78 deletions src/main/java/org/apache/commons/xml/StockJdkProvider.java

This file was deleted.

99 changes: 99 additions & 0 deletions src/main/java/org/apache/commons/xml/TransformerHardener.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,99 @@
/*
* 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 static org.apache.commons.xml.JaxpSetters.setOptionalAttribute;

import java.util.Arrays;
import java.util.Collections;
import java.util.HashSet;
import java.util.Set;

import javax.xml.XMLConstants;
import javax.xml.transform.Source;
import javax.xml.transform.TransformerFactory;
import javax.xml.transform.URIResolver;
import javax.xml.transform.sax.SAXTransformerFactory;

/**
* Capability-driven hardening for any {@link TransformerFactory} on the classpath.
*
* <p>Rather than branching on the implementation class, {@link #harden(TransformerFactory)} probes what the factory supports and adapts:</p>
* <ul>
* <li><strong>Saxon</strong> ({@code net.sf.saxon}): recognized by class name and handed to {@link SaxonProvider#configure(TransformerFactory)}. Unlike XSLTC
* and Xalan, Saxon reaches external resources through several channels (the {@link URIResolver}, a collection finder, an unparsed-text resolver) on top
* of reflection-based extension functions, none of which the standard JAXP knobs can close; only a locked-down Saxon {@code Configuration} can. This is
* the TrAX counterpart of the Android special case in {@link DocumentBuilderHardener}, kept as a documented class-name exception because the required
* hardening surface is reachable only through a vendor API.</li>
* <li><strong>FSP</strong> ({@link XMLConstants#FEATURE_SECURE_PROCESSING}): required. On XSLTC it enables the runtime evaluator limits; on Xalan it disables
* reflection-based extension functions.</li>
* <li><strong>Limits</strong>: applied best-effort by {@link Limits#tryApply(TransformerFactory)}. XSLTC honours the JDK attribute limits; Xalan ignores them
* (its caps come from FSP).</li>
* <li><strong>{@code ACCESS_EXTERNAL_DTD}</strong> (set to {@code ""}): required on XSLTC. XSLTC copies this factory attribute onto the reader that parses the
* stylesheet ({@code Util.getInputSource}), overwriting the {@code ACCESS_EXTERNAL_DTD} the wrapper's hardened reader had already set; without it a
* permissive default re-opens the external-DTD/entity channel during stylesheet compilation. Xalan rejects the attribute (best-effort, ignored) and closes
* that channel through the hardened reader instead. Its sibling {@code ACCESS_EXTERNAL_STYLESHEET} is <em>not</em> set: the deny-all resolver below already
* guards the only channel it covers.</li>
* <li><strong>{@link Resolvers.DenyAll#URI}</strong>: required. A deny-all {@link URIResolver} blocks {@code xsl:import}/{@code xsl:include} at compile time
* and {@code document()} at runtime, the one channel both XSLTC and Xalan route through.</li>
* <li><strong>{@link HardeningTransformerFactory}</strong>: required. Both implementations fall back to {@code SAXParserFactory.newInstance()} to parse a
* stylesheet or source document that does not carry its own reader, and only set FSP on it; wrapping the factory rewrites every {@link Source} through an
* {@link XmlFactories}-hardened reader instead. On Xalan that reader (its deny-all {@link org.xml.sax.EntityResolver} or its own
* {@code ACCESS_EXTERNAL_DTD}) is what blocks external DTDs and entities.</li>
* </ul>
*
* <h2>Caveats</h2>
* <ul>
* <li>Replacing the {@code URIResolver} on the returned factory or on a produced transformer cancels the deny-all block for XSLT URI fetches; neither XSLTC
* nor Xalan exposes a tamper-resistant equivalent of JAXP 1.5 {@code ACCESS_EXTERNAL_STYLESHEET}.</li>
* </ul>
*/
final class TransformerHardener {

/**
* Class names of Saxon's {@link TransformerFactory} (open-source and commercial editions), hardened through a Saxon {@code Configuration} rather than the
* standard JAXP knobs.
*/
private static final Set<String> SAXON_TRANSFORMER_FACTORIES = Collections.unmodifiableSet(new HashSet<>(Arrays.asList(
"net.sf.saxon.TransformerFactoryImpl",
"com.saxonica.config.ProfessionalTransformerFactory",
"com.saxonica.config.EnterpriseTransformerFactory")));

static TransformerFactory harden(final TransformerFactory factory) {
if (SAXON_TRANSFORMER_FACTORIES.contains(factory.getClass().getName())) {
// Saxon: only a locked-down Configuration can close all of its resource-resolution channels and its extension-function surface.
return SaxonProvider.configure(factory);
}
// Required: enables secure processing (XSLTC runtime limits; Xalan's extension-function block).
setFeature(factory, XMLConstants.FEATURE_SECURE_PROCESSING, true);
// Best-effort: JDK's XSLTC honors the JDK attribute limits, pinning them to JDK 25 secure values; Xalan ignores them.
Limits.tryApply(factory);
// Required on JDK's XSLTC: it copies this factory attribute onto the reader that parses the stylesheet (Util.getInputSource).
// A permissive default here would re-open the external-DTD/entity channel.
// Xalan rejects the attribute and blocks that channel through a deny-all resolver instead.
setOptionalAttribute(factory, XMLConstants.ACCESS_EXTERNAL_DTD, "");
// Required: the one channel both implementations honor; blocks xsl:import/include at compile time and document() at runtime.
factory.setURIResolver(Resolvers.DenyAll.URI);
// Required: source/stylesheet parsing provisions its own SAX reader otherwise; the wrapper routes every Source through a hardened one.
return new HardeningTransformerFactory((SAXTransformerFactory) factory);
}

private TransformerHardener() {
}
}
78 changes: 78 additions & 0 deletions src/main/java/org/apache/commons/xml/XPathHardener.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,78 @@
/*
* 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 static org.apache.commons.xml.JaxpSetters.setOptionalFeature;

import java.util.Arrays;
import java.util.Collections;
import java.util.HashSet;
import java.util.Set;

import javax.xml.XMLConstants;
import javax.xml.xpath.XPathFactory;

/**
* Capability-driven hardening for any {@link XPathFactory} on the classpath.
*
* <p>The XPath object model mirrors TrAX: the stock JDK and Apache Xalan ship an XPath 1.0 engine with no URI-fetching functions, while Saxon adds the XPath 3.1
* {@code fn:doc}, {@code fn:collection} and {@code fn:unparsed-text} functions that can reach external resources. Rather than branching on the implementation
* class, {@link #harden(XPathFactory)} probes what the factory supports and adapts:</p>
* <ul>
* <li><strong>Saxon</strong> ({@code net.sf.saxon}): recognized by class name and handed to {@link SaxonProvider#configure(XPathFactory)}. Its URI-fetching
* functions and reflection-based extension calls are reachable only through a locked-down Saxon {@code Configuration}, not the standard JAXP knobs; this
* is the XPath counterpart of the Saxon exception in {@link TransformerHardener}, kept as a documented class-name exception because the required
* hardening surface is reachable only through a vendor API.</li>
* <li><strong>FODP</strong> ({@code jdk.xml.overrideDefaultParser}, set to {@code false}): best-effort. On the stock JDK it pins the internal parser lookup to
* the bundled SAX parser, blocking a sysprop swap to a third-party parser (defense-in-depth); Xalan rejects the feature and is left unchanged.</li>
* <li><strong>FSP</strong> ({@link XMLConstants#FEATURE_SECURE_PROCESSING}): required. It is the only knob both the stock JDK and Xalan XPath engines expose,
* and switches on their secure-processing limits. {@link XPathFactory} has no attribute API for finer control.</li>
* </ul>
*/
final class XPathHardener {

/**
* Class names of Saxon's {@link XPathFactory} (open-source and commercial editions), hardened through a Saxon {@code Configuration} rather than the standard
* JAXP knobs. The commercial editions subclass the open-source {@code net.sf.saxon.xpath.XPathFactoryImpl}, so {@link SaxonProvider} handles all three.
*/
private static final Set<String> SAXON_XPATH_FACTORIES = Collections.unmodifiableSet(new HashSet<>(Arrays.asList(
"net.sf.saxon.xpath.XPathFactoryImpl",
"com.saxonica.config.ProfessionalXPathFactory",
"com.saxonica.config.EnterpriseXPathFactory")));

/**
* {@code jdk.xml.overrideDefaultParser}: pin to the JDK's bundled SAX parser; defense-in-depth against a sysprop swap to a third-party parser.
*/
private static final String FEATURE_OVERRIDE_DEFAULT_PARSER = "jdk.xml.overrideDefaultParser";

static XPathFactory harden(final XPathFactory factory) {
if (SAXON_XPATH_FACTORIES.contains(factory.getClass().getName())) {
// Saxon: only a locked-down Configuration can close its URI-fetching functions and extension-function surface.
return SaxonProvider.configure(factory);
}
// Best-effort: the stock JDK pins its bundled SAX parser (defense-in-depth); Xalan rejects the feature.
setOptionalFeature(factory, FEATURE_OVERRIDE_DEFAULT_PARSER, false);
// Required: enables the engine's secure-processing limits; XPathFactory has no attribute API for finer control.
setFeature(factory, XMLConstants.FEATURE_SECURE_PROCESSING, true);
return factory;
}

private XPathHardener() {
}
}
Loading
Loading