Skip to content
Permalink
Browse files
8264859: Implement Context-Specific Deserialization Filters
Reviewed-by: bchristi, dfuchs, chegar
  • Loading branch information
Roger Riggs committed Jun 9, 2021
1 parent dd34a4c commit 13d618042112aa761ef256aa35ec0a8b808cd78b
Showing 9 changed files with 2,554 additions and 185 deletions.

Large diffs are not rendered by default.

@@ -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 @@
* 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 @@
* 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 @@ private static class Logging {
*/
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 ObjectInputStream(InputStream in) throws IOException {
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 ObjectInputStream(InputStream in) throws IOException {
* 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 @@ protected ObjectInputStream() throws IOException, SecurityException {
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 @@ protected ObjectInputStream() throws IOException, SecurityException {
* 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 @@ protected ObjectInputStream() throws IOException, SecurityException {
* @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 @@ protected Object readObjectOverride()
* 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 @@ protected Class<?> resolveProxyClass(String[] interfaces)
* <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,22 +1240,37 @@ public String readUTF() throws IOException {
}

/**
* 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() {
return serialFilter;
}

/**
* 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 final ObjectInputFilter getObjectInputFilter() {
* 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 final ObjectInputFilter getObjectInputFilter() {
* @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 final void setObjectInputFilter(ObjectInputFilter filter) {
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 @@ private 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 static String jdkSerialFilter() {
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


#
# Deserialization system-wide filter factory
#
# 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>

#
# Serialization system-wide filter
# Deserialization system-wide filter
#
# A filter, if configured, is used by java.io.ObjectInputStream during
# deserialization to check the contents of the stream.
# 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).

1 comment on commit 13d6180

@openjdk-notifier
Copy link

@openjdk-notifier openjdk-notifier bot commented on 13d6180 Jun 9, 2021

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Please sign in to comment.