1 /* 2 * Copyright (c) 2016, 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 java.io; 27 28 import java.security.AccessController; 29 import java.security.PrivilegedAction; 30 import java.security.Security; 31 import java.util.ArrayList; 32 import java.util.List; 33 import java.util.Objects; 34 import java.util.Optional; 35 import java.util.function.Function; 36 37 38 /** 39 * Filter classes, array lengths, and graph metrics during deserialization. 40 * If set on an {@link ObjectInputStream}, the {@link #checkInput checkInput(FilterInfo)} 41 * method is called to validate classes, the length of each array, 42 * the number of objects being read from the stream, the depth of the graph, 43 * and the total number of bytes read from the stream. 44 * <p> 45 * A filter can be set via {@link ObjectInputStream#setObjectInputFilter setObjectInputFilter} 46 * for an individual ObjectInputStream. 47 * A filter can be set via {@link Config#setSerialFilter(ObjectInputFilter) Config.setSerialFilter} 48 * to affect every {@code ObjectInputStream} that does not otherwise set a filter. 49 * <p> 50 * A filter determines whether the arguments are {@link Status#ALLOWED ALLOWED} 51 * or {@link Status#REJECTED REJECTED} and should return the appropriate status. 52 * If the filter cannot determine the status it should return 53 * {@link Status#UNDECIDED UNDECIDED}. 54 * Filters should be designed for the specific use case and expected types. 55 * A filter designed for a particular use may be passed a class that is outside 56 * of the scope of the filter. If the purpose of the filter is to black-list classes 57 * then it can reject a candidate class that matches and report UNDECIDED for others. 58 * A filter may be called with class equals {@code null}, {@code arrayLength} equal -1, 59 * the depth, number of references, and stream size and return a status 60 * that reflects only one or only some of the values. 61 * This allows a filter to specific about the choice it is reporting and 62 * to use other filters without forcing either allowed or rejected status. 63 * 64 * <p> 65 * Typically, a custom filter should check if a process-wide filter 66 * is configured and defer to it if so. For example, 67 * <pre>{@code 68 * ObjectInputFilter.Status checkInput(FilterInfo info) { 69 * ObjectInputFilter serialFilter = ObjectInputFilter.Config.getSerialFilter(); 70 * if (serialFilter != null) { 71 * ObjectInputFilter.Status status = serialFilter.checkInput(info); 72 * if (status != ObjectInputFilter.Status.UNDECIDED) { 73 * // The process-wide filter overrides this filter 74 * return status; 75 * } 76 * } 77 * if (info.serialClass() != null && 78 * Remote.class.isAssignableFrom(info.serialClass())) { 79 * return Status.REJECTED; // Do not allow Remote objects 80 * } 81 * return Status.UNDECIDED; 82 * } 83 *}</pre> 84 * <p> 85 * Unless otherwise noted, passing a {@code null} argument to a 86 * method in this interface and its nested classes will cause a 87 * {@link NullPointerException} to be thrown. 88 * 89 * @see ObjectInputStream#setObjectInputFilter(ObjectInputFilter) 90 * @since 9 91 */ 92 @FunctionalInterface 93 public interface ObjectInputFilter { 94 95 /** 96 * Check the class, array length, number of object references, depth, 97 * stream size, and other available filtering information. 98 * Implementations of this method check the contents of the object graph being created 99 * during deserialization. The filter returns {@link Status#ALLOWED Status.ALLOWED}, 100 * {@link Status#REJECTED Status.REJECTED}, or {@link Status#UNDECIDED Status.UNDECIDED}. 101 * 102 * @param filterInfo provides information about the current object being deserialized, 103 * if any, and the status of the {@link ObjectInputStream} 104 * @return {@link Status#ALLOWED Status.ALLOWED} if accepted, 105 * {@link Status#REJECTED Status.REJECTED} if rejected, 106 * {@link Status#UNDECIDED Status.UNDECIDED} if undecided. 107 */ 108 Status checkInput(FilterInfo filterInfo); 109 110 /** 111 * FilterInfo provides access to information about the current object 112 * being deserialized and the status of the {@link ObjectInputStream}. 113 * @since 9 114 */ 115 interface FilterInfo { 116 /** 117 * The class of an object being deserialized. 118 * For arrays, it is the array type. 119 * For example, the array class name of a 2 dimensional array of strings is 120 * "{@code [[Ljava.lang.String;}". 121 * To check the array's element type, iteratively use 122 * {@link Class#getComponentType() Class.getComponentType} while the result 123 * is an array and then check the class. 124 * The {@code serialClass is null} in the case where a new object is not being 125 * created and to give the filter a chance to check the depth, number of 126 * references to existing objects, and the stream size. 127 * 128 * @return class of an object being deserialized; may be null 129 */ 130 Class<?> serialClass(); 131 132 /** 133 * The number of array elements when deserializing an array of the class. 134 * 135 * @return the non-negative number of array elements when deserializing 136 * an array of the class, otherwise -1 137 */ 138 long arrayLength(); 139 140 /** 141 * The current depth. 142 * The depth starts at {@code 1} and increases for each nested object and 143 * decrements when each nested object returns. 144 * 145 * @return the current depth 146 */ 147 long depth(); 148 149 /** 150 * The current number of object references. 151 * 152 * @return the non-negative current number of object references 153 */ 154 long references(); 155 156 /** 157 * The current number of bytes consumed. 158 * @implSpec {@code streamBytes} is implementation specific 159 * and may not be directly related to the object in the stream 160 * that caused the callback. 161 * 162 * @return the non-negative current number of bytes consumed 163 */ 164 long streamBytes(); 165 } 166 167 /** 168 * The status of a check on the class, array length, number of references, 169 * depth, and stream size. 170 * 171 * @since 9 172 */ 173 enum Status { 174 /** 175 * The status is undecided, not allowed and not rejected. 176 */ 177 UNDECIDED, 178 /** 179 * The status is allowed. 180 */ 181 ALLOWED, 182 /** 183 * The status is rejected. 184 */ 185 REJECTED; 186 } 187 188 /** 189 * A utility class to set and get the process-wide filter or create a filter 190 * from a pattern string. If a process-wide filter is set, it will be 191 * used for each {@link ObjectInputStream} that does not set its own filter. 192 * <p> 193 * When setting the filter, it should be stateless and idempotent, 194 * reporting the same result when passed the same arguments. 195 * <p> 196 * The filter is configured during the initialization of the {@code ObjectInputFilter.Config} 197 * class. For example, by calling {@link #getSerialFilter() Config.getSerialFilter}. 198 * If the system property {@code jdk.serialFilter} is defined, it is used 199 * to configure the filter. 200 * If the system property is not defined, and the {@link java.security.Security} 201 * property {@code jdk.serialFilter} is defined then it is used to configure the filter. 202 * Otherwise, the filter is not configured during initialization. 203 * The syntax for each property is the same as for the 204 * {@link #createFilter(String) createFilter} method. 205 * If a filter is not configured, it can be set with 206 * {@link #setSerialFilter(ObjectInputFilter) Config.setSerialFilter}. 207 * 208 * @since 9 209 */ 210 final class Config { 211 /* No instances. */ 212 private Config() {} 213 214 /** 215 * Lock object for process-wide filter. 216 */ 217 private final static Object serialFilterLock = new Object(); 218 219 /** 220 * Debug: Logger 221 */ 222 private final static System.Logger configLog; 223 224 /** 225 * Logger for debugging. 226 */ 227 static void filterLog(System.Logger.Level level, String msg, Object... args) { 228 if (configLog != null) { 229 configLog.log(level, msg, args); 230 } 231 } 232 233 /** 234 * The name for the process-wide deserialization filter. 235 * Used as a system property and a java.security.Security property. 236 */ 237 private final static String SERIAL_FILTER_PROPNAME = "jdk.serialFilter"; 238 239 /** 240 * The process-wide filter; may be null. 241 * Lookup the filter in java.security.Security or 242 * the system property. 243 */ 244 private final static ObjectInputFilter configuredFilter; 245 246 static { 247 configuredFilter = AccessController 248 .doPrivileged((PrivilegedAction<ObjectInputFilter>) () -> { 249 String props = System.getProperty(SERIAL_FILTER_PROPNAME); 250 if (props == null) { 251 props = Security.getProperty(SERIAL_FILTER_PROPNAME); 252 } 253 if (props != null) { 254 System.Logger log = 255 System.getLogger("java.io.serialization"); 256 log.log(System.Logger.Level.INFO, 257 "Creating serialization filter from {0}", props); 258 try { 259 return createFilter(props); 260 } catch (RuntimeException re) { 261 log.log(System.Logger.Level.ERROR, 262 "Error configuring filter: {0}", re); 263 } 264 } 265 return null; 266 }); 267 configLog = (configuredFilter != null) ? System.getLogger("java.io.serialization") : null; 268 } 269 270 /** 271 * Current configured filter. 272 */ 273 private static ObjectInputFilter serialFilter = configuredFilter; 274 275 /** 276 * Returns the process-wide serialization filter or {@code null} if not configured. 277 * 278 * @return the process-wide serialization filter or {@code null} if not configured 279 */ 280 public static ObjectInputFilter getSerialFilter() { 281 synchronized (serialFilterLock) { 282 return serialFilter; 283 } 284 } 285 286 /** 287 * Set the process-wide filter if it has not already been configured or set. 288 * 289 * @param filter the serialization filter to set as the process-wide filter; not null 290 * @throws SecurityException if there is security manager and the 291 * {@code SerializablePermission("serialFilter")} is not granted 292 * @throws IllegalStateException if the filter has already been set {@code non-null} 293 */ 294 public static void setSerialFilter(ObjectInputFilter filter) { 295 Objects.requireNonNull(filter, "filter"); 296 SecurityManager sm = System.getSecurityManager(); 297 if (sm != null) { 298 sm.checkPermission(ObjectStreamConstants.SERIAL_FILTER_PERMISSION); 299 } 300 synchronized (serialFilterLock) { 301 if (serialFilter != null) { 302 throw new IllegalStateException("Serial filter can only be set once"); 303 } 304 serialFilter = filter; 305 } 306 } 307 308 /** 309 * Returns an ObjectInputFilter from a string of patterns. 310 * <p> 311 * Patterns are separated by ";" (semicolon). Whitespace is significant and 312 * is considered part of the pattern. 313 * If a pattern includes an equals assignment, "{@code =}" it sets a limit. 314 * If a limit appears more than once the last value is used. 315 * <ul> 316 * <li>maxdepth={@code value} - the maximum depth of a graph</li> 317 * <li>maxrefs={@code value} - the maximum number of internal references</li> 318 * <li>maxbytes={@code value} - the maximum number of bytes in the input stream</li> 319 * <li>maxarray={@code value} - the maximum array length allowed</li> 320 * </ul> 321 * <p> 322 * Other patterns match or reject class or package name 323 * as returned from {@link Class#getName() Class.getName()} and 324 * if an optional module name is present 325 * {@link java.lang.reflect.Module#getName() class.getModule().getName()}. 326 * Note that for arrays the element type is used in the pattern, 327 * not the array type. 328 * <ul> 329 * <li>If the pattern starts with "!", the class is rejected if the remaining pattern is matched; 330 * otherwise the class is allowed if the pattern matches. 331 * <li>If the pattern contains "/", the non-empty prefix up to the "/" is the module name; 332 * if the module name matches the module name of the class then 333 * the remaining pattern is matched with the class name. 334 * If there is no "/", the module name is not compared. 335 * <li>If the pattern ends with ".**" it matches any class in the package and all subpackages. 336 * <li>If the pattern ends with ".*" it matches any class in the package. 337 * <li>If the pattern ends with "*", it matches any class with the pattern as a prefix. 338 * <li>If the pattern is equal to the class name, it matches. 339 * <li>Otherwise, the pattern is not matched. 340 * </ul> 341 * <p> 342 * The resulting filter performs the limit checks and then 343 * tries to match the class, if any. If any of the limits are exceeded, 344 * the filter returns {@link Status#REJECTED Status.REJECTED}. 345 * If the class is an array type, the class to be matched is the element type. 346 * Arrays of any number of dimensions are treated the same as the element type. 347 * For example, a pattern of "{@code !example.Foo}", 348 * rejects creation of any instance or array of {@code example.Foo}. 349 * The first pattern that matches, working from left to right, determines 350 * the {@link Status#ALLOWED Status.ALLOWED} 351 * or {@link Status#REJECTED Status.REJECTED} result. 352 * If the limits are not exceeded and no pattern matches the class, 353 * the result is {@link Status#UNDECIDED Status.UNDECIDED}. 354 * 355 * @param pattern the pattern string to parse; not null 356 * @return a filter to check a class being deserialized; 357 * {@code null} if no patterns 358 * @throws IllegalArgumentException if the pattern string is illegal or 359 * malformed and cannot be parsed. 360 * In particular, if any of the following is true: 361 * <ul> 362 * <li> if a limit is missing the name or the name is not one of 363 * "maxdepth", "maxrefs", "maxbytes", or "maxarray" 364 * <li> if the value of the limit can not be parsed by 365 * {@link Long#parseLong Long.parseLong} or is negative 366 * <li> if the pattern contains "/" and the module name is missing 367 * or the remaining pattern is empty 368 * <li> if the package is missing for ".*" and ".**" 369 * </ul> 370 */ 371 public static ObjectInputFilter createFilter(String pattern) { 372 Objects.requireNonNull(pattern, "pattern"); 373 return Global.createFilter(pattern); 374 } 375 376 /** 377 * Implementation of ObjectInputFilter that performs the checks of 378 * the process-wide serialization filter. If configured, it will be 379 * used for all ObjectInputStreams that do not set their own filters. 380 * 381 */ 382 final static class Global implements ObjectInputFilter { 383 /** 384 * The pattern used to create the filter. 385 */ 386 private final String pattern; 387 /** 388 * The list of class filters. 389 */ 390 private final List<Function<Class<?>, Status>> filters; 391 /** 392 * Maximum allowed bytes in the stream. 393 */ 394 private long maxStreamBytes; 395 /** 396 * Maximum depth of the graph allowed. 397 */ 398 private long maxDepth; 399 /** 400 * Maximum number of references in a graph. 401 */ 402 private long maxReferences; 403 /** 404 * Maximum length of any array. 405 */ 406 private long maxArrayLength; 407 408 /** 409 * Returns an ObjectInputFilter from a string of patterns. 410 * 411 * @param pattern the pattern string to parse 412 * @return a filter to check a class being deserialized; 413 * {@code null} if no patterns 414 * @throws IllegalArgumentException if the parameter is malformed 415 * if the pattern is missing the name, the long value 416 * is not a number or is negative. 417 */ 418 static ObjectInputFilter createFilter(String pattern) { 419 try { 420 return new Global(pattern); 421 } catch (UnsupportedOperationException uoe) { 422 // no non-empty patterns 423 return null; 424 } 425 } 426 427 /** 428 * Construct a new filter from the pattern String. 429 * 430 * @param pattern a pattern string of filters 431 * @throws IllegalArgumentException if the pattern is malformed 432 * @throws UnsupportedOperationException if there are no non-empty patterns 433 */ 434 private Global(String pattern) { 435 boolean hasLimits = false; 436 this.pattern = pattern; 437 438 maxArrayLength = Long.MAX_VALUE; // Default values are unlimited 439 maxDepth = Long.MAX_VALUE; 440 maxReferences = Long.MAX_VALUE; 441 maxStreamBytes = Long.MAX_VALUE; 442 443 String[] patterns = pattern.split(";"); 444 filters = new ArrayList<>(patterns.length); 445 for (int i = 0; i < patterns.length; i++) { 446 String p = patterns[i]; 447 int nameLen = p.length(); 448 if (nameLen == 0) { 449 continue; 450 } 451 if (parseLimit(p)) { 452 // If the pattern contained a limit setting, i.e. type=value 453 hasLimits = true; 454 continue; 455 } 456 boolean negate = p.charAt(0) == '!'; 457 int poffset = negate ? 1 : 0; 458 459 // isolate module name, if any 460 int slash = p.indexOf('/', poffset); 461 if (slash == poffset) { 462 throw new IllegalArgumentException("module name is missing in: \"" + pattern + "\""); 463 } 464 final String moduleName = (slash >= 0) ? p.substring(poffset, slash) : null; 465 poffset = (slash >= 0) ? slash + 1 : poffset; 466 467 final Function<Class<?>, Status> patternFilter; 468 if (p.endsWith("*")) { 469 // Wildcard cases 470 if (p.endsWith(".*")) { 471 // Pattern is a package name with a wildcard 472 final String pkg = p.substring(poffset, nameLen - 1); 473 if (pkg.length() < 2) { 474 throw new IllegalArgumentException("package missing in: \"" + pattern + "\""); 475 } 476 if (negate) { 477 // A Function that fails if the class starts with the pattern, otherwise don't care 478 patternFilter = c -> matchesPackage(c, pkg) ? Status.REJECTED : Status.UNDECIDED; 479 } else { 480 // A Function that succeeds if the class starts with the pattern, otherwise don't care 481 patternFilter = c -> matchesPackage(c, pkg) ? Status.ALLOWED : Status.UNDECIDED; 482 } 483 } else if (p.endsWith(".**")) { 484 // Pattern is a package prefix with a double wildcard 485 final String pkgs = p.substring(poffset, nameLen - 2); 486 if (pkgs.length() < 2) { 487 throw new IllegalArgumentException("package missing in: \"" + pattern + "\""); 488 } 489 if (negate) { 490 // A Function that fails if the class starts with the pattern, otherwise don't care 491 patternFilter = c -> c.getName().startsWith(pkgs) ? Status.REJECTED : Status.UNDECIDED; 492 } else { 493 // A Function that succeeds if the class starts with the pattern, otherwise don't care 494 patternFilter = c -> c.getName().startsWith(pkgs) ? Status.ALLOWED : Status.UNDECIDED; 495 } 496 } else { 497 // Pattern is a classname (possibly empty) with a trailing wildcard 498 final String className = p.substring(poffset, nameLen - 1); 499 if (negate) { 500 // A Function that fails if the class starts with the pattern, otherwise don't care 501 patternFilter = c -> c.getName().startsWith(className) ? Status.REJECTED : Status.UNDECIDED; 502 } else { 503 // A Function that succeeds if the class starts with the pattern, otherwise don't care 504 patternFilter = c -> c.getName().startsWith(className) ? Status.ALLOWED : Status.UNDECIDED; 505 } 506 } 507 } else { 508 final String name = p.substring(poffset); 509 if (name.isEmpty()) { 510 throw new IllegalArgumentException("class or package missing in: \"" + pattern + "\""); 511 } 512 // Pattern is a class name 513 if (negate) { 514 // A Function that fails if the class equals the pattern, otherwise don't care 515 patternFilter = c -> c.getName().equals(name) ? Status.REJECTED : Status.UNDECIDED; 516 } else { 517 // A Function that succeeds if the class equals the pattern, otherwise don't care 518 patternFilter = c -> c.getName().equals(name) ? Status.ALLOWED : Status.UNDECIDED; 519 } 520 } 521 // If there is a moduleName, combine the module name check with the package/class check 522 if (moduleName == null) { 523 filters.add(patternFilter); 524 } else { 525 filters.add(c -> moduleName.equals(c.getModule().getName()) ? patternFilter.apply(c) : Status.UNDECIDED); 526 } 527 } 528 if (filters.isEmpty() && !hasLimits) { 529 throw new UnsupportedOperationException("no non-empty patterns"); 530 } 531 } 532 533 /** 534 * Parse out a limit for one of maxarray, maxdepth, maxbytes, maxreferences. 535 * 536 * @param pattern a string with a type name, '=' and a value 537 * @return {@code true} if a limit was parsed, else {@code false} 538 * @throws IllegalArgumentException if the pattern is missing 539 * the name, the Long value is not a number or is negative. 540 */ 541 private boolean parseLimit(String pattern) { 542 int eqNdx = pattern.indexOf('='); 543 if (eqNdx < 0) { 544 // not a limit pattern 545 return false; 546 } 547 String valueString = pattern.substring(eqNdx + 1); 548 if (pattern.startsWith("maxdepth=")) { 549 maxDepth = parseValue(valueString); 550 } else if (pattern.startsWith("maxarray=")) { 551 maxArrayLength = parseValue(valueString); 552 } else if (pattern.startsWith("maxrefs=")) { 553 maxReferences = parseValue(valueString); 554 } else if (pattern.startsWith("maxbytes=")) { 555 maxStreamBytes = parseValue(valueString); 556 } else { 557 throw new IllegalArgumentException("unknown limit: " + pattern.substring(0, eqNdx)); 558 } 559 return true; 560 } 561 562 /** 563 * Parse the value of a limit and check that it is non-negative. 564 * @param string inputstring 565 * @return the parsed value 566 * @throws IllegalArgumentException if parsing the value fails or the value is negative 567 */ 568 private static long parseValue(String string) throws IllegalArgumentException { 569 // Parse a Long from after the '=' to the end 570 long value = Long.parseLong(string); 571 if (value < 0) { 572 throw new IllegalArgumentException("negative limit: " + string); 573 } 574 return value; 575 } 576 577 /** 578 * {@inheritDoc} 579 */ 580 @Override 581 public Status checkInput(FilterInfo filterInfo) { 582 if (filterInfo.references() < 0 583 || filterInfo.depth() < 0 584 || filterInfo.streamBytes() < 0 585 || filterInfo.references() > maxReferences 586 || filterInfo.depth() > maxDepth 587 || filterInfo.streamBytes() > maxStreamBytes) { 588 return Status.REJECTED; 589 } 590 591 Class<?> clazz = filterInfo.serialClass(); 592 if (clazz != null) { 593 if (clazz.isArray()) { 594 if (filterInfo.arrayLength() >= 0 && filterInfo.arrayLength() > maxArrayLength) { 595 // array length is too big 596 return Status.REJECTED; 597 } 598 do { 599 // Arrays are decided based on the component type 600 clazz = clazz.getComponentType(); 601 } while (clazz.isArray()); 602 } 603 604 if (clazz.isPrimitive()) { 605 // Primitive types are undecided; let someone else decide 606 return Status.UNDECIDED; 607 } else { 608 // Find any filter that allowed or rejected the class 609 final Class<?> cl = clazz; 610 Optional<Status> status = filters.stream() 611 .map(f -> f.apply(cl)) 612 .filter(p -> p != Status.UNDECIDED) 613 .findFirst(); 614 return status.orElse(Status.UNDECIDED); 615 } 616 } 617 return Status.UNDECIDED; 618 } 619 620 /** 621 * Returns {@code true} if the class is in the package. 622 * 623 * @param c a class 624 * @param pkg a package name (including the trailing ".") 625 * @return {@code true} if the class is in the package, 626 * otherwise {@code false} 627 */ 628 private static boolean matchesPackage(Class<?> c, String pkg) { 629 String n = c.getName(); 630 return n.startsWith(pkg) && n.lastIndexOf('.') == pkg.length() - 1; 631 } 632 633 /** 634 * Returns the pattern used to create this filter. 635 * @return the pattern used to create this filter 636 */ 637 @Override 638 public String toString() { 639 return pattern; 640 } 641 } 642 } 643 }