Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

8264859: Implement Context-Specific Deserialization Filters #3996

Closed
Closed
Show file tree
Hide file tree
Changes from 5 commits
Commits
Show all changes
17 commits
Select commit Hold shift + click to select a range
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Jump to
Jump to file
Failed to load files.
Diff view
Diff view
901 changes: 813 additions & 88 deletions src/java.base/share/classes/java/io/ObjectInputFilter.java

Large diffs are not rendered by default.

114 changes: 70 additions & 44 deletions src/java.base/share/classes/java/io/ObjectInputStream.java
Expand Up @@ -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;
Expand All @@ -44,6 +45,7 @@
import java.util.Objects;
import java.util.concurrent.ConcurrentHashMap;
import java.util.concurrent.ConcurrentMap;
import java.util.function.BinaryOperator;

import static java.io.ObjectStreamClass.processQueue;

Expand Down Expand Up @@ -187,16 +189,22 @@
* the case that the fields of that class are accessible (public, package, or
* 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
* <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. 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 and it is updated by {@link #setObjectInputFilter}.
RogerRiggs marked this conversation as resolved.
Show resolved Hide resolved
* {@link ObjectInputFilter} describes how to use filters and
* {@link ObjectInputFilter.Config} describes how to configure the filter and filter factory.
* <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.
* 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.
Expand Down Expand Up @@ -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 deserialization filter is initialized to the filter returned
* by invoking the {@link Config#getSerialFilterFactory()} with {@code null} for the current filter
RogerRiggs marked this conversation as resolved.
Show resolved Hide resolved
* 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
Expand All @@ -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.getSerialFilterFactory().apply(null, Config.getSerialFilter());
enableOverride = false;
readStreamHeader();
bin.setBlockDataMode(true);
Expand All @@ -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 deserialization filter is initialized 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
Expand All @@ -411,7 +427,8 @@ protected ObjectInputStream() throws IOException, SecurityException {
bin = null;
handles = null;
vlist = null;
serialFilter = ObjectInputFilter.Config.getSerialFilter();
streamFilterSet = false;
serialFilter = Config.getSerialFilterFactory().apply(null, Config.getSerialFilter());
enableOverride = true;
}

Expand All @@ -430,7 +447,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.
*
Expand All @@ -442,7 +459,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
Expand Down Expand Up @@ -563,7 +580,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.
*
Expand Down Expand Up @@ -871,7 +888,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
Expand Down Expand Up @@ -1221,22 +1238,32 @@ 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}
RogerRiggs marked this conversation as resolved.
Show resolved Hide resolved
* 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 filter can be set and only set once before reading any objects from the stream;
* for example, by calling {@link #readObject} or {@link #readUnshared}.
*
* <p>The deserialization filter is set to the filter returned
* by invoking the {@linkplain Config#getSerialFilterFactory() JVM-wide filter factory}
* with the current filter and the {@code filter} parameter.
* If there is a non-null filter for the stream, one was set in the constructor, and the filter factory
* must return a non-null filter. It is not permitted to remove filtering once established.
* See the {@linkplain ObjectInputFilter filter models} for examples of composition and delegation.
*
RogerRiggs marked this conversation as resolved.
Show resolved Hide resolved
* <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.
Expand All @@ -1245,21 +1272,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}
Expand Down Expand Up @@ -1301,30 +1321,36 @@ 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) {
SecurityManager sm = System.getSecurityManager();
if (sm != null) {
// TBD double checks, here and in the default serialFilterFactory
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 replaced");
}
// 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.
*
Expand Down
16 changes: 16 additions & 0 deletions src/java.base/share/classes/jdk/internal/util/StaticProperty.java
Expand Up @@ -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;

Expand All @@ -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");
}

Expand Down Expand Up @@ -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.
*
Expand Down