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.
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
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.