On 01/02/2015 11:53 PM, Brian Goetz wrote:
Overall the direction seems promising.  Poking at it a bit...

- ReadSerial methods are caller-sensitive and only show a class a view of its own fields. - Invariant checking is separate from deserialization, and does not seem entirely built-in -- subclass constructors seem responsible for asking parents to do validity-checking? - I don't see how this invariant-checking mechanism can enforce invariants between superclass fields and subclass fields. For example:

class A {
    int lower, upper;  // invariant: lower <= upper
}

class B extends A {
    int cur;  // invariant: lower <= cur <= upper
}

To check such an invariant, the serialization library would have to construct the object (in a potentially bad state), invoke the checker at each layer, and then fail deserialization if any checker said no. But, an evil checker could still squirrel away a reference under the carpet.

Hi,

I would like to know what are the potential issues with simple constructor chaining where each constructor checks the invariant of its class state (with the already initialized state of superclass(es)). For example, the above would be written as:

class A {
    final int lower, upper;
    A(ReadSerial rs) {
        int l = rs.getInt("lower");
        int u = rs.getInt("upper");
        if (l > u) throw new IllegalArgumentException();
        lower = l;
        upper = u;
    }
}

class B extends A {
    final int cur;
    B(ReadSerial rs) {
        super(rs);
        int c = rs.getInt("cur");
        if (c < lower || c > upper) throw new IllegalArgumentException();
        cur = c;
    }
}



Another challenge in invariant checking is circular data structures. If you have two objects:

class Brother {
    final Brother brother;
}

that refer to each other, an invariant you might want to check after deserialization is that
  this.brother.brother == this

This is tricky, yes. In general, the graph of objects has to be linked somehow gradually where links are established to half-initialized objects. So there would have to be a special post-deserialization call-back over the deserialized objects just for checking the invariants. But what to do with the instances that "escape" during the process? This is why explicit (de)serialization is tricky - it allows arbitrary user code to be executed during the construction of the object graph.

Perhaps we just need some kind of declarative prescription of serialized state and mapping to fields of objects in the form of annotations or such (see what JPA is doing in this field). Combined with post-deserialization invariant-checking call-back method perhaps.

Regards, Peter


Obviously you have to patch one or the other instance after construction to retain the circular references; at what point do you do invariant checking?

On 1/1/2015 7:43 AM, Peter Firmstone wrote:
Subclass example:

class SubFoo extends BaseFoo {

public static ReadSerial check(ReadSerial rs){
if (rs.getInt("y") > 128) throw Exception("too big");
return rs;
}

private final int y;

public SubFoo( int x , int y) {
super(x);
this.y = y;
}

public SubFoo( ReadSerial rs ){
super(BaseFoo.check(check(rs)));
// SubFoo can't get at BaseFoo's rs.getInt("x"),
// it's visible only to BaseFoo. Instead SubFoo would get
// the default int value of 0. Just in case both classes have
// private fields named "x".
// ReadSerial is caller sensitive.
this.y = rs.getInt("y");
}
}

Classes in the heirarchy can provide a static method that throws an
exception to check invarients while preventing a finaliser attack. We'd
want to check invarients for other constructors also, but for berevity...

Eg:

class BaseFoo implements Serializable{

public static ReadSerial check(ReadSerial rs) throws Exception
{
if (rs.getInt("x") < 1)
throw IllegalArgumentException("message");
return rs;
}
....


Sent from my Nokia N900.

----- Original message -----
 > So, if I understand this correctly, the way this would get used is:
 >
 > class BaseFoo implements Serializable {
 >          private final int x;
 >
 >          public BaseFoo(ReadSerial rs) {
 >                  this(rs.getInt("x"));
 >          }
 >
 >          public BaseFoo(int x) {
 >                  this.x = x;
 >          }
 > }
 >
 > Right?
 >
> What happens with subclasses? I think then I need an extra RS arg in my
 > constructors:
 >
 > class SubFoo extends BaseFoo {
 >          private final int y;
 >
 >          public SubFoo(ReadSerial rs) {
 >                  this(rs.getInt("y"));
 >          }
 >
 >          public BaseFoo(ReadSerial rs, int y) {
 >                  super(rs);
 >                  this.y = y;
 >          }
 > }
 >
 > Is this what you envision?
 >
 >
 >
 >
 >
 > On 12/27/2014 8:03 PM, Peter Firmstone wrote:
 > > Is there any interest in developing an explicit API for
Serialization?:
 > >
 > > 1. Use a public constructor signature with a single argument,
 > > ReadSerialParameters (read only, writable only by the
 > > serialization framework) to recreate objects, subclasses (when
 > > permitted) call this first from their own constructor, they have
 > > an identical constructor signature. ReadSerialParameters that are
 > > null may contain a circular reference and will be available after
 > > construction, see #3 below.
> > 2. Use a factory method (defined by an interface) with one parameter,
 > > WriteSerialParameters (write only, readable only by the
 > > serialization framework), this method can be overridden by
 > > subclasses (when permitted)
> > 3. For circular links, a public method (defined by an interface) that
 > > accepts one argument, ReadSerialParameters, this method is called
 > > after the constructor completes, subclasses overriding this should
 > > call the superclass method.  If this method is not called, an
 > > implementation, if known to possibly contain circular links,
 > > should check it has been fully initialized in each object method
 > > called.
 > > 4. Retains compatibility with current serialization stream format.
> > 5. Each serial field has a name, calling class and object reference,
 > > similar to explicitly declaring "private static final
 > > ObjectStreamField[] serialPersistentFields ".
 > >
 > > Benefits:
 > >
 > > 1. An object's internal form is not publicised.
 > > 2. Each class in an object's heirarchy can use a static method to
 > > check invarients and throw an exception, prior to
 > > java.lang.Object's constructor being called, preventing
 > > construction and avoiding finalizer attacks.
 > > 3. Final field friendly.
 > > 4. Compatible with existing serial form.
 > > 5. Flexible serial form evolution.
 > > 6. All methods are public and explicitly defined.
 > > 7. All class ProtectionDomain's exist in the current execution
 > > context, allowing an object to throw a SecurityException before
 > > construction.
 > > 8. Less susceptible to deserialization attacks.
 > >
 > > Problems:
 > >
 > > 1. Implementations cannot be package private or private.  Implicit
 > > serialization publicises internal form, any thoughts?
 > >
 > > Recommendations:
 > >
 > > 1. Create a security check in the serialization framework for
 > > implicit serialization, allowing administrators to reduce their
 > > deserialization attack surface.
 > > 2. For improved security, disallow classes implementing explicit
 > > serialization from having static state and static initializer
 > > blocks, only allow static methods, this would require complier and
 > > verifier changes.
 > > 3. Alternative to #2, allow final static fields, but don't allow
 > > static initializer blocks or mutable static fields, similar to
 > > interfaces.
 > >
 > > Penny for your thoughts?
 > >
 > > Regards,
 > >
 > > Peter Firmstone.


Reply via email to