/** * Initialize the registryFilter from the security properties or system property; if any * @return an ObjectInputFilter, or null */ private static ObjectInputFilter initRegistryFilter() { ObjectInputFilter filter = null; String props = System.getProperty(REGISTRY_FILTER_PROPNAME); if (props == null) { props = Security.getProperty(REGISTRY_FILTER_PROPNAME); } if (props != null) { filter = ObjectInputFilter.Config.createFilter(props); Log regLog = Log.getLog("sun.rmi.registry", "registry", -1); if (regLog.isLoggable(Log.BRIEF)) { regLog.log(Log.BRIEF, "registryFilter = " + filter); } } return filter; }
/** * Creates a new <code>MarshalledObjectInputStream</code> that * reads its objects from <code>objIn</code> and annotations * from <code>locIn</code>. If <code>locIn</code> is * <code>null</code>, then all annotations will be * <code>null</code>. */ MarshalledObjectInputStream(InputStream objIn, InputStream locIn, ObjectInputFilter filter) throws IOException { super(objIn); this.locIn = (locIn == null ? null : new ObjectInputStream(locIn)); if (filter != null) { AccessController.doPrivileged(new PrivilegedAction<Void>() { @Override public Void run() { ObjectInputFilter.Config.setObjectInputFilter(MarshalledObjectInputStream.this, filter); if (MarshalledObjectInputStream.this.locIn != null) { ObjectInputFilter.Config.setObjectInputFilter(MarshalledObjectInputStream.this.locIn, filter); } return null; } }); } }
/** * Initialize the dgcFilter from the security properties or system property; if any * @return an ObjectInputFilter, or null */ private static ObjectInputFilter initDgcFilter() { ObjectInputFilter filter = null; String props = System.getProperty(DGC_FILTER_PROPNAME); if (props == null) { props = Security.getProperty(DGC_FILTER_PROPNAME); } if (props != null) { filter = ObjectInputFilter.Config.createFilter(props); if (dgcLog.isLoggable(Log.BRIEF)) { dgcLog.log(Log.BRIEF, "dgcFilter = " + filter); } } return filter; }
/** * Sets a filter for invocation arguments, if a filter has been set. * Called by dispatch before the arguments are read. */ protected void unmarshalCustomCallData(ObjectInput in) throws IOException, ClassNotFoundException { if (filter != null && in instanceof ObjectInputStream) { // Set the filter on the stream ObjectInputStream ois = (ObjectInputStream) in; AccessController.doPrivileged(new PrivilegedAction<Void>() { @Override public Void run() { ObjectInputFilter.Config.setObjectInputFilter(ois, filter); return null; } }); } }
/** * Read objects from the serialized stream, validated with the filter. * * @param bytes a byte array to read objects from * @param filter the ObjectInputFilter * @return the object deserialized if any * @throws IOException can be thrown */ static Object validate(byte[] bytes, ObjectInputFilter filter) throws IOException { try (ByteArrayInputStream bais = new ByteArrayInputStream(bytes); ObjectInputStream ois = new ObjectInputStream(bais)) { ObjectInputFilter.Config.setObjectInputFilter(ois, filter); Object o = ois.readObject(); return o; } catch (EOFException eof) { // normal completion } catch (ClassNotFoundException cnf) { Assert.fail("Deserializing", cnf); } return null; }
@Override public ObjectInputFilter.Status checkInput(FilterInfo filter) { count++; if (filter.serialClass() != null) { if (filter.serialClass().getName().contains("$$Lambda$")) { // TBD: proper identification of serialized Lambdas? // Fold the serialized Lambda into the SerializedLambda type classes.add(SerializedLambda.class); } else if (Proxy.isProxyClass(filter.serialClass())) { classes.add(Proxy.class); } else { classes.add(filter.serialClass()); } } this.maxArray = Math.max(this.maxArray, filter.arrayLength()); this.maxRefs = Math.max(this.maxRefs, filter.references()); this.maxDepth = Math.max(this.maxDepth, filter.depth()); this.maxBytes = Math.max(this.maxBytes, filter.streamBytes()); return ObjectInputFilter.Status.UNDECIDED; }
/** * Test that setting process-wide filter is checked by security manager. */ @Test public void testGlobalFilter() throws Exception { if (ObjectInputFilter.Config.getSerialFilter() == null) { return; } try (ByteArrayInputStream bais = new ByteArrayInputStream(bytes); ObjectInputStream ois = new ObjectInputStream(bais)) { ObjectInputFilter.Config.setSerialFilter(filter); assertFalse(setSecurityManager, "When SecurityManager exists, without " + "java.security.SerializablePermission(serialFilter) Exception should be thrown"); Object o = ois.readObject(); } catch (AccessControlException ex) { assertTrue(setSecurityManager); assertTrue(ex.getMessage().contains("java.io.SerializablePermission")); assertTrue(ex.getMessage().contains("serialFilter")); } }
@Override public ObjectInputFilter.Status checkInput(ObjectInputFilter.FilterInfo info) { // First run a custom filter long nestedDepth = info.depth(); if ((nestedDepth == 1 && info.serialClass() != SealedObjectForKeyProtector.class) || nestedDepth > MAX_NESTED_DEPTH) { return Status.REJECTED; } // Next run the default filter, if available ObjectInputFilter defaultFilter = ObjectInputFilter.Config.getSerialFilter(); if (defaultFilter != null) { return defaultFilter.checkInput(info); } return Status.UNDECIDED; }
@Override public ObjectInputFilter.Status checkInput(FilterInfo filter) { Class<?> serialClass = filter.serialClass(); System.out.printf(" checkInput: class: %s, arrayLen: %d, refs: %d, depth: %d, bytes; %d%n", serialClass, filter.arrayLength(), filter.references(), filter.depth(), filter.streamBytes()); count++; if (serialClass != null) { if (serialClass.getName().contains("$$Lambda$")) { // TBD: proper identification of serialized Lambdas? // Fold the serialized Lambda into the SerializedLambda type classes.add(SerializedLambda.class); } else if (Proxy.isProxyClass(serialClass)) { classes.add(Proxy.class); } else { classes.add(serialClass); } } this.maxArray = Math.max(this.maxArray, filter.arrayLength()); this.maxRefs = Math.max(this.maxRefs, filter.references()); this.maxDepth = Math.max(this.maxDepth, filter.depth()); this.maxBytes = Math.max(this.maxBytes, filter.streamBytes()); return ObjectInputFilter.Status.UNDECIDED; }
/** * Invoke the serialization filter if non-null. * If the filter rejects or an exception is thrown, throws InvalidClassException. * * @param clazz the class; may be null * @param arrayLength the array length requested; use {@code -1} if not creating an array * @throws InvalidClassException if it rejected by the filter or * a {@link RuntimeException} is thrown */ private void filterCheck(Class<?> clazz, int arrayLength) throws InvalidClassException { if (serialFilter != null) { RuntimeException ex = null; ObjectInputFilter.Status status; try { status = serialFilter.checkInput(new FilterValues(clazz, arrayLength, totalObjectRefs, depth, bin.getBytesRead())); } catch (RuntimeException e) { // Preventive interception of an exception to log status = ObjectInputFilter.Status.REJECTED; ex = e; } if (status == null || status == ObjectInputFilter.Status.REJECTED) { // Debug logging of filter checks that fail if (Logging.infoLogger != null) { Logging.infoLogger.info( "ObjectInputFilter {0}: {1}, array length: {2}, nRefs: {3}, depth: {4}, bytes: {5}, ex: {6}", status, clazz, arrayLength, totalObjectRefs, depth, bin.getBytesRead(), Objects.toString(ex, "n/a")); } InvalidClassException ice = new InvalidClassException("filter status: " + status); ice.initCause(ex); throw ice; } else { // Trace logging for those that succeed if (Logging.traceLogger != null) { Logging.traceLogger.finer( "ObjectInputFilter {0}: {1}, array length: {2}, nRefs: {3}, depth: {4}, bytes: {5}, ex: {6}", status, clazz, arrayLength, totalObjectRefs, depth, bin.getBytesRead(), Objects.toString(ex, "n/a")); } } } }
/** * ObjectInputFilter to filter DGC input objects. * The list of acceptable classes is very short and explicit. * The depth and array sizes are limited. * * @param filterInfo access to class, arrayLength, etc. * @return {@link ObjectInputFilter.Status#ALLOWED} if allowed, * {@link ObjectInputFilter.Status#REJECTED} if rejected, * otherwise {@link ObjectInputFilter.Status#UNDECIDED} */ private static ObjectInputFilter.Status checkInput(ObjectInputFilter.FilterInfo filterInfo) { if (dgcFilter != null) { ObjectInputFilter.Status status = dgcFilter.checkInput(filterInfo); if (status != ObjectInputFilter.Status.UNDECIDED) { // The DGC filter can override the built-in white-list return status; } } if (filterInfo.depth() > DGC_MAX_DEPTH) { return ObjectInputFilter.Status.REJECTED; } Class<?> clazz = filterInfo.serialClass(); if (clazz != null) { while (clazz.isArray()) { if (filterInfo.arrayLength() >= 0 && filterInfo.arrayLength() > DGC_MAX_ARRAY_SIZE) { return ObjectInputFilter.Status.REJECTED; } // Arrays are allowed depending on the component type clazz = clazz.getComponentType(); } if (clazz.isPrimitive()) { // Arrays of primitives are allowed return ObjectInputFilter.Status.ALLOWED; } return (clazz == ObjID.class || clazz == UID.class || clazz == VMID.class || clazz == Lease.class) ? ObjectInputFilter.Status.ALLOWED : ObjectInputFilter.Status.REJECTED; } // Not a class, not size limited return ObjectInputFilter.Status.UNDECIDED; }
/** * Construct a Unicast server remote reference to be exported * on the specified port. */ public UnicastServerRef2(int port, RMIClientSocketFactory csf, RMIServerSocketFactory ssf, ObjectInputFilter filter) { super(new LiveRef(port, csf, ssf), filter); }
/** * Test that MarshalledObject inherits the ObjectInputFilter from * the stream it was deserialized from. */ @Test(dataProvider="FilterCases") static void delegatesToMO(boolean withFilter) { try { Serializable testobj = Integer.valueOf(5); MarshalledObject<Serializable> mo = new MarshalledObject<>(testobj); Assert.assertEquals(mo.get(), testobj, "MarshalledObject.get returned a non-equals test object"); byte[] bytes = writeObjects(mo); try (ByteArrayInputStream bais = new ByteArrayInputStream(bytes); ObjectInputStream ois = new ObjectInputStream(bais)) { CountingFilter filter1 = new CountingFilter(); ObjectInputFilter.Config.setObjectInputFilter(ois, withFilter ? filter1 : null); MarshalledObject<?> actualMO = (MarshalledObject<?>)ois.readObject(); int count = filter1.getCount(); actualMO.get(); int expectedCount = withFilter ? count + 2 : count; int actualCount = filter1.getCount(); Assert.assertEquals(actualCount, expectedCount, "filter called wrong number of times during get()"); } } catch (IOException ioe) { Assert.fail("Unexpected IOException", ioe); } catch (ClassNotFoundException cnf) { Assert.fail("Deserializing", cnf); } }
/** * Test that the filter on a OIS can be set only on a fresh OIS, * before deserializing any objects. * This test is agnostic the global filter being set or not. */ @Test static void nonResettableFilter() { Validator validator1 = new Validator(); Validator validator2 = new Validator(); try { byte[] bytes = writeObjects("text1"); // an object try (ByteArrayInputStream bais = new ByteArrayInputStream(bytes); ObjectInputStream ois = new ObjectInputStream(bais)) { // Check the initial filter is the global filter; may be null ObjectInputFilter global = ObjectInputFilter.Config.getSerialFilter(); ObjectInputFilter initial = ObjectInputFilter.Config.getObjectInputFilter(ois); Assert.assertEquals(global, initial, "initial filter should be the global filter"); // Check if it can be set to null ObjectInputFilter.Config.setObjectInputFilter(ois, null); ObjectInputFilter filter = ObjectInputFilter.Config.getObjectInputFilter(ois); Assert.assertNull(filter, "set to null should be null"); ObjectInputFilter.Config.setObjectInputFilter(ois, validator1); Object o = ois.readObject(); try { ObjectInputFilter.Config.setObjectInputFilter(ois, validator2); Assert.fail("Should not be able to set filter twice"); } catch (IllegalStateException ise) { // success, the exception was expected } } catch (EOFException eof) { Assert.fail("Should not reach end-of-file", eof); } catch (ClassNotFoundException cnf) { Assert.fail("Deserializing", cnf); } } catch (IOException ex) { Assert.fail("Unexpected IOException", ex); } }
/** * Test repeated limits use the last value. * Construct a filter with the limit and the limit repeated -1. * Invoke the filter with the limit to make sure it is rejected. * Invoke the filter with the limit -1 to make sure it is accepted. * @param name the name of the limit to test * @param value a test value */ @Test(dataProvider="Limits") static void testLimits(String name, int value) { Class<?> arrayClass = new int[0].getClass(); String pattern = String.format("%s=%d;%s=%d", name, value, name, value - 1); ObjectInputFilter filter = ObjectInputFilter.Config.createFilter(pattern); Assert.assertEquals( filter.checkInput(new FilterValues(arrayClass, value, value, value, value)), ObjectInputFilter.Status.REJECTED, "last limit value not used: " + filter); Assert.assertEquals( filter.checkInput(new FilterValues(arrayClass, value-1, value-1, value-1, value-1)), ObjectInputFilter.Status.UNDECIDED, "last limit value not used: " + filter); }
/** * Test that returning null from a filter causes deserialization to fail. */ @Test(expectedExceptions=InvalidClassException.class) static void testNullStatus() throws IOException { byte[] bytes = writeObjects(0); // an Integer try { Object o = validate(bytes, new ObjectInputFilter() { public ObjectInputFilter.Status checkInput(ObjectInputFilter.FilterInfo f) { return null; } }); } catch (InvalidClassException ice) { System.out.printf("Success exception: %s%n", ice); throw ice; } }
/** * Verify that malformed patterns throw IAE. * @param pattern pattern from the data source */ @Test(dataProvider="InvalidPatterns", expectedExceptions=IllegalArgumentException.class) static void testInvalidPatterns(String pattern) { try { ObjectInputFilter.Config.createFilter(pattern); } catch (IllegalArgumentException iae) { System.out.printf(" success exception: %s%n", iae); throw iae; } }
/** * Test that Config.create returns null if the argument does not contain any patterns or limits. */ @Test() static void testEmptyPattern() { ObjectInputFilter filter = ObjectInputFilter.Config.createFilter(""); Assert.assertNull(filter, "empty pattern did not return null"); filter = ObjectInputFilter.Config.createFilter(";;;;"); Assert.assertNull(filter, "pattern with only delimiters did not return null"); }
/** * Create a filter from a pattern and API factory, then serialize and * deserialize an object and check allowed or reject. * * @param pattern the pattern * @param object the test object * @param allowed the expected result from ObjectInputStream (exception or not) */ static void testPatterns(String pattern, Object object, boolean allowed) { try { byte[] bytes = SerialFilterTest.writeObjects(object); ObjectInputFilter filter = ObjectInputFilter.Config.createFilter(pattern); validate(bytes, filter); Assert.assertTrue(allowed, "filter should have thrown an exception"); } catch (IllegalArgumentException iae) { Assert.fail("bad format pattern", iae); } catch (InvalidClassException ice) { Assert.assertFalse(allowed, "filter should not have thrown an exception: " + ice); } catch (IOException ioe) { Assert.fail("Unexpected IOException", ioe); } }
@Override public ObjectInputFilter.Status checkInput(FilterInfo filter) { if (ReadResolveToArray.class.isAssignableFrom(filter.serialClass())) { return ObjectInputFilter.Status.ALLOWED; } if (filter.serialClass() != array.getClass() || (filter.arrayLength() >= 0 && filter.arrayLength() != length)) { return ObjectInputFilter.Status.REJECTED; } return ObjectInputFilter.Status.UNDECIDED; }
/** * Test that the process-wide filter is set when the properties are set * and has the toString matching the configured pattern. */ @Test() static void globalFilter() { String pattern = System.getProperty("jdk.serialFilter", Security.getProperty("jdk.serialFilter")); ObjectInputFilter filter = ObjectInputFilter.Config.getSerialFilter(); System.out.printf("global pattern: %s, filter: %s%n", pattern, filter); Assert.assertEquals(pattern, Objects.toString(filter, null), "process-wide filter pattern does not match"); }
/** * Test: * "global filter reject" + "specific ObjectInputStream filter is empty" => should reject * "global filter reject" + "specific ObjectInputStream filter allow" => should allow */ @Test(dataProvider="Patterns") public void testRejectedInGlobal(Object toDeserialized, String pattern, boolean allowed) throws Exception { byte[] bytes = SerialFilterTest.writeObjects(toDeserialized); ObjectInputFilter filter = ObjectInputFilter.Config.createFilter(pattern); try (ByteArrayInputStream bais = new ByteArrayInputStream(bytes); ObjectInputStream ois = new ObjectInputStream(bais)) { ObjectInputFilter.Config.setObjectInputFilter(ois, filter); Object o = ois.readObject(); assertTrue(allowed, "filter should have thrown an exception"); } catch (InvalidClassException ice) { assertFalse(allowed, "filter should have thrown an exception"); } }
@BeforeClass public void setup() throws Exception { setSecurityManager = System.getSecurityManager() != null; Object toDeserialized = Long.MAX_VALUE; bytes = SerialFilterTest.writeObjects(toDeserialized); filter = ObjectInputFilter.Config.createFilter("java.lang.Long"); }
/** * Test that setting specific filter is checked by security manager. */ @Test(dependsOnMethods = { "testGlobalFilter" }) public void testSpecificFilter() throws Exception { try (ByteArrayInputStream bais = new ByteArrayInputStream(bytes); ObjectInputStream ois = new ObjectInputStream(bais)) { ObjectInputFilter.Config.setObjectInputFilter(ois, filter); Object o = ois.readObject(); } catch (AccessControlException ex) { assertTrue(setSecurityManager); assertTrue(ex.getMessage().contains("java.io.SerializablePermission")); assertTrue(ex.getMessage().contains("serialFilter")); } }
@Override public Status checkInput(ObjectInputFilter.FilterInfo info) { if (info.serialClass() == RejectME.class) { return Status.REJECTED; } count++; return Status.UNDECIDED; }