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;
  53         State(String literal) {
  54             this.literal = literal;
  55         }
  56 
  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 
  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 
 100         public boolean equalsAPIPropertyName(String propertyName) {
 101             return (propertyName == null) ? false : apiProperty.equals(propertyName);
 102         }
 103 
 104         public boolean equalsSystemPropertyName(String propertyName) {
 105             return (propertyName == null) ? false : systemProperty.equals(propertyName);
 106         }
 107 
 108         public String key() {
 109             return key;
 110         }
 111 
 112         public String apiProperty() {
 113             return apiProperty;
 114         }
 115 
 116         String systemProperty() {
 117             return systemProperty;
 118         }
 119 
 120         public int defaultValue() {
 121             return defaultValue;
 122         }
 123 
 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 
 140         NameMap(String newName, String oldName) {
 141             this.newName = newName;
 142             this.oldName = oldName;
 143         }
 144 
 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      */
 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      */
 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      */
 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      */
 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      */
 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      */
 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      */
 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      */
 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      */
 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      */
 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      */
 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      */
 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      */
 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      */
 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      */
 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      */
 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      */
 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      */
 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      */
 444     public boolean isOverLimit(Limit limit, XMLLimitAnalyzer limitAnalyzer) {
 445         return isOverLimit(limit.ordinal(), limitAnalyzer);
 446     }
 447 
 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 
 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      */
 478     public boolean isSet(int index) {
 479         return isSet[index];
 480     }
 481 
 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      */
 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      */
 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      */
 528     private boolean getSystemProperty(Limit limit, String sysPropertyName) {
 529         try {
 530             String value = SecuritySupport.getSystemProperty(sysPropertyName);
 531             if (value != null && !value.isEmpty()) {
 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.isEmpty()) {
 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      */
 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 }