Skip to content

Commit 13d6180

Browse files
author
Roger Riggs
committed
8264859: Implement Context-Specific Deserialization Filters
Reviewed-by: bchristi, dfuchs, chegar
1 parent dd34a4c commit 13d6180

File tree

9 files changed

+2554
-185
lines changed

9 files changed

+2554
-185
lines changed

src/java.base/share/classes/java/io/ObjectInputFilter.java

Lines changed: 857 additions & 94 deletions
Large diffs are not rendered by default.

src/java.base/share/classes/java/io/ObjectInputStream.java

Lines changed: 78 additions & 47 deletions
Original file line numberDiff line numberDiff line change
@@ -25,6 +25,7 @@
2525

2626
package java.io;
2727

28+
import java.io.ObjectInputFilter.Config;
2829
import java.io.ObjectStreamClass.WeakClassKey;
2930
import java.io.ObjectStreamClass.RecordSupport;
3031
import java.lang.System.Logger;
@@ -66,6 +67,23 @@
6667
* practices for defensive use of serial filters.
6768
* </strong></p>
6869
*
70+
* <p>The key to disabling deserialization attacks is to prevent instances of
71+
* arbitrary classes from being deserialized, thereby preventing the direct or
72+
* indirect execution of their methods.
73+
* {@link ObjectInputFilter} describes how to use filters and
74+
* {@link ObjectInputFilter.Config} describes how to configure the filter and filter factory.
75+
* Each stream has an optional deserialization filter
76+
* to check the classes and resource limits during deserialization.
77+
* The JVM-wide filter factory ensures that a filter can be set on every {@link ObjectInputStream}
78+
* and every object read from the stream can be checked.
79+
* The {@linkplain #ObjectInputStream() ObjectInputStream constructors} invoke the filter factory
80+
* to select the initial filter which may be updated or replaced by {@link #setObjectInputFilter}.
81+
* <p>
82+
* If an ObjectInputStream has a filter, the {@link ObjectInputFilter} can check that
83+
* the classes, array lengths, number of references in the stream, depth, and
84+
* number of bytes consumed from the input stream are allowed and
85+
* if not, can terminate deserialization.
86+
*
6987
* <p>ObjectOutputStream and ObjectInputStream can provide an application with
7088
* persistent storage for graphs of objects when used with a FileOutputStream
7189
* and FileInputStream respectively. ObjectInputStream is used to recover
@@ -188,16 +206,6 @@
188206
* protected) or that there are get and set methods that can be used to restore
189207
* the state.
190208
*
191-
* <p>The contents of the stream can be filtered during deserialization.
192-
* If a {@linkplain #setObjectInputFilter(ObjectInputFilter) filter is set}
193-
* on an ObjectInputStream, the {@link ObjectInputFilter} can check that
194-
* the classes, array lengths, number of references in the stream, depth, and
195-
* number of bytes consumed from the input stream are allowed and
196-
* if not, can terminate deserialization.
197-
* A {@linkplain ObjectInputFilter.Config#setSerialFilter(ObjectInputFilter) system-wide filter}
198-
* can be configured that is applied to each {@code ObjectInputStream} unless replaced
199-
* using {@link #setObjectInputFilter(ObjectInputFilter) setObjectInputFilter}.
200-
*
201209
* <p>Any exception that occurs while deserializing an object will be caught by
202210
* the ObjectInputStream and abort the reading process.
203211
*
@@ -347,14 +355,20 @@ private static class Logging {
347355
*/
348356
private ObjectInputFilter serialFilter;
349357

358+
/**
359+
* True if the stream-specific filter has been set; initially false.
360+
*/
361+
private boolean streamFilterSet;
362+
350363
/**
351364
* Creates an ObjectInputStream that reads from the specified InputStream.
352365
* A serialization stream header is read from the stream and verified.
353366
* This constructor will block until the corresponding ObjectOutputStream
354367
* has written and flushed the header.
355368
*
356-
* <p>The serialization filter is initialized to the value of
357-
* {@linkplain ObjectInputFilter.Config#getSerialFilter() the system-wide filter}.
369+
* <p>The constructor initializes the deserialization filter to the filter returned
370+
* by invoking the {@link Config#getSerialFilterFactory()} with {@code null} for the current filter
371+
* and the {@linkplain Config#getSerialFilter() static JVM-wide filter} for the requested filter.
358372
*
359373
* <p>If a security manager is installed, this constructor will check for
360374
* the "enableSubclassImplementation" SerializablePermission when invoked
@@ -377,7 +391,8 @@ public ObjectInputStream(InputStream in) throws IOException {
377391
bin = new BlockDataInputStream(in);
378392
handles = new HandleTable(10);
379393
vlist = new ValidationList();
380-
serialFilter = ObjectInputFilter.Config.getSerialFilter();
394+
streamFilterSet = false;
395+
serialFilter = Config.getSerialFilterFactorySingleton().apply(null, Config.getSerialFilter());
381396
enableOverride = false;
382397
readStreamHeader();
383398
bin.setBlockDataMode(true);
@@ -388,8 +403,9 @@ public ObjectInputStream(InputStream in) throws IOException {
388403
* ObjectInputStream to not have to allocate private data just used by this
389404
* implementation of ObjectInputStream.
390405
*
391-
* <p>The serialization filter is initialized to the value of
392-
* {@linkplain ObjectInputFilter.Config#getSerialFilter() the system-wide filter}.
406+
* <p>The constructor initializes the deserialization filter to the filter returned
407+
* by invoking the {@link Config#getSerialFilterFactory()} with {@code null} for the current filter
408+
* and the {@linkplain Config#getSerialFilter() static JVM-wide filter} for the requested filter.
393409
*
394410
* <p>If there is a security manager installed, this method first calls the
395411
* security manager's {@code checkPermission} method with the
@@ -412,7 +428,8 @@ protected ObjectInputStream() throws IOException, SecurityException {
412428
bin = null;
413429
handles = null;
414430
vlist = null;
415-
serialFilter = ObjectInputFilter.Config.getSerialFilter();
431+
streamFilterSet = false;
432+
serialFilter = Config.getSerialFilterFactorySingleton().apply(null, Config.getSerialFilter());
416433
enableOverride = true;
417434
}
418435

@@ -431,7 +448,7 @@ protected ObjectInputStream() throws IOException, SecurityException {
431448
* priorities. The callbacks are registered by objects (in the readObject
432449
* special methods) as they are individually restored.
433450
*
434-
* <p>The serialization filter, when not {@code null}, is invoked for
451+
* <p>The deserialization filter, when not {@code null}, is invoked for
435452
* each object (regular or class) read to reconstruct the root object.
436453
* See {@link #setObjectInputFilter(ObjectInputFilter) setObjectInputFilter} for details.
437454
*
@@ -443,7 +460,7 @@ protected ObjectInputStream() throws IOException, SecurityException {
443460
* @throws ClassNotFoundException Class of a serialized object cannot be
444461
* found.
445462
* @throws InvalidClassException Something is wrong with a class used by
446-
* serialization.
463+
* deserialization.
447464
* @throws StreamCorruptedException Control information in the
448465
* stream is inconsistent.
449466
* @throws OptionalDataException Primitive data was found in the
@@ -564,7 +581,7 @@ protected Object readObjectOverride()
564581
* invocation of readObject or readUnshared on the ObjectInputStream,
565582
* even if the underlying data stream has been manipulated.
566583
*
567-
* <p>The serialization filter, when not {@code null}, is invoked for
584+
* <p>The deserialization filter, when not {@code null}, is invoked for
568585
* each object (regular or class) read to reconstruct the root object.
569586
* See {@link #setObjectInputFilter(ObjectInputFilter) setObjectInputFilter} for details.
570587
*
@@ -872,7 +889,7 @@ protected Class<?> resolveProxyClass(String[] interfaces)
872889
* <p>When a subclass is replacing objects it must insure that the
873890
* substituted object is compatible with every field where the reference
874891
* will be stored. Objects whose type is not a subclass of the type of the
875-
* field or array element abort the serialization by raising an exception
892+
* field or array element abort the deserialization by raising an exception
876893
* and the object is not be stored.
877894
*
878895
* <p>This method is called only once when each object is first
@@ -1223,22 +1240,37 @@ public String readUTF() throws IOException {
12231240
}
12241241

12251242
/**
1226-
* Returns the serialization filter for this stream.
1227-
* The serialization filter is the most recent filter set in
1228-
* {@link #setObjectInputFilter setObjectInputFilter} or
1229-
* the initial system-wide filter from
1230-
* {@link ObjectInputFilter.Config#getSerialFilter() ObjectInputFilter.Config.getSerialFilter}.
1243+
* Returns the deserialization filter for this stream.
1244+
* The filter is the result of invoking the
1245+
* {@link Config#getSerialFilterFactory() JVM-wide filter factory}
1246+
* either by the {@linkplain #ObjectInputStream() constructor} or the most recent invocation of
1247+
* {@link #setObjectInputFilter setObjectInputFilter}.
12311248
*
1232-
* @return the serialization filter for the stream; may be null
1249+
* @return the deserialization filter for the stream; may be null
12331250
* @since 9
12341251
*/
12351252
public final ObjectInputFilter getObjectInputFilter() {
12361253
return serialFilter;
12371254
}
12381255

12391256
/**
1240-
* Set the serialization filter for the stream.
1241-
* The filter's {@link ObjectInputFilter#checkInput checkInput} method is called
1257+
* Set the deserialization filter for the stream.
1258+
*
1259+
* The deserialization filter is set to the filter returned by invoking the
1260+
* {@linkplain Config#getSerialFilterFactory() JVM-wide filter factory}
1261+
* with the {@linkplain #getObjectInputFilter() current filter} and the {@code filter} parameter.
1262+
* The current filter was set in the
1263+
* {@linkplain #ObjectInputStream() ObjectInputStream constructors} by invoking the
1264+
* {@linkplain Config#getSerialFilterFactory() JVM-wide filter factory} and may be {@code null}.
1265+
* {@linkplain #setObjectInputFilter(ObjectInputFilter)} This method} can be called
1266+
* once and only once before reading any objects from the stream;
1267+
* for example, by calling {@link #readObject} or {@link #readUnshared}.
1268+
*
1269+
* <p>It is not permitted to replace a {@code non-null} filter with a {@code null} filter.
1270+
* If the {@linkplain #getObjectInputFilter() current filter} is {@code non-null},
1271+
* the value returned from the filter factory must be {@code non-null}.
1272+
*
1273+
* <p>The filter's {@link ObjectInputFilter#checkInput checkInput} method is called
12421274
* for each class and reference in the stream.
12431275
* The filter can check any or all of the class, the array length, the number
12441276
* of references, the depth of the graph, and the size of the input stream.
@@ -1247,21 +1279,14 @@ public final ObjectInputFilter getObjectInputFilter() {
12471279
* and the current object being deserialized.
12481280
* The number of references is the cumulative number of objects and references
12491281
* to objects already read from the stream including the current object being read.
1250-
* The filter is invoked only when reading objects from the stream and for
1251-
* not primitives.
1282+
* The filter is invoked only when reading objects from the stream and not for
1283+
* primitives.
12521284
* <p>
12531285
* If the filter returns {@link ObjectInputFilter.Status#REJECTED Status.REJECTED},
12541286
* {@code null} or throws a {@link RuntimeException},
12551287
* the active {@code readObject} or {@code readUnshared}
12561288
* throws {@link InvalidClassException}, otherwise deserialization
12571289
* continues uninterrupted.
1258-
* <p>
1259-
* The serialization filter is initialized to the value of
1260-
* {@link ObjectInputFilter.Config#getSerialFilter() ObjectInputFilter.Config.getSerialFilter}
1261-
* when the {@code ObjectInputStream} is constructed and can be set
1262-
* to a custom filter only once.
1263-
* The filter must be set before reading any objects from the stream;
1264-
* for example, by calling {@link #readObject} or {@link #readUnshared}.
12651290
*
12661291
* @implSpec
12671292
* The filter, when not {@code null}, is invoked during {@link #readObject readObject}
@@ -1303,9 +1328,10 @@ public final ObjectInputFilter getObjectInputFilter() {
13031328
* @param filter the filter, may be null
13041329
* @throws SecurityException if there is security manager and the
13051330
* {@code SerializablePermission("serialFilter")} is not granted
1306-
* @throws IllegalStateException if the {@linkplain #getObjectInputFilter() current filter}
1307-
* is not {@code null} and is not the system-wide filter, or
1308-
* if an object has been read
1331+
* @throws IllegalStateException if an object has been read,
1332+
* if the filter factory returns {@code null} when the
1333+
* {@linkplain #getObjectInputFilter() current filter} is non-null, or
1334+
* if the filter has already been set.
13091335
* @since 9
13101336
*/
13111337
public final void setObjectInputFilter(ObjectInputFilter filter) {
@@ -1314,20 +1340,25 @@ public final void setObjectInputFilter(ObjectInputFilter filter) {
13141340
if (sm != null) {
13151341
sm.checkPermission(ObjectStreamConstants.SERIAL_FILTER_PERMISSION);
13161342
}
1317-
// Allow replacement of the system-wide filter if not already set
1318-
if (serialFilter != null &&
1319-
serialFilter != ObjectInputFilter.Config.getSerialFilter()) {
1320-
throw new IllegalStateException("filter can not be set more than once");
1321-
}
13221343
if (totalObjectRefs > 0 && !Caches.SET_FILTER_AFTER_READ) {
13231344
throw new IllegalStateException(
13241345
"filter can not be set after an object has been read");
13251346
}
1326-
this.serialFilter = filter;
1347+
if (streamFilterSet) {
1348+
throw new IllegalStateException("filter can not be set more than once");
1349+
}
1350+
streamFilterSet = true;
1351+
// Delegate to serialFilterFactory to compute stream filter
1352+
ObjectInputFilter next = Config.getSerialFilterFactory()
1353+
.apply(serialFilter, filter);
1354+
if (serialFilter != null && next == null) {
1355+
throw new IllegalStateException("filter can not be replaced with null filter");
1356+
}
1357+
serialFilter = next;
13271358
}
13281359

13291360
/**
1330-
* Invokes the serialization filter if non-null.
1361+
* Invokes the deserialization filter if non-null.
13311362
*
13321363
* If the filter rejects or an exception is thrown, throws InvalidClassException.
13331364
*

src/java.base/share/classes/jdk/internal/util/StaticProperty.java

Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -47,6 +47,7 @@ public final class StaticProperty {
4747
private static final String JAVA_LIBRARY_PATH;
4848
private static final String SUN_BOOT_LIBRARY_PATH;
4949
private static final String JDK_SERIAL_FILTER;
50+
private static final String JDK_SERIAL_FILTER_FACTORY;
5051
private static final String JAVA_IO_TMPDIR;
5152
private static final String NATIVE_ENCODING;
5253

@@ -62,6 +63,7 @@ private StaticProperty() {}
6263
JAVA_LIBRARY_PATH = getProperty(props, "java.library.path", "");
6364
SUN_BOOT_LIBRARY_PATH = getProperty(props, "sun.boot.library.path", "");
6465
JDK_SERIAL_FILTER = getProperty(props, "jdk.serialFilter", null);
66+
JDK_SERIAL_FILTER_FACTORY = getProperty(props, "jdk.serialFilterFactory", null);
6567
NATIVE_ENCODING = getProperty(props, "native.encoding");
6668
}
6769

@@ -184,6 +186,20 @@ public static String jdkSerialFilter() {
184186
return JDK_SERIAL_FILTER;
185187
}
186188

189+
190+
/**
191+
* Return the {@code jdk.serialFilterFactory} system property.
192+
*
193+
* <strong>{@link SecurityManager#checkPropertyAccess} is NOT checked
194+
* in this method. The caller of this method should take care to ensure
195+
* that the returned property is not made accessible to untrusted code.</strong>
196+
*
197+
* @return the {@code user.name} system property
198+
*/
199+
public static String jdkSerialFilterFactory() {
200+
return JDK_SERIAL_FILTER_FACTORY;
201+
}
202+
187203
/**
188204
* Return the {@code native.encoding} system property.
189205
*

src/java.base/share/conf/security/java.security

Lines changed: 21 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -979,11 +979,29 @@ jdk.xml.dsig.secureValidationPolicy=\
979979
noDuplicateIds,\
980980
noRetrievalMethodLoops
981981

982+
983+
#
984+
# Deserialization system-wide filter factory
985+
#
986+
# A filter factory class name is used to configure the system-wide filter factory.
987+
# The filter factory value "OVERRIDE" in combination with setting "jdk.serialFilter"
988+
# indicates that the builtin filter factory can be overridden by the application.
989+
# The class must be public, must have a public zero-argument constructor, implement the
990+
# java.util.stream.BinaryOperator<ObjectInputFilter> interface, provide its implementation and
991+
# be accessible via the application class loader.
992+
# A builtin filter factory is used if no filter factory is defined.
993+
# See java.io.ObjectInputFilter.Config for more information.
994+
#
995+
# If the system property jdk.serialFilterFactory is also specified, it supersedes
996+
# the security property value defined here.
997+
#
998+
#jdk.serialFilterFactory=<classname>
999+
9821000
#
983-
# Serialization system-wide filter
1001+
# Deserialization system-wide filter
9841002
#
985-
# A filter, if configured, is used by java.io.ObjectInputStream during
986-
# deserialization to check the contents of the stream.
1003+
# A filter, if configured, is used by the filter factory to provide the filter used by
1004+
# java.io.ObjectInputStream during deserialization to check the contents of the stream.
9871005
# A filter is configured as a sequence of patterns, each pattern is either
9881006
# matched against the name of a class in the stream or defines a limit.
9891007
# Patterns are separated by ";" (semicolon).

0 commit comments

Comments
 (0)