Interface ObjectInputFilter
- Functional Interface:
- This is a functional interface and can therefore be used as the assignment target for a lambda expression or method reference.
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 theSecure 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.
Deserialization Filtering Factory and Filters
The parts of deserialization filtering are the filters, composite filters, and filter factory. Each filter performs checks on classes and resource limits to determine the status as rejected, allowed, or undecided. Filters can be composed of other filters and merge or combine their results. The filter factory is responsible for establishing and updating the filter for eachObjectInputStream
.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 callingConfig.setSerialFilter. No custom filter factory needs to be specified, defaulting to the builtin filter factory. The builtin filter factory provides thestatic JVM-wide filter for eachObjectInputStream.
For example, a filter that allows example classes, allows classes in thejava.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 afilter 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 everyObjectInputStream
and every object read from the stream can be checked.
Invoking the Filter Factory
The JVM-wide filter factory is a function invoked when eachObjectInputStream
isconstructed and when thestream-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 theObjectInputStream constructors, the first parameter isnull
and the second parameter is thestatic JVM-wide filter. When invoked fromObjectInputStream.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 toObjectInputStream.setObjectInputFilter
. The current and new filter may each benull
and the factory may returnnull
. 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:
- The application specific filter factory set via
ObjectInputFilter.Config.setSerialFilterFactory(BinaryOperator)
or the system propertyjdk.serialFilterFactory
or the security propertyjdk.serialFilterFactory
. - Otherwise, a builtin deserialization filter factory provides thestatic JVM-wide filter when invoked from theObjectInputStream constructors and replaces the static filter when invoked from
ObjectInputStream.setObjectInputFilter(ObjectInputFilter)
. SeegetSerialFilterFactory.
Filters
Filters can be created from apattern string, or based on apredicate of a class toallow orreject classes.The filter'scheckInput(FilterInfo)
method is invoked zero or more times whilereading 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. Themerge(filter, anotherFilter)
filter combines the status value of two filters. TherejectUndecidedClass(filter)
checks the result of a filter for classes when the status isUNDECIDED
. In many cases any class notALLOWED
by the filter should beREJECTED
.
A deserialization filter determines whether the arguments are allowed or rejected and should return the appropriate status:ALLOWED
orREJECTED
. If the filter cannot determine the status it should returnUNDECIDED
. 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 reportUNDECIDED
for others. A filter may be called with class equalsnull
,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.
Filter Model Examples
For simple applications, a single predefined filter listing allowed or rejected classes may be sufficient to manage the risk of deserializing unexpected classes.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 eachObjectInputStream
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:
- Thread-local state can hold the filter to be applied or composed with a stream-specific filter. Filters could be pushed and popped from a virtual stack of filters maintained by the application or libraries.
- The filter factory can identify the caller of the deserialization method and use module or library context to select a filter or compose an appropriate context-specific filter. A mechanism could identify a callee with restricted or unrestricted access to serialized classes and choose a filter accordingly.
Example to filter every deserialization in a thread
This class shows how an application provided filter factory can combine filters to check every deserialization operation that takes place in a thread. It defines a thread-local variable to hold the thread-specific filter, and construct a filter factory that composes that filter with the static JVM-wide filter and the stream-specific filter, rejecting any classes not handled by those two filters. If a stream specific filter is set and does not accept or reject a class, the combined JVM-wide filter and thread filter is applied. ThedoWithSerialFilter
method does the setup of the thread-specific filter and invokes the application providedRunnable
.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); } }}
Using the Filter Factory
To useFilterInThread
utility create an instance and configure it as the JVM-wide filter factory. ThedoWithSerialFilter
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 anull
argument to a method in this interface and its nested classes will cause aNullPointerException
to be thrown.
- Since:
- 9
- See Also:
Nested Class Summary
Nested ClassesModifier and TypeInterfaceDescriptionstatic final class
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
FilterInfo provides access to information about the current object being deserialized and the status of theObjectInputStream
.static enum
The status of a check on the class, array length, number of references, depth, and stream size.Method Summary
Modifier and TypeMethodDescriptionstaticObjectInputFilter
allowFilter
(Predicate<Class<?>> predicate,ObjectInputFilter.Status otherStatus) Returns a filter that returnsStatus.ALLOWED
if the predicate on the class istrue
.checkInput
(ObjectInputFilter.FilterInfo filterInfo) Check the class, array length, number of object references, depth, stream size, and other available filtering information.staticObjectInputFilter
merge
(ObjectInputFilter filter,ObjectInputFilter anotherFilter) Returns a filter that merges the status of a filter and another filter.staticObjectInputFilter
rejectFilter
(Predicate<Class<?>> predicate,ObjectInputFilter.Status otherStatus) Returns a filter that returnsStatus.REJECTED
if the predicate on the class istrue
.staticObjectInputFilter
Returns a filter that invokes a given filter and mapsUNDECIDED
toREJECTED
for classes, with some special cases, and otherwise returns the status.
Method Details
checkInput
Check the class, array length, number of object references, depth, stream size, and other available filtering information. Implementations of this method check the contents of the object graph being created during deserialization. The filter returnsStatus.ALLOWED
,Status.REJECTED
, orStatus.UNDECIDED
.If
filterInfo.serialClass()
isnon-null
, there is a class to be checked. IfserialClass()
isnull
, 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.- API Note:
- Each filter implementing
checkInput
should return one of the values ofObjectInputFilter.Status
. Returningnull
may result in aNullPointerException
or other unpredictable behavior. - Parameters:
filterInfo
- provides information about the current object being deserialized, if any, and the status of theObjectInputStream
- Returns:
Status.ALLOWED
if accepted,Status.REJECTED
if rejected,Status.UNDECIDED
if undecided.
allowFilter
static ObjectInputFilter allowFilter(Predicate<Class<?>> predicate,ObjectInputFilter.Status otherStatus) Returns a filter that returnsStatus.ALLOWED
if the predicate on the class istrue
. The filter returnsALLOWED
or theotherStatus
based on the predicate of thenon-null
class andUNDECIDED
if the class isnull
.When the filter's
checkInput(info)
method is invoked, the predicate is applied to theinfo.serialClass()
, the return Status is:UNDECIDED
, if theserialClass
isnull
,ALLOWED
, if the predicate on the class returnstrue
,- Otherwise, return
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);
- Parameters:
predicate
- a predicate to test a non-null ClassotherStatus
- a Status to use if the predicate isfalse
- Returns:
- a filter that returns
ALLOWED
if the predicate on the class istrue
- Since:
- 17
rejectFilter
static ObjectInputFilter rejectFilter(Predicate<Class<?>> predicate,ObjectInputFilter.Status otherStatus) Returns a filter that returnsStatus.REJECTED
if the predicate on the class istrue
. The filter returnsREJECTED
or theotherStatus
based on the predicate of thenon-null
class andUNDECIDED
if the class isnull
. When the filter'scheckInput(info)
method is invoked, the predicate is applied to theserialClass()
, the return Status is:UNDECIDED
, if theserialClass
isnull
,REJECTED
, if the predicate on the class returnstrue
,- Otherwise, return
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);
- Parameters:
predicate
- a predicate to test a non-null ClassotherStatus
- a Status to use if the predicate isfalse
- Returns:
- returns a filter that returns
REJECTED
if the predicate on the class istrue
- Since:
- 17
merge
Returns a filter that merges the status of a filter and another filter. Ifanother
filter isnull
, thefilter
is returned. Otherwise, afilter
is returned to merge the pair ofnon-null
filters. The filter returned implements thecheckInput(FilterInfo)
method as follows:- Invoke
filter
on theFilterInfo
to get itsstatus
; - Return
REJECTED
if thestatus
isREJECTED
; - Invoke
anotherFilter
to get theotherStatus
; - Return
REJECTED
if theotherStatus
isREJECTED
; - Return
ALLOWED
, if eitherstatus
orotherStatus
isALLOWED
, - Otherwise, return
UNDECIDED
- Parameters:
filter
- a filteranotherFilter
- a filter to be merged with the filter, may benull
- Returns:
- an
ObjectInputFilter
that merges the status of the filter and another filter - Since:
- 17
- Invoke
rejectUndecidedClass
Returns a filter that invokes a given filter and mapsUNDECIDED
toREJECTED
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 isREJECTED
. 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.- Implementation Requirements:
- The filter returned implements the
checkInput(FilterInfo)
method as follows:- Invoke the filter on the
FilterInfo
to get itsstatus
; - Return the
status
if the status isREJECTED
orALLOWED
; - Return
UNDECIDED
if thefilterInfo.getSerialClass() serialClass
isnull
; - Return
REJECTED
if the class is not anarray; - Determine the base component type if the
serialClass
is anarray; - Return
UNDECIDED
if the base component type is aprimitive class; - Invoke the filter on the
base component type
to get itscomponent status
; - Return
ALLOWED
if the component status isALLOWED
; - Otherwise, return
REJECTED
.
- Invoke the filter on the
- Parameters:
filter
- a filter- Returns:
- an
ObjectInputFilter
that maps anObjectInputFilter.Status.UNDECIDED
status toObjectInputFilter.Status.REJECTED
for classes, otherwise returns the filter status - Since:
- 17