Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
44 changes: 34 additions & 10 deletions src/com/esotericsoftware/kryo/serializers/JavaSerializer.java
Original file line number Diff line number Diff line change
Expand Up @@ -32,6 +32,7 @@
import java.io.ObjectInputStream;
import java.io.ObjectOutputStream;
import java.io.ObjectStreamClass;
import java.util.function.Predicate;

/** Serializes objects using Java's built in serialization mechanism. Note that this is very inefficient and should be avoided if
* possible.
Expand All @@ -40,6 +41,21 @@
* @see KryoSerializable
* @author Nathan Sweet */
public class JavaSerializer extends Serializer {
private Predicate<Class> classFilter;

/** Sets an optional filter applied to each class encountered while deserializing. When set, a class for which the predicate
* returns false is rejected with a {@link KryoException} before it is used, providing opt-in, defense-in-depth protection when
* reading serialized data from an untrusted source. When null (the default) no filtering is applied and behavior is unchanged.
* @param classFilter May be null. */
public void setClassFilter (Predicate<Class> classFilter) {
this.classFilter = classFilter;
}

/** @return May be null. */
public Predicate<Class> getClassFilter () {
return classFilter;
}

public void write (Kryo kryo, Output output, Object object) {
try {
ObjectMap graphContext = kryo.getGraphContext();
Expand All @@ -60,7 +76,7 @@ public Object read (Kryo kryo, Input input, Class type) {
ObjectMap graphContext = kryo.getGraphContext();
ObjectInputStream objectStream = (ObjectInputStream)graphContext.get(this);
if (objectStream == null) {
objectStream = new ObjectInputStreamWithKryoClassLoader(input, kryo);
objectStream = new ObjectInputStreamWithKryoClassLoader(input, kryo, classFilter);
graphContext.put(this, objectStream);
}
return objectStream.readObject();
Expand All @@ -75,23 +91,31 @@ public Object read (Kryo kryo, Input input, Class type) {
* https://issues.apache.org/jira/browse/GROOVY-1627 */
private static class ObjectInputStreamWithKryoClassLoader extends ObjectInputStream {
private final Kryo kryo;
private final Predicate<Class> classFilter;

ObjectInputStreamWithKryoClassLoader (InputStream in, Kryo kryo) throws IOException {
ObjectInputStreamWithKryoClassLoader (InputStream in, Kryo kryo, Predicate<Class> classFilter) throws IOException {
super(in);
this.kryo = kryo;
this.classFilter = classFilter;
}

protected Class resolveClass (ObjectStreamClass type) {
Class resolved;
try {
return Class.forName(type.getName(), false, kryo.getClassLoader());
} catch (ClassNotFoundException ignored) {}
try {
return super.resolveClass(type);
} catch (ClassNotFoundException ex) {
throw new KryoException("Class not found: " + type.getName(), ex);
} catch (IOException ex) {
throw new KryoException("Could not load class: " + type.getName(), ex);
resolved = Class.forName(type.getName(), false, kryo.getClassLoader());
} catch (ClassNotFoundException ignored) {
try {
resolved = super.resolveClass(type);
} catch (ClassNotFoundException ex) {
throw new KryoException("Class not found: " + type.getName(), ex);
} catch (IOException ex) {
throw new KryoException("Could not load class: " + type.getName(), ex);
}
}
if (classFilter != null && !classFilter.test(resolved)) {
throw new KryoException("Deserialization is not allowed for class: " + type.getName());
}
return resolved;
}
}
}
40 changes: 40 additions & 0 deletions test/com/esotericsoftware/kryo/serializers/JavaSerializerTest.java
Original file line number Diff line number Diff line change
Expand Up @@ -19,14 +19,20 @@

package com.esotericsoftware.kryo.serializers;

import com.esotericsoftware.kryo.KryoException;
import com.esotericsoftware.kryo.KryoTestCase;
import com.esotericsoftware.kryo.io.Input;
import com.esotericsoftware.kryo.io.Output;

import java.io.Serializable;
import java.net.URL;
import java.net.URLClassLoader;

import org.junit.jupiter.api.Test;

import static org.junit.jupiter.api.Assertions.assertEquals;
import static org.junit.jupiter.api.Assertions.assertThrows;

/** @author Nathan Sweet */
class JavaSerializerTest extends KryoTestCase {
@Test
Expand Down Expand Up @@ -55,6 +61,40 @@ void testJavaSerializerFallbackToDefaultClassLoader () {
roundTrip(139, test);
}

@Test
void testClassFilterRejectsDisallowedClass () {
JavaSerializer serializer = new JavaSerializer();
serializer.setClassFilter(type -> type != TestClass.class);
kryo.register(TestClass.class, serializer);

TestClass test = new TestClass();
test.stringField = "fubar";
test.intField = 54321;

Output output = new Output(1024, -1);
kryo.writeObject(output, test);

Input input = new Input(output.toBytes());
assertThrows(KryoException.class, () -> kryo.readObject(input, TestClass.class));
}

@Test
void testClassFilterAllowsClass () {
JavaSerializer serializer = new JavaSerializer();
serializer.setClassFilter(type -> true);
kryo.register(TestClass.class, serializer);

TestClass test = new TestClass();
test.stringField = "fubar";
test.intField = 54321;

Output output = new Output(1024, -1);
kryo.writeObject(output, test);

Input input = new Input(output.toBytes());
assertEquals(test, kryo.readObject(input, TestClass.class));
}

public static class TestClass implements Serializable {
String stringField;
int intField;
Expand Down