State of Serialization

Peter Firmstone peter.firmstone at zeus.net.au
Sun Jul 20 10:57:20 UTC 2014


David,

I've gone over the list archives and read your earlier post about 
serializable constructors and agree with your post.

The class SerialFields below, represents the serial form of a 
Serializable object, the stream protocol would remain unchanged.

SerialFields would be the equivalent of an existing Serializable object 
that declares

private static final ObjectStreamField[] serialPersistentFields

and uses PutField in it's writeObject method and GetField in its readObject method.


Regards,

Peter.

*State of Java Serialization*


  Introduction

The Java Serialization framework enables object state to be frozen, 
stored to disk or transferred over a network and unfrozen again into 
objects. While Java's Serialization capabilities are arguably more 
sophisticated than most at reconstructing complex object relationships, 
the “magic” nothing to do, marker interface, Serializable is 
problematic. Due to the complexity of serialising state from an object 
with Serializable superclasses in inheritance hierarchies, private 
methods were chosen, allowing objects to write and read objects to and 
from streams. Each class in an Object's inheritance heirarchy that 
implements or inherits Serializable must be able to write out and read 
in serialized state, private methods cannot be overridden or called by 
subclasses, nor is their implementation enforced by the Java language 
syntax, hence Serializable is a marker interface only.


  Background

Serialization was introduced in Java 1.1. The marker interface 
Serializable is problematic since implementation of its methods are 
optional. Developers can make objects serializable by simply declaring 
implements Serializable and providing a default zero argument constructor.

Since private methods are only be called by the ObjectOutputStream / 
ObjectInputStream, during de-serialisation, subclass are not responsible 
for calling these methods, hence subclass ProtectionDomain's are not 
present in the Thread's AccessControlContext and as such are missing 
from security checks, this is why it's currently essential for classes 
to ensure that de-serialisation isn't performed in a privileged context.

To improve security, it would be preferable to use a deserialization 
constructor, required to be called by subclasses in the class 
hierarchies, placing their ProtectionDomains in the stack context, 
avoiding a number of security issues. Another benefit is the ability to 
use final fields, while checking invariants during construction.


import java.io.Serializable;

/**
* Object's that implement this interface must provide a public constructor
* with the signature:
*
* (SerialFields fields) throws IOException ClassNotFoundException;
*
* This constructor, must avoid finalizer attacks by checking invariants
* using static methods to retrieve fields, throwing any Exception before
* calling another constructor.
*
* Classes that extend other classes that implement Serializable2, must call
* a static method that checks invariants and throw any Exceptions prior to
* calling a super class constructor.
*
* @author peter
*/
public interface Serializable2 extends Serializable {

/**
* Allows an object to write its fields, the order each field
* is written is important if compatibility with existing Serializable
* object implementations is necessary.
*
* Classes with superclass's that implement Serializable2, must
* first call the overridden superclass method, before writing their own
* fields.
*
* @param fields
*/
void writeObject(SerialFields fields);

}


import java.io.IOException;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;

/**
* To be used by ObjectInput, ObjectOutput frameworks.
*
* Represents all fields in order as stored in an inheritance hierarchy for
* one Object.
*
* This class is not thread safe.
*
* @author peter
*/
public final class SerialFields {

public static SerialFields create() throws SecurityException {
// Perform security check.
return new SerialFields();
}

private final Map<FieldKey,Object> fields;
private final List<FieldKey> order;

/**
* Should only be instantiated by frameworks.
*/
private SerialFields(){
// TODO Security check in a static method that throws an exception.
fields = new HashMap<FieldKey,Object>();
order = new ArrayList<FieldKey>();
}

/**
* Fields may be added in order of their serial form, this will be used
* by the Serializable 2 Framework to retain compatibility with older
* Serializable implementations.
*
* @param caller
* @param name
* @param obj
* @throws NullPointerException if caller or name is null.
*/
public void putField(Class caller, String name, Object obj){
// TODO Check if caller is the caller, perform security check if not.
if (caller == null) throw new NullPointerException("caller cannot be null");
if (name == null) throw new NullPointerException("name cannot be null");
FieldKey key = new FieldKey(caller, name);
// TODO allowable Exception?
fields.put(key, obj); //obj can be null or an exception.
order.add(key);
}

/**
*
* @param caller
* @param name
* @return
* @throws IOException
* @throws ClassNotFoundException
*/
public Object getField(Class caller, String name) throws IOException, 
ClassNotFoundException {
// TODO Check if caller is the caller, perform security check if not.
if (caller == null) throw new NullPointerException("caller cannot be null");
if (name == null) throw new NullPointerException("name cannot be null");
Object field = fields.get(new FieldKey(caller, name));
if (field instanceof IOException) throw (IOException) field;
if (field instanceof ClassNotFoundException) throw 
(ClassNotFoundException) field;
if (field instanceof RuntimeException) throw (RuntimeException) field;
if (field instanceof Error) throw (Error) field;
return field;
}

private static class FieldKey {
private final Class caller;
private final String fieldName;
private final int hash;

FieldKey(Class caller, String fieldName){
this.caller = caller;
this.fieldName = fieldName;
int hash = 3;
hash = 79 * hash + (this.caller != null ? this.caller.hashCode() : 0);
hash = 79 * hash + (this.fieldName != null ? this.fieldName.hashCode() : 0);
this.hash = hash;
}

@Override
public int hashCode() {
return hash;
}

@Override
public boolean equals(Object o){
if (!(o instanceof FieldKey)) return false;
FieldKey that = (FieldKey) o;
if (!caller.equals(that.caller)) return false;
return fieldName.equals(that.fieldName);
}
}
}



More information about the core-libs-dev mailing list