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