8264859: Implement Context-Specific Deserialization Filters
Reviewed-by: bchristi, dfuchs, chegar
This commit is contained in:
parent
dd34a4c28d
commit
13d6180421
File diff suppressed because it is too large
Load Diff
@ -25,6 +25,7 @@
|
||||
|
||||
package java.io;
|
||||
|
||||
import java.io.ObjectInputFilter.Config;
|
||||
import java.io.ObjectStreamClass.WeakClassKey;
|
||||
import java.io.ObjectStreamClass.RecordSupport;
|
||||
import java.lang.System.Logger;
|
||||
@ -66,6 +67,23 @@ import sun.security.action.GetIntegerAction;
|
||||
* practices for defensive use of serial filters.
|
||||
* </strong></p>
|
||||
*
|
||||
* <p>The key to disabling deserialization attacks is to prevent instances of
|
||||
* arbitrary classes from being deserialized, thereby preventing the direct or
|
||||
* indirect execution of their methods.
|
||||
* {@link ObjectInputFilter} describes how to use filters and
|
||||
* {@link ObjectInputFilter.Config} describes how to configure the filter and filter factory.
|
||||
* Each stream has an optional deserialization filter
|
||||
* to check the classes and resource limits during deserialization.
|
||||
* The JVM-wide filter factory ensures that a filter can be set on every {@link ObjectInputStream}
|
||||
* and every object read from the stream can be checked.
|
||||
* The {@linkplain #ObjectInputStream() ObjectInputStream constructors} invoke the filter factory
|
||||
* to select the initial filter which may be updated or replaced by {@link #setObjectInputFilter}.
|
||||
* <p>
|
||||
* If an ObjectInputStream has a filter, the {@link ObjectInputFilter} can check that
|
||||
* the classes, array lengths, number of references in the stream, depth, and
|
||||
* number of bytes consumed from the input stream are allowed and
|
||||
* if not, can terminate deserialization.
|
||||
*
|
||||
* <p>ObjectOutputStream and ObjectInputStream can provide an application with
|
||||
* persistent storage for graphs of objects when used with a FileOutputStream
|
||||
* and FileInputStream respectively. ObjectInputStream is used to recover
|
||||
@ -188,16 +206,6 @@ import sun.security.action.GetIntegerAction;
|
||||
* protected) or that there are get and set methods that can be used to restore
|
||||
* the state.
|
||||
*
|
||||
* <p>The contents of the stream can be filtered during deserialization.
|
||||
* If a {@linkplain #setObjectInputFilter(ObjectInputFilter) filter is set}
|
||||
* on an ObjectInputStream, the {@link ObjectInputFilter} can check that
|
||||
* the classes, array lengths, number of references in the stream, depth, and
|
||||
* number of bytes consumed from the input stream are allowed and
|
||||
* if not, can terminate deserialization.
|
||||
* A {@linkplain ObjectInputFilter.Config#setSerialFilter(ObjectInputFilter) system-wide filter}
|
||||
* can be configured that is applied to each {@code ObjectInputStream} unless replaced
|
||||
* using {@link #setObjectInputFilter(ObjectInputFilter) setObjectInputFilter}.
|
||||
*
|
||||
* <p>Any exception that occurs while deserializing an object will be caught by
|
||||
* the ObjectInputStream and abort the reading process.
|
||||
*
|
||||
@ -347,14 +355,20 @@ public class ObjectInputStream
|
||||
*/
|
||||
private ObjectInputFilter serialFilter;
|
||||
|
||||
/**
|
||||
* True if the stream-specific filter has been set; initially false.
|
||||
*/
|
||||
private boolean streamFilterSet;
|
||||
|
||||
/**
|
||||
* Creates an ObjectInputStream that reads from the specified InputStream.
|
||||
* A serialization stream header is read from the stream and verified.
|
||||
* This constructor will block until the corresponding ObjectOutputStream
|
||||
* has written and flushed the header.
|
||||
*
|
||||
* <p>The serialization filter is initialized to the value of
|
||||
* {@linkplain ObjectInputFilter.Config#getSerialFilter() the system-wide filter}.
|
||||
* <p>The constructor initializes the deserialization filter to the filter returned
|
||||
* by invoking the {@link Config#getSerialFilterFactory()} with {@code null} for the current filter
|
||||
* and the {@linkplain Config#getSerialFilter() static JVM-wide filter} for the requested filter.
|
||||
*
|
||||
* <p>If a security manager is installed, this constructor will check for
|
||||
* the "enableSubclassImplementation" SerializablePermission when invoked
|
||||
@ -377,7 +391,8 @@ public class ObjectInputStream
|
||||
bin = new BlockDataInputStream(in);
|
||||
handles = new HandleTable(10);
|
||||
vlist = new ValidationList();
|
||||
serialFilter = ObjectInputFilter.Config.getSerialFilter();
|
||||
streamFilterSet = false;
|
||||
serialFilter = Config.getSerialFilterFactorySingleton().apply(null, Config.getSerialFilter());
|
||||
enableOverride = false;
|
||||
readStreamHeader();
|
||||
bin.setBlockDataMode(true);
|
||||
@ -388,8 +403,9 @@ public class ObjectInputStream
|
||||
* ObjectInputStream to not have to allocate private data just used by this
|
||||
* implementation of ObjectInputStream.
|
||||
*
|
||||
* <p>The serialization filter is initialized to the value of
|
||||
* {@linkplain ObjectInputFilter.Config#getSerialFilter() the system-wide filter}.
|
||||
* <p>The constructor initializes the deserialization filter to the filter returned
|
||||
* by invoking the {@link Config#getSerialFilterFactory()} with {@code null} for the current filter
|
||||
* and the {@linkplain Config#getSerialFilter() static JVM-wide filter} for the requested filter.
|
||||
*
|
||||
* <p>If there is a security manager installed, this method first calls the
|
||||
* security manager's {@code checkPermission} method with the
|
||||
@ -412,7 +428,8 @@ public class ObjectInputStream
|
||||
bin = null;
|
||||
handles = null;
|
||||
vlist = null;
|
||||
serialFilter = ObjectInputFilter.Config.getSerialFilter();
|
||||
streamFilterSet = false;
|
||||
serialFilter = Config.getSerialFilterFactorySingleton().apply(null, Config.getSerialFilter());
|
||||
enableOverride = true;
|
||||
}
|
||||
|
||||
@ -431,7 +448,7 @@ public class ObjectInputStream
|
||||
* priorities. The callbacks are registered by objects (in the readObject
|
||||
* special methods) as they are individually restored.
|
||||
*
|
||||
* <p>The serialization filter, when not {@code null}, is invoked for
|
||||
* <p>The deserialization filter, when not {@code null}, is invoked for
|
||||
* each object (regular or class) read to reconstruct the root object.
|
||||
* See {@link #setObjectInputFilter(ObjectInputFilter) setObjectInputFilter} for details.
|
||||
*
|
||||
@ -443,7 +460,7 @@ public class ObjectInputStream
|
||||
* @throws ClassNotFoundException Class of a serialized object cannot be
|
||||
* found.
|
||||
* @throws InvalidClassException Something is wrong with a class used by
|
||||
* serialization.
|
||||
* deserialization.
|
||||
* @throws StreamCorruptedException Control information in the
|
||||
* stream is inconsistent.
|
||||
* @throws OptionalDataException Primitive data was found in the
|
||||
@ -564,7 +581,7 @@ public class ObjectInputStream
|
||||
* invocation of readObject or readUnshared on the ObjectInputStream,
|
||||
* even if the underlying data stream has been manipulated.
|
||||
*
|
||||
* <p>The serialization filter, when not {@code null}, is invoked for
|
||||
* <p>The deserialization filter, when not {@code null}, is invoked for
|
||||
* each object (regular or class) read to reconstruct the root object.
|
||||
* See {@link #setObjectInputFilter(ObjectInputFilter) setObjectInputFilter} for details.
|
||||
*
|
||||
@ -872,7 +889,7 @@ public class ObjectInputStream
|
||||
* <p>When a subclass is replacing objects it must insure that the
|
||||
* substituted object is compatible with every field where the reference
|
||||
* will be stored. Objects whose type is not a subclass of the type of the
|
||||
* field or array element abort the serialization by raising an exception
|
||||
* field or array element abort the deserialization by raising an exception
|
||||
* and the object is not be stored.
|
||||
*
|
||||
* <p>This method is called only once when each object is first
|
||||
@ -1223,13 +1240,13 @@ public class ObjectInputStream
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the serialization filter for this stream.
|
||||
* The serialization filter is the most recent filter set in
|
||||
* {@link #setObjectInputFilter setObjectInputFilter} or
|
||||
* the initial system-wide filter from
|
||||
* {@link ObjectInputFilter.Config#getSerialFilter() ObjectInputFilter.Config.getSerialFilter}.
|
||||
* Returns the deserialization filter for this stream.
|
||||
* The filter is the result of invoking the
|
||||
* {@link Config#getSerialFilterFactory() JVM-wide filter factory}
|
||||
* either by the {@linkplain #ObjectInputStream() constructor} or the most recent invocation of
|
||||
* {@link #setObjectInputFilter setObjectInputFilter}.
|
||||
*
|
||||
* @return the serialization filter for the stream; may be null
|
||||
* @return the deserialization filter for the stream; may be null
|
||||
* @since 9
|
||||
*/
|
||||
public final ObjectInputFilter getObjectInputFilter() {
|
||||
@ -1237,8 +1254,23 @@ public class ObjectInputStream
|
||||
}
|
||||
|
||||
/**
|
||||
* Set the serialization filter for the stream.
|
||||
* The filter's {@link ObjectInputFilter#checkInput checkInput} method is called
|
||||
* Set the deserialization filter for the stream.
|
||||
*
|
||||
* The deserialization filter is set to the filter returned by invoking the
|
||||
* {@linkplain Config#getSerialFilterFactory() JVM-wide filter factory}
|
||||
* with the {@linkplain #getObjectInputFilter() current filter} and the {@code filter} parameter.
|
||||
* The current filter was set in the
|
||||
* {@linkplain #ObjectInputStream() ObjectInputStream constructors} by invoking the
|
||||
* {@linkplain Config#getSerialFilterFactory() JVM-wide filter factory} and may be {@code null}.
|
||||
* {@linkplain #setObjectInputFilter(ObjectInputFilter)} This method} can be called
|
||||
* once and only once before reading any objects from the stream;
|
||||
* for example, by calling {@link #readObject} or {@link #readUnshared}.
|
||||
*
|
||||
* <p>It is not permitted to replace a {@code non-null} filter with a {@code null} filter.
|
||||
* If the {@linkplain #getObjectInputFilter() current filter} is {@code non-null},
|
||||
* the value returned from the filter factory must be {@code non-null}.
|
||||
*
|
||||
* <p>The filter's {@link ObjectInputFilter#checkInput checkInput} method is called
|
||||
* for each class and reference in the stream.
|
||||
* The filter can check any or all of the class, the array length, the number
|
||||
* of references, the depth of the graph, and the size of the input stream.
|
||||
@ -1247,21 +1279,14 @@ public class ObjectInputStream
|
||||
* and the current object being deserialized.
|
||||
* The number of references is the cumulative number of objects and references
|
||||
* to objects already read from the stream including the current object being read.
|
||||
* The filter is invoked only when reading objects from the stream and for
|
||||
* not primitives.
|
||||
* The filter is invoked only when reading objects from the stream and not for
|
||||
* primitives.
|
||||
* <p>
|
||||
* If the filter returns {@link ObjectInputFilter.Status#REJECTED Status.REJECTED},
|
||||
* {@code null} or throws a {@link RuntimeException},
|
||||
* the active {@code readObject} or {@code readUnshared}
|
||||
* throws {@link InvalidClassException}, otherwise deserialization
|
||||
* continues uninterrupted.
|
||||
* <p>
|
||||
* The serialization filter is initialized to the value of
|
||||
* {@link ObjectInputFilter.Config#getSerialFilter() ObjectInputFilter.Config.getSerialFilter}
|
||||
* when the {@code ObjectInputStream} is constructed and can be set
|
||||
* to a custom filter only once.
|
||||
* The filter must be set before reading any objects from the stream;
|
||||
* for example, by calling {@link #readObject} or {@link #readUnshared}.
|
||||
*
|
||||
* @implSpec
|
||||
* The filter, when not {@code null}, is invoked during {@link #readObject readObject}
|
||||
@ -1303,9 +1328,10 @@ public class ObjectInputStream
|
||||
* @param filter the filter, may be null
|
||||
* @throws SecurityException if there is security manager and the
|
||||
* {@code SerializablePermission("serialFilter")} is not granted
|
||||
* @throws IllegalStateException if the {@linkplain #getObjectInputFilter() current filter}
|
||||
* is not {@code null} and is not the system-wide filter, or
|
||||
* if an object has been read
|
||||
* @throws IllegalStateException if an object has been read,
|
||||
* if the filter factory returns {@code null} when the
|
||||
* {@linkplain #getObjectInputFilter() current filter} is non-null, or
|
||||
* if the filter has already been set.
|
||||
* @since 9
|
||||
*/
|
||||
public final void setObjectInputFilter(ObjectInputFilter filter) {
|
||||
@ -1314,20 +1340,25 @@ public class ObjectInputStream
|
||||
if (sm != null) {
|
||||
sm.checkPermission(ObjectStreamConstants.SERIAL_FILTER_PERMISSION);
|
||||
}
|
||||
// Allow replacement of the system-wide filter if not already set
|
||||
if (serialFilter != null &&
|
||||
serialFilter != ObjectInputFilter.Config.getSerialFilter()) {
|
||||
throw new IllegalStateException("filter can not be set more than once");
|
||||
}
|
||||
if (totalObjectRefs > 0 && !Caches.SET_FILTER_AFTER_READ) {
|
||||
throw new IllegalStateException(
|
||||
"filter can not be set after an object has been read");
|
||||
}
|
||||
this.serialFilter = filter;
|
||||
if (streamFilterSet) {
|
||||
throw new IllegalStateException("filter can not be set more than once");
|
||||
}
|
||||
streamFilterSet = true;
|
||||
// Delegate to serialFilterFactory to compute stream filter
|
||||
ObjectInputFilter next = Config.getSerialFilterFactory()
|
||||
.apply(serialFilter, filter);
|
||||
if (serialFilter != null && next == null) {
|
||||
throw new IllegalStateException("filter can not be replaced with null filter");
|
||||
}
|
||||
serialFilter = next;
|
||||
}
|
||||
|
||||
/**
|
||||
* Invokes the serialization filter if non-null.
|
||||
* Invokes the deserialization filter if non-null.
|
||||
*
|
||||
* If the filter rejects or an exception is thrown, throws InvalidClassException.
|
||||
*
|
||||
|
@ -47,6 +47,7 @@ public final class StaticProperty {
|
||||
private static final String JAVA_LIBRARY_PATH;
|
||||
private static final String SUN_BOOT_LIBRARY_PATH;
|
||||
private static final String JDK_SERIAL_FILTER;
|
||||
private static final String JDK_SERIAL_FILTER_FACTORY;
|
||||
private static final String JAVA_IO_TMPDIR;
|
||||
private static final String NATIVE_ENCODING;
|
||||
|
||||
@ -62,6 +63,7 @@ public final class StaticProperty {
|
||||
JAVA_LIBRARY_PATH = getProperty(props, "java.library.path", "");
|
||||
SUN_BOOT_LIBRARY_PATH = getProperty(props, "sun.boot.library.path", "");
|
||||
JDK_SERIAL_FILTER = getProperty(props, "jdk.serialFilter", null);
|
||||
JDK_SERIAL_FILTER_FACTORY = getProperty(props, "jdk.serialFilterFactory", null);
|
||||
NATIVE_ENCODING = getProperty(props, "native.encoding");
|
||||
}
|
||||
|
||||
@ -184,6 +186,20 @@ public final class StaticProperty {
|
||||
return JDK_SERIAL_FILTER;
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Return the {@code jdk.serialFilterFactory} system property.
|
||||
*
|
||||
* <strong>{@link SecurityManager#checkPropertyAccess} is NOT checked
|
||||
* in this method. The caller of this method should take care to ensure
|
||||
* that the returned property is not made accessible to untrusted code.</strong>
|
||||
*
|
||||
* @return the {@code user.name} system property
|
||||
*/
|
||||
public static String jdkSerialFilterFactory() {
|
||||
return JDK_SERIAL_FILTER_FACTORY;
|
||||
}
|
||||
|
||||
/**
|
||||
* Return the {@code native.encoding} system property.
|
||||
*
|
||||
|
@ -979,11 +979,29 @@ jdk.xml.dsig.secureValidationPolicy=\
|
||||
noDuplicateIds,\
|
||||
noRetrievalMethodLoops
|
||||
|
||||
|
||||
#
|
||||
# Serialization system-wide filter
|
||||
# Deserialization system-wide filter factory
|
||||
#
|
||||
# A filter, if configured, is used by java.io.ObjectInputStream during
|
||||
# deserialization to check the contents of the stream.
|
||||
# A filter factory class name is used to configure the system-wide filter factory.
|
||||
# The filter factory value "OVERRIDE" in combination with setting "jdk.serialFilter"
|
||||
# indicates that the builtin filter factory can be overridden by the application.
|
||||
# The class must be public, must have a public zero-argument constructor, implement the
|
||||
# java.util.stream.BinaryOperator<ObjectInputFilter> interface, provide its implementation and
|
||||
# be accessible via the application class loader.
|
||||
# A builtin filter factory is used if no filter factory is defined.
|
||||
# See java.io.ObjectInputFilter.Config for more information.
|
||||
#
|
||||
# If the system property jdk.serialFilterFactory is also specified, it supersedes
|
||||
# the security property value defined here.
|
||||
#
|
||||
#jdk.serialFilterFactory=<classname>
|
||||
|
||||
#
|
||||
# Deserialization system-wide filter
|
||||
#
|
||||
# A filter, if configured, is used by the filter factory to provide the filter used by
|
||||
# java.io.ObjectInputStream during deserialization to check the contents of the stream.
|
||||
# A filter is configured as a sequence of patterns, each pattern is either
|
||||
# matched against the name of a class in the stream or defines a limit.
|
||||
# Patterns are separated by ";" (semicolon).
|
||||
|
@ -0,0 +1,887 @@
|
||||
/*
|
||||
* Copyright (c) 2021, Oracle and/or its affiliates. All rights reserved.
|
||||
* DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
|
||||
*
|
||||
* This code is free software; you can redistribute it and/or modify it
|
||||
* under the terms of the GNU General Public License version 2 only, as
|
||||
* published by the Free Software Foundation.
|
||||
*
|
||||
* This code is distributed in the hope that it will be useful, but WITHOUT
|
||||
* ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
|
||||
* FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
|
||||
* version 2 for more details (a copy is included in the LICENSE file that
|
||||
* accompanied this code).
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License version
|
||||
* 2 along with this work; if not, write to the Free Software Foundation,
|
||||
* Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
|
||||
*
|
||||
* Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
|
||||
* or visit www.oracle.com if you need additional information or have any
|
||||
* questions.
|
||||
*/
|
||||
|
||||
import org.testng.Assert;
|
||||
import org.testng.annotations.DataProvider;
|
||||
import org.testng.annotations.Test;
|
||||
|
||||
import java.io.ByteArrayInputStream;
|
||||
import java.io.ByteArrayOutputStream;
|
||||
import java.io.IOException;
|
||||
import java.io.InputStream;
|
||||
import java.io.InvalidClassException;
|
||||
import java.io.ObjectInputFilter;
|
||||
import java.io.ObjectInputFilter.Status;
|
||||
import java.io.ObjectInputStream;
|
||||
import java.io.ObjectOutputStream;
|
||||
import java.io.Serializable;
|
||||
import java.io.UncheckedIOException;
|
||||
import java.util.ArrayDeque;
|
||||
import java.util.List;
|
||||
import java.util.Objects;
|
||||
import java.util.function.BinaryOperator;
|
||||
import java.util.function.Predicate;
|
||||
|
||||
import static java.io.ObjectInputFilter.Status.ALLOWED;
|
||||
import static java.io.ObjectInputFilter.Status.REJECTED;
|
||||
import static java.io.ObjectInputFilter.Status.UNDECIDED;
|
||||
|
||||
/* @test
|
||||
* @run testng/othervm -Djdk.serialFilterTrace=true SerialFactoryExample
|
||||
* @run testng/othervm -Djdk.serialFilterFactory=SerialFactoryExample$FilterInThread -Djdk.serialFilterTrace=true SerialFactoryExample
|
||||
* @summary Test SerialFactoryExample
|
||||
*/
|
||||
|
||||
/*
|
||||
* Context-specific Deserialization Filter Example
|
||||
*
|
||||
* To protect deserialization of a thread or a call to an untrusted library function,
|
||||
* a filter is set that applies to every deserialization within the thread.
|
||||
*
|
||||
* The `doWithSerialFilter` method arguments are a serial filter and
|
||||
* a lambda to invoke with the filter in force. Its implementation creates a stack of filters
|
||||
* using a `ThreadLocal`. The stack of filters is composed with the static JVM-wide filter,
|
||||
* and an optional stream-specific filter.
|
||||
*
|
||||
* The FilterInThread filter factory is set as the JVM-wide filter factory.
|
||||
* When the filter factory is invoked during the construction of each `ObjectInputStream`,
|
||||
* it retrieves the filter(s) from the thread local and combines it with the static JVM-wide filter,
|
||||
* and the stream-specific filter.
|
||||
*
|
||||
* If more than one filter is to be applied to the stream, two filters can be composed
|
||||
* using `ObjectInputFilter.merge`. When invoked, each of the filters is invoked and the results
|
||||
* are combined such that if either filter rejects a class, the result is rejected.
|
||||
* If either filter allows the class, then it is allowed, otherwise it is undecided.
|
||||
* Hierarchies and chains of filters can be built using `ObjectInputFilter.merge`.
|
||||
*
|
||||
* The `doWithSerialFilter` calls can be nested. When nested, the filters are concatenated.
|
||||
*/
|
||||
@Test
|
||||
public class SerialFactoryExample {
|
||||
|
||||
@DataProvider(name = "Examples")
|
||||
static Object[][] examples() {
|
||||
return new Object[][]{
|
||||
{new Point(1, 2), null,
|
||||
ALLOWED},
|
||||
{new Point(1, 2), ObjectInputFilter.Config.createFilter("SerialFactoryExample$Point"),
|
||||
ALLOWED},
|
||||
{Integer.valueOf(10), Filters.allowPlatformClasses(),
|
||||
ALLOWED}, // Integer is a platform class
|
||||
{new int[10], ObjectInputFilter.Config.createFilter("SerialFactoryExample$Point"),
|
||||
UNDECIDED}, // arrays of primitives are UNDECIDED -> allowed
|
||||
{int.class, ObjectInputFilter.Config.createFilter("SerialFactoryExample$Point"),
|
||||
UNDECIDED}, // primitive classes are UNDECIDED -> allowed
|
||||
{new Point[] {new Point(1, 1)}, ObjectInputFilter.Config.createFilter("SerialFactoryExample$Point"),
|
||||
ALLOWED}, // Arrays of allowed classes are allowed
|
||||
{new Integer[10], ObjectInputFilter.Config.createFilter("SerialFactoryExample$Point"),
|
||||
REJECTED}, // Base component type is checked -> REJECTED
|
||||
{new Point(1, 2), ObjectInputFilter.Config.createFilter("!SerialFactoryExample$Point"),
|
||||
REJECTED}, // Denied
|
||||
{new Point(1, 3), Filters.allowPlatformClasses(),
|
||||
REJECTED}, // Not a platform class
|
||||
{new Point(1, 4), ObjectInputFilter.Config.createFilter("java.lang.Integer"),
|
||||
REJECTED}, // Only Integer is ALLOWED
|
||||
{new Point(1, 5), ObjectInputFilter.allowFilter(cl -> cl.getClassLoader() == ClassLoader.getPlatformClassLoader(), UNDECIDED),
|
||||
REJECTED}, // Not platform loader is UNDECIDED -> a class that should not be undecided -> rejected
|
||||
};
|
||||
}
|
||||
|
||||
|
||||
@Test(dataProvider = "Examples")
|
||||
void examples(Serializable obj, ObjectInputFilter filter, Status expected) {
|
||||
// Establish FilterInThread as the application-wide filter factory
|
||||
FilterInThread filterInThread;
|
||||
if (ObjectInputFilter.Config.getSerialFilterFactory() instanceof FilterInThread fit) {
|
||||
// Filter factory selected on the command line with -Djdk.serialFilterFactory=<classname>
|
||||
filterInThread = fit;
|
||||
} else {
|
||||
// Create a FilterInThread filter factory and set
|
||||
// An IllegalStateException will be thrown if the filter factory was already
|
||||
// initialized to an incompatible filter factory.
|
||||
filterInThread = new FilterInThread();
|
||||
ObjectInputFilter.Config.setSerialFilterFactory(filterInThread);
|
||||
}
|
||||
try {
|
||||
filterInThread.doWithSerialFilter(filter, () -> {
|
||||
byte[] bytes = writeObject(obj);
|
||||
Object o = deserializeObject(bytes);
|
||||
});
|
||||
if (expected.equals(REJECTED))
|
||||
Assert.fail("IllegalClassException should have occurred");
|
||||
} catch (UncheckedIOException uioe) {
|
||||
IOException ioe = uioe.getCause();
|
||||
Assert.assertEquals(ioe.getClass(), InvalidClassException.class, "Wrong exception");
|
||||
Assert.assertEquals(REJECTED, expected, "Exception should not have occurred");
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Test various filters with various objects and the resulting status
|
||||
* @param obj an object
|
||||
* @param filter a filter
|
||||
* @param expected status
|
||||
*/
|
||||
@Test(dataProvider = "Examples")
|
||||
void checkStatus(Serializable obj, ObjectInputFilter filter, Status expected) {
|
||||
// Establish FilterInThread as the application-wide filter factory
|
||||
FilterInThread filterInThread;
|
||||
if (ObjectInputFilter.Config.getSerialFilterFactory() instanceof FilterInThread fit) {
|
||||
// Filter factory selected on the command line with -Djdk.serialFilterFactory=<classname>
|
||||
filterInThread = fit;
|
||||
} else {
|
||||
// Create a FilterInThread filter factory and set
|
||||
// An IllegalStateException will be thrown if the filter factory was already
|
||||
// initialized to an incompatible filter factory.
|
||||
filterInThread = new FilterInThread();
|
||||
ObjectInputFilter.Config.setSerialFilterFactory(filterInThread);
|
||||
}
|
||||
|
||||
try {
|
||||
filterInThread.doWithSerialFilter(filter, () -> {
|
||||
// Classes are serialized as themselves, otherwise pass the object's class
|
||||
Class<?> clazz = (obj instanceof Class<?>) ? (Class<?>)obj : obj.getClass();
|
||||
ObjectInputFilter.FilterInfo info = new SerialInfo(clazz);
|
||||
var compositeFilter = filterInThread.apply(null, ObjectInputFilter.Config.getSerialFilter());
|
||||
System.out.println(" filter in effect: " + filterInThread.currFilter);
|
||||
if (compositeFilter != null) {
|
||||
Status actualStatus = compositeFilter.checkInput(info);
|
||||
Assert.assertEquals(actualStatus, expected, "Wrong Status");
|
||||
}
|
||||
});
|
||||
|
||||
} catch (Exception ex) {
|
||||
Assert.fail("unexpected exception", ex);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* A Context-specific Deserialization Filter Factory to create filters that apply
|
||||
* a serial filter to all of the deserializations performed in a thread.
|
||||
* The purpose is to establish a deserialization filter that will reject all classes
|
||||
* that are not explicitly included.
|
||||
* <p>
|
||||
* The filter factory creates a composite filter of the stream-specific filter,
|
||||
* the thread-specific filter, the static JVM-wide filter, and a filter to reject all UNDECIDED cases.
|
||||
* The static JVM-wide filter is always included, if it is configured;
|
||||
* see ObjectInputFilter.Config.getSerialFilter().
|
||||
* <p>
|
||||
* To enable these protections the FilterInThread instance should be set as the
|
||||
* JVM-wide filter factory in ObjectInputFilter.Config.setSerialFilterFactory.
|
||||
*
|
||||
* The {@code doWithSerialFilter} is invoked with a serial filter and a lambda
|
||||
* to be invoked after the filter is applied.
|
||||
*/
|
||||
public static final class FilterInThread
|
||||
implements BinaryOperator<ObjectInputFilter> {
|
||||
|
||||
// ThreadLocal holding the Deque of serial filters to be applied, not null
|
||||
private final ThreadLocal<ArrayDeque<ObjectInputFilter>> filterThreadLocal =
|
||||
ThreadLocal.withInitial(() -> new ArrayDeque<>());
|
||||
|
||||
private ObjectInputFilter currFilter;
|
||||
|
||||
/**
|
||||
* Construct a FilterInThread deserialization filter factory.
|
||||
* The constructor is public so FilterInThread can be set on the command line
|
||||
* with {@code -Djdk.serialFilterFactory=SerialFactoryExample$FilterInThread}.
|
||||
*/
|
||||
public FilterInThread() {
|
||||
}
|
||||
|
||||
/**
|
||||
* Applies the filter to the thread and invokes the runnable.
|
||||
* The filter is pushed to a ThreadLocal, saving the old value.
|
||||
* If there was a previous thread filter, the new filter is appended
|
||||
* and made the active filter.
|
||||
* The runnable is invoked.
|
||||
* The previous filter is restored to the ThreadLocal.
|
||||
*
|
||||
* @param filter the serial filter to apply
|
||||
* @param runnable a runnable to invoke
|
||||
*/
|
||||
public void doWithSerialFilter(ObjectInputFilter filter, Runnable runnable) {
|
||||
var prevFilters = filterThreadLocal.get();
|
||||
try {
|
||||
if (filter != null)
|
||||
prevFilters.addLast(filter);
|
||||
runnable.run();
|
||||
} finally {
|
||||
if (filter != null) {
|
||||
var lastFilter = prevFilters.removeLast();
|
||||
assert lastFilter == filter : "Filter removed out of order";
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns a composite filter of the stream-specific filter, the thread-specific filter,
|
||||
* the static JVM-wide filter, and a filter to reject all UNDECIDED cases.
|
||||
* The purpose is to establish a deserialization filter that will reject all classes
|
||||
* that are not explicitly included.
|
||||
* The static JVM-wide filter is always checked, if it is configured;
|
||||
* see ObjectInputFilter.Config.getSerialFilter().
|
||||
* Any or all of the filters are optional and if not supplied or configured are null.
|
||||
* <p>
|
||||
* This method is first called from the constructor with current == null and
|
||||
* next == static JVM-wide filter.
|
||||
* The filter returned is the static JVM-wide filter merged with the thread-specific filter
|
||||
* and followed by a filter to map all UNDECIDED status values to REJECTED.
|
||||
* This last step ensures that the collective group of filters covers every possible case,
|
||||
* any classes that are not ALLOWED will be REJECTED.
|
||||
* <p>
|
||||
* The method may be called a second time from {@code ObjectInputStream.setObjectInputFilter(next)}
|
||||
* to add a stream-specific filter. The stream-specific filter is prepended to the
|
||||
* composite filter created above when called from the constructor.
|
||||
* <p>
|
||||
*
|
||||
* @param curr the current filter, may be null
|
||||
* @param next the next filter, may be null
|
||||
* @return a deserialization filter to use for the stream, may be null
|
||||
*/
|
||||
public ObjectInputFilter apply(ObjectInputFilter curr, ObjectInputFilter next) {
|
||||
if (curr == null) {
|
||||
// Called from the OIS constructor or perhaps OIS.setObjectInputFilter with no current filter
|
||||
// no current filter, prepend next to threadFilter, both may be null or non-null
|
||||
|
||||
// Assemble the filters in sequence, most recently added first
|
||||
var filters = filterThreadLocal.get();
|
||||
ObjectInputFilter filter = null;
|
||||
for (ObjectInputFilter f : filters) {
|
||||
filter = ObjectInputFilter.merge(f, filter);
|
||||
}
|
||||
if (next != null) {
|
||||
// Prepend a filter to reject all UNDECIDED results
|
||||
if (filter != null) {
|
||||
filter = ObjectInputFilter.rejectUndecidedClass(filter);
|
||||
}
|
||||
|
||||
// Prepend the next filter to the thread filter, if any
|
||||
// Initially this would be the static JVM-wide filter passed from the OIS constructor
|
||||
// The static JVM-wide filter allow, reject, or leave classes undecided
|
||||
filter = ObjectInputFilter.merge(next, filter);
|
||||
}
|
||||
// Check that the static JVM-wide filter did not leave any classes undecided
|
||||
if (filter != null) {
|
||||
// Append the filter to reject all UNDECIDED results
|
||||
filter = ObjectInputFilter.rejectUndecidedClass(filter);
|
||||
}
|
||||
// Return the filter, unless a stream-specific filter is set later
|
||||
// The filter may be null if no filters are configured
|
||||
currFilter = filter;
|
||||
return currFilter;
|
||||
} else {
|
||||
// Called from OIS.setObjectInputFilter with a previously set filter.
|
||||
// The curr filter already incorporates the thread filter and rejection of undecided status
|
||||
// Prepend the stream-specific filter or the current filter if no stream-specific filter
|
||||
currFilter = (next == null) ? curr : ObjectInputFilter.rejectUndecidedClass(ObjectInputFilter.merge(next, curr));
|
||||
return currFilter;
|
||||
}
|
||||
}
|
||||
|
||||
public String toString() {
|
||||
return Objects.toString(currFilter, "none");
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Simple example code from the ObjectInputFilter Class javadoc.
|
||||
*/
|
||||
public static final class SimpleFilterInThread implements BinaryOperator<ObjectInputFilter> {
|
||||
|
||||
// ThreadLocal to hold the serial filter to be applied
|
||||
private final ThreadLocal<ObjectInputFilter> filterThreadLocal = new ThreadLocal<>();
|
||||
|
||||
// Construct a FilterInThread deserialization filter factory.
|
||||
public SimpleFilterInThread() {}
|
||||
|
||||
/**
|
||||
* The filter factory, which is invoked every time a new ObjectInputStream
|
||||
* is created. If a per-stream filter is already set then it returns a
|
||||
* filter that combines the results of invoking each filter.
|
||||
*
|
||||
* @param curr the current filter on the stream
|
||||
* @param next a per stream filter
|
||||
* @return the selected filter
|
||||
*/
|
||||
public ObjectInputFilter apply(ObjectInputFilter curr, ObjectInputFilter next) {
|
||||
if (curr == null) {
|
||||
// Called from the OIS constructor or perhaps OIS.setObjectInputFilter with no current filter
|
||||
var filter = filterThreadLocal.get();
|
||||
if (filter != null) {
|
||||
// Prepend a filter to reject all UNDECIDED results
|
||||
filter = ObjectInputFilter.rejectUndecidedClass(filter);
|
||||
}
|
||||
if (next != null) {
|
||||
// Prepend the next filter to the thread filter, if any
|
||||
// Initially this is the static JVM-wide filter passed from the OIS constructor
|
||||
// Append the filter to reject all UNDECIDED results
|
||||
filter = ObjectInputFilter.merge(next, filter);
|
||||
filter = ObjectInputFilter.rejectUndecidedClass(filter);
|
||||
}
|
||||
return filter;
|
||||
} else {
|
||||
// Called from OIS.setObjectInputFilter with a current filter and a stream-specific filter.
|
||||
// The curr filter already incorporates the thread filter and static JVM-wide filter
|
||||
// and rejection of undecided classes
|
||||
// If there is a stream-specific filter prepend it and a filter to recheck for undecided
|
||||
if (next != null) {
|
||||
next = ObjectInputFilter.merge(next, curr);
|
||||
next = ObjectInputFilter.rejectUndecidedClass(next);
|
||||
return next;
|
||||
}
|
||||
return curr;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Applies the filter to the thread and invokes the runnable.
|
||||
*
|
||||
* @param filter the serial filter to apply to every deserialization in the thread
|
||||
* @param runnable a Runnable to invoke
|
||||
*/
|
||||
public void doWithSerialFilter(ObjectInputFilter filter, Runnable runnable) {
|
||||
var prevFilter = filterThreadLocal.get();
|
||||
try {
|
||||
filterThreadLocal.set(filter);
|
||||
runnable.run();
|
||||
} finally {
|
||||
filterThreadLocal.set(prevFilter);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Write an object and return a byte array with the bytes.
|
||||
*
|
||||
* @param object object to serialize
|
||||
* @return the byte array of the serialized object
|
||||
* @throws UncheckedIOException if an exception occurs
|
||||
*/
|
||||
private static byte[] writeObject(Object object) {
|
||||
try (ByteArrayOutputStream baos = new ByteArrayOutputStream();
|
||||
ObjectOutputStream oos = new ObjectOutputStream(baos)) {
|
||||
oos.writeObject(object);
|
||||
return baos.toByteArray();
|
||||
} catch (IOException ioe) {
|
||||
throw new UncheckedIOException(ioe);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Deserialize an object.
|
||||
*
|
||||
* @param bytes an object.
|
||||
* @throws UncheckedIOException for I/O exceptions and ClassNotFoundException
|
||||
*/
|
||||
private static Object deserializeObject(byte[] bytes) {
|
||||
try {
|
||||
InputStream is = new ByteArrayInputStream(bytes);
|
||||
ObjectInputStream ois = new ObjectInputStream(is);
|
||||
System.out.println(" filter in effect: " + ois.getObjectInputFilter());
|
||||
return ois.readObject();
|
||||
} catch (IOException ioe) {
|
||||
throw new UncheckedIOException(ioe);
|
||||
} catch (ClassNotFoundException cnfe) {
|
||||
throw new UncheckedIOException(new InvalidClassException(cnfe.getMessage()));
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* ObjectInputFilter utilities to create filters that combine the results of other filters.
|
||||
*/
|
||||
public static final class Filters {
|
||||
/**
|
||||
* Returns a filter that returns {@code Status.ALLOWED} if the predicate
|
||||
* on the class is {@code true}.
|
||||
* The filter returns {@code ALLOWED} or the {@code otherStatus} based on the predicate
|
||||
* of the {@code non-null} class and {@code UNDECIDED} if the class is {@code null}.
|
||||
*
|
||||
* <p>When the filter's {@link ObjectInputFilter#checkInput checkInput(info)} method is invoked,
|
||||
* the predicate is applied to the {@link ObjectInputFilter.FilterInfo#serialClass() info.serialClass()},
|
||||
* the return Status is:
|
||||
* <ul>
|
||||
* <li>{@link Status#UNDECIDED UNDECIDED}, if the {@code serialClass} is {@code null},</li>
|
||||
* <li>{@link Status#ALLOWED ALLOWED}, if the predicate on the class returns {@code true},</li>
|
||||
* <li>Otherwise, return {@code otherStatus}.</li>
|
||||
* </ul>
|
||||
* <p>
|
||||
* Example, to create a filter that will allow any class loaded from the platform classloader.
|
||||
* <pre><code>
|
||||
* ObjectInputFilter f = allowFilter(cl -> cl.getClassLoader() == ClassLoader.getPlatformClassLoader()
|
||||
* || cl.getClassLoader() == null, Status.UNDECIDED);
|
||||
* </code></pre>
|
||||
*
|
||||
* @param predicate a predicate to test a non-null Class, non-null
|
||||
* @param otherStatus a Status to use if the predicate is {@code false}
|
||||
* @return a filter than returns {@code ALLOWED} if the predicate on the class returns {@code true},
|
||||
* otherwise the {@code otherStatus}
|
||||
* @since 17
|
||||
*/
|
||||
public static ObjectInputFilter allowFilter(Predicate<Class<?>> predicate, Status otherStatus) {
|
||||
Objects.requireNonNull(predicate, "predicate");
|
||||
Objects.requireNonNull(otherStatus, "otherStatus");
|
||||
return new PredicateFilter(predicate, ALLOWED, otherStatus);
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns a filter that returns {@code Status.REJECTED} if the predicate
|
||||
* on the class is {@code true}.
|
||||
* The filter returns {@code ALLOWED} or the {@code otherStatus} based on the predicate
|
||||
* of the {@code non-null} class and {@code UNDECIDED} if the class is {@code null}.
|
||||
*
|
||||
* When the filter's {@link ObjectInputFilter#checkInput checkInput(info)} method is invoked,
|
||||
* the predicate is applied to the {@link ObjectInputFilter.FilterInfo#serialClass() serialClass()},
|
||||
* the return Status is:
|
||||
* <ul>
|
||||
* <li>{@link Status#UNDECIDED UNDECIDED}, if the {@code serialClass} is {@code null},</li>
|
||||
* <li>{@link Status#REJECTED REJECTED}, if the predicate on the class returns {@code true},</li>
|
||||
* <li>Otherwise, return {@code otherStatus}.</li>
|
||||
* </ul>
|
||||
* <p>
|
||||
* Example, to create a filter that will reject any class loaded from the application classloader.
|
||||
* <pre><code>
|
||||
* ObjectInputFilter f = rejectFilter(cl ->
|
||||
* cl.getClassLoader() == ClassLoader.ClassLoader.getSystemClassLoader(), Status.UNDECIDED);
|
||||
* </code></pre>
|
||||
*
|
||||
* @param predicate a predicate to test a non-null Class, non-null
|
||||
* @param otherStatus a Status to use if the predicate is {@code false}
|
||||
* @return returns a filter that returns {@link Status#REJECTED REJECTED} if the predicate on the class
|
||||
* returns {@code true}, otherwise {@link Status#UNDECIDED UNDECIDED}
|
||||
* @since 17
|
||||
*/
|
||||
public static ObjectInputFilter rejectFilter(Predicate<Class<?>> predicate, Status otherStatus) {
|
||||
Objects.requireNonNull(predicate, "predicate");
|
||||
Objects.requireNonNull(otherStatus, "otherStatus");
|
||||
return new PredicateFilter(predicate, REJECTED, otherStatus);
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns a filter that returns {@code Status.ALLOWED} if the check is for limits
|
||||
* and not checking a class; otherwise {@code Status.UNDECIDED}.
|
||||
* If the {@link ObjectInputFilter.FilterInfo#serialClass() serialClass()} is {@code null}, the filter returns
|
||||
* {@code Status.ALLOWED}, otherwise return {@code Status.UNDECIDED}.
|
||||
* The limit values of {@link ObjectInputFilter.FilterInfo#arrayLength() arrayLength()},
|
||||
* {@link ObjectInputFilter.FilterInfo#depth() depth()}, {@link ObjectInputFilter.FilterInfo#references() references()},
|
||||
* and {@link ObjectInputFilter.FilterInfo#streamBytes() streamBytes()} are not checked.
|
||||
* To place a limit, create a separate filter with limits such as:
|
||||
* <pre>{@code
|
||||
* Config.createFilter("maxarray=10000,maxdepth=40");
|
||||
* }</pre>
|
||||
*
|
||||
* When the filter's {@link ObjectInputFilter#checkInput} method is invoked,
|
||||
* the Status returned is:
|
||||
* <ul>
|
||||
* <li>{@link Status#ALLOWED ALLOWED}, if the {@code serialClass} is {@code null},</li>
|
||||
* <li>Otherwise, return {@link Status#UNDECIDED UNDECIDED}</li>
|
||||
* </ul>
|
||||
*
|
||||
* @return a filter that returns {@code Status.ALLOWED} if the check is for limits
|
||||
* and not checking a class; otherwise {@code Status.UNDECIDED}
|
||||
* @since 17
|
||||
*/
|
||||
public static ObjectInputFilter allowMaxLimits() {
|
||||
return new AllowMaxLimitsFilter(ALLOWED, UNDECIDED);
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns a filter that merges the status of a filter and another filter.
|
||||
* If the other filter is {@code null}, the filter is returned.
|
||||
* Otherwise, a filter is returned to merge the pair of {@code non-null} filters.
|
||||
*
|
||||
* The filter returned implements the {@link ObjectInputFilter#checkInput(ObjectInputFilter.FilterInfo)} method
|
||||
* as follows:
|
||||
* <ul>
|
||||
* <li>Invoke {@code filter} on the {@code FilterInfo} to get its {@code status};
|
||||
* <li>Return {@code REJECTED} if the {@code status} is {@code REJECTED};
|
||||
* <li>Invoke the {@code otherFilter} to get the {@code otherStatus};
|
||||
* <li>Return {@code REJECTED} if the {@code otherStatus} is {@code REJECTED};
|
||||
* <li>Return {@code ALLOWED}, if either {@code status} or {@code otherStatus}
|
||||
* is {@code ALLOWED}, </li>
|
||||
* <li>Otherwise, return {@code UNDECIDED}</li>
|
||||
* </ul>
|
||||
*
|
||||
* @param filter a filter, non-null
|
||||
* @param anotherFilter a filter to be merged with the filter, may be {@code null}
|
||||
* @return an {@link ObjectInputFilter} that merges the status of the filter and another filter
|
||||
* @since 17
|
||||
*/
|
||||
public static ObjectInputFilter merge(ObjectInputFilter filter, ObjectInputFilter anotherFilter) {
|
||||
Objects.requireNonNull(filter, "filter");
|
||||
return (anotherFilter == null) ? filter : new MergeFilter(filter, anotherFilter);
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns a filter that invokes a filter and maps {@code UNDECIDED} to {@code REJECTED}
|
||||
* for classes, with some exceptions, and otherwise returns the status.
|
||||
* The filter returned checks that classes not {@code ALLOWED} and not {@code REJECTED} by the filter
|
||||
* are {@code REJECTED}, if the class is an array and the base component type is not allowed,
|
||||
* otherwise the result is {@code UNDECIDED}.
|
||||
*
|
||||
* <p>
|
||||
* Object deserialization accepts a class if the filter returns {@code UNDECIDED}.
|
||||
* Adding a filter to reject undecided results for classes that have not been
|
||||
* either allowed or rejected can prevent classes from slipping through the filter.
|
||||
*
|
||||
* @implSpec
|
||||
* The filter returned implements the {@link ObjectInputFilter#checkInput(ObjectInputFilter.FilterInfo)} method
|
||||
* as follows:
|
||||
* <ul>
|
||||
* <li>Invoke the filter on the {@code FilterInfo} to get its {@code status};
|
||||
* <li>Return the {@code status} if the status is {@code REJECTED} or {@code ALLOWED};
|
||||
* <li>Return {@code UNDECIDED} if the {@code filterInfo.getSerialClass() serialClass}
|
||||
* is {@code null};
|
||||
* <li>Determine the base component type if the {@code serialClass} is
|
||||
* an {@linkplain Class#isArray() array};
|
||||
* <li>Return {@code UNDECIDED} if the base component type is
|
||||
* a {@linkplain Class#isPrimitive() primitive class};
|
||||
* <li>Invoke the filter on the {@code base component type} to get its
|
||||
* {@code component status};</li>
|
||||
* <li>Return {@code ALLOWED} if the component status is {@code ALLOWED};
|
||||
* <li>Otherwise, return {@code REJECTED}.</li>
|
||||
* </ul>
|
||||
*
|
||||
* @param filter a filter, non-null
|
||||
* @return an {@link ObjectInputFilter} that maps an {@link Status#UNDECIDED}
|
||||
* status to {@link Status#REJECTED} for classes, otherwise returns the
|
||||
* filter status
|
||||
* @since 17
|
||||
*/
|
||||
public static ObjectInputFilter rejectUndecidedClass(ObjectInputFilter filter) {
|
||||
Objects.requireNonNull(filter, "filter");
|
||||
return new RejectUndecidedFilter(filter);
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns a filter that allows a class only if the class was loaded by the platform class loader.
|
||||
* Otherwise, it returns UNDECIDED; leaving the choice to another filter.
|
||||
* @return a filter that allows a class only if the class was loaded by the platform class loader
|
||||
*/
|
||||
public static ObjectInputFilter allowPlatformClasses() {
|
||||
return new AllowPlatformClassFilter();
|
||||
}
|
||||
|
||||
/**
|
||||
* An ObjectInputFilter to evaluate a predicate mapping a class to a boolean.
|
||||
*/
|
||||
private static class PredicateFilter implements ObjectInputFilter {
|
||||
private final Predicate<Class<?>> predicate;
|
||||
private final Status ifTrueStatus;
|
||||
private final Status ifFalseStatus;
|
||||
|
||||
PredicateFilter(Predicate<Class<?>> predicate, Status ifTrueStatus, Status ifFalseStatus) {
|
||||
this.predicate = predicate;
|
||||
this.ifTrueStatus = ifTrueStatus;
|
||||
this.ifFalseStatus = ifFalseStatus;
|
||||
}
|
||||
|
||||
/**
|
||||
* Apply the predicate to the class being deserialized, if the class is non-null
|
||||
* and if it returns {@code true}, return the requested status. Otherwise, return UNDECIDED.
|
||||
*
|
||||
* @param info the FilterInfo
|
||||
* @return the status of applying the predicate, otherwise {@code UNDECIDED}
|
||||
*/
|
||||
public ObjectInputFilter.Status checkInput(FilterInfo info) {
|
||||
Class<?> clazz = info.serialClass();
|
||||
return (clazz != null && predicate.test(clazz)) ? ifTrueStatus : ifFalseStatus;
|
||||
}
|
||||
|
||||
public String toString() {
|
||||
return "predicate(" + predicate + ")";
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* An ObjectInputFilter to evaluate if a FilterInfo is checking only limits,
|
||||
* and not classes.
|
||||
*/
|
||||
private static class AllowMaxLimitsFilter implements ObjectInputFilter {
|
||||
private final Status limitCheck;
|
||||
private final Status classCheck;
|
||||
|
||||
AllowMaxLimitsFilter(Status limitCheck, Status classCheck) {
|
||||
this.limitCheck = limitCheck;
|
||||
this.classCheck = classCheck;
|
||||
}
|
||||
|
||||
/**
|
||||
* If the FilterInfo is only checking a limit, return the requested
|
||||
* status, otherwise the other status.
|
||||
*
|
||||
* @param info the FilterInfo
|
||||
* @return the status of corresponding to serialClass == null or not
|
||||
*/
|
||||
public ObjectInputFilter.Status checkInput(FilterInfo info) {
|
||||
return (info.serialClass() == null) ? limitCheck : classCheck;
|
||||
}
|
||||
|
||||
public String toString() {
|
||||
return "allowMaxLimits()";
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* An ObjectInputFilter that merges the status of two filters.
|
||||
*/
|
||||
private static class MergeFilter implements ObjectInputFilter {
|
||||
private final ObjectInputFilter first;
|
||||
private final ObjectInputFilter second;
|
||||
|
||||
MergeFilter(ObjectInputFilter first, ObjectInputFilter second) {
|
||||
this.first = first;
|
||||
this.second = second;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns REJECTED if either of the filters returns REJECTED,
|
||||
* and ALLOWED if either of the filters returns ALLOWED.
|
||||
* Returns {@code UNDECIDED} if either filter returns {@code UNDECIDED}.
|
||||
*
|
||||
* @param info the FilterInfo
|
||||
* @return Status.REJECTED if either of the filters returns REJECTED,
|
||||
* and ALLOWED if either filter returns ALLOWED; otherwise returns
|
||||
* {@code UNDECIDED} if both filters returned {@code UNDECIDED}
|
||||
*/
|
||||
public ObjectInputFilter.Status checkInput(FilterInfo info) {
|
||||
Status firstStatus = Objects.requireNonNull(first.checkInput(info), "status");
|
||||
if (REJECTED.equals(firstStatus)) {
|
||||
return REJECTED;
|
||||
}
|
||||
Status secondStatus = Objects.requireNonNull(second.checkInput(info), "other status");
|
||||
if (REJECTED.equals(secondStatus)) {
|
||||
return REJECTED;
|
||||
}
|
||||
if (ALLOWED.equals(firstStatus) || ALLOWED.equals(secondStatus)) {
|
||||
return ALLOWED;
|
||||
}
|
||||
return UNDECIDED;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String toString() {
|
||||
return "merge(" + first + ", " + second + ")";
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* A filter that maps the status {@code UNDECIDED} to {@code REJECTED} when checking a class.
|
||||
*/
|
||||
private static class RejectUndecidedFilter implements ObjectInputFilter {
|
||||
private final ObjectInputFilter filter;
|
||||
|
||||
private RejectUndecidedFilter(ObjectInputFilter filter) {
|
||||
this.filter = Objects.requireNonNull(filter, "filter");
|
||||
}
|
||||
|
||||
/**
|
||||
* Apply the filter and return the status if not UNDECIDED and checking a class.
|
||||
* For array classes, re-check the final component type against the filter.
|
||||
* Make an exception for Primitive classes that are implicitly allowed by the pattern based filter.
|
||||
* @param info the FilterInfo
|
||||
* @return the status of applying the filter and checking the class
|
||||
*/
|
||||
public ObjectInputFilter.Status checkInput(FilterInfo info) {
|
||||
Status status = Objects.requireNonNull(filter.checkInput(info), "status");
|
||||
Class<?> clazz = info.serialClass();
|
||||
if (clazz == null || !UNDECIDED.equals(status))
|
||||
return status;
|
||||
status = REJECTED;
|
||||
// Find the base component type
|
||||
while (clazz.isArray()) {
|
||||
clazz = clazz.getComponentType();
|
||||
}
|
||||
if (clazz.isPrimitive()) {
|
||||
status = UNDECIDED;
|
||||
} else {
|
||||
// for non-primitive types; re-filter the base component type
|
||||
FilterInfo clazzInfo = new SerialInfo(info, clazz);
|
||||
Status clazzStatus = filter.checkInput(clazzInfo);
|
||||
status = (ALLOWED.equals(clazzStatus)) ? ALLOWED : REJECTED;
|
||||
}
|
||||
return status;
|
||||
}
|
||||
|
||||
public String toString() {
|
||||
return "rejectUndecidedClass(" + filter + ")";
|
||||
}
|
||||
|
||||
/**
|
||||
* FilterInfo instance with a specific class and delegating to an existing FilterInfo.
|
||||
* Nested in the rejectUndecided class.
|
||||
*/
|
||||
static class SerialInfo implements ObjectInputFilter.FilterInfo {
|
||||
private final FilterInfo base;
|
||||
private final Class<?> clazz;
|
||||
|
||||
SerialInfo(FilterInfo base, Class<?> clazz) {
|
||||
this.base = base;
|
||||
this.clazz = clazz;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Class<?> serialClass() {
|
||||
return clazz;
|
||||
}
|
||||
|
||||
@Override
|
||||
public long arrayLength() {
|
||||
return base.arrayLength();
|
||||
}
|
||||
|
||||
@Override
|
||||
public long depth() {
|
||||
return base.depth();
|
||||
}
|
||||
|
||||
@Override
|
||||
public long references() {
|
||||
return base.references();
|
||||
}
|
||||
|
||||
@Override
|
||||
public long streamBytes() {
|
||||
return base.streamBytes();
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
/**
|
||||
* An ObjectInputFilter that merges the results of two filters.
|
||||
*/
|
||||
private static class MergeManyFilter implements ObjectInputFilter {
|
||||
private final List<ObjectInputFilter> filters;
|
||||
private final Status otherStatus;
|
||||
|
||||
MergeManyFilter(List<ObjectInputFilter> first, Status otherStatus) {
|
||||
this.filters = Objects.requireNonNull(first, "filters");
|
||||
this.otherStatus = Objects.requireNonNull(otherStatus, "otherStatus");
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns REJECTED if any of the filters returns REJECTED,
|
||||
* and ALLOWED if any of the filters returns ALLOWED.
|
||||
* Returns UNDECIDED if there is no class to be checked or all filters return UNDECIDED.
|
||||
*
|
||||
* @param info the FilterInfo
|
||||
* @return Status.UNDECIDED if there is no class to check,
|
||||
* Status.REJECTED if any of the filters returns REJECTED,
|
||||
* Status.ALLOWED if any filter returns ALLOWED;
|
||||
* otherwise returns {@code otherStatus}
|
||||
*/
|
||||
public ObjectInputFilter.Status checkInput(FilterInfo info) {
|
||||
if (info.serialClass() == null)
|
||||
return UNDECIDED;
|
||||
Status status = otherStatus;
|
||||
for (ObjectInputFilter filter : filters) {
|
||||
Status aStatus = filter.checkInput(info);
|
||||
if (REJECTED.equals(aStatus)) {
|
||||
return REJECTED;
|
||||
}
|
||||
if (ALLOWED.equals(aStatus)) {
|
||||
status = ALLOWED;
|
||||
}
|
||||
}
|
||||
return status;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String toString() {
|
||||
return "mergeManyFilter(" + filters + ")";
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* An ObjectInputFilter that allows a class only if the class was loaded by the platform class loader.
|
||||
* Otherwise, it returns undecided; leaving the choice to another filter.
|
||||
*/
|
||||
private static class AllowPlatformClassFilter implements ObjectInputFilter {
|
||||
|
||||
/**
|
||||
* Returns ALLOWED only if the class, if non-null, was loaded by the platformClassLoader.
|
||||
*
|
||||
* @param filter the FilterInfo
|
||||
* @return Status.ALLOWED only if the class loader of the class was the PlatformClassLoader;
|
||||
* otherwise Status.UNDECIDED
|
||||
*/
|
||||
public ObjectInputFilter.Status checkInput(FilterInfo filter) {
|
||||
final Class<?> serialClass = filter.serialClass();
|
||||
return (serialClass != null &&
|
||||
(serialClass.getClassLoader() == null ||
|
||||
ClassLoader.getPlatformClassLoader().equals(serialClass.getClassLoader())))
|
||||
? ObjectInputFilter.Status.ALLOWED
|
||||
: ObjectInputFilter.Status.UNDECIDED;
|
||||
}
|
||||
|
||||
public String toString() {
|
||||
return "allowPlatformClasses";
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* FilterInfo instance with a specific class.
|
||||
*/
|
||||
static class SerialInfo implements ObjectInputFilter.FilterInfo {
|
||||
private final Class<?> clazz;
|
||||
|
||||
SerialInfo(Class<?> clazz) {
|
||||
this.clazz = clazz;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Class<?> serialClass() {
|
||||
return clazz;
|
||||
}
|
||||
|
||||
@Override
|
||||
public long arrayLength() {
|
||||
return 0;
|
||||
}
|
||||
|
||||
@Override
|
||||
public long depth() {
|
||||
return 0;
|
||||
}
|
||||
|
||||
@Override
|
||||
public long references() {
|
||||
return 0;
|
||||
}
|
||||
|
||||
@Override
|
||||
public long streamBytes() {
|
||||
return 0;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* A test class.
|
||||
*/
|
||||
static record Point(int x, int y) implements Serializable {
|
||||
}
|
||||
}
|
@ -0,0 +1,411 @@
|
||||
/*
|
||||
* Copyright (c) 2021, Oracle and/or its affiliates. All rights reserved.
|
||||
* DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
|
||||
*
|
||||
* This code is free software; you can redistribute it and/or modify it
|
||||
* under the terms of the GNU General Public License version 2 only, as
|
||||
* published by the Free Software Foundation.
|
||||
*
|
||||
* This code is distributed in the hope that it will be useful, but WITHOUT
|
||||
* ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
|
||||
* FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
|
||||
* version 2 for more details (a copy is included in the LICENSE file that
|
||||
* accompanied this code).
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License version
|
||||
* 2 along with this work; if not, write to the Free Software Foundation,
|
||||
* Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
|
||||
*
|
||||
* Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
|
||||
* or visit www.oracle.com if you need additional information or have any
|
||||
* questions.
|
||||
*/
|
||||
|
||||
|
||||
import org.testng.Assert;
|
||||
import org.testng.annotations.DataProvider;
|
||||
import org.testng.annotations.Test;
|
||||
|
||||
import java.io.ByteArrayInputStream;
|
||||
import java.io.ByteArrayOutputStream;
|
||||
import java.io.IOException;
|
||||
import java.io.InputStream;
|
||||
import java.io.ObjectInputFilter;
|
||||
import java.io.ObjectInputFilter.Config;
|
||||
import java.io.ObjectInputStream;
|
||||
import java.io.ObjectOutputStream;
|
||||
import java.io.Serial;
|
||||
import java.io.Serializable;
|
||||
import java.io.SerializablePermission;
|
||||
import java.security.AccessControlException;
|
||||
import java.security.Permission;
|
||||
import java.util.function.BinaryOperator;
|
||||
|
||||
/* @test
|
||||
* @build SerialFilterFactoryTest
|
||||
* @run testng/othervm SerialFilterFactoryTest
|
||||
* @run testng/othervm -Djdk.serialFilter="*" -Djdk.serialFilterFactory=OVERRIDE SerialFilterFactoryTest
|
||||
* @run testng/othervm -Djdk.serialFilterFactory=SerialFilterFactoryTest$PropertyFilterFactory SerialFilterFactoryTest
|
||||
* @run testng/othervm -Djdk.serialFilterFactory=SerialFilterFactoryTest$NotMyFilterFactory SerialFilterFactoryTest
|
||||
* @run testng/othervm/policy=security.policy
|
||||
* -Djava.security.properties=${test.src}/java.security-extra-factory
|
||||
* -Djava.security.debug=properties SerialFilterFactoryTest
|
||||
* @run testng/othervm/fail -Djdk.serialFilterFactory=ForcedError_NoSuchClass SerialFilterFactoryTest
|
||||
* @run testng/othervm/policy=security.policy SerialFilterFactoryTest
|
||||
* @run testng/othervm/policy=security.policy.without.globalFilter SerialFilterFactoryTest
|
||||
|
||||
*
|
||||
* @summary Test Context-specific Deserialization Filters
|
||||
*/
|
||||
@Test
|
||||
public class SerialFilterFactoryTest {
|
||||
|
||||
// A stream with just the header, enough to create a OIS
|
||||
private static final byte[] simpleStream = simpleStream();
|
||||
private static final Validator v1 = new Validator("v1");
|
||||
private static final Validator v2 = new Validator("v2");
|
||||
private static final BinaryOperator<ObjectInputFilter> jdkSerialFilterFactory
|
||||
= Config.getSerialFilterFactory();
|
||||
private static final MyFilterFactory contextFilterFactory = new MyFilterFactory("DynFF");
|
||||
private static final String jdkSerialFilterFactoryProp = System.getProperty("jdk.serialFilterFactory");
|
||||
|
||||
/**
|
||||
* Return a byte array with a simple stream containing an Dummy object.
|
||||
* @return a byte with a simple serialization object
|
||||
*/
|
||||
private static byte[] simpleStream() {
|
||||
ByteArrayOutputStream boas = new ByteArrayOutputStream();
|
||||
try (ObjectOutputStream ois = new ObjectOutputStream(boas)) {
|
||||
ois.writeObject(new Dummy("Here"));
|
||||
return boas.toByteArray();
|
||||
} catch (IOException ioe) {
|
||||
Assert.fail("unexpected IOE", ioe);
|
||||
}
|
||||
throw new RuntimeException("should not reach here");
|
||||
}
|
||||
|
||||
/**
|
||||
* Initialize the filter factory, supplying one if not already set.
|
||||
* Does not/can not replace any MyFilterFactory.
|
||||
*
|
||||
* @param dynFilterFactory a filter factory to use if not already set
|
||||
* @return the filter factory in effect
|
||||
*/
|
||||
private static MyFilterFactory setupFilterFactory(MyFilterFactory dynFilterFactory) {
|
||||
if ((Config.getSerialFilterFactory() instanceof MyFilterFactory ff))
|
||||
return ff;
|
||||
Config.setSerialFilterFactory(dynFilterFactory);
|
||||
return dynFilterFactory;
|
||||
}
|
||||
|
||||
// If the configured filter has not been set, set it
|
||||
// It can only be set once for the process, so avoid setting it again
|
||||
private static ObjectInputFilter setupFilter(ObjectInputFilter serialFilter) {
|
||||
|
||||
var configFilter = Config.getSerialFilter();
|
||||
if (configFilter == serialFilter || configFilter instanceof Validator)
|
||||
return configFilter; // if already set or a type we can use, no change
|
||||
|
||||
if (configFilter == null) {
|
||||
Config.setSerialFilter(serialFilter);
|
||||
return serialFilter; // none set already, set it
|
||||
}
|
||||
|
||||
return configFilter;
|
||||
}
|
||||
|
||||
private static boolean isValidFilterFactory() {
|
||||
return !(ObjectInputFilter.Config.getSerialFilterFactory() instanceof NotMyFilterFactory);
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns true if serialFilter actions are ok, either no SM or SM has serialFilter Permission
|
||||
*/
|
||||
private static boolean hasFilterPerm() {
|
||||
boolean hasSerialPerm = true;
|
||||
SecurityManager sm = System.getSecurityManager();
|
||||
if (sm != null) {
|
||||
try {
|
||||
Permission p = new SerializablePermission("serialFilter");
|
||||
sm.checkPermission(p);
|
||||
hasSerialPerm = true;
|
||||
} catch (AccessControlException ace2) {
|
||||
hasSerialPerm = false; // SM and serialFilter not allowed
|
||||
}
|
||||
}
|
||||
return hasSerialPerm;
|
||||
}
|
||||
|
||||
@DataProvider(name="FilterCases")
|
||||
static Object[][] filterCases() {
|
||||
if (isValidFilterFactory()) {
|
||||
return new Object[][]{
|
||||
{contextFilterFactory, null, null}, // no overrides
|
||||
{contextFilterFactory, v1, null}, // context filter
|
||||
{contextFilterFactory, v1, v2}, // per stream filter
|
||||
};
|
||||
} else {
|
||||
// There are zero cases to run with an unknown filter factory. (NotMyFilterFactory)
|
||||
return new Object[0][0];
|
||||
}
|
||||
}
|
||||
|
||||
// Setting the filter factory to null is not allowed.
|
||||
@Test(expectedExceptions=NullPointerException.class)
|
||||
void testNull() {
|
||||
Config.setSerialFilterFactory(null);
|
||||
}
|
||||
|
||||
/**
|
||||
* Setting and resetting the filter factory is not allowed.
|
||||
* The filter factory may have been on the command line (depending on which @run this is).
|
||||
* If the jdk.SerialFilterFactory is the built-in filter factory, set it once.
|
||||
* Try to set it again, the second should throw.
|
||||
*/
|
||||
@Test
|
||||
void testSecondSetShouldThrow() {
|
||||
if (System.getSecurityManager() != null) {
|
||||
// Skip test when running with SM
|
||||
return;
|
||||
}
|
||||
var currFF = Config.getSerialFilterFactory();
|
||||
if (currFF.getClass().getClassLoader() == null) {
|
||||
try {
|
||||
// Not already set, set it
|
||||
Config.setSerialFilterFactory(contextFilterFactory);
|
||||
currFF = contextFilterFactory;
|
||||
} catch (IllegalStateException ise) {
|
||||
Assert.fail("First setSerialFilterFactory should not throw");
|
||||
}
|
||||
}
|
||||
// Setting it again will throw
|
||||
Assert.expectThrows(IllegalStateException.class,
|
||||
() -> Config.setSerialFilterFactory(new MyFilterFactory("f11")));
|
||||
var resetFF = Config.getSerialFilterFactory();
|
||||
Assert.assertEquals(resetFF, currFF, "Setting again should not change filter factory");
|
||||
}
|
||||
|
||||
/**
|
||||
* Test that the filter factory is set when expected and is called when expected.
|
||||
* This test only covers the cases when a filter factory is supplied
|
||||
* either via a command line property or via the API.
|
||||
* The cases where the builtin filter factory applies are tested in SerialFilterTest.
|
||||
*
|
||||
* @param dynFilterFactory a FilterFactory to set
|
||||
* @param dynFilter a serial filter to be used for the configured filter
|
||||
* @param streamFilter a serial filter to be used for the stream filter
|
||||
* @throws IOException if an I/O error occurs (should not occur)
|
||||
* @throws ClassNotFoundException for class not found (should not occur)
|
||||
*/
|
||||
@Test(dataProvider="FilterCases")
|
||||
void testCase(MyFilterFactory dynFilterFactory, Validator dynFilter, Validator streamFilter)
|
||||
throws IOException, ClassNotFoundException {
|
||||
|
||||
// Set the Filter Factory and System-wide filter
|
||||
ObjectInputFilter configFilter;
|
||||
MyFilterFactory factory;
|
||||
try {
|
||||
configFilter = setupFilter(dynFilter);
|
||||
factory = setupFilterFactory(dynFilterFactory);
|
||||
Assert.assertTrue(hasFilterPerm(),
|
||||
"setSerialFilterFactory and setFilterFactory succeeded without serialFilter permission");
|
||||
} catch (AccessControlException ace) {
|
||||
Assert.assertFalse(hasFilterPerm(),
|
||||
"setSerialFilterFactory failed even with serialFilter permission");
|
||||
return; // test complete
|
||||
}
|
||||
factory.reset();
|
||||
|
||||
InputStream is = new ByteArrayInputStream(simpleStream);
|
||||
ObjectInputStream ois = new ObjectInputStream(is);
|
||||
|
||||
Assert.assertNull(factory.current(), "initially current should be null");
|
||||
Assert.assertEquals(factory.next(), configFilter, "initially next should be the configured filter");
|
||||
var currFilter = ois.getObjectInputFilter();
|
||||
if (currFilter != null && currFilter.getClass().getClassLoader() == null) {
|
||||
// Builtin loader; defaults to configured filter
|
||||
Assert.assertEquals(currFilter, configFilter, "getObjectInputFilter should be configured filter");
|
||||
} else {
|
||||
Assert.assertEquals(currFilter, configFilter, "getObjectInputFilter should be null");
|
||||
}
|
||||
if (streamFilter != null) {
|
||||
ois.setObjectInputFilter(streamFilter);
|
||||
// MyFilterFactory is called when the stream filter is changed; verify values passed it
|
||||
Assert.assertEquals(factory.current(), currFilter, "when setObjectInputFilter, current should be current filter");
|
||||
Assert.assertEquals(factory.next(), streamFilter, "next should be stream specific filter");
|
||||
|
||||
// Check the OIS filter after the factory has updated it.
|
||||
currFilter = ois.getObjectInputFilter();
|
||||
Assert.assertEquals(currFilter, streamFilter, "getObjectInputFilter should be set");
|
||||
|
||||
// Verify that it can not be set again
|
||||
Assert.assertThrows(IllegalStateException.class, () -> ois.setObjectInputFilter(streamFilter));
|
||||
}
|
||||
if (currFilter instanceof Validator validator) {
|
||||
validator.reset();
|
||||
Object o = ois.readObject(); // Invoke only for the side effect of calling the Filter
|
||||
Assert.assertEquals(validator.count, 1, "Wrong number of calls to the stream filter");
|
||||
} else {
|
||||
Object o = ois.readObject(); // Invoke only for the side effect of calling the filter
|
||||
}
|
||||
}
|
||||
|
||||
// Test that if the property jdk-serialFilterFactory is set, then initial factory has the same classname
|
||||
@Test
|
||||
void testPropertyFilterFactory() {
|
||||
if (jdkSerialFilterFactoryProp != null && !jdkSerialFilterFactoryProp.equals("OVERRIDE")) {
|
||||
Assert.assertEquals(jdkSerialFilterFactory.getClass().getName(), jdkSerialFilterFactoryProp,
|
||||
"jdk.serialFilterFactory property classname mismatch");
|
||||
}
|
||||
}
|
||||
|
||||
// Test that setting the filter factory after any deserialization (any testCase)
|
||||
// throws IllegalStateException with the specific message
|
||||
@Test(dependsOnMethods="testCase")
|
||||
void testSetFactoryAfterDeserialization() {
|
||||
if (hasFilterPerm()) {
|
||||
// Only test if is allowed by SM.
|
||||
BinaryOperator<ObjectInputFilter> factory = Config.getSerialFilterFactory();
|
||||
IllegalStateException ise = Assert.expectThrows(IllegalStateException.class, () -> Config.setSerialFilterFactory(factory));
|
||||
Assert.assertTrue(ise.getMessage().startsWith("Cannot replace filter factory: "));
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
// Test that OIS.setObjectInputFilter does not allow a null filter to replace
|
||||
// a non-null filter. And does allow a null filter to replace a null filter
|
||||
@Test
|
||||
void testDisableFailFilter() throws IOException {
|
||||
if (hasFilterPerm()) {
|
||||
// Only test if is allowed by SM.
|
||||
ObjectInputFilter curr = null;
|
||||
try (ObjectInputStream ois = new ObjectInputStream(new ByteArrayInputStream(simpleStream))) {
|
||||
curr = ois.getObjectInputFilter();
|
||||
// Try to set the filter to null
|
||||
ois.setObjectInputFilter(null);
|
||||
if (curr != null) {
|
||||
Assert.fail("setting filter to null after a non-null filter should throw");
|
||||
}
|
||||
} catch (IllegalStateException ise) {
|
||||
if (curr == null) {
|
||||
Assert.fail("setting filter to null after a null filter should not throw");
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* A simple filter factory that retains its arguments.
|
||||
*/
|
||||
private static class MyFilterFactory
|
||||
implements BinaryOperator<ObjectInputFilter> {
|
||||
private final String name;
|
||||
private ObjectInputFilter current;
|
||||
private ObjectInputFilter next;
|
||||
|
||||
MyFilterFactory(String name) {
|
||||
this.name = name;
|
||||
current = new Validator("UnsetCurrent");
|
||||
next = new Validator("UnsetNext");
|
||||
}
|
||||
|
||||
public ObjectInputFilter apply(ObjectInputFilter curr, ObjectInputFilter next) {
|
||||
this.current = curr;
|
||||
this.next = next;
|
||||
if (curr == null & next == null)
|
||||
return Config.getSerialFilter(); // Default to the configured filter
|
||||
return next;
|
||||
}
|
||||
|
||||
public void reset() {
|
||||
current = new Validator("UnsetCurrent");
|
||||
next = new Validator("UnsetNext");
|
||||
}
|
||||
|
||||
public ObjectInputFilter current() {
|
||||
return current;
|
||||
}
|
||||
|
||||
public ObjectInputFilter next() {
|
||||
return next;
|
||||
}
|
||||
|
||||
public String toString() {
|
||||
return name + ":: curr: " + current + ", next: " + next;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* A subclass of MyFilterFactory with a name, used when testing setting the factory using
|
||||
* -Djdk.setFilterFactory.
|
||||
*/
|
||||
public static class PropertyFilterFactory extends MyFilterFactory {
|
||||
public PropertyFilterFactory() {
|
||||
super("UNNAMED");
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* A filter factory that is not compatible with MyFilterFactory test.
|
||||
* Used for testing incorrect initialization.
|
||||
*/
|
||||
public static class NotMyFilterFactory
|
||||
implements BinaryOperator<ObjectInputFilter> {
|
||||
|
||||
public NotMyFilterFactory() {}
|
||||
|
||||
/**
|
||||
* Returns null as the filter to be used for an ObjectInputStream.
|
||||
*
|
||||
* @param curr the current filter, if any
|
||||
* @param next the next filter, if any
|
||||
* @return null
|
||||
*/
|
||||
public ObjectInputFilter apply(ObjectInputFilter curr, ObjectInputFilter next) {
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* A filter that accumulates information about the checkInput callbacks
|
||||
* that can be checked after readObject completes.
|
||||
*/
|
||||
static class Validator implements ObjectInputFilter {
|
||||
private final String name;
|
||||
long count; // Count of calls to checkInput
|
||||
|
||||
Validator(String name) {
|
||||
this.name = name;
|
||||
count = 0;
|
||||
}
|
||||
|
||||
void reset() {
|
||||
count = 0;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Status checkInput(FilterInfo filter) {
|
||||
count++;
|
||||
return Status.ALLOWED;
|
||||
}
|
||||
|
||||
public String toString(){
|
||||
return name + ": count: " + count;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* A simple class to serialize.
|
||||
*/
|
||||
private static final class Dummy implements Serializable {
|
||||
@Serial
|
||||
private static final long serialVersionUID = 1L;
|
||||
final String s;
|
||||
Dummy(String s) {
|
||||
this.s = s;
|
||||
}
|
||||
public String toString() {
|
||||
return this.getClass().getName() + "::" + s;
|
||||
}
|
||||
}
|
||||
}
|
@ -0,0 +1,228 @@
|
||||
/*
|
||||
* Copyright (c) 2021, Oracle and/or its affiliates. All rights reserved.
|
||||
* DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
|
||||
*
|
||||
* This code is free software; you can redistribute it and/or modify it
|
||||
* under the terms of the GNU General Public License version 2 only, as
|
||||
* published by the Free Software Foundation.
|
||||
*
|
||||
* This code is distributed in the hope that it will be useful, but WITHOUT
|
||||
* ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
|
||||
* FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
|
||||
* version 2 for more details (a copy is included in the LICENSE file that
|
||||
* accompanied this code).
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License version
|
||||
* 2 along with this work; if not, write to the Free Software Foundation,
|
||||
* Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
|
||||
*
|
||||
* Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
|
||||
* or visit www.oracle.com if you need additional information or have any
|
||||
* questions.
|
||||
*/
|
||||
|
||||
|
||||
import org.testng.Assert;
|
||||
import org.testng.annotations.DataProvider;
|
||||
import org.testng.annotations.Test;
|
||||
|
||||
import java.io.ObjectInputFilter;
|
||||
import java.io.ObjectInputFilter.FilterInfo;
|
||||
import java.util.function.Predicate;
|
||||
|
||||
import static java.io.ObjectInputFilter.Status;
|
||||
import static java.io.ObjectInputFilter.Status.ALLOWED;
|
||||
import static java.io.ObjectInputFilter.Status.REJECTED;
|
||||
import static java.io.ObjectInputFilter.Status.UNDECIDED;
|
||||
|
||||
/* @test
|
||||
* @run testng/othervm -Djdk.serialFilterTrace=true SerialFilterFunctionTest
|
||||
* @summary ObjectInputFilter.Config Function Tests
|
||||
*/
|
||||
@Test
|
||||
public class SerialFilterFunctionTest {
|
||||
|
||||
@Test
|
||||
void testMerge() {
|
||||
Status[] cases = Status.values();
|
||||
FilterInfo info = new SerialInfo(Object.class);
|
||||
for (Status st1 : cases) {
|
||||
ObjectInputFilter filter1 = getFilter(st1);
|
||||
for (Status st2 : cases) {
|
||||
ObjectInputFilter filter2 = getFilter(st2);
|
||||
ObjectInputFilter f = ObjectInputFilter.merge(filter1, filter2);
|
||||
Status r = f.checkInput(info);
|
||||
Assert.assertEquals(merge(st1, st2), r, "merge");
|
||||
}
|
||||
Assert.assertSame(ObjectInputFilter.merge(filter1, null), filter1, "merge with null fail");
|
||||
Assert.assertThrows(NullPointerException.class, () -> ObjectInputFilter.merge(null, filter1));
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Return REJECTED if either is REJECTED; otherwise return ALLOWED if either is ALLOWED, else UNDECIDED.
|
||||
* @param status a status
|
||||
* @param otherStatus another status
|
||||
* @return REJECTED if either is REJECTED; otherwise return ALLOWED if either is ALLOWED, else UNDECIDED
|
||||
*/
|
||||
private Status merge(Status status, Status otherStatus) {
|
||||
if (REJECTED.equals(status) || REJECTED.equals(otherStatus))
|
||||
return REJECTED;
|
||||
|
||||
if (ALLOWED.equals(status) || ALLOWED.equals(otherStatus))
|
||||
return ALLOWED;
|
||||
|
||||
return UNDECIDED;
|
||||
}
|
||||
|
||||
/**
|
||||
* Return a predicate mapping Class<?> to a boolean that returns true if the argument is Integer.class.
|
||||
* @return a predicate mapping Class<?> to a boolean that returns true if the argument is Integer.class
|
||||
*/
|
||||
static Predicate<Class<?>> isInteger() {
|
||||
return (cl) -> cl.equals(Integer.class);
|
||||
}
|
||||
|
||||
@DataProvider(name = "AllowPredicateCases")
|
||||
static Object[][] allowPredicateCases() {
|
||||
return new Object[][]{
|
||||
{ Integer.class, isInteger(), REJECTED, ALLOWED},
|
||||
{ Double.class, isInteger(), REJECTED, REJECTED},
|
||||
{ null, isInteger(), REJECTED, UNDECIDED}, // no class -> UNDECIDED
|
||||
{ Double.class, isInteger(), null, null}, // NPE
|
||||
{ Double.class, null, REJECTED, null}, // NPE
|
||||
};
|
||||
}
|
||||
|
||||
@Test(dataProvider = "AllowPredicateCases")
|
||||
void testAllowPredicates(Class<?> clazz, Predicate<Class<?>> predicate, Status otherStatus, Status expected) {
|
||||
ObjectInputFilter.FilterInfo info = new SerialInfo(clazz);
|
||||
if (predicate == null || expected == null) {
|
||||
Assert.assertThrows(NullPointerException.class, () -> ObjectInputFilter.allowFilter(predicate, expected));
|
||||
} else {
|
||||
Assert.assertEquals(ObjectInputFilter.allowFilter(predicate, otherStatus).checkInput(info),
|
||||
expected, "Predicate result");
|
||||
}
|
||||
}
|
||||
|
||||
@DataProvider(name = "RejectPredicateCases")
|
||||
static Object[][] rejectPredicateCases() {
|
||||
return new Object[][]{
|
||||
{ Integer.class, isInteger(), REJECTED, REJECTED},
|
||||
{ Double.class, isInteger(), ALLOWED, ALLOWED},
|
||||
{ null, isInteger(), REJECTED, UNDECIDED}, // no class -> UNDECIDED
|
||||
{ Double.class, isInteger(), null, null}, // NPE
|
||||
{ Double.class, null, UNDECIDED, null}, // NPE
|
||||
};
|
||||
}
|
||||
|
||||
@Test(dataProvider = "RejectPredicateCases")
|
||||
void testRejectPredicates(Class<?> clazz, Predicate<Class<?>> predicate, Status otherStatus, Status expected) {
|
||||
ObjectInputFilter.FilterInfo info = new SerialInfo(clazz);
|
||||
if (predicate == null || expected == null) {
|
||||
Assert.assertThrows(NullPointerException.class, () -> ObjectInputFilter.allowFilter(predicate, expected));
|
||||
} else {
|
||||
Assert.assertEquals(ObjectInputFilter.rejectFilter(predicate, otherStatus)
|
||||
.checkInput(info), expected, "Predicate result");
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
void testRejectUndecided() {
|
||||
FilterInfo info = new SerialInfo(Object.class); // an info structure, unused
|
||||
|
||||
ObjectInputFilter undecided = getFilter(UNDECIDED);
|
||||
Assert.assertEquals(ObjectInputFilter.rejectUndecidedClass(undecided).checkInput(info), REJECTED, "undecided -> rejected");
|
||||
ObjectInputFilter allowed = getFilter(ALLOWED);
|
||||
Assert.assertEquals(ObjectInputFilter.rejectUndecidedClass(allowed).checkInput(info), ALLOWED, "allowed -> rejected");
|
||||
ObjectInputFilter rejected = getFilter(REJECTED);
|
||||
Assert.assertEquals(ObjectInputFilter.rejectUndecidedClass(rejected).checkInput(info), REJECTED, "rejected -> rejected");
|
||||
|
||||
// Specific cases of Classes the result in allowed, rejected, and undecided status
|
||||
ObjectInputFilter numberFilter = ObjectInputFilter.Config.createFilter("java.lang.Integer;!java.lang.Double");
|
||||
Object[] testObjs = {
|
||||
Integer.valueOf(1), // Integer is allowed -> allowed
|
||||
new Integer[1], // Integer is allowed -> allowed
|
||||
new Integer[0][0][0], // Integer is allowed -> allowed
|
||||
Long.valueOf(2), // Long is undecided -> rejected
|
||||
new Long[1], // Long is undecided -> rejected
|
||||
new Long[0][0][0], // Long is undecided -> rejected
|
||||
Double.valueOf(2.0d), // Double is rejected -> rejected
|
||||
new Double[1], // Double is rejected -> rejected
|
||||
new Double[0][0][0], // Double is rejected -> rejected
|
||||
new int[1], // int is primitive undecided -> undecided
|
||||
new int[1][1][1], // int is primitive undecided -> undecided
|
||||
};
|
||||
|
||||
for (Object obj : testObjs) {
|
||||
Class<?> clazz = obj.getClass();
|
||||
info = new SerialInfo(clazz);
|
||||
Status rawSt = numberFilter.checkInput(info);
|
||||
Status st = ObjectInputFilter.rejectUndecidedClass(numberFilter).checkInput(info);
|
||||
if (UNDECIDED.equals(rawSt)) {
|
||||
while (clazz.isArray())
|
||||
clazz = clazz.getComponentType();
|
||||
Status expected = (clazz.isPrimitive()) ? UNDECIDED : REJECTED;
|
||||
Assert.assertEquals(st, expected, "Wrong status for class: " + obj.getClass());
|
||||
} else {
|
||||
Assert.assertEquals(rawSt, st, "raw filter and rejectUndecided filter disagree");
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns an ObjectInputFilter that returns the requested Status.
|
||||
* @param status a Status, may be null
|
||||
* @return an ObjectInputFilter that returns the requested Status
|
||||
*/
|
||||
private static ObjectInputFilter getFilter(ObjectInputFilter.Status status) {
|
||||
return (info) -> status;
|
||||
}
|
||||
|
||||
/**
|
||||
* FilterInfo instance with a specific class.
|
||||
*/
|
||||
static class SerialInfo implements ObjectInputFilter.FilterInfo {
|
||||
private final Class<?> clazz;
|
||||
|
||||
SerialInfo(Class<?> clazz) {
|
||||
this.clazz = clazz;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Class<?> serialClass() {
|
||||
return clazz;
|
||||
}
|
||||
|
||||
@Override
|
||||
public long arrayLength() {
|
||||
return 0;
|
||||
}
|
||||
|
||||
@Override
|
||||
public long depth() {
|
||||
return 0;
|
||||
}
|
||||
|
||||
@Override
|
||||
public long references() {
|
||||
return 0;
|
||||
}
|
||||
|
||||
@Override
|
||||
public long streamBytes() {
|
||||
return 0;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String toString() {
|
||||
StringBuilder sb = new StringBuilder();
|
||||
sb.append("serialClass: " + serialClass());
|
||||
sb.append(", arrayLength: " + arrayLength());
|
||||
sb.append(", depth: " + depth());
|
||||
sb.append(", references: " + references());
|
||||
sb.append(", streamBytes: " + streamBytes());
|
||||
return sb.toString();
|
||||
}
|
||||
}
|
||||
}
|
@ -1,5 +1,5 @@
|
||||
/*
|
||||
* Copyright (c) 2016, 2020, Oracle and/or its affiliates. All rights reserved.
|
||||
* Copyright (c) 2016, 2021, Oracle and/or its affiliates. All rights reserved.
|
||||
* DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
|
||||
*
|
||||
* This code is free software; you can redistribute it and/or modify it
|
||||
@ -29,6 +29,7 @@ import java.io.InvalidClassException;
|
||||
import java.io.ObjectInputFilter;
|
||||
import java.io.ObjectInputStream;
|
||||
import java.io.ObjectOutputStream;
|
||||
import java.io.Serial;
|
||||
import java.io.Serializable;
|
||||
import java.lang.invoke.SerializedLambda;
|
||||
import java.lang.reflect.Constructor;
|
||||
@ -52,14 +53,15 @@ import org.testng.annotations.DataProvider;
|
||||
/* @test
|
||||
* @bug 8234836
|
||||
* @build SerialFilterTest
|
||||
* @run testng/othervm SerialFilterTest
|
||||
* @run testng/othervm -Djdk.serialSetFilterAfterRead=true SerialFilterTest
|
||||
* @run testng/othervm -Djdk.serialFilterTrace=true SerialFilterTest
|
||||
* @run testng/othervm -Djdk.serialSetFilterAfterRead=true -Djdk.serialFilterTrace=true SerialFilterTest
|
||||
*
|
||||
* @summary Test ObjectInputFilters
|
||||
* @summary Test ObjectInputFilters using Builtin Filter Factory
|
||||
*/
|
||||
@Test
|
||||
public class SerialFilterTest implements Serializable {
|
||||
|
||||
@Serial
|
||||
private static final long serialVersionUID = -6999613679881262446L;
|
||||
|
||||
/**
|
||||
@ -242,7 +244,7 @@ public class SerialFilterTest implements Serializable {
|
||||
* @throws IOException
|
||||
*/
|
||||
@Test(dataProvider="Objects")
|
||||
public static void t1(Object object,
|
||||
void t1(Object object,
|
||||
long count, long maxArray, long maxRefs, long maxDepth, long maxBytes,
|
||||
List<Class<?>> classes) throws IOException {
|
||||
byte[] bytes = writeObjects(object);
|
||||
@ -267,7 +269,7 @@ public class SerialFilterTest implements Serializable {
|
||||
* @param pattern a pattern
|
||||
*/
|
||||
@Test(dataProvider="Patterns")
|
||||
static void testPatterns(String pattern) {
|
||||
void testPatterns(String pattern) {
|
||||
evalPattern(pattern, (p, o, neg) -> testPatterns(p, o, neg));
|
||||
}
|
||||
|
||||
@ -277,40 +279,41 @@ public class SerialFilterTest implements Serializable {
|
||||
* This test is agnostic the global filter being set or not.
|
||||
*/
|
||||
@Test
|
||||
static void nonResettableFilter() {
|
||||
void nonResettableFilter() {
|
||||
Validator validator1 = new Validator();
|
||||
Validator validator2 = new Validator();
|
||||
|
||||
try {
|
||||
byte[] bytes = writeObjects("text1"); // an object
|
||||
Validator[] filterCases = {
|
||||
validator1, // setting filter to a non-null filter
|
||||
null, // setting stream-specific filter to null
|
||||
};
|
||||
|
||||
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 = ois.getObjectInputFilter();
|
||||
Assert.assertEquals(global, initial, "initial filter should be the global filter");
|
||||
for (Validator validator : filterCases) {
|
||||
try {
|
||||
byte[] bytes = writeObjects("text1"); // an object
|
||||
|
||||
// Check if it can be set to null
|
||||
ois.setObjectInputFilter(null);
|
||||
ObjectInputFilter filter = ois.getObjectInputFilter();
|
||||
Assert.assertNull(filter, "set to null should be null");
|
||||
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 = ois.getObjectInputFilter();
|
||||
Assert.assertEquals(global, initial, "initial filter should be the global filter");
|
||||
|
||||
ois.setObjectInputFilter(validator1);
|
||||
Object o = ois.readObject();
|
||||
try {
|
||||
ois.setObjectInputFilter(validator2);
|
||||
Assert.fail("Should not be able to set filter twice");
|
||||
} catch (IllegalStateException ise) {
|
||||
// success, the exception was expected
|
||||
ois.setObjectInputFilter(validator);
|
||||
Object o = ois.readObject();
|
||||
try {
|
||||
ois.setObjectInputFilter(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 (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);
|
||||
}
|
||||
} catch (IOException ex) {
|
||||
Assert.fail("Unexpected IOException", ex);
|
||||
}
|
||||
}
|
||||
|
||||
@ -323,7 +326,7 @@ public class SerialFilterTest implements Serializable {
|
||||
* to revert to the old behavior but it re-enables the incorrect use.
|
||||
*/
|
||||
@Test
|
||||
static void testNonSettableAfterReadObject() throws IOException, ClassNotFoundException {
|
||||
void testNonSettableAfterReadObject() throws IOException, ClassNotFoundException {
|
||||
String expected1 = "text1";
|
||||
String expected2 = "text2";
|
||||
byte[] bytes = writeObjects(expected1, expected2);
|
||||
@ -359,7 +362,7 @@ public class SerialFilterTest implements Serializable {
|
||||
* @throws IOException if an error occurs
|
||||
*/
|
||||
@Test(dataProvider="Arrays")
|
||||
static void testReadResolveToArray(Object array, int length) throws IOException {
|
||||
void testReadResolveToArray(Object array, int length) throws IOException {
|
||||
ReadResolveToArray object = new ReadResolveToArray(array, length);
|
||||
byte[] bytes = writeObjects(object);
|
||||
Object o = validate(bytes, object); // the object is its own filter
|
||||
@ -376,7 +379,7 @@ public class SerialFilterTest implements Serializable {
|
||||
* @param value a test value
|
||||
*/
|
||||
@Test(dataProvider="Limits")
|
||||
static void testLimits(String name, long value) {
|
||||
void testLimits(String name, long 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);
|
||||
@ -396,7 +399,7 @@ public class SerialFilterTest implements Serializable {
|
||||
* @param pattern a pattern to test
|
||||
*/
|
||||
@Test(dataProvider="InvalidLimits", expectedExceptions=java.lang.IllegalArgumentException.class)
|
||||
static void testInvalidLimits(String pattern) {
|
||||
void testInvalidLimits(String pattern) {
|
||||
try {
|
||||
ObjectInputFilter filter = ObjectInputFilter.Config.createFilter(pattern);
|
||||
} catch (IllegalArgumentException iae) {
|
||||
@ -409,7 +412,7 @@ public class SerialFilterTest implements Serializable {
|
||||
* Test that returning null from a filter causes deserialization to fail.
|
||||
*/
|
||||
@Test(expectedExceptions=InvalidClassException.class)
|
||||
static void testNullStatus() throws IOException {
|
||||
void testNullStatus() throws IOException {
|
||||
byte[] bytes = writeObjects(0); // an Integer
|
||||
try {
|
||||
Object o = validate(bytes, new ObjectInputFilter() {
|
||||
@ -428,7 +431,7 @@ public class SerialFilterTest implements Serializable {
|
||||
* @param pattern pattern from the data source
|
||||
*/
|
||||
@Test(dataProvider="InvalidPatterns", expectedExceptions=IllegalArgumentException.class)
|
||||
static void testInvalidPatterns(String pattern) {
|
||||
void testInvalidPatterns(String pattern) {
|
||||
try {
|
||||
ObjectInputFilter.Config.createFilter(pattern);
|
||||
} catch (IllegalArgumentException iae) {
|
||||
@ -441,7 +444,7 @@ public class SerialFilterTest implements Serializable {
|
||||
* Test that Config.create returns null if the argument does not contain any patterns or limits.
|
||||
*/
|
||||
@Test()
|
||||
static void testEmptyPattern() {
|
||||
void testEmptyPattern() {
|
||||
ObjectInputFilter filter = ObjectInputFilter.Config.createFilter("");
|
||||
Assert.assertNull(filter, "empty pattern did not return null");
|
||||
|
||||
@ -773,6 +776,7 @@ public class SerialFilterTest implements Serializable {
|
||||
* the ObjectInputFilter to check that it has the expected length.
|
||||
*/
|
||||
static class ReadResolveToArray implements Serializable, ObjectInputFilter {
|
||||
@Serial
|
||||
private static final long serialVersionUID = 123456789L;
|
||||
|
||||
@SuppressWarnings("serial") /* Incorrect declarations are being tested */
|
||||
@ -784,6 +788,7 @@ public class SerialFilterTest implements Serializable {
|
||||
this.length = length;
|
||||
}
|
||||
|
||||
@Serial
|
||||
Object readResolve() {
|
||||
return array;
|
||||
}
|
||||
@ -844,21 +849,27 @@ public class SerialFilterTest implements Serializable {
|
||||
|
||||
// Deeper superclass hierarchy
|
||||
static class A implements Serializable {
|
||||
@Serial
|
||||
private static final long serialVersionUID = 1L;
|
||||
};
|
||||
static class B extends A {
|
||||
@Serial
|
||||
private static final long serialVersionUID = 2L;
|
||||
}
|
||||
static class C extends B {
|
||||
@Serial
|
||||
private static final long serialVersionUID = 3L;
|
||||
}
|
||||
static class D extends C {
|
||||
@Serial
|
||||
private static final long serialVersionUID = 4L;
|
||||
}
|
||||
static class E extends D {
|
||||
@Serial
|
||||
private static final long serialVersionUID = 5L;
|
||||
}
|
||||
static class F extends E {
|
||||
@Serial
|
||||
private static final long serialVersionUID = 6L;
|
||||
}
|
||||
|
||||
|
@ -0,0 +1,4 @@
|
||||
# Deserialization Input Filter Factory
|
||||
# See conf/security/java.security for pattern synatx
|
||||
#
|
||||
jdk.serialFilterFactory=SerialFilterFactoryTest$PropertyFilterFactory
|
Loading…
x
Reference in New Issue
Block a user