@FunctionalInterface public interface ObjectInputFilter
Warning: Deserialization of untrusted data is inherently dangerous and should be avoided. Untrusted data should be carefully validated according to the "Serialization and Deserialization" section of the Secure Coding Guidelines for Java SE. Serialization Filtering describes best practices for defensive use of serial filters.
To protect against deserialization vulnerabilities, application developers need a clear description of the objects that can be deserialized by each component or library. For each context and use case, developers should construct and apply an appropriate filter.
ObjectInputStream
. For simple cases, a static JVM-wide filter can be set for the entire application, without setting a filter factory. The JVM-wide filter can be set either with a system property on the command line or by calling Config.setSerialFilter. No custom filter factory needs to be specified, defaulting to the builtin filter factory. The builtin filter factory provides the static JVM-wide filter for each ObjectInputStream.
For example, a filter that allows example classes, allows classes in the java.base
module, and rejects all other classes can be set: As a command line property:
% java -Djdk.serialFilter="example.*;java.base/*;!*" ...
var filter = ObjectInputFilter.Config.createFilter("example.*;java.base/*;!*")
ObjectInputFilter.Config.setSerialFilter(filter);
In an application with multiple execution contexts, the application can provide a filter factory to protect individual contexts by providing a custom filter for each. When the stream is constructed, the filter factory is called to identify the execution context from the available information, including the current thread-local state, hierarchy of callers, library, module, and class loader. At that point, the filter factory policy for creating or selecting filters can choose a specific filter or composition of filters based on the context. The JVM-wide deserialization filter factory ensures that a context-specific deserialization filter can be set on every ObjectInputStream
and every object read from the stream can be checked.
The JVM-wide filter factory is a function invoked when each ObjectInputStream
is constructed and when the stream-specific filter is set. The parameters are the current filter and a requested filter and it returns the filter to be used for the stream. When invoked from the ObjectInputStream constructors, the first parameter is null
and the second parameter is the static JVM-wide filter. When invoked from ObjectInputStream.setObjectInputFilter
, the first parameter is the filter currently set on the stream (which was set in the constructor), and the second parameter is the filter given to ObjectInputStream.setObjectInputFilter
. The current and new filter may each be null
and the factory may return null
. Note that the filter factory implementation can also use any contextual information at its disposal, for example, extracted from the application thread context, or its call stack, to compose and combine a new filter. It is not restricted to only use its two parameters.
The active deserialization filter factory is either:
ObjectInputFilter.Config.setSerialFilterFactory(BinaryOperator)
or the system property jdk.serialFilterFactory
or the security property jdk.serialFilterFactory
. ObjectInputStream.setObjectInputFilter(ObjectInputFilter)
. See getSerialFilterFactory. The filter's checkInput(FilterInfo)
method is invoked zero or more times while reading objects. The method is called to validate classes, the length of each array, the number of objects being read from the stream, the depth of the graph, and the total number of bytes read from the stream.
Composite filters combine or check the results of other filters. The merge(filter, anotherFilter)
filter combines the status value of two filters. The rejectUndecidedClass(filter)
checks the result of a filter for classes when the status is UNDECIDED
. In many cases any class not ALLOWED
by the filter should be REJECTED
.
A deserialization filter determines whether the arguments are allowed or rejected and should return the appropriate status: ALLOWED
or REJECTED
. If the filter cannot determine the status it should return UNDECIDED
. Filters should be designed for the specific use case and expected types. A filter designed for a particular use may be passed a class outside of the scope of the filter. If the purpose of the filter is to reject classes then it can reject a candidate class that matches and report UNDECIDED
for others. A filter may be called with class equals null
, arrayLength
equal -1, the depth, number of references, and stream size and return a status that reflects only one or only some of the values. This allows a filter to be specific about the choice it is reporting and to use other filters without forcing either allowed or rejected status.
For an application composed from multiple modules or libraries, the structure of the application can be used to identify the classes to be allowed or rejected by each ObjectInputStream
in each context of the application. The deserialization filter factory is invoked when each stream is constructed and can examine the thread or program to determine a context-specific filter to be applied. Some possible examples:
doWithSerialFilter
method does the setup of the thread-specific filter and invokes the application provided Runnable
. public static final class FilterInThread implements BinaryOperator<ObjectInputFilter> {
private final ThreadLocal<ObjectInputFilter> filterThreadLocal = new ThreadLocal<>();
// Construct a FilterInThread deserialization filter factory.
public FilterInThread() {}
// Returns a composite filter of the static JVM-wide filter, a thread-specific filter,
// and the stream-specific 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) {
// Merge to invoke the thread local filter and then the JVM-wide filter (if any)
filter = ObjectInputFilter.merge(filter, next);
return ObjectInputFilter.rejectUndecidedClass(filter);
}
return (next == null) ? null : ObjectInputFilter.rejectUndecidedClass(next);
} 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 merge to invoke it and then the current filter.
if (next != null) {
return ObjectInputFilter.merge(next, curr);
}
return curr;
}
}
// Applies the filter to the thread and invokes the runnable.
public void doWithSerialFilter(ObjectInputFilter filter, Runnable runnable) {
var prevFilter = filterThreadLocal.get();
try {
filterThreadLocal.set(filter);
runnable.run();
} finally {
filterThreadLocal.set(prevFilter);
}
}
}
FilterInThread
utility create an instance and configure it as the JVM-wide filter factory. The doWithSerialFilter
method is invoked with a filter allowing the example application and core classes: // Create a FilterInThread filter factory and set
var filterInThread = new FilterInThread();
ObjectInputFilter.Config.setSerialFilterFactory(filterInThread);
// Create a filter to allow example.* classes and reject all others
var filter = ObjectInputFilter.Config.createFilter("example.*;java.base/*;!*");
filterInThread.doWithSerialFilter(filter, () -> {
byte[] bytes = ...;
var o = deserializeObject(bytes);
});
Unless otherwise noted, passing a null
argument to a method in this interface and its nested classes will cause a NullPointerException
to be thrown.
Modifier and Type | Interface | Description |
---|---|---|
static final class |
ObjectInputFilter.Config |
A utility class to set and get the JVM-wide deserialization filter factory, the static JVM-wide filter, or to create a filter from a pattern string. |
static interface |
ObjectInputFilter.FilterInfo |
FilterInfo provides access to information about the current object being deserialized and the status of the ObjectInputStream . |
static enum |
ObjectInputFilter.Status |
The status of a check on the class, array length, number of references, depth, and stream size. |
Modifier and Type | Method | Description |
---|---|---|
static ObjectInputFilter |
allowFilter |
Returns a filter that returns Status.ALLOWED if the predicate on the class is true . |
ObjectInputFilter.Status |
checkInput |
Check the class, array length, number of object references, depth, stream size, and other available filtering information. |
static ObjectInputFilter |
merge |
Returns a filter that merges the status of a filter and another filter. |
static ObjectInputFilter |
rejectFilter |
Returns a filter that returns Status.REJECTED if the predicate on the class is true . |
static ObjectInputFilter |
rejectUndecidedClass |
Returns a filter that invokes a given filter and maps UNDECIDED to REJECTED for classes, with some special cases, and otherwise returns the status. |
ObjectInputFilter.Status checkInput(ObjectInputFilter.FilterInfo filterInfo)
Status.ALLOWED
, Status.REJECTED
, or Status.UNDECIDED
. If filterInfo.serialClass()
is non-null
, there is a class to be checked. If serialClass()
is null
, there is no class and the info contains only metrics related to the depth of the graph being deserialized, the number of references, and the size of the stream read.
checkInput
should return one of the values of ObjectInputFilter.Status
. Returning null
may result in a NullPointerException
or other unpredictable behavior.filterInfo
- provides information about the current object being deserialized, if any, and the status of the ObjectInputStream
Status.ALLOWED
if accepted, Status.REJECTED
if rejected, Status.UNDECIDED
if undecided.static ObjectInputFilter allowFilter(Predicate<Class<?>> predicate, ObjectInputFilter.Status otherStatus)
Status.ALLOWED
if the predicate on the class is true
. The filter returns ALLOWED
or the otherStatus
based on the predicate of the non-null
class and UNDECIDED
if the class is null
. When the filter's checkInput(info)
method is invoked, the predicate is applied to the info.serialClass()
, the return Status is:
UNDECIDED
, if the serialClass
is null
,ALLOWED
, if the predicate on the class returns true
,otherStatus
.Example, to create a filter that will allow any class loaded from the platform or bootstrap classloaders.
ObjectInputFilter f
= allowFilter(cl -> cl.getClassLoader() == ClassLoader.getPlatformClassLoader() ||
cl.getClassLoader() == null, Status.UNDECIDED);
predicate
- a predicate to test a non-null ClassotherStatus
- a Status to use if the predicate is false
ALLOWED
if the predicate on the class is true
static ObjectInputFilter rejectFilter(Predicate<Class<?>> predicate, ObjectInputFilter.Status otherStatus)
Status.REJECTED
if the predicate on the class is true
. The filter returns REJECTED
or the otherStatus
based on the predicate of the non-null
class and UNDECIDED
if the class is null
. When the filter's checkInput(info)
method is invoked, the predicate is applied to the serialClass()
, the return Status is: UNDECIDED
, if the serialClass
is null
,REJECTED
, if the predicate on the class returns true
,otherStatus
.Example, to create a filter that will reject any class loaded from the application classloader.
ObjectInputFilter f = rejectFilter(cl ->
cl.getClassLoader() == ClassLoader.ClassLoader.getSystemClassLoader(), Status.UNDECIDED);
predicate
- a predicate to test a non-null ClassotherStatus
- a Status to use if the predicate is false
REJECTED
if the predicate on the class is true
static ObjectInputFilter merge(ObjectInputFilter filter, ObjectInputFilter anotherFilter)
another
filter is null
, the filter
is returned. Otherwise, a filter
is returned to merge the pair of non-null
filters. The filter returned implements the checkInput(FilterInfo)
method as follows: filter
on the FilterInfo
to get its status
; REJECTED
if the status
is REJECTED
; anotherFilter
to get the otherStatus
; REJECTED
if the otherStatus
is REJECTED
; ALLOWED
, if either status
or otherStatus
is ALLOWED
, UNDECIDED
filter
- a filteranotherFilter
- a filter to be merged with the filter, may be null
ObjectInputFilter
that merges the status of the filter and another filterstatic ObjectInputFilter rejectUndecidedClass(ObjectInputFilter filter)
UNDECIDED
to REJECTED
for classes, with some special cases, and otherwise returns the status. If the class is not a primitive class and not an array, the status returned is REJECTED
. If the class is a primitive class or an array class additional checks are performed; see the list below for details. Object deserialization accepts a class if the filter returns 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.
checkInput(FilterInfo)
method as follows: FilterInfo
to get its status
; status
if the status is REJECTED
or ALLOWED
; UNDECIDED
if the filterInfo.getSerialClass() serialClass
is null
; REJECTED
if the class is not an array; serialClass
is an array; UNDECIDED
if the base component type is a primitive class; base component type
to get its component status
;ALLOWED
if the component status is ALLOWED
; REJECTED
.filter
- a filterObjectInputFilter
that maps an ObjectInputFilter.Status.UNDECIDED
status to ObjectInputFilter.Status.REJECTED
for classes, otherwise returns the filter status
© 1993, 2023, Oracle and/or its affiliates. All rights reserved.
Documentation extracted from Debian's OpenJDK Development Kit package.
Licensed under the GNU General Public License, version 2, with the Classpath Exception.
Various third party code in OpenJDK is licensed under different licenses (see Debian package).
Java and OpenJDK are trademarks or registered trademarks of Oracle and/or its affiliates.
https://docs.oracle.com/en/java/javase/21/docs/api/java.base/java/io/ObjectInputFilter.html