1 /*
2  * Copyright (c) 2013, 2017, Oracle and/or its affiliates. All rights reserved.
3  * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
4  *
5  * This code is free software; you can redistribute it and/or modify it
6  * under the terms of the GNU General Public License version 2 only, as
7  * published by the Free Software Foundation.  Oracle designates this
8  * particular file as subject to the "Classpath" exception as provided
9  * by Oracle in the LICENSE file that accompanied this code.
10  *
11  * This code is distributed in the hope that it will be useful, but WITHOUT
12  * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
13  * FITNESS FOR A PARTICULAR PURPOSE.  See the GNU General Public License
14  * version 2 for more details (a copy is included in the LICENSE file that
15  * accompanied this code).
16  *
17  * You should have received a copy of the GNU General Public License version
18  * 2 along with this work; if not, write to the Free Software Foundation,
19  * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
20  *
21  * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
22  * or visit www.oracle.com if you need additional information or have any
23  * questions.
24  */
25 
26 package com.sun.org.apache.xerces.internal.utils;
27 
28 import com.sun.org.apache.xerces.internal.impl.Constants;
29 import com.sun.org.apache.xerces.internal.util.SecurityManager;
30 import java.util.concurrent.CopyOnWriteArrayList;
31 import jdk.xml.internal.SecuritySupport;
32 import org.xml.sax.SAXException;
33 
34 /**
35  * This class manages standard and implementation-specific limitations.
36  *
37  */
38 public final class XMLSecurityManager {
39 
40     /**
41      * States of the settings of a property, in the order: default value, value
42      * set by FEATURE_SECURE_PROCESSING, jaxp.properties file, jaxp system
43      * properties, and jaxp api properties
44      */
45     public static enum State {
46         //this order reflects the overriding order
47 
48         DEFAULT("default"), FSP("FEATURE_SECURE_PROCESSING"),
49         JAXPDOTPROPERTIES("jaxp.properties"), SYSTEMPROPERTY("system property"),
50         APIPROPERTY("property");
51 
52         final String literal;
State(String literal)53         State(String literal) {
54             this.literal = literal;
55         }
56 
literal()57         String literal() {
58             return literal;
59         }
60     }
61 
62     /**
63      * Limits managed by the security manager
64      */
65     public static enum Limit {
66 
67         ENTITY_EXPANSION_LIMIT("EntityExpansionLimit",
68                 Constants.JDK_ENTITY_EXPANSION_LIMIT, Constants.SP_ENTITY_EXPANSION_LIMIT, 0, 64000),
69         MAX_OCCUR_NODE_LIMIT("MaxOccurLimit",
70                 Constants.JDK_MAX_OCCUR_LIMIT, Constants.SP_MAX_OCCUR_LIMIT, 0, 5000),
71         ELEMENT_ATTRIBUTE_LIMIT("ElementAttributeLimit",
72                 Constants.JDK_ELEMENT_ATTRIBUTE_LIMIT, Constants.SP_ELEMENT_ATTRIBUTE_LIMIT, 0, 10000),
73         TOTAL_ENTITY_SIZE_LIMIT("TotalEntitySizeLimit",
74                 Constants.JDK_TOTAL_ENTITY_SIZE_LIMIT, Constants.SP_TOTAL_ENTITY_SIZE_LIMIT, 0, 50000000),
75         GENERAL_ENTITY_SIZE_LIMIT("MaxEntitySizeLimit",
76                 Constants.JDK_GENERAL_ENTITY_SIZE_LIMIT, Constants.SP_GENERAL_ENTITY_SIZE_LIMIT, 0, 0),
77         PARAMETER_ENTITY_SIZE_LIMIT("MaxEntitySizeLimit",
78                 Constants.JDK_PARAMETER_ENTITY_SIZE_LIMIT, Constants.SP_PARAMETER_ENTITY_SIZE_LIMIT, 0, 1000000),
79         MAX_ELEMENT_DEPTH_LIMIT("MaxElementDepthLimit",
80                 Constants.JDK_MAX_ELEMENT_DEPTH, Constants.SP_MAX_ELEMENT_DEPTH, 0, 0),
81         MAX_NAME_LIMIT("MaxXMLNameLimit",
82                 Constants.JDK_XML_NAME_LIMIT, Constants.SP_XML_NAME_LIMIT, 1000, 1000),
83         ENTITY_REPLACEMENT_LIMIT("EntityReplacementLimit",
84                 Constants.JDK_ENTITY_REPLACEMENT_LIMIT, Constants.SP_ENTITY_REPLACEMENT_LIMIT, 0, 3000000);
85 
86         final String key;
87         final String apiProperty;
88         final String systemProperty;
89         final int defaultValue;
90         final int secureValue;
91 
Limit(String key, String apiProperty, String systemProperty, int value, int secureValue)92         Limit(String key, String apiProperty, String systemProperty, int value, int secureValue) {
93             this.key = key;
94             this.apiProperty = apiProperty;
95             this.systemProperty = systemProperty;
96             this.defaultValue = value;
97             this.secureValue = secureValue;
98         }
99 
equalsAPIPropertyName(String propertyName)100         public boolean equalsAPIPropertyName(String propertyName) {
101             return (propertyName == null) ? false : apiProperty.equals(propertyName);
102         }
103 
equalsSystemPropertyName(String propertyName)104         public boolean equalsSystemPropertyName(String propertyName) {
105             return (propertyName == null) ? false : systemProperty.equals(propertyName);
106         }
107 
key()108         public String key() {
109             return key;
110         }
111 
apiProperty()112         public String apiProperty() {
113             return apiProperty;
114         }
115 
systemProperty()116         String systemProperty() {
117             return systemProperty;
118         }
119 
defaultValue()120         public int defaultValue() {
121             return defaultValue;
122         }
123 
secureValue()124         int secureValue() {
125             return secureValue;
126         }
127     }
128 
129     /**
130      * Map old property names with the new ones
131      */
132     public static enum NameMap {
133 
134         ENTITY_EXPANSION_LIMIT(Constants.SP_ENTITY_EXPANSION_LIMIT, Constants.ENTITY_EXPANSION_LIMIT),
135         MAX_OCCUR_NODE_LIMIT(Constants.SP_MAX_OCCUR_LIMIT, Constants.MAX_OCCUR_LIMIT),
136         ELEMENT_ATTRIBUTE_LIMIT(Constants.SP_ELEMENT_ATTRIBUTE_LIMIT, Constants.ELEMENT_ATTRIBUTE_LIMIT);
137         final String newName;
138         final String oldName;
139 
NameMap(String newName, String oldName)140         NameMap(String newName, String oldName) {
141             this.newName = newName;
142             this.oldName = oldName;
143         }
144 
getOldName(String newName)145         String getOldName(String newName) {
146             if (newName.equals(this.newName)) {
147                 return oldName;
148             }
149             return null;
150         }
151     }
152     private static final int NO_LIMIT = 0;
153     /**
154      * Values of the properties
155      */
156     private final int[] values;
157     /**
158      * States of the settings for each property
159      */
160     private State[] states;
161     /**
162      * Flag indicating if secure processing is set
163      */
164     boolean secureProcessing;
165 
166     /**
167      * States that determine if properties are set explicitly
168      */
169     private boolean[] isSet;
170 
171 
172     /**
173      * Index of the special entityCountInfo property
174      */
175     private final int indexEntityCountInfo = 10000;
176     private String printEntityCountInfo = "";
177 
178     /**
179      * Default constructor. Establishes default values for known security
180      * vulnerabilities.
181      */
XMLSecurityManager()182     public XMLSecurityManager() {
183         this(false);
184     }
185 
186     /**
187      * Instantiate Security Manager in accordance with the status of
188      * secure processing
189      * @param secureProcessing
190      */
XMLSecurityManager(boolean secureProcessing)191     public XMLSecurityManager(boolean secureProcessing) {
192         values = new int[Limit.values().length];
193         states = new State[Limit.values().length];
194         isSet = new boolean[Limit.values().length];
195         this.secureProcessing = secureProcessing;
196         for (Limit limit : Limit.values()) {
197             if (secureProcessing) {
198                 values[limit.ordinal()] = limit.secureValue;
199                 states[limit.ordinal()] = State.FSP;
200             } else {
201                 values[limit.ordinal()] = limit.defaultValue();
202                 states[limit.ordinal()] = State.DEFAULT;
203             }
204         }
205         //read system properties or jaxp.properties
206         readSystemProperties();
207     }
208 
209     /**
210      * Setting FEATURE_SECURE_PROCESSING explicitly
211      */
setSecureProcessing(boolean secure)212     public void setSecureProcessing(boolean secure) {
213         secureProcessing = secure;
214         for (Limit limit : Limit.values()) {
215             if (secure) {
216                 setLimit(limit.ordinal(), State.FSP, limit.secureValue());
217             } else {
218                 setLimit(limit.ordinal(), State.FSP, limit.defaultValue());
219             }
220         }
221     }
222 
223     /**
224      * Return the state of secure processing
225      * @return the state of secure processing
226      */
isSecureProcessing()227     public boolean isSecureProcessing() {
228         return secureProcessing;
229     }
230 
231 
232     /**
233      * Set limit by property name and state
234      * @param propertyName property name
235      * @param state the state of the property
236      * @param value the value of the property
237      * @return true if the property is managed by the security manager; false
238      *              if otherwise.
239      */
setLimit(String propertyName, State state, Object value)240     public boolean setLimit(String propertyName, State state, Object value) {
241         int index = getIndex(propertyName);
242         if (index > -1) {
243             setLimit(index, state, value);
244             return true;
245         }
246         return false;
247     }
248 
249     /**
250      * Set the value for a specific limit.
251      *
252      * @param limit the limit
253      * @param state the state of the property
254      * @param value the value of the property
255      */
setLimit(Limit limit, State state, int value)256     public void setLimit(Limit limit, State state, int value) {
257         setLimit(limit.ordinal(), state, value);
258     }
259 
260     /**
261      * Set the value of a property by its index
262      *
263      * @param index the index of the property
264      * @param state the state of the property
265      * @param value the value of the property
266      */
setLimit(int index, State state, Object value)267     public void setLimit(int index, State state, Object value) {
268         if (index == indexEntityCountInfo) {
269             printEntityCountInfo = (String)value;
270         } else {
271             int temp;
272             if (Integer.class.isAssignableFrom(value.getClass())) {
273                 temp = ((Integer)value).intValue();
274             } else {
275                 temp = Integer.parseInt((String) value);
276                 if (temp < 0) {
277                     temp = 0;
278                 }
279             }
280             setLimit(index, state, temp);
281         }
282     }
283 
284     /**
285      * Set the value of a property by its index
286      *
287      * @param index the index of the property
288      * @param state the state of the property
289      * @param value the value of the property
290      */
setLimit(int index, State state, int value)291     public void setLimit(int index, State state, int value) {
292         if (index == indexEntityCountInfo) {
293             //if it's explicitly set, it's treated as yes no matter the value
294             printEntityCountInfo = Constants.JDK_YES;
295         } else {
296             //only update if it shall override
297             if (state.compareTo(states[index]) >= 0) {
298                 values[index] = value;
299                 states[index] = state;
300                 isSet[index] = true;
301             }
302         }
303     }
304 
305     /**
306      * Return the value of the specified property
307      *
308      * @param propertyName the property name
309      * @return the value of the property as a string. If a property is managed
310      * by this manager, its value shall not be null.
311      */
getLimitAsString(String propertyName)312     public String getLimitAsString(String propertyName) {
313         int index = getIndex(propertyName);
314         if (index > -1) {
315             return getLimitValueByIndex(index);
316         }
317 
318         return null;
319     }
320     /**
321      * Return the value of the specified property
322      *
323      * @param limit the property
324      * @return the value of the property
325      */
getLimit(Limit limit)326     public int getLimit(Limit limit) {
327         return values[limit.ordinal()];
328     }
329 
330     /**
331      * Return the value of a property by its ordinal
332      *
333      * @param limit the property
334      * @return value of a property
335      */
getLimitValueAsString(Limit limit)336     public String getLimitValueAsString(Limit limit) {
337         return Integer.toString(values[limit.ordinal()]);
338     }
339 
340     /**
341      * Return the value of a property by its ordinal
342      *
343      * @param index the index of a property
344      * @return limit of a property as a string
345      */
getLimitValueByIndex(int index)346     public String getLimitValueByIndex(int index) {
347         if (index == indexEntityCountInfo) {
348             return printEntityCountInfo;
349         }
350 
351         return Integer.toString(values[index]);
352     }
353 
354     /**
355      * Return the state of the limit property
356      *
357      * @param limit the limit
358      * @return the state of the limit property
359      */
getState(Limit limit)360     public State getState(Limit limit) {
361         return states[limit.ordinal()];
362     }
363 
364     /**
365      * Return the state of the limit property
366      *
367      * @param limit the limit
368      * @return the state of the limit property
369      */
getStateLiteral(Limit limit)370     public String getStateLiteral(Limit limit) {
371         return states[limit.ordinal()].literal();
372     }
373 
374     /**
375      * Get the index by property name
376      *
377      * @param propertyName property name
378      * @return the index of the property if found; return -1 if not
379      */
getIndex(String propertyName)380     public int getIndex(String propertyName) {
381         for (Limit limit : Limit.values()) {
382             if (limit.equalsAPIPropertyName(propertyName)) {
383                 //internally, ordinal is used as index
384                 return limit.ordinal();
385             }
386         }
387         //special property to return entity count info
388         if (propertyName.equals(Constants.JDK_ENTITY_COUNT_INFO)) {
389             return indexEntityCountInfo;
390         }
391         return -1;
392     }
393 
394     /**
395      * Check if there's no limit defined by the Security Manager
396      * @param limit
397      * @return
398      */
isNoLimit(int limit)399     public boolean isNoLimit(int limit) {
400         return limit==NO_LIMIT;
401     }
402     /**
403      * Check if the size (length or count) of the specified limit property is
404      * over the limit
405      *
406      * @param limit the type of the limit property
407      * @param entityName the name of the entity
408      * @param size the size (count or length) of the entity
409      * @return true if the size is over the limit, false otherwise
410      */
isOverLimit(Limit limit, String entityName, int size, XMLLimitAnalyzer limitAnalyzer)411     public boolean isOverLimit(Limit limit, String entityName, int size,
412             XMLLimitAnalyzer limitAnalyzer) {
413         return isOverLimit(limit.ordinal(), entityName, size, limitAnalyzer);
414     }
415 
416     /**
417      * Check if the value (length or count) of the specified limit property is
418      * over the limit
419      *
420      * @param index the index of the limit property
421      * @param entityName the name of the entity
422      * @param size the size (count or length) of the entity
423      * @return true if the size is over the limit, false otherwise
424      */
isOverLimit(int index, String entityName, int size, XMLLimitAnalyzer limitAnalyzer)425     public boolean isOverLimit(int index, String entityName, int size,
426             XMLLimitAnalyzer limitAnalyzer) {
427         if (values[index] == NO_LIMIT) {
428             return false;
429         }
430         if (size > values[index]) {
431             limitAnalyzer.addValue(index, entityName, size);
432             return true;
433         }
434         return false;
435     }
436 
437     /**
438      * Check against cumulated value
439      *
440      * @param limit the type of the limit property
441      * @param size the size (count or length) of the entity
442      * @return true if the size is over the limit, false otherwise
443      */
isOverLimit(Limit limit, XMLLimitAnalyzer limitAnalyzer)444     public boolean isOverLimit(Limit limit, XMLLimitAnalyzer limitAnalyzer) {
445         return isOverLimit(limit.ordinal(), limitAnalyzer);
446     }
447 
isOverLimit(int index, XMLLimitAnalyzer limitAnalyzer)448     public boolean isOverLimit(int index, XMLLimitAnalyzer limitAnalyzer) {
449         if (values[index] == NO_LIMIT) {
450             return false;
451         }
452 
453         if (index == Limit.ELEMENT_ATTRIBUTE_LIMIT.ordinal() ||
454                 index == Limit.ENTITY_EXPANSION_LIMIT.ordinal() ||
455                 index == Limit.TOTAL_ENTITY_SIZE_LIMIT.ordinal() ||
456                 index == Limit.ENTITY_REPLACEMENT_LIMIT.ordinal() ||
457                 index == Limit.MAX_ELEMENT_DEPTH_LIMIT.ordinal() ||
458                 index == Limit.MAX_NAME_LIMIT.ordinal()
459                 ) {
460             return (limitAnalyzer.getTotalValue(index) > values[index]);
461         } else {
462             return (limitAnalyzer.getValue(index) > values[index]);
463         }
464     }
465 
debugPrint(XMLLimitAnalyzer limitAnalyzer)466     public void debugPrint(XMLLimitAnalyzer limitAnalyzer) {
467         if (printEntityCountInfo.equals(Constants.JDK_YES)) {
468             limitAnalyzer.debugPrint(this);
469         }
470     }
471 
472 
473     /**
474      * Indicate if a property is set explicitly
475      * @param index
476      * @return
477      */
isSet(int index)478     public boolean isSet(int index) {
479         return isSet[index];
480     }
481 
printEntityCountInfo()482     public boolean printEntityCountInfo() {
483         return printEntityCountInfo.equals(Constants.JDK_YES);
484     }
485 
486     /**
487      * Read from system properties, or those in jaxp.properties
488      */
readSystemProperties()489     private void readSystemProperties() {
490 
491         for (Limit limit : Limit.values()) {
492             if (!getSystemProperty(limit, limit.systemProperty())) {
493                 //if system property is not found, try the older form if any
494                 for (NameMap nameMap : NameMap.values()) {
495                     String oldName = nameMap.getOldName(limit.systemProperty());
496                     if (oldName != null) {
497                         getSystemProperty(limit, oldName);
498                     }
499                 }
500             }
501         }
502 
503     }
504 
505     // Array list to store printed warnings for each SAX parser used
506     private static final CopyOnWriteArrayList<String> printedWarnings = new CopyOnWriteArrayList<>();
507 
508     /**
509      * Prints out warnings if a parser does not support the specified feature/property.
510      *
511      * @param parserClassName the name of the parser class
512      * @param propertyName the property name
513      * @param exception the exception thrown by the parser
514      */
printWarning(String parserClassName, String propertyName, SAXException exception)515     public static void printWarning(String parserClassName, String propertyName, SAXException exception) {
516         String key = parserClassName+":"+propertyName;
517         if (printedWarnings.addIfAbsent(key)) {
518             System.err.println( "Warning: "+parserClassName+": "+exception.getMessage());
519         }
520     }
521 
522     /**
523      * Read from system properties, or those in jaxp.properties
524      *
525      * @param property the type of the property
526      * @param sysPropertyName the name of system property
527      */
getSystemProperty(Limit limit, String sysPropertyName)528     private boolean getSystemProperty(Limit limit, String sysPropertyName) {
529         try {
530             String value = SecuritySupport.getSystemProperty(sysPropertyName);
531             if (value != null && !value.equals("")) {
532                 values[limit.ordinal()] = Integer.parseInt(value);
533                 states[limit.ordinal()] = State.SYSTEMPROPERTY;
534                 return true;
535             }
536 
537             value = SecuritySupport.readJAXPProperty(sysPropertyName);
538             if (value != null && !value.equals("")) {
539                 values[limit.ordinal()] = Integer.parseInt(value);
540                 states[limit.ordinal()] = State.JAXPDOTPROPERTIES;
541                 return true;
542             }
543         } catch (NumberFormatException e) {
544             //invalid setting
545             throw new NumberFormatException("Invalid setting for system property: " + limit.systemProperty());
546         }
547         return false;
548     }
549 
550 
551     /**
552      * Convert a value set through setProperty to XMLSecurityManager.
553      * If the value is an instance of XMLSecurityManager, use it to override the default;
554      * If the value is an old SecurityManager, convert to the new XMLSecurityManager.
555      *
556      * @param value user specified security manager
557      * @param securityManager an instance of XMLSecurityManager
558      * @return an instance of the new security manager XMLSecurityManager
559      */
convert(Object value, XMLSecurityManager securityManager)560     static public XMLSecurityManager convert(Object value, XMLSecurityManager securityManager) {
561         if (value == null) {
562             if (securityManager == null) {
563                 securityManager = new XMLSecurityManager(true);
564             }
565             return securityManager;
566         }
567         if (XMLSecurityManager.class.isAssignableFrom(value.getClass())) {
568             return (XMLSecurityManager)value;
569         } else {
570             if (securityManager == null) {
571                 securityManager = new XMLSecurityManager(true);
572             }
573             if (SecurityManager.class.isAssignableFrom(value.getClass())) {
574                 SecurityManager origSM = (SecurityManager)value;
575                 securityManager.setLimit(Limit.MAX_OCCUR_NODE_LIMIT, State.APIPROPERTY, origSM.getMaxOccurNodeLimit());
576                 securityManager.setLimit(Limit.ENTITY_EXPANSION_LIMIT, State.APIPROPERTY, origSM.getEntityExpansionLimit());
577                 securityManager.setLimit(Limit.ELEMENT_ATTRIBUTE_LIMIT, State.APIPROPERTY, origSM.getElementAttrLimit());
578             }
579             return securityManager;
580         }
581     }
582 }
583