1 /* 2 * Copyright (c) 2016, 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. 8 * 9 * This code is distributed in the hope that it will be useful, but WITHOUT 10 * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or 11 * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License 12 * version 2 for more details (a copy is included in the LICENSE file that 13 * accompanied this code). 14 * 15 * You should have received a copy of the GNU General Public License version 16 * 2 along with this work; if not, write to the Free Software Foundation, 17 * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. 18 * 19 * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA 20 * or visit www.oracle.com if you need additional information or have any 21 * questions. 22 */ 23 24 import java.io.ByteArrayInputStream; 25 import java.io.ByteArrayOutputStream; 26 import java.io.EOFException; 27 import java.io.IOException; 28 import java.io.InvalidClassException; 29 import java.io.ObjectInputFilter; 30 import java.io.ObjectInputStream; 31 import java.io.ObjectOutputStream; 32 import java.io.Serializable; 33 import java.lang.invoke.SerializedLambda; 34 import java.lang.reflect.Constructor; 35 import java.lang.reflect.InvocationTargetException; 36 import java.lang.reflect.Proxy; 37 import java.util.ArrayList; 38 import java.util.Arrays; 39 import java.util.Collections; 40 import java.util.HashSet; 41 import java.util.Hashtable; 42 import java.util.List; 43 import java.util.Map; 44 import java.util.concurrent.atomic.LongAdder; 45 46 import javax.net.ssl.SSLEngineResult; 47 48 import org.testng.Assert; 49 import org.testng.annotations.Test; 50 import org.testng.annotations.DataProvider; 51 52 /* @test 53 * @bug 8234836 54 * @build SerialFilterTest 55 * @run testng/othervm SerialFilterTest 56 * @run testng/othervm -Djdk.serialSetFilterAfterRead=true SerialFilterTest 57 * 58 * @summary Test ObjectInputFilters 59 */ 60 @Test 61 public class SerialFilterTest implements Serializable { 62 63 private static final long serialVersionUID = -6999613679881262446L; 64 65 /** 66 * Enable three arg lambda. 67 * @param <T> The pattern 68 * @param <U> The test object 69 * @param <V> Boolean for if the filter should allow or reject 70 */ 71 interface TriConsumer< T, U, V> { accept(T t, U u, V v)72 void accept(T t, U u, V v); 73 } 74 75 /** 76 * Misc object to use that should always be accepted. 77 */ 78 private static final Object otherObject = Integer.valueOf(0); 79 80 // Cache value of jdk.serialSetFilterAfterRead property. 81 static final boolean SET_FILTER_AFTER_READ = 82 Boolean.getBoolean("jdk.serialSetFilterAfterRead"); 83 84 /** 85 * DataProvider for the individual patterns to test. 86 * Expand the patterns into cases for each of the Std and Compatibility APIs. 87 * @return an array of arrays of the parameters including factories 88 */ 89 @DataProvider(name="Patterns") patterns()90 static Object[][] patterns() { 91 Object[][] patterns = new Object[][]{ 92 {"java.util.Hashtable"}, 93 {"java.util.Hash*"}, 94 {"javax.net.ssl.*"}, 95 {"javax.net.**"}, 96 {"*"}, 97 {"maxarray=47"}, 98 {"maxdepth=5"}, 99 {"maxrefs=10"}, 100 {"maxbytes=100"}, 101 {"maxbytes=72"}, 102 {"maxbytes=+1024"}, 103 {"java.base/java.util.Hashtable"}, 104 }; 105 return patterns; 106 } 107 108 @DataProvider(name="InvalidPatterns") invalidPatterns()109 static Object[][] invalidPatterns() { 110 return new Object [][] { 111 {".*"}, 112 {".**"}, 113 {"!"}, 114 {"/java.util.Hashtable"}, 115 {"java.base/"}, 116 {"/"}, 117 }; 118 } 119 120 @DataProvider(name="Limits") limits()121 static Object[][] limits() { 122 // The numbers are arbitrary > 1 123 return new Object[][] { 124 {"maxrefs", 1}, // 0 is tested as n-1 125 {"maxrefs", 10}, 126 {"maxdepth", 5}, 127 {"maxbytes", 100}, 128 {"maxarray", 16}, 129 {"maxbytes", Long.MAX_VALUE}, 130 }; 131 } 132 133 @DataProvider(name="InvalidLimits") invalidLimits()134 static Object[][] invalidLimits() { 135 return new Object[][] { 136 {"maxrefs=-1"}, 137 {"maxdepth=-1"}, 138 {"maxbytes=-1"}, 139 {"maxarray=-1"}, 140 {"xyz=0"}, 141 {"xyz=-1"}, 142 {"maxrefs=0xabc"}, 143 {"maxrefs=abc"}, 144 {"maxrefs="}, 145 {"maxrefs=+"}, 146 {"maxbytes=-1"}, 147 {"maxbytes=9223372036854775808"}, 148 {"maxbytes=-9223372036854775807"}, 149 }; 150 } 151 152 /** 153 * DataProvider of individual objects. Used to check the information 154 * available to the filter. 155 * @return Arrays of parameters with objects 156 */ 157 @DataProvider(name="Objects") objects()158 static Object[][] objects() { 159 byte[] byteArray = new byte[0]; 160 Object[] objArray = new Object[7]; 161 objArray[objArray.length - 1] = objArray; 162 163 Class<?> serClass = null; 164 String className = "java.util.concurrent.atomic.LongAdder$SerializationProxy"; 165 try { 166 serClass = Class.forName(className); 167 } catch (Exception e) { 168 Assert.fail("missing class: " + className, e); 169 } 170 171 Class<?>[] interfaces = {Runnable.class}; 172 Runnable proxy = (Runnable) Proxy.newProxyInstance(null, 173 interfaces, (p, m, args) -> p); 174 175 Runnable runnable = (Runnable & Serializable) SerialFilterTest::noop; 176 177 List<Class<?>> classList = new ArrayList<>(); 178 classList.add(HashSet.class); 179 classList.addAll(Collections.nCopies(21, Map.Entry[].class)); 180 181 Object[][] objects = { 182 { null, 0, -1, 0, 0, 0, 183 Arrays.asList()}, // no callback, no values 184 { objArray, 3, 7, 9, 2, 55, 185 Arrays.asList(objArray.getClass(), objArray.getClass())}, 186 { Object[].class, 1, -1, 1, 1, 38, 187 Arrays.asList(Object[].class)}, 188 { new SerialFilterTest(), 1, -1, 1, 1, 35, 189 Arrays.asList(SerialFilterTest.class)}, 190 { new LongAdder(), 2, -1, 2, 1, 93, 191 Arrays.asList(serClass, LongAdder.class)}, 192 { new byte[14], 2, 14, 2, 1, 27, 193 Arrays.asList(byteArray.getClass(), byteArray.getClass())}, 194 { runnable, 13, 0, 13, 2, 514, 195 Arrays.asList(java.lang.invoke.SerializedLambda.class, 196 objArray.getClass(), 197 objArray.getClass(), 198 SerialFilterTest.class, 199 java.lang.invoke.SerializedLambda.class)}, 200 { deepHashSet(10), 69, 4, 50, 11, 619, classList }, 201 { proxy.getClass(), 3, -1, 2, 2, 112, 202 Arrays.asList(Runnable.class, 203 java.lang.reflect.Proxy.class, 204 java.lang.reflect.Proxy.class)}, 205 { new F(), 6, -1, 6, 6, 202, 206 Arrays.asList(F.class, E.class, D.class, 207 C.class, B.class, A.class)}, 208 209 }; 210 return objects; 211 } 212 213 @DataProvider(name="Arrays") arrays()214 static Object[][] arrays() { 215 return new Object[][]{ 216 {new Object[16], 16}, 217 {new boolean[16], 16}, 218 {new byte[16], 16}, 219 {new char[16], 16}, 220 {new int[16], 16}, 221 {new long[16], 16}, 222 {new short[16], 16}, 223 {new float[16], 16}, 224 {new double[16], 16}, 225 }; 226 } 227 228 229 /** 230 * Test each object and verify the classes identified by the filter, 231 * the count of calls to the filter, the max array size, max refs, max depth, 232 * max bytes. 233 * This test ignores/is not dependent on the global filter settings. 234 * 235 * @param object a Serializable object 236 * @param count the expected count of calls to the filter 237 * @param maxArray the maximum array size 238 * @param maxRefs the maximum references 239 * @param maxDepth the maximum depth 240 * @param maxBytes the maximum stream size 241 * @param classes the expected (unique) classes 242 * @throws IOException 243 */ 244 @Test(dataProvider="Objects") t1(Object object, long count, long maxArray, long maxRefs, long maxDepth, long maxBytes, List<Class<?>> classes)245 public static void t1(Object object, 246 long count, long maxArray, long maxRefs, long maxDepth, long maxBytes, 247 List<Class<?>> classes) throws IOException { 248 byte[] bytes = writeObjects(object); 249 Validator validator = new Validator(); 250 validate(bytes, validator); 251 System.out.printf("v: %s%n", validator); 252 253 Assert.assertEquals(validator.count, count, "callback count wrong"); 254 Assert.assertEquals(validator.classes, classes, "classes mismatch"); 255 Assert.assertEquals(validator.maxArray, maxArray, "maxArray mismatch"); 256 Assert.assertEquals(validator.maxRefs, maxRefs, "maxRefs wrong"); 257 Assert.assertEquals(validator.maxDepth, maxDepth, "depth wrong"); 258 Assert.assertEquals(validator.maxBytes, maxBytes, "maxBytes wrong"); 259 } 260 261 /** 262 * Test each pattern with an appropriate object. 263 * A filter is created from the pattern and used to serialize and 264 * deserialize a generated object with both the positive and negative case. 265 * This test ignores/is not dependent on the global filter settings. 266 * 267 * @param pattern a pattern 268 */ 269 @Test(dataProvider="Patterns") testPatterns(String pattern)270 static void testPatterns(String pattern) { 271 evalPattern(pattern, (p, o, neg) -> testPatterns(p, o, neg)); 272 } 273 274 /** 275 * Test that the filter on a OIS can be set only on a fresh OIS, 276 * before deserializing any objects. 277 * This test is agnostic the global filter being set or not. 278 */ 279 @Test nonResettableFilter()280 static void nonResettableFilter() { 281 Validator validator1 = new Validator(); 282 Validator validator2 = new Validator(); 283 284 try { 285 byte[] bytes = writeObjects("text1"); // an object 286 287 try (ByteArrayInputStream bais = new ByteArrayInputStream(bytes); 288 ObjectInputStream ois = new ObjectInputStream(bais)) { 289 // Check the initial filter is the global filter; may be null 290 ObjectInputFilter global = ObjectInputFilter.Config.getSerialFilter(); 291 ObjectInputFilter initial = ois.getObjectInputFilter(); 292 Assert.assertEquals(global, initial, "initial filter should be the global filter"); 293 294 // Check if it can be set to null 295 ois.setObjectInputFilter(null); 296 ObjectInputFilter filter = ois.getObjectInputFilter(); 297 Assert.assertNull(filter, "set to null should be null"); 298 299 ois.setObjectInputFilter(validator1); 300 Object o = ois.readObject(); 301 try { 302 ois.setObjectInputFilter(validator2); 303 Assert.fail("Should not be able to set filter twice"); 304 } catch (IllegalStateException ise) { 305 // success, the exception was expected 306 } 307 } catch (EOFException eof) { 308 Assert.fail("Should not reach end-of-file", eof); 309 } catch (ClassNotFoundException cnf) { 310 Assert.fail("Deserializing", cnf); 311 } 312 } catch (IOException ex) { 313 Assert.fail("Unexpected IOException", ex); 314 } 315 } 316 317 /** 318 * After reading some objects from the stream, setting a filter is disallowed. 319 * If the filter was allowed to be set, it would have unpredictable behavior. 320 * Objects already read would not be checked again, including class descriptors. 321 * 322 * Note: To mitigate possible incompatibility a system property can be set 323 * to revert to the old behavior but it re-enables the incorrect use. 324 */ 325 @Test testNonSettableAfterReadObject()326 static void testNonSettableAfterReadObject() throws IOException, ClassNotFoundException { 327 String expected1 = "text1"; 328 String expected2 = "text2"; 329 byte[] bytes = writeObjects(expected1, expected2); 330 331 for (boolean toggle: new boolean[] {true, false}) { 332 try (ByteArrayInputStream bais = new ByteArrayInputStream(bytes); 333 ObjectInputStream ois = new ObjectInputStream(bais)) { 334 Object actual1 = toggle ? ois.readObject() : ois.readUnshared(); 335 Assert.assertEquals(actual1, expected1, "unexpected string"); 336 // Attempt to set filter 337 ois.setObjectInputFilter(new ObjectInputFilter() { 338 @Override 339 public Status checkInput(FilterInfo filterInfo) { 340 return null; 341 } 342 }); 343 if (!SET_FILTER_AFTER_READ) 344 Assert.fail("Should not be able to set filter after readObject has been called"); 345 } catch (IllegalStateException ise) { 346 // success, the exception was expected 347 if (SET_FILTER_AFTER_READ) 348 Assert.fail("With jdk.serialSetFilterAfterRead property set = true; " + 349 "should be able to set the filter after a read"); 350 } catch (EOFException eof) { 351 Assert.fail("Should not reach end-of-file", eof); 352 } 353 } 354 } 355 356 /** 357 * Test that if an Objects readReadResolve method returns an array 358 * that the callback to the filter includes the proper array length. 359 * @throws IOException if an error occurs 360 */ 361 @Test(dataProvider="Arrays") testReadResolveToArray(Object array, int length)362 static void testReadResolveToArray(Object array, int length) throws IOException { 363 ReadResolveToArray object = new ReadResolveToArray(array, length); 364 byte[] bytes = writeObjects(object); 365 Object o = validate(bytes, object); // the object is its own filter 366 Assert.assertEquals(o.getClass(), array.getClass(), "Filter not called with the array"); 367 } 368 369 370 /** 371 * Test repeated limits use the last value. 372 * Construct a filter with the limit and the limit repeated -1. 373 * Invoke the filter with the limit to make sure it is rejected. 374 * Invoke the filter with the limit -1 to make sure it is accepted. 375 * @param name the name of the limit to test 376 * @param value a test value 377 */ 378 @Test(dataProvider="Limits") testLimits(String name, long value)379 static void testLimits(String name, long value) { 380 Class<?> arrayClass = new int[0].getClass(); 381 String pattern = String.format("%s=%d;%s=%d", name, value, name, value - 1); 382 ObjectInputFilter filter = ObjectInputFilter.Config.createFilter(pattern); 383 Assert.assertEquals( 384 filter.checkInput(new FilterValues(arrayClass, value, value, value, value)), 385 ObjectInputFilter.Status.REJECTED, 386 "last limit value not used: " + filter); 387 Assert.assertEquals( 388 filter.checkInput(new FilterValues(arrayClass, value-1, value-1, value-1, value-1)), 389 ObjectInputFilter.Status.UNDECIDED, 390 "last limit value not used: " + filter); 391 } 392 393 /** 394 * Test invalid limits. 395 * Construct a filter with the limit, it should throw IllegalArgumentException. 396 * @param pattern a pattern to test 397 */ 398 @Test(dataProvider="InvalidLimits", expectedExceptions=java.lang.IllegalArgumentException.class) testInvalidLimits(String pattern)399 static void testInvalidLimits(String pattern) { 400 try { 401 ObjectInputFilter filter = ObjectInputFilter.Config.createFilter(pattern); 402 } catch (IllegalArgumentException iae) { 403 System.out.printf(" success exception: %s%n", iae); 404 throw iae; 405 } 406 } 407 408 /** 409 * Test that returning null from a filter causes deserialization to fail. 410 */ 411 @Test(expectedExceptions=InvalidClassException.class) testNullStatus()412 static void testNullStatus() throws IOException { 413 byte[] bytes = writeObjects(0); // an Integer 414 try { 415 Object o = validate(bytes, new ObjectInputFilter() { 416 public ObjectInputFilter.Status checkInput(ObjectInputFilter.FilterInfo f) { 417 return null; 418 } 419 }); 420 } catch (InvalidClassException ice) { 421 System.out.printf(" success exception: %s%n", ice); 422 throw ice; 423 } 424 } 425 426 /** 427 * Verify that malformed patterns throw IAE. 428 * @param pattern pattern from the data source 429 */ 430 @Test(dataProvider="InvalidPatterns", expectedExceptions=IllegalArgumentException.class) testInvalidPatterns(String pattern)431 static void testInvalidPatterns(String pattern) { 432 try { 433 ObjectInputFilter.Config.createFilter(pattern); 434 } catch (IllegalArgumentException iae) { 435 System.out.printf(" success exception: %s%n", iae); 436 throw iae; 437 } 438 } 439 440 /** 441 * Test that Config.create returns null if the argument does not contain any patterns or limits. 442 */ 443 @Test() testEmptyPattern()444 static void testEmptyPattern() { 445 ObjectInputFilter filter = ObjectInputFilter.Config.createFilter(""); 446 Assert.assertNull(filter, "empty pattern did not return null"); 447 448 filter = ObjectInputFilter.Config.createFilter(";;;;"); 449 Assert.assertNull(filter, "pattern with only delimiters did not return null"); 450 } 451 452 /** 453 * Read objects from the serialized stream, validated with the filter. 454 * 455 * @param bytes a byte array to read objects from 456 * @param filter the ObjectInputFilter 457 * @return the object deserialized if any 458 * @throws IOException can be thrown 459 */ validate(byte[] bytes, ObjectInputFilter filter)460 static Object validate(byte[] bytes, 461 ObjectInputFilter filter) throws IOException { 462 try (ByteArrayInputStream bais = new ByteArrayInputStream(bytes); 463 ObjectInputStream ois = new ObjectInputStream(bais)) { 464 ois.setObjectInputFilter(filter); 465 466 Object o = ois.readObject(); 467 return o; 468 } catch (EOFException eof) { 469 // normal completion 470 } catch (ClassNotFoundException cnf) { 471 Assert.fail("Deserializing", cnf); 472 } 473 return null; 474 } 475 476 /** 477 * Write objects and return a byte array with the bytes. 478 * 479 * @param objects zero or more objects to serialize 480 * @return the byte array of the serialized objects 481 * @throws IOException if an exception occurs 482 */ writeObjects(Object... objects)483 static byte[] writeObjects(Object... objects) throws IOException { 484 byte[] bytes; 485 try (ByteArrayOutputStream baos = new ByteArrayOutputStream(); 486 ObjectOutputStream oos = new ObjectOutputStream(baos)) { 487 for (Object o : objects) { 488 oos.writeObject(o); 489 } 490 bytes = baos.toByteArray(); 491 } 492 return bytes; 493 } 494 495 /** 496 * A filter that accumulates information about the checkInput callbacks 497 * that can be checked after readObject completes. 498 */ 499 static class Validator implements ObjectInputFilter { 500 long count; // Count of calls to checkInput 501 List<Class<?>> classes = new ArrayList<>(); 502 long maxArray = -1; 503 long maxRefs; 504 long maxDepth; 505 long maxBytes; 506 Validator()507 Validator() { 508 } 509 510 @Override checkInput(FilterInfo filter)511 public ObjectInputFilter.Status checkInput(FilterInfo filter) { 512 Class<?> serialClass = filter.serialClass(); 513 System.out.printf(" checkInput: class: %s, arrayLen: %d, refs: %d, depth: %d, bytes; %d%n", 514 serialClass, filter.arrayLength(), filter.references(), 515 filter.depth(), filter.streamBytes()); 516 count++; 517 if (serialClass != null) { 518 if (serialClass.getName().contains("$$Lambda$")) { 519 // TBD: proper identification of serialized Lambdas? 520 // Fold the serialized Lambda into the SerializedLambda type 521 classes.add(SerializedLambda.class); 522 } else if (Proxy.isProxyClass(serialClass)) { 523 classes.add(Proxy.class); 524 } else { 525 classes.add(serialClass); 526 } 527 528 } 529 this.maxArray = Math.max(this.maxArray, filter.arrayLength()); 530 this.maxRefs = Math.max(this.maxRefs, filter.references()); 531 this.maxDepth = Math.max(this.maxDepth, filter.depth()); 532 this.maxBytes = Math.max(this.maxBytes, filter.streamBytes()); 533 return ObjectInputFilter.Status.UNDECIDED; 534 } 535 toString()536 public String toString(){ 537 return "count: " + count 538 + ", classes: " + classes.toString() 539 + ", maxArray: " + maxArray 540 + ", maxRefs: " + maxRefs 541 + ", maxDepth: " + maxDepth 542 + ", maxBytes: " + maxBytes; 543 } 544 } 545 546 547 /** 548 * Create a filter from a pattern and API factory, then serialize and 549 * deserialize an object and check allowed or reject. 550 * 551 * @param pattern the pattern 552 * @param object the test object 553 * @param allowed the expected result from ObjectInputStream (exception or not) 554 */ testPatterns(String pattern, Object object, boolean allowed)555 static void testPatterns(String pattern, Object object, boolean allowed) { 556 try { 557 byte[] bytes = SerialFilterTest.writeObjects(object); 558 ObjectInputFilter filter = ObjectInputFilter.Config.createFilter(pattern); 559 validate(bytes, filter); 560 Assert.assertTrue(allowed, "filter should have thrown an exception"); 561 } catch (IllegalArgumentException iae) { 562 Assert.fail("bad format pattern", iae); 563 } catch (InvalidClassException ice) { 564 Assert.assertFalse(allowed, "filter should not have thrown an exception: " + ice); 565 } catch (IOException ioe) { 566 Assert.fail("Unexpected IOException", ioe); 567 } 568 } 569 570 /** 571 * For a filter pattern, generate and apply a test object to the action. 572 * @param pattern a pattern 573 * @param action an action to perform on positive and negative cases 574 */ evalPattern(String pattern, TriConsumer<String, Object, Boolean> action)575 static void evalPattern(String pattern, TriConsumer<String, Object, Boolean> action) { 576 Object o = genTestObject(pattern, true); 577 Assert.assertNotNull(o, "success generation failed"); 578 action.accept(pattern, o, true); 579 580 // Test the negative pattern 581 o = genTestObject(pattern, false); 582 Assert.assertNotNull(o, "fail generation failed"); 583 String negPattern = pattern.contains("=") ? pattern : "!" + pattern; 584 action.accept(negPattern, o, false); 585 } 586 587 /** 588 * Generate a test object based on the pattern. 589 * Handles each of the forms of the pattern, wildcards, 590 * class name, various limit forms. 591 * @param pattern a pattern 592 * @param allowed a boolean indicating to generate the allowed or disallowed case 593 * @return an object or {@code null} to indicate no suitable object could be generated 594 */ genTestObject(String pattern, boolean allowed)595 static Object genTestObject(String pattern, boolean allowed) { 596 if (pattern.contains("=")) { 597 return genTestLimit(pattern, allowed); 598 } else if (pattern.endsWith("*")) { 599 return genTestObjectWildcard(pattern, allowed); 600 } else { 601 // class 602 // isolate module name, if any 603 int poffset = 0; 604 int soffset = pattern.indexOf('/', poffset); 605 String module = null; 606 if (soffset >= 0) { 607 poffset = soffset + 1; 608 module = pattern.substring(0, soffset); 609 } 610 try { 611 Class<?> clazz = Class.forName(pattern.substring(poffset)); 612 Constructor<?> cons = clazz.getConstructor(); 613 return cons.newInstance(); 614 } catch (ClassNotFoundException ex) { 615 Assert.fail("no such class available: " + pattern); 616 } catch (InvocationTargetException 617 | NoSuchMethodException 618 | InstantiationException 619 | IllegalAccessException ex1) { 620 Assert.fail("newInstance: " + ex1); 621 } 622 } 623 return null; 624 } 625 626 /** 627 * Generate an object to be used with the various wildcard pattern forms. 628 * Explicitly supports only specific package wildcards with specific objects. 629 * @param pattern a wildcard pattern ending in "*" 630 * @param allowed a boolean indicating to generate the allowed or disallowed case 631 * @return an object within or outside the wildcard 632 */ genTestObjectWildcard(String pattern, boolean allowed)633 static Object genTestObjectWildcard(String pattern, boolean allowed) { 634 if (pattern.endsWith(".**")) { 635 // package hierarchy wildcard 636 if (pattern.startsWith("javax.net.")) { 637 return SSLEngineResult.Status.BUFFER_OVERFLOW; 638 } 639 if (pattern.startsWith("java.")) { 640 return 4; 641 } 642 if (pattern.startsWith("javax.")) { 643 return SSLEngineResult.Status.BUFFER_UNDERFLOW; 644 } 645 return otherObject; 646 } else if (pattern.endsWith(".*")) { 647 // package wildcard 648 if (pattern.startsWith("javax.net.ssl")) { 649 return SSLEngineResult.Status.BUFFER_UNDERFLOW; 650 } 651 } else { 652 // class wildcard 653 if (pattern.equals("*")) { 654 return otherObject; // any object will do 655 } 656 if (pattern.startsWith("java.util.Hash")) { 657 return new Hashtable<String, String>(); 658 } 659 } 660 Assert.fail("Object could not be generated for pattern: " 661 + pattern 662 + ", allowed: " + allowed); 663 return null; 664 } 665 666 /** 667 * Generate a limit test object for the pattern. 668 * For positive cases, the object exactly hits the limit. 669 * For negative cases, the object is 1 greater than the limit 670 * @param pattern the pattern, containing "=" and a maxXXX keyword 671 * @param allowed a boolean indicating to generate the allowed or disallowed case 672 * @return a sitable object 673 */ genTestLimit(String pattern, boolean allowed)674 static Object genTestLimit(String pattern, boolean allowed) { 675 int ndx = pattern.indexOf('='); 676 Assert.assertNotEquals(ndx, -1, "missing value in limit"); 677 long value = Long.parseUnsignedLong(pattern.substring(ndx+1)); 678 if (pattern.startsWith("maxdepth=")) { 679 // Return an object with the requested depth (or 1 greater) 680 long depth = allowed ? value : value + 1; 681 Object[] array = new Object[1]; 682 for (int i = 1; i < depth; i++) { 683 Object[] n = new Object[1]; 684 n[0] = array; 685 array = n; 686 } 687 return array; 688 } else if (pattern.startsWith("maxbytes=")) { 689 // Return a byte array that when written to OOS creates 690 // a stream of exactly the size requested. 691 return genMaxBytesObject(allowed, value); 692 } else if (pattern.startsWith("maxrefs=")) { 693 // 4 references to classes in addition to the array contents 694 Object[] array = new Object[allowed ? (int)value - 4 : (int)value - 3]; 695 for (int i = 0; i < array.length; i++) { 696 array[i] = otherObject; 697 } 698 return array; 699 } else if (pattern.startsWith("maxarray=")) { 700 return allowed ? new int[(int)value] : new int[(int)value+1]; 701 } 702 Assert.fail("Object could not be generated for pattern: " 703 + pattern 704 + ", allowed: " + allowed); 705 return null; 706 } 707 708 /** 709 * Generate an an object that will be serialized to some number of bytes. 710 * Or 1 greater if allowed is false. 711 * It returns a two element Object array holding a byte array sized 712 * to achieve the desired total size. 713 * @param allowed true if the stream should be allowed at that size, 714 * false if the stream should be larger 715 * @param maxBytes the number of bytes desired in the stream; 716 * should not be less than 72 (due to protocol overhead). 717 * @return a object that will be serialized to the length requested 718 */ genMaxBytesObject(boolean allowed, long maxBytes)719 private static Object genMaxBytesObject(boolean allowed, long maxBytes) { 720 Object[] holder = new Object[2]; 721 long desiredSize = allowed ? maxBytes : maxBytes + 1; 722 long actualSize = desiredSize; 723 long byteSize = desiredSize - 72; // estimate needed array size 724 do { 725 byteSize += (desiredSize - actualSize); 726 byte[] a = new byte[(int)byteSize]; 727 holder[0] = a; 728 holder[1] = a; 729 try (ByteArrayOutputStream baos = new ByteArrayOutputStream(); 730 ObjectOutputStream os = new ObjectOutputStream(baos)) { 731 os.writeObject(holder); 732 os.flush(); 733 actualSize = baos.size(); 734 } catch (IOException ie) { 735 Assert.fail("exception generating stream", ie); 736 } 737 } while (actualSize != desiredSize); 738 return holder; 739 } 740 741 /** 742 * Returns a HashSet of a requested depth. 743 * @param depth the depth 744 * @return a HashSet of HashSets... 745 */ deepHashSet(int depth)746 static HashSet<Object> deepHashSet(int depth) { 747 HashSet<Object> hashSet = new HashSet<>(); 748 HashSet<Object> s1 = hashSet; 749 HashSet<Object> s2 = new HashSet<>(); 750 for (int i = 0; i < depth; i++ ) { 751 HashSet<Object> t1 = new HashSet<>(); 752 HashSet<Object> t2 = new HashSet<>(); 753 // make t1 not equal to t2 754 t1.add("by Jimminy"); 755 s1.add(t1); 756 s1.add(t2); 757 s2.add(t1); 758 s2.add(t2); 759 s1 = t1; 760 s2 = t2; 761 } 762 return hashSet; 763 } 764 765 /** 766 * Simple method to use with Serialized Lambda. 767 */ noop()768 private static void noop() {} 769 770 771 /** 772 * Class that returns an array from readResolve and also implements 773 * the ObjectInputFilter to check that it has the expected length. 774 */ 775 static class ReadResolveToArray implements Serializable, ObjectInputFilter { 776 private static final long serialVersionUID = 123456789L; 777 778 private final Object array; 779 private final int length; 780 ReadResolveToArray(Object array, int length)781 ReadResolveToArray(Object array, int length) { 782 this.array = array; 783 this.length = length; 784 } 785 readResolve()786 Object readResolve() { 787 return array; 788 } 789 790 @Override checkInput(FilterInfo filter)791 public ObjectInputFilter.Status checkInput(FilterInfo filter) { 792 if (ReadResolveToArray.class.isAssignableFrom(filter.serialClass())) { 793 return ObjectInputFilter.Status.ALLOWED; 794 } 795 if (filter.serialClass() != array.getClass() || 796 (filter.arrayLength() >= 0 && filter.arrayLength() != length)) { 797 return ObjectInputFilter.Status.REJECTED; 798 } 799 return ObjectInputFilter.Status.UNDECIDED; 800 } 801 802 } 803 804 /** 805 * Hold a snapshot of values to be passed to an ObjectInputFilter. 806 */ 807 static class FilterValues implements ObjectInputFilter.FilterInfo { 808 private final Class<?> clazz; 809 private final long arrayLength; 810 private final long depth; 811 private final long references; 812 private final long streamBytes; 813 FilterValues(Class<?> clazz, long arrayLength, long depth, long references, long streamBytes)814 public FilterValues(Class<?> clazz, long arrayLength, long depth, long references, long streamBytes) { 815 this.clazz = clazz; 816 this.arrayLength = arrayLength; 817 this.depth = depth; 818 this.references = references; 819 this.streamBytes = streamBytes; 820 } 821 822 @Override serialClass()823 public Class<?> serialClass() { 824 return clazz; 825 } 826 arrayLength()827 public long arrayLength() { 828 return arrayLength; 829 } 830 depth()831 public long depth() { 832 return depth; 833 } 834 references()835 public long references() { 836 return references; 837 } 838 streamBytes()839 public long streamBytes() { 840 return streamBytes; 841 } 842 } 843 844 // Deeper superclass hierarchy 845 static class A implements Serializable { 846 private static final long serialVersionUID = 1L; 847 }; 848 static class B extends A { 849 private static final long serialVersionUID = 2L; 850 } 851 static class C extends B { 852 private static final long serialVersionUID = 3L; 853 } 854 static class D extends C { 855 private static final long serialVersionUID = 4L; 856 } 857 static class E extends D { 858 private static final long serialVersionUID = 5L; 859 } 860 static class F extends E { 861 private static final long serialVersionUID = 6L; 862 } 863 864 } 865