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.DocumentBuilder; 025import javax.xml.parsers.DocumentBuilderFactory; 026import javax.xml.parsers.FactoryConfigurationError; 027import javax.xml.parsers.ParserConfigurationException; 028import javax.xml.validation.Schema; 029 030import org.xml.sax.EntityResolver; 031 032/** 033 * Creates new, secure {@link DocumentBuilderFactory} instances. 034 * <p> 035 * Beyond the three universal guarantees on {@link org.apache.commons.xml.secure}, XInclude resolution is denied by default. When 036 * {@link DocumentBuilderFactory#setXIncludeAware(boolean) setXIncludeAware(true)} is called on the returned factory, the parser will process {@code xi:include} 037 * elements but every external resource lookup is rejected. To permit specific trusted resources, install an {@link org.xml.sax.EntityResolver EntityResolver} 038 * on the {@link DocumentBuilder} that allow-lists them; any href the resolver does not explicitly allow stays blocked. 039 * </p> 040 * <p> 041 * This class is not itself a {@link DocumentBuilderFactory}, so it inherits none of the static JAXP factory methods. A caller therefore cannot obtain an 042 * unsecured factory through this class by calling a method such as {@code newDefaultInstance()}. The secure factories are instances of a nested, non-public 043 * wrapper class. 044 * </p> 045 * 046 * @see org.apache.commons.xml.secure 047 */ 048public final class SecureDocumentBuilderFactory { 049 050 /** 051 * {@link DocumentBuilderFactory} wrapper that keeps an ignore-all {@link EntityResolver} floor on every {@link DocumentBuilder} produced. 052 * <p> 053 * Wraps each produced builder in a {@link SecureDocumentBuilder}; required when the underlying factory carries no resolver of its own and does not honor 054 * JAXP 1.5 {@code ACCESS_EXTERNAL_*} (e.g., the external Xerces distribution). A caller-set resolver is routed through the floor rather than replacing it. 055 * </p> 056 */ 057 private static final class Wrapper extends DocumentBuilderFactory { 058 059 private final DocumentBuilderFactory delegate; 060 061 /** 062 * Constructs a new instance. 063 * 064 * @param delegate The delegate to wrap; must not be {@code null}. 065 * @throws NullPointerException Thrown if {@code delegate} is {@code null}. 066 */ 067 private Wrapper(final DocumentBuilderFactory delegate) { 068 this.delegate = Objects.requireNonNull(delegate, "delegate"); 069 } 070 071 @Override 072 public Object getAttribute(final String name) { 073 return delegate.getAttribute(name); 074 } 075 076 @Override 077 public boolean getFeature(final String name) throws ParserConfigurationException { 078 return delegate.getFeature(name); 079 } 080 081 @Override 082 public Schema getSchema() { 083 return delegate.getSchema(); 084 } 085 086 @Override 087 public boolean isCoalescing() { 088 return delegate.isCoalescing(); 089 } 090 091 @Override 092 public boolean isExpandEntityReferences() { 093 return delegate.isExpandEntityReferences(); 094 } 095 096 @Override 097 public boolean isIgnoringComments() { 098 return delegate.isIgnoringComments(); 099 } 100 101 @Override 102 public boolean isIgnoringElementContentWhitespace() { 103 return delegate.isIgnoringElementContentWhitespace(); 104 } 105 106 @Override 107 public boolean isNamespaceAware() { 108 return delegate.isNamespaceAware(); 109 } 110 111 @Override 112 public boolean isValidating() { 113 return delegate.isValidating(); 114 } 115 116 @Override 117 public boolean isXIncludeAware() { 118 return delegate.isXIncludeAware(); 119 } 120 121 @Override 122 public DocumentBuilder newDocumentBuilder() throws ParserConfigurationException { 123 return new SecureDocumentBuilder(delegate.newDocumentBuilder()); 124 } 125 126 @Override 127 public void setAttribute(final String name, final Object value) { 128 delegate.setAttribute(name, value); 129 } 130 131 @Override 132 public void setCoalescing(final boolean coalescing) { 133 delegate.setCoalescing(coalescing); 134 } 135 136 @Override 137 public void setExpandEntityReferences(final boolean expandEntityRef) { 138 delegate.setExpandEntityReferences(expandEntityRef); 139 } 140 141 @Override 142 public void setFeature(final String name, final boolean value) throws ParserConfigurationException { 143 delegate.setFeature(name, value); 144 } 145 146 @Override 147 public void setIgnoringComments(final boolean ignoreComments) { 148 delegate.setIgnoringComments(ignoreComments); 149 } 150 151 @Override 152 public void setIgnoringElementContentWhitespace(final boolean whitespace) { 153 delegate.setIgnoringElementContentWhitespace(whitespace); 154 } 155 156 @Override 157 public void setNamespaceAware(final boolean awareness) { 158 delegate.setNamespaceAware(awareness); 159 } 160 161 @Override 162 public void setSchema(final Schema schema) { 163 delegate.setSchema(schema); 164 } 165 166 @Override 167 public void setValidating(final boolean validating) { 168 delegate.setValidating(validating); 169 } 170 171 @Override 172 public void setXIncludeAware(final boolean state) { 173 delegate.setXIncludeAware(state); 174 } 175 } 176 /** 177 * Class name of Android's Harmony-based {@link DocumentBuilderFactory}, which exposes no secure surface. 178 */ 179 private static final String ANDROID_DOCUMENT_BUILDER_FACTORY = "org.apache.harmony.xml.parsers.DocumentBuilderFactoryImpl"; 180 /** 181 * System property naming the {@link DocumentBuilderFactory} implementation, the JDK's own mechanism for reconfiguring the default parser. 182 */ 183 private static final String DOM_FACTORY_ID = "javax.xml.parsers.DocumentBuilderFactory"; 184 185 /** 186 * Class name of the JDK's built-in default implementation, the Java 8 fallback for {@link #newDefaultInstance()}. 187 */ 188 static final String JDK_DOCUMENT_BUILDER_FACTORY = "com.sun.org.apache.xerces.internal.jaxp.DocumentBuilderFactoryImpl"; 189 190 private static final MethodHandle MH_newDefaultInstance = MethodHandleFactory.findStatic(DocumentBuilderFactory.class, "newDefaultInstance"); 191 192 /** 193 * Enables namespace awareness on the given factory; the {@code NSInstance} counterpart of each factory method routes its result through here. 194 * 195 * @param factory The factory to configure; never {@code null}. 196 * @return The given factory, namespace-aware. 197 */ 198 private static DocumentBuilderFactory makeNSAware(final DocumentBuilderFactory factory) { 199 factory.setNamespaceAware(true); 200 return factory; 201 } 202 203 /** 204 * Returns a new, secure {@link DocumentBuilderFactory} of the system-default implementation. 205 * <p> 206 * Obtained from {@code DocumentBuilderFactory.newDefaultInstance()} where the platform provides it (Java 9 or later), 207 * by instantiating the JDK's built-in implementation directly on Java 8, 208 * and by the standard {@link #newInstance()} lookup where the platform provides neither 209 * (for example, Android, whose lookup is itself pinned to the platform implementation). 210 * </p> 211 * 212 * @return A secure factory. 213 * @throws IllegalStateException Thrown if a required secure setting cannot be applied to the underlying implementation. 214 * @throws FactoryConfigurationError Thrown from the {@link #newInstance()} lookup this method falls back to on a platform that provides neither 215 * {@code newDefaultInstance()} nor the JDK's built-in implementation (for example, Android). 216 */ 217 public static DocumentBuilderFactory newDefaultInstance() { 218 if (MH_newDefaultInstance != null) { 219 return secure(MethodHandleFactory.invokeExact(() -> (DocumentBuilderFactory) MH_newDefaultInstance.invokeExact(), FactoryConfigurationError.class)); 220 } 221 try { 222 // Java 8: the method does not exist; instantiate the JDK's built-in default by its class name instead. 223 // Load it from the system class loader: the context class loader may not see it (for example an OSGi bundle class loader). 224 return newInstance(JDK_DOCUMENT_BUILDER_FACTORY, ClassLoader.getSystemClassLoader()); 225 } catch (final FactoryConfigurationError e) { 226 // Neither exists (for example, Android): degrade to the regular lookup, which such platforms pin to their built-in parser. 227 return newInstance(); 228 } 229 } 230 231 /** 232 * Returns a new, secure, namespace-aware {@link DocumentBuilderFactory} of the system-default implementation, enabling namespace awareness on 233 * {@link #newDefaultInstance()}, the behavior {@code DocumentBuilderFactory.newDefaultNSInstance()} (Java 13 or later) is specified to have. 234 * 235 * @return A secure, namespace-aware factory. 236 * @throws IllegalStateException Thrown if a required secure setting cannot be applied to the underlying implementation. 237 * @throws FactoryConfigurationError Thrown from the {@link #newInstance()} lookup {@link #newDefaultInstance()} falls back to on a platform that provides 238 * neither {@code newDefaultInstance()} nor the JDK's built-in implementation (for example, Android). 239 */ 240 public static DocumentBuilderFactory newDefaultNSInstance() { 241 return makeNSAware(newDefaultInstance()); 242 } 243 244 /** 245 * Returns a new, secure {@link DocumentBuilderFactory}. 246 * 247 * @return A secure factory. 248 * @throws IllegalStateException Thrown if a required secure setting cannot be applied to the underlying implementation. 249 * @throws IllegalStateException Thrown if a (non-Android) factory cannot support the secure processing feature 250 * {@link XMLConstants#FEATURE_SECURE_PROCESSING}. 251 * @throws FactoryConfigurationError Thrown from a factory in case of a {@link java.util.ServiceConfigurationError service configuration error} or if the 252 * implementation is not available or cannot be instantiated. 253 */ 254 public static DocumentBuilderFactory newInstance() { 255 return secure(DocumentBuilderFactory.newInstance()); 256 } 257 258 /** 259 * Returns a new, secure {@link DocumentBuilderFactory} of the given implementation class. 260 * 261 * @param factoryClassName The fully qualified class name of the {@link DocumentBuilderFactory} implementation. 262 * @param classLoader The class loader used to load the factory class; {@code null} means the current thread's context class loader. 263 * @return A secure factory. 264 * @throws IllegalStateException Thrown if a required secure setting cannot be applied to the underlying implementation. 265 * @throws IllegalStateException Thrown if a (non-Android) factory cannot support the secure processing feature 266 * {@link XMLConstants#FEATURE_SECURE_PROCESSING}. 267 * @throws FactoryConfigurationError Thrown if {@code factoryClassName} is {@code null} or the factory class cannot be loaded or instantiated. 268 */ 269 public static DocumentBuilderFactory newInstance(final String factoryClassName, final ClassLoader classLoader) { 270 return secure(DocumentBuilderFactory.newInstance(factoryClassName, classLoader)); 271 } 272 273 /** 274 * Creates a new, secure, namespace-aware {@link DocumentBuilder} from {@link #newNSInstance()}. 275 * <p> 276 * No factory is cached: each call configures a fresh one. To parse many documents, keep the returned builder and call {@link DocumentBuilder#reset()} 277 * between documents. Reusing the builder saves more than caching the factory would, and {@code reset()} costs next to nothing while keeping handler state 278 * from leaking between parses. A builder is not thread-safe, so reuse it within one thread. 279 * </p> 280 * 281 * @return A secure, namespace-aware builder. 282 * @throws IllegalStateException Thrown if a required secure setting cannot be applied to the underlying implementation, or if the implementation cannot 283 * create a builder. 284 * @throws FactoryConfigurationError Thrown from a factory in case of a {@link java.util.ServiceConfigurationError service configuration error} or if the 285 * implementation is not available or cannot be instantiated. 286 * @since 1.1.0 287 */ 288 public static DocumentBuilder newNSDocumentBuilder() { 289 try { 290 return newNSInstance().newDocumentBuilder(); 291 } catch (final ParserConfigurationException e) { 292 // Implementations reject settings when they are set on the factory, not here: a failure means a broken environment. 293 throw SecureException.creationFailed(DocumentBuilder.class, e); 294 } 295 } 296 297 /** 298 * Returns a new, secure, namespace-aware {@link DocumentBuilderFactory}, enabling namespace awareness on {@link #newInstance()}, the behavior 299 * {@code DocumentBuilderFactory.newNSInstance()} (Java 13 or later) is specified to have. 300 * 301 * @return A secure, namespace-aware factory. 302 * @throws IllegalStateException Thrown if a required secure setting cannot be applied to the underlying implementation. 303 * @throws FactoryConfigurationError Thrown from a factory in case of a {@link java.util.ServiceConfigurationError service configuration error} or if the 304 * implementation is not available or cannot be instantiated. 305 */ 306 public static DocumentBuilderFactory newNSInstance() { 307 return makeNSAware(newInstance()); 308 } 309 310 /** 311 * Returns the secure, namespace-aware factory the Source-rewriting wrappers parse with. 312 * <p> 313 * While {@code overrideDefaultParser} is {@code false}, the factory is the JDK's "default parser" factory, determined the way the JDK itself determines it: 314 * the built-in implementation, unless the {@value #DOM_FACTORY_ID} system property is set. That property is the JDK's own mechanism for 315 * reconfiguring the default parser, so it is honored through the standard lookup rather than bypassed. 316 * </p> 317 * 318 * @param overrideDefaultParser whether {@value SecureSAXParserFactory#OVERRIDE_DEFAULT_PARSER} on the originating factory asks to override the JDK's 319 * default parser. 320 * @return A secure, namespace-aware factory. 321 * @throws IllegalStateException Thrown if a required secure setting cannot be applied to the underlying implementation. 322 * @throws FactoryConfigurationError Thrown from a factory in case of a {@link java.util.ServiceConfigurationError service configuration error} or if the 323 * implementation is not available or cannot be instantiated. 324 */ 325 static DocumentBuilderFactory newNSInstance(final boolean overrideDefaultParser) { 326 return overrideDefaultParser || System.getProperty(DOM_FACTORY_ID) != null ? newNSInstance() : newDefaultNSInstance(); 327 } 328 329 /** 330 * Returns a new, secure, namespace-aware {@link DocumentBuilderFactory} of the given implementation class, enabling namespace awareness on 331 * {@link #newInstance(String, ClassLoader)}, the behavior {@code DocumentBuilderFactory.newNSInstance(String, ClassLoader)} (Java 13 or later) is specified 332 * to have. 333 * 334 * @param factoryClassName The fully qualified class name of the {@link DocumentBuilderFactory} implementation. 335 * @param classLoader The class loader used to load the factory class; {@code null} means the current thread's context class loader. 336 * @return A secure, namespace-aware factory. 337 * @throws IllegalStateException Thrown if a required secure setting cannot be applied to the underlying implementation. 338 * @throws FactoryConfigurationError Thrown if {@code factoryClassName} is {@code null} or the factory class cannot be loaded or instantiated. 339 */ 340 public static DocumentBuilderFactory newNSInstance(final String factoryClassName, final ClassLoader classLoader) { 341 return makeNSAware(newInstance(factoryClassName, classLoader)); 342 } 343 344 /** 345 * Applies capability-driven secure settings to any {@link DocumentBuilderFactory} on the classpath. 346 * 347 * <p> 348 * Rather than branching on the implementation class, this method probes what the factory supports and adapts: 349 * </p> 350 * <ul> 351 * <li><strong>Android</strong> (Harmony / KXmlParser): recognized by class name and left untouched. It exposes no {@link 352 * XMLConstants#FEATURE_SECURE_PROCESSING 353 * FSP}, no JAXP 1.5 {@code ACCESS_EXTERNAL_*} and no attribute API at all, while KXmlParser silently drops user-defined entities, so there is nothing to 354 * apply.</li> 355 * <li><strong>FSP</strong>: required. It switches on the implementation's built-in security manager, which is what carries the processing limits.</li> 356 * <li><strong>Ignore-all resolver floor</strong>: every produced {@link DocumentBuilder} is wrapped by the nested wrapper, which keeps an 357 * ignore-all {@link EntityResolver} floor. That floor blocks external DTD, entity, schema and {@code xi:include} fetches in one place: the stock JDK's 358 * XInclude processor ignores {@code ACCESS_EXTERNAL_*} and consults the {@link EntityResolver} instead, so no {@code ACCESS_EXTERNAL_*} attributes are 359 * needed here. A caller can chain its own resolver onto the floor to allow-list resources, but cannot remove it.</li> 360 * </ul> 361 * 362 * @param factory The factory to secure. 363 * @return A new secure factory or the original factory, as-is, if it is a known Android factory. 364 * @throws SecureException Thrown if a (non-Android) factory cannot support the secure processing feature {@link XMLConstants#FEATURE_SECURE_PROCESSING}. 365 */ 366 static DocumentBuilderFactory secure(final DocumentBuilderFactory factory) { 367 // Android exposes no FSP, ACCESS_EXTERNAL_* or attribute API, and KXmlParser drops user-defined entities; nothing to apply. 368 if (ANDROID_DOCUMENT_BUILDER_FACTORY.equals(factory.getClass().getName())) { 369 return factory; 370 } 371 // Required: enables the implementation's security manager, which carries the limits. 372 setFeature(factory, XMLConstants.FEATURE_SECURE_PROCESSING, true); 373 // Required: the wrapper installs an ignore-all EntityResolver floor on every DocumentBuilder. 374 // That floor blocks external DTD, entity, schema and xi:include fetches in one place: no ACCESS_EXTERNAL_* attributes are needed here. 375 // Callers can chain their resolvers, but not override the floor. 376 return new Wrapper(factory); 377 } 378 379 /** 380 * Sets a feature on the given factory, throwing a {@link SecureException} if the implementation does not recognize it. 381 * 382 * @param factory The factory to secure. 383 * @param feature The feature to set. 384 * @param value The value to set. 385 * @throws SecureException Thrown if this factory cannot support this feature. 386 * @throws NullPointerException Thrown if the {@code feature} parameter is null. 387 */ 388 private static void setFeature(final DocumentBuilderFactory factory, final String feature, final boolean value) { 389 try { 390 factory.setFeature(feature, value); 391 } catch (final ParserConfigurationException e) { 392 throw SecureException.featureFailed(feature, factory, e); 393 } 394 } 395 396 private SecureDocumentBuilderFactory() { 397 // static only 398 } 399}