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