Thank you all for participating in this discussion.

Initially a constructor signature for deserialization was proposed to enforce invariants and encapsulation, however there appears to be interest in using alternative methods, although they appear to be improvements over the status quo, I'm having trouble working out how to:

   * Enforce intra class invariants with alternate proposals without
     breaking encapsulation.
   * How the static method proposal can be used to replace final fields
     referents without needing to implement readObject().
   * And how the alternative GetFields readObject() implementation can
     enforce invariants and prevent finalizer attack, while also
     allowing subclassing?

What started me down this path, is our project (Apache River) is heavily dependant on Serialization, I wanted to create a secure ObjectInputStream subclass that restricted deserialized classes to those I had audited. My aim was to allow deserialization to enforce security using verification, similar to how an http server verifies its input.

Unfortunately before I can achieve the goal of secure deserialization, there's a denial of service issue in ObjectInputStream: I'd also like to propose a system property, to allow limiting the size of arrays, created during deserialization. Presently I can craft an inputstream to take down the JVM, if I can do it, so can an attacker, I'd like to get that fixed if possible.

I've attached a text file that contains three classes using the original proposed serial constructor, A, B and C which have intra class invariants that must be satiisfied. C also contains a circular link and implements an interface called Circular, the Serialization framework calls it after construction to provide access to fields with circular links. Can the other proposals provide similar safety?

Notes:

  1. The class with the circular link is final and every method checks
     if invariants are satisfied.
  2. The method with @SerialConstructor annotation, is the only
     constructor called by the serialization frame work, other than the
     Circular interface method, there are no other methods that need to
     be implemented.
  3. Encapsulation is preserved, classes have full control over their
     invariants, their internal implementation and what's serialized.

Regards,

Peter.
// A simple complete example of safely constructed explicit Serialization.
//
// No special framework requirements, other than @CallerSensitive ReadSerial
// to preserve encapsulation.
// Object's serial form can add or remove fields without breaking encapsulation 
or
// causing compatibility errors.
// When a field is not present in the stream, the default value is returned
// and the programmers invariant checking code does the rest.
// Classes can implement finalizer method if they want, no need to 
// disallow it.
// Programmer only uses public api, IDE friendly.
// Class inheritance heirarchy can be changed and still all invariants can be 
satisfied.
// Public api is unit test friendly.
// You can't do intra class invariant checks with readObject() without breaking
// encapsulation.
//
// It is not possible to construct an object that doesn't satisfy invariants.
//
// Easy to audit for security.



public class A implements Serializable {

        private final int lower, upper; 

        static boolean check(int lower, int upper){
                if (upper < lower) throw new IllegalArgumentException(
                        “upper limit cannot be less than lower”);
                return true;
        }

        public A(int lower, int upper){
                this(check(lower,upper),lower, upper);
        }

        @SerialConstructor
        public A(ReadSerial rs){
                this(rs.getInt("lower"), rs.getInt("upper"));
        }

        private A(boolean checked, int lower, int upper){
                this.lower = lower;
                this.upper = upper;
        }

        public final int lowerLimit(){
                return lower;
        }

        public final int upperLimit(){
                return upper;
        }

}



public class B extends A {
        
        private final cur;

        static boolean check(int lower, int cur, int upper){
                if (cur < lower || cur > upper) 
                        throw new IllegalArgumentException("cur out of bounds");
                return true;
        }

        static boolean check(ReadSerial rs){
                A a = new A(rs);
                return check(a.lowerLimit(), rs.getInt("cur"), 
a.getUpperLimit());
        } 
        
        public B(int lower, int cur, int upper){
                this(check(lower, cur, upper), lower, cur, upper);
        }

        private B(boolean check, int lower, int cur, int upper){
                super(lower, upper);
                this.cur = cur;
        }

        @SerialConstructor
        public B(ReadSerial rs){
                this(check(rs), rs);
        }

        private B(boolean check, ReadSerial rs){
                super(rs);
                cur = rs.getInt("cur");
        }

        public final int getCur(){
                return cur;
        }

}


// For Circular links, implementing object declares method final
// and if extension is allowed, provides a protected
// method for subclasses to optionally implement.
//
// Class is final to prevent finalizer attack or methods
// being overridden.
//
// Due to the circular link, some invariants can't be
// checked until after construction, this makes invariants
// much more difficult to enforce.

public interface Circular {
        public void setMutableFields(ReadSerial rs);
}


public final class C extends B implements Circular {

        private final B [] circularArray; //Has reference to all members and is 
a member.
        private final int number;
        private transient boolean safe;

        static boolean check(int lower, int upper, int number){
                if (number < upper - lower) 
                throw new IllegalArgumentException("insufficent array length");
                if (number > 512)  
                throw new IllegalArgumentException("number exceeds allowed 
limit");
        }

        static boolean check(ReadSerial rs){
                A a = new A(rs);
                return check(a.lowerLimit(), a.upperLimit(), 
rs.getInt("number"));
        }

        public C (int lower, int cur, int upper, int number){
                this(check(lower, upper, number), lower, cur, upper, number);
        }

        private C(boolean check, int lower, int cur, int upper, int number){
                super(lower, cur, upper);
                this.number = number;
                circularArray = new B[number];
                circularArray[cur] = this;
                safe = true;
        }

        public C(ReadSerial rs){
                this(check(rs), rs);
        }

        private C(boolean check, ReadSerial rs){
                super(rs);
                number = rs.getInt("number");
                circularArray = new B[number];
                safe = false;
        }

        public B set(B b){
                int cur = b.getCur();
                B previous;     
                synchronized (circularArray){
                        if (!safe) throw new IllegalArgumentException("object 
incorrectly constructed");
                        previous = circularArray[cur];
                        circularArray[cur] = b;
                }
                return previous;
        }

        public void setMutableFields(ReadSerial rs){
                synchronized(circularArray){
                        if (safe) throw new IllegalArgumentException("fields 
must be set using public api");
                        B [] circular = rs.getArray(new B [number]);
                        if ( circular[getCur()] != this){
                                safe = false;
                                throw new IllegalArgumentException("incorrect 
position cur in array");
                        }
                        for (int i = 0; i < number; i++){
                                if (circular[i] != null && circular[i].getCur() 
!= i){
                                        safe = false;
                                        throw new 
IllegalArgumentException("incorrect position cur in array");
                                }
                        }
                        safe = true;
                }
        }
                        
}

Reply via email to