Source code
Description: source: package: org.apache.commons.xml.secure, class: SecureSAXParserFactory

    001/*
    002 * Licensed to the Apache Software Foundation (ASF) under one or more
    003 * contributor license agreements.  See the NOTICE file distributed with
    004 * this work for additional information regarding copyright ownership.
    005 * The ASF licenses this file to You under the Apache License, Version 2.0
    006 * (the "License"); you may not use this file except in compliance with
    007 * the License.  You may obtain a copy of the License at
    008 *
    009 *      https://www.apache.org/licenses/LICENSE-2.0
    010 *
    011 * Unless required by applicable law or agreed to in writing, software
    012 * distributed under the License is distributed on an "AS IS" BASIS,
    013 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
    014 * See the License for the specific language governing permissions and
    015 * limitations under the License.
    016 */
    017
    018package org.apache.commons.xml.secure;
    019
    020import java.lang.invoke.MethodHandle;
    021import java.util.Objects;
    022
    023import javax.xml.XMLConstants;
    024import javax.xml.parsers.FactoryConfigurationError;
    025import javax.xml.parsers.ParserConfigurationException;
    026import javax.xml.parsers.SAXParser;
    027import javax.xml.parsers.SAXParserFactory;
    028import javax.xml.transform.Source;
    029import javax.xml.transform.sax.SAXSource;
    030import javax.xml.transform.stream.StreamSource;
    031import javax.xml.validation.Schema;
    032
    033import org.xml.sax.EntityResolver;
    034import org.xml.sax.InputSource;
    035import org.xml.sax.SAXException;
    036import org.xml.sax.SAXNotRecognizedException;
    037import org.xml.sax.SAXNotSupportedException;
    038import org.xml.sax.XMLReader;
    039
    040/**
    041 * Creates new, secure {@link SAXParserFactory} instances.
    042 * <p>
    043 * Beyond the three universal guarantees on {@link org.apache.commons.xml.secure}, XInclude resolution is denied by default. When
    044 * {@link SAXParserFactory#setXIncludeAware(boolean) setXIncludeAware(true)} is called on the returned factory, the parser will process {@code xi:include}
    045 * elements but every external resource lookup is rejected. To permit specific trusted resources, install an {@link org.xml.sax.EntityResolver EntityResolver}
    046 * on the {@link org.xml.sax.XMLReader} that allow-lists them; any href the resolver does not explicitly allow stays blocked.
    047 * </p>
    048 * <p>
    049 * This class is not itself a {@link SAXParserFactory}, so it inherits none of the static JAXP factory methods. A caller therefore cannot obtain an unsecured
    050 * factory through this class by calling a method such as {@code newDefaultInstance()}. The secure factories are instances of a nested, non-public wrapper
    051 * class.
    052 * </p>
    053 *
    054 * @see org.apache.commons.xml.secure
    055 */
    056public final class SecureSAXParserFactory {
    057
    058    /**
    059     * {@link SecureXMLReader} for Android's {@code org.apache.harmony.xml.ExpatReader} that additionally surfaces its {@code namespace-prefixes} limitation at
    060     * configuration time.
    061     *
    062     * <p>ExpatReader does not actually support the {@code namespace-prefixes} feature: enabling it is accepted by {@code setFeature} but fails later, during
    063     * {@code parse}, with a {@link SAXNotSupportedException}. Reporting the rejection eagerly from {@link #setFeature(String, boolean)} lets consumers that probe
    064     * the feature, such as Xalan's identity transformer, catch the exception and fall back instead of failing the whole parse.</p>
    065     */
    066    static final class SecureExpatXMLReader extends SecureXMLReader {
    067
    068        private static final String NAMESPACE_PREFIXES_FEATURE = "http://xml.org/sax/features/namespace-prefixes";
    069
    070        SecureExpatXMLReader(final XMLReader delegate) {
    071            super(delegate);
    072        }
    073
    074        @Override
    075        public void setFeature(final String name, final boolean value) throws SAXNotRecognizedException, SAXNotSupportedException {
    076            if (value && NAMESPACE_PREFIXES_FEATURE.equals(name)) {
    077                throw new SAXNotSupportedException("ExpatReader does not support enabling the '" + NAMESPACE_PREFIXES_FEATURE + "' feature");
    078            }
    079            super.setFeature(name, value);
    080        }
    081    }
    082    /**
    083     * Universal SAX factory wrapper that funnels every produced parser through {@link SecureSAXParserFactory#secure(XMLReader)}.
    084     * <p>
    085     * {@link SAXParserFactory} exposes only a feature API and no property API, so the per-parse secure (limits, entity blocking, implementation-specific fixups)
    086     * has to run on each {@link XMLReader} the factory produces. This wrapper returns a {@link SecureSAXParser}, which applies that securing lazily to both the
    087     * SAX 2 {@link XMLReader} and the SAX 1 {@link org.xml.sax.Parser} it exposes.
    088     * </p>
    089     */
    090    private static final class Wrapper extends SAXParserFactory {
    091
    092        private final SAXParserFactory delegate;
    093
    094        /**
    095         * Constructs a new instance.
    096         *
    097         * @param delegate The delegate to wrap; must not be {@code null}.
    098         * @throws NullPointerException Thrown if {@code delegate} is {@code null}.
    099         */
    100        private Wrapper(final SAXParserFactory delegate) {
    101            this.delegate = Objects.requireNonNull(delegate, "delegate");
    102        }
    103
    104        @Override
    105        public boolean getFeature(final String name) throws ParserConfigurationException, SAXNotRecognizedException, SAXNotSupportedException {
    106            return delegate.getFeature(name);
    107        }
    108
    109        @Override
    110        public Schema getSchema() {
    111            return delegate.getSchema();
    112        }
    113
    114        @Override
    115        public boolean isNamespaceAware() {
    116            return delegate.isNamespaceAware();
    117        }
    118
    119        @Override
    120        public boolean isValidating() {
    121            return delegate.isValidating();
    122        }
    123
    124        @Override
    125        public boolean isXIncludeAware() {
    126            return delegate.isXIncludeAware();
    127        }
    128
    129        @Override
    130        public SAXParser newSAXParser() throws ParserConfigurationException, SAXException {
    131            return new SecureSAXParser(delegate.newSAXParser());
    132        }
    133
    134        @Override
    135        public void setFeature(final String name, final boolean value) throws ParserConfigurationException, SAXNotRecognizedException, SAXNotSupportedException {
    136            delegate.setFeature(name, value);
    137        }
    138
    139        @Override
    140        public void setNamespaceAware(final boolean awareness) {
    141            delegate.setNamespaceAware(awareness);
    142        }
    143
    144        @Override
    145        public void setSchema(final Schema schema) {
    146            delegate.setSchema(schema);
    147        }
    148
    149        @Override
    150        public void setValidating(final boolean validating) {
    151            delegate.setValidating(validating);
    152        }
    153
    154        @Override
    155        public void setXIncludeAware(final boolean state) {
    156            delegate.setXIncludeAware(state);
    157        }
    158    }
    159    /** Class name of Android's Expat-backed {@link XMLReader}. */
    160    private static final String ANDROID_EXPAT_READER = "org.apache.harmony.xml.ExpatReader";
    161
    162    /** Class name of Android's Harmony-based {@link SAXParserFactory}, backed by the native Expat parser. */
    163    private static final String ANDROID_SAX_PARSER_FACTORY = "org.apache.harmony.xml.parsers.SAXParserFactoryImpl";
    164
    165    /** Class name of the JDK's built-in default implementation, the Java 8 fallback for {@link #newDefaultInstance()}. */
    166    static final String JDK_SAX_PARSER_FACTORY = "com.sun.org.apache.xerces.internal.jaxp.SAXParserFactoryImpl";
    167
    168    /**
    169     * The JDK feature governing whether an implementation's internal parser lookup may resolve a third-party parser. The secure wrappers parse every source
    170     * themselves, so instead of configuring the implementation the TrAX, XPath and schema wrappers read this feature and pick the rewrite parser accordingly.
    171     */
    172    static final String OVERRIDE_DEFAULT_PARSER = "jdk.xml.overrideDefaultParser";
    173
    174    /** System property naming the {@link SAXParserFactory} implementation, the JDK's own mechanism for reconfiguring the default parser. */
    175    private static final String SAX_FACTORY_ID = "javax.xml.parsers.SAXParserFactory";
    176
    177    private static final MethodHandle MH_newDefaultInstance = MethodHandleFactory.findStatic(SAXParserFactory.class, "newDefaultInstance");
    178
    179    /**
    180     * Enables namespace awareness on the given factory; the {@code NSInstance} counterpart of each factory method routes its result through here.
    181     *
    182     * @param factory The factory to configure; never {@code null}.
    183     * @return The given factory, namespace-aware.
    184     */
    185    private static SAXParserFactory makeNSAware(final SAXParserFactory factory) {
    186        factory.setNamespaceAware(true);
    187        return factory;
    188    }
    189
    190    /**
    191     * Returns a new, secure {@link SAXParserFactory} of the system-default implementation.
    192     * <p>
    193     * Obtained from {@code SAXParserFactory.newDefaultInstance()} where the platform provides it (Java 9 or later),
    194     * by instantiating the JDK's built-in implementation directly on Java 8,
    195     * and by the standard {@link #newInstance()} lookup where the platform provides neither
    196     * (for example, Android, whose lookup is itself pinned to the platform implementation).
    197     * </p>
    198     *
    199     * @return A secure factory.
    200     * @throws IllegalStateException     Thrown if a required secure setting cannot be applied to the underlying implementation.
    201     * @throws FactoryConfigurationError Thrown from the {@link #newInstance()} lookup this method falls back to on a platform that provides neither
    202     *                                   {@code newDefaultInstance()} nor the JDK's built-in implementation (for example Android).
    203     */
    204    public static SAXParserFactory newDefaultInstance() {
    205        if (MH_newDefaultInstance != null) {
    206            return secure(MethodHandleFactory.invokeExact(() -> (SAXParserFactory) MH_newDefaultInstance.invokeExact(), FactoryConfigurationError.class));
    207        }
    208        try {
    209            // Java 8: the method does not exist; instantiate the JDK's built-in default by its class name instead.
    210            return newInstance(JDK_SAX_PARSER_FACTORY, null);
    211        } catch (final FactoryConfigurationError e) {
    212            // Neither exists (for example, Android): degrade to the regular lookup, which such platforms pin to their built-in parser.
    213            return newInstance();
    214        }
    215    }
    216
    217    /**
    218     * Returns a new, secure, namespace-aware {@link SAXParserFactory} of the system-default implementation, enabling namespace awareness on
    219     * {@link #newDefaultInstance()}, the behavior {@code SAXParserFactory.newDefaultNSInstance()} (Java 13 or later) is specified to have.
    220     *
    221     * @return A secure, namespace-aware factory.
    222     * @throws IllegalStateException     Thrown if a required secure setting cannot be applied to the underlying implementation.
    223     * @throws FactoryConfigurationError Thrown from the {@link #newInstance()} lookup {@link #newDefaultInstance()} falls back to on a platform that provides
    224     *                                   neither {@code newDefaultInstance()} nor the JDK's built-in implementation (for example Android).
    225     */
    226    public static SAXParserFactory newDefaultNSInstance() {
    227        return makeNSAware(newDefaultInstance());
    228    }
    229
    230    /**
    231     * Returns a new, secure {@link SAXParserFactory}.
    232     *
    233     * @return A secure factory.
    234     * @throws IllegalStateException     Thrown if a required secure setting cannot be applied to the underlying implementation.
    235     * @throws FactoryConfigurationError Thrown from {@link SAXParserFactory} in case of a {@link java.util.ServiceConfigurationError service configuration
    236     *                                   error} or if the implementation is not available or cannot be instantiated.
    237     */
    238    public static SAXParserFactory newInstance() {
    239        return secure(SAXParserFactory.newInstance());
    240    }
    241
    242    /**
    243     * Returns a new, secure {@link SAXParserFactory} of the given implementation class.
    244     *
    245     * @param factoryClassName The fully qualified class name of the {@link SAXParserFactory} implementation.
    246     * @param classLoader      The class loader used to load the factory class; {@code null} means the current thread's context class loader.
    247     * @return A secure factory.
    248     * @throws IllegalStateException     Thrown if a required secure setting cannot be applied to the underlying implementation.
    249     * @throws FactoryConfigurationError Thrown if {@code factoryClassName} is {@code null} or the factory class cannot be loaded or instantiated.
    250     */
    251    public static SAXParserFactory newInstance(final String factoryClassName, final ClassLoader classLoader) {
    252        return secure(SAXParserFactory.newInstance(factoryClassName, classLoader));
    253    }
    254
    255    /**
    256     * Returns a new, secure, namespace-aware {@link SAXParserFactory}, enabling namespace awareness on {@link #newInstance()}, the behavior
    257     * {@code SAXParserFactory.newNSInstance()} (Java 13 or later) is specified to have.
    258     *
    259     * @return A secure, namespace-aware factory.
    260     * @throws IllegalStateException     Thrown if a required secure setting cannot be applied to the underlying implementation.
    261     * @throws FactoryConfigurationError Thrown from {@link SAXParserFactory} in case of a {@link java.util.ServiceConfigurationError service configuration
    262     *                                   error} or if the implementation is not available or cannot be instantiated.
    263     */
    264    public static SAXParserFactory newNSInstance() {
    265        return makeNSAware(newInstance());
    266    }
    267
    268    /**
    269     * Returns the secure, namespace-aware factory the Source-rewriting wrappers parse with.
    270     * <p>
    271     * While {@code overrideDefaultParser} is {@code false} the factory is the JDK's "default parser" factory, determined the way the JDK itself determines it: the built-in parser,
    272     * unless the {@value #SAX_FACTORY_ID} system property is set — that property is the JDK's own mechanism for reconfiguring the default
    273     * parser, so it is honored through the standard lookup rather than bypassed.
    274     * </p>
    275     *
    276     * @param overrideDefaultParser whether {@value #OVERRIDE_DEFAULT_PARSER} on the originating factory asks to override the JDK's default parser.
    277     * @return A secure, namespace-aware factory.
    278     * @throws IllegalStateException     Thrown if a required secure setting cannot be applied to the underlying implementation.
    279     * @throws FactoryConfigurationError Thrown from a factory in case of a {@link java.util.ServiceConfigurationError service configuration error} or if the
    280     *                                   implementation is not available or cannot be instantiated.
    281     */
    282    static SAXParserFactory newNSInstance(final boolean overrideDefaultParser) {
    283        return overrideDefaultParser || System.getProperty(SAX_FACTORY_ID) != null ? newNSInstance() : newDefaultNSInstance();
    284    }
    285
    286    /**
    287     * Returns a new, secure, namespace-aware {@link SAXParserFactory} of the given implementation class, enabling namespace awareness on
    288     * {@link #newInstance(String, ClassLoader)}, the behavior {@code SAXParserFactory.newNSInstance(String, ClassLoader)} (Java 13 or later) is specified to have.
    289     *
    290     * @param factoryClassName The fully qualified class name of the {@link SAXParserFactory} implementation.
    291     * @param classLoader      The class loader used to load the factory class; {@code null} means the current thread's context class loader.
    292     * @return A secure, namespace-aware factory.
    293     * @throws IllegalStateException     Thrown if a required secure setting cannot be applied to the underlying implementation.
    294     * @throws FactoryConfigurationError Thrown if {@code factoryClassName} is {@code null} or the factory class cannot be loaded or instantiated.
    295     */
    296    public static SAXParserFactory newNSInstance(final String factoryClassName, final ClassLoader classLoader) {
    297        return makeNSAware(newInstance(factoryClassName, classLoader));
    298    }
    299
    300    /**
    301     * Creates a new secure, namespace-aware {@link XMLReader} for the TrAX, XPath and schema wrappers to parse sources with, from the factory
    302     * {@link #newNSInstance(boolean)} selects.
    303     *
    304     * @param overrideDefaultParser whether {@value #OVERRIDE_DEFAULT_PARSER} on the originating factory asks to override the JDK's default parser.
    305     * @return a secure reader.
    306     * @throws IllegalStateException     Thrown if the underlying implementation cannot provide a secure reader; providing one is a routine capability of every
    307     *                                   supported implementation, so a failure signals a broken environment, not a per-parse condition.
    308     * @throws FactoryConfigurationError Thrown from a factory in case of a {@link java.util.ServiceConfigurationError service
    309     *                                   configuration error} or if the implementation is not available or cannot be instantiated.
    310     */
    311    static XMLReader newXMLReader(final boolean overrideDefaultParser) {
    312        try {
    313            return newNSInstance(overrideDefaultParser).newSAXParser().getXMLReader();
    314        } catch (ParserConfigurationException | SAXException e) {
    315            throw SecureException.readerFailed(e);
    316        }
    317    }
    318
    319    /**
    320     * Capability-driven securing for any {@link SAXParserFactory} on the classpath.
    321     *
    322     * <p>Rather than branching on the implementation class, this method probes what the factory supports and adapts. Because
    323     * {@link SAXParserFactory} exposes only a feature API and no property API, the per-parse configuration runs on each {@link XMLReader} the factory produces,
    324     * funneled through the nested wrapper into {@link #secure(XMLReader)}:</p>
    325     * <ul>
    326     *     <li><strong>Android</strong> (Harmony / Expat): {@link XMLConstants#FEATURE_SECURE_PROCESSING FSP} and the JAXP 1.5 {@code ACCESS_EXTERNAL_*} properties
    327     *         are not recognized, and libexpat enforces its own Billion Laughs check, so neither is applied. Two fixups are still needed: an ignore-all resolver
    328     *         (Expat ignores external fetches silently when no resolver is set; the floor keeps that behavior non-bypassable, resolving anything unresolved to
    329     *         empty), and a {@link SecureExpatXMLReader} so the unsupported {@code namespace-prefixes} feature is rejected at
    330     *         configuration time rather than mid-parse.</li>
    331     *     <li><strong>FSP</strong>: required on every other reader. It switches on the implementation's built-in security manager, which is what carries the
    332     *         processing limits.</li>
    333     *     <li><strong>Ignore-all resolver floor</strong>: every reader is wrapped in a {@link SecureXMLReader} that keeps an ignore-all {@link EntityResolver} floor.
    334     *         That floor blocks external DTD, entity, schema and {@code xi:include} fetches in one place: the stock JDK's XInclude processor ignores
    335     *         {@code ACCESS_EXTERNAL_*} and consults the {@link EntityResolver} instead, so no {@code ACCESS_EXTERNAL_*} properties are needed here. A caller can
    336     *         chain its own resolver onto the floor to allow-list resources, but cannot remove it.</li>
    337     * </ul>
    338     *
    339     * @param factory The factory to secure; never {@code null}.
    340     * @return a secure factory.
    341     */
    342    static SAXParserFactory secure(final SAXParserFactory factory) {
    343        // Required: enables the implementation's security manager, which carries the limits. Android's Expat rejects FSP, so it is skipped there.
    344        if (!ANDROID_SAX_PARSER_FACTORY.equals(factory.getClass().getName())) {
    345            setFeature(factory, XMLConstants.FEATURE_SECURE_PROCESSING, true);
    346        }
    347        // The per-parse securing (limits, entity blocking, Android fixups) lives in secure(XMLReader) because SAXParserFactory has no property API.
    348        return new Wrapper(factory);
    349    }
    350
    351    /**
    352     * Rewrites a {@link Source} so that any SAX parsing it triggers runs through a secure {@link XMLReader}.
    353     * <p>
    354     * Only a {@link StreamSource} or a {@link SAXSource} without a reader is enriched with a secure, namespace-aware reader; other source kinds are returned
    355     * as-is. Used by the TrAX and schema wrappers to route every source they parse through the secure SAX path.
    356     * </p>
    357     *
    358     * @param source           The source to secure; never {@code null}.
    359     * @param overrideDefaultParser whether {@value #OVERRIDE_DEFAULT_PARSER} on the originating factory asks to override the JDK's default parser.
    360     * @return a secure source.
    361     * @throws IllegalStateException     Thrown if the underlying implementation cannot provide a secure reader.
    362     * @throws FactoryConfigurationError Thrown from a factory in case of a {@link java.util.ServiceConfigurationError service
    363     *                                   configuration error} or if the implementation is not available or cannot be instantiated.
    364     */
    365    static Source secure(final Source source, final boolean overrideDefaultParser) {
    366        if (source instanceof StreamSource || source instanceof SAXSource && ((SAXSource) source).getXMLReader() == null) {
    367            final InputSource inputSource = SAXSource.sourceToInputSource(source);
    368            return inputSource == null ? source : new SAXSource(newXMLReader(overrideDefaultParser), inputSource);
    369        }
    370        return source;
    371    }
    372
    373    /**
    374     * Secures an existing {@link XMLReader}.
    375     *
    376     * @param reader The reader to secure; never {@code null}.
    377     * @return A secure reader.
    378     * @throws IllegalStateException Thrown if a required secure setting cannot be applied to the underlying implementation.
    379     */
    380    static XMLReader secure(final XMLReader reader) {
    381        if (reader instanceof SecureXMLReader) {
    382            // Already secure (for example, a reader from a secure factory passed back through secure(XMLReader)); the floor is already in place.
    383            return reader;
    384        }
    385        if (ANDROID_EXPAT_READER.equals(reader.getClass().getName())) {
    386            // Expat ignores external fetches when no resolver is set; the ignore-all floor keeps that behavior non-bypassable (routing a caller-set resolver,
    387            // including SAXParser.parse's handler, through it and resolving anything unresolved to empty) and, via SecureExpatXMLReader, rejects the
    388            // unsupported namespace-prefixes feature eagerly rather than mid-parse.
    389            return new SecureExpatXMLReader(reader);
    390        }
    391        // Required: enables the JDK XMLSecurityManager / Xerces SecurityManager limits.
    392        setFeature(reader, XMLConstants.FEATURE_SECURE_PROCESSING, true);
    393        // Required: SecureXMLReader installs an ignore-all EntityResolver floor on the reader.
    394        // That floor blocks external DTD, entity, schema and xi:include fetches in one place: no ACCESS_EXTERNAL_* properties are needed here.
    395        // Callers can chain their resolvers, but not override the floor.
    396        return new SecureXMLReader(reader);
    397    }
    398
    399    private static void setFeature(final SAXParserFactory factory, final String feature, final boolean value) {
    400        try {
    401            factory.setFeature(feature, value);
    402        } catch (final Exception e) {
    403            throw SecureException.featureFailed(feature, factory, e);
    404        }
    405    }
    406
    407    private static void setFeature(final XMLReader reader, final String feature, final boolean value) {
    408        try {
    409            reader.setFeature(feature, value);
    410        } catch (final Exception e) {
    411            throw SecureException.featureFailed(feature, reader, e);
    412        }
    413    }
    414
    415    private SecureSAXParserFactory() {
    416        // static only
    417    }
    418}