JEP 290: Filter Incoming Serialization Data

OwnerRoger Riggs
TypeFeature
ScopeSE
StatusClosed / Delivered
Release9
Componentcore-libs / java.io:serialization
Discussioncore dash libs dash dev at openjdk dot java dot net
EffortS
DurationS
Relates toJEP 415: Context-Specific Deserialization Filters
Reviewed byAlan Bateman, Andrew Gross, Brian Goetz
Endorsed byBrian Goetz
Created2016/04/22 16:06
Updated2022/08/15 16:17
Issue8154961

Summary

Allow incoming streams of object-serialization data to be filtered in order to improve both security and robustness.

Goals

Non-Goals

Success Metrics

Motivation

Security guidelines consistently require that input from external sources be validated before use. The filter mechanism will allow object-serialization clients to more easily validate their inputs, and exported RMI objects to validate invocation arguments.

Description

The core mechanism is a filter interface implemented by serialization clients and set on an ObjectInputStream. The filter interface methods are called during the deserialization process to validate the classes being deserialized, the sizes of arrays being created, and metrics describing stream length, stream depth, and number of references as the stream is being decoded. The filter returns a status to accept, reject, or leave the status undecided.

For each new object in the stream the filter is called, with the class of the object, before the object is instantiated and deserialized. The filter is not called for primitives or java.lang.String instances that are encoded concretely in the stream. For each array, regardless of whether it is an array of primitives, array of strings, or array of objects the filter is called with the array class and the array length. For each reference to an object already read from the stream, the filter is called so it can check the depth, number of references, and stream length. Filter actions are logged to the java.io.serialization logger, if logging is enabled.

For RMI, the object is exported via a UnicastServerRef that sets the filter on the MarshalInputStream to validate the invocation arguments as they are unmarshalled. Exporting objects via UnicastRemoteObject should support setting a filter to be used for unmarshalling.

Process-wide Filter

A process-wide filter is configured via a system property or a configuration file. The system property, if supplied, supersedes the security property value.

A filter is configured as a sequence of patterns, each pattern is either matched against the name of a class in the stream or a limit. Patterns are separated by ";" (semi-colon). Whitespace is significant and is considered part of the pattern.

A limit pattern contains a "=" and sets a limit. If a limit appears more than once the last value is used. If any of the values in the call to ObjectInputFilter.checkInput(...) exceeds the respective limit, the filter returns Status.REJECTED. Limits are checked before classes regardless of the order in the sequence of patterns.

Other patterns, from left to right, match the class or package name as returned from Class::getName. If the class is an array type, the class or package to be matched is the element type. Arrays for any number of dimensions are treated the same as the element type. For example, a pattern of "!example.Foo", rejects creation of any instance or array of example.Foo.

ObjectInputFilter Interface and API

The object-input filter interface is implemented by clients of RMI and serialization, and provides the behaviors of the process-wide configurable filter.

interface ObjectInputFilter {
    Status checkInput(FilterInput filterInfo);

    enum Status { 
        UNDECIDED, 
        ALLOWED, 
        REJECTED; 
    }

   interface FilterInfo {
         Class<?> serialClass();
         long arrayLength();
         long depth();
         long references();
         long streamBytes();
   }

    public static class Config {
        public static void setSerialFilter(ObjectInputFilter filter);
        public static ObjectInputFilter getSerialFilter(ObjectInputFilter filter) ;
        public static ObjectInputFilter createFilter(String patterns);
    }   
}

ObjectInputStream Filter

ObjectInputStream has additional methods to set and get the current filter. If no filter is set for an ObjectInputStream then the global filter is used, if any.

public class ObjectInputStream ... {
    public final void setObjectInputFilter(ObjectInputFilter filter);
    public final ObjectInputFilter getObjectInputFilter(ObjectInputFilter filter);
}

Alternatives

Modify existing subclasses and methods, but that would require changes that would inhibit use in third party implementations.

Testing

No existing tests need to be updated. New unit tests will test the filter mechanisms with serialized streams, RMI exported objects, and the global filtering mechanism.

Risks and Assumptions

The metrics presented to the filter supporting reject lists, accept lists, and stream metrics should be sufficient. When applied to the known use cases, some additional filter mechanisms may be discovered.

New APIs and interfaces will be introduced for JDK 9. Backporting this feature to previous versions will require the introduction of implementation-specific APIs to avoid changes to older versions of the Java SE specification.