Explicit Serialization API and Security

David M. Lloyd david.lloyd at redhat.com
Wed Jan 21 21:43:13 UTC 2015


At some point, the responsibility *must* fall on the author of the 
serializable class in question to avoid constructs which are exploitable 
- like finalizers, and classes that can have side-effects before 
validation can be completed.

On 01/21/2015 02:26 PM, Peter Firmstone wrote:
> Don't forget that "null" may also be an invalid state.
>
> What I have learnt so far:
>
>    1. An attacker can craft a stream to obtain a reference to any object
>       created during deserialization, using finalizers or circular links
>       (there may be yet other undiscovered methods).
>    2. An attacker can craft a stream that deliberately doesn't satisfy
>       invariants, in order to use an object to perform a function that
>       wasn't intended by its developer.
>    3. Objects that interact with the stream directly using readObject et
>       al, are often prone to DOS.  Example, many objects read a length
>       integer from the stream when creating an array or collection,
>       without first validating it.
>    4. Objects that interact directly with the stream become an implicit
>       part of the stream protocol.
>    5. Once you allow an object to be created, it's too late to
>       invalidate invariants, unless the class is final and invariants
>       are checked in every method call.
>    6. We need to be able to restrict classes used for deserialization to
>       those we trust to check invariants properly (but we haven't
>       provided a way for them to avoid object construction yet).
>    7. A static validator method can ONLY be used to check field
>       invariants, not other objects and primitives that are read
>       directly from the stream by an arbitrary Object during the process
>       of deserialization.
>    8. The jvm can be modified to delay finalizer registration for
>       deserialization.
>    9. Circular links can be disallowed.
>
> Ultimately however, all proposed changes add complexity, but when an
> object has been created with invalid invariants, an attacker will find a
> way.
>
> Thank you all for your time, this has been a very good discussion.
>
> Regards,
>
> Peter.
>
> On 22/01/2015 2:27 AM, Chris Hegarty wrote:
>> On 20/01/15 20:22, Peter Levart wrote:
>>> Hi Chris and Peter,
>>>
>>> It just occurred to me that the following scheme would provide failure
>>> atomicity for the whole Object regardless of whether readObject methods
>>> are used or not for various classes in hierarchy:
>>>
>>>
>>> - allocate uninitialized instance
>>> - call default accessible constructor of the most specific
>>> non-Serializable class
>>> - deserialize (by calling readObject methods where provided) the fields
>>> of all classes in hierarchy like normally
>>> (up to this point, nothing is changed from what we have now)
>>> - if deserialization fails anywhere, undo everything by setting all the
>>> fields in the Serializable part of the hierarchy to default values (null
>>> for references, 0 for primitives), abandon the object and propagate
>>> failure.
>>
>> I think this is a good idea, and I can prototype something to this
>> affect.
>>
>> -Chris.
>>
>>
>>> While deserializing, the object is in inconsistent state. If
>>> deserialization fails, this state is rolled-back to uninitialized state.
>>> finalize() can still get to the instance, but it will be uninitialized.
>>>
>>>
>>> Peter
>>>
>>> On 01/14/2015 01:58 PM, Peter Firmstone wrote:
>>>>
>>>> Hi Chris,
>>>>
>>>> Sorry, no.
>>>>
>>>> Currently when an ObjectStreamClass is read in from the stream, the
>>>> framework searches for the first zero arg constructor of a non
>>>> serializable class and creates and instance of the class read and
>>>> resolved from the stream, however it does so using a super class
>>>> constructor.
>>>>
>>>> Then from the super class down, fields are read in and set in order
>>>> for each class in the object's inheritance hierarchy.
>>>>
>>>> The alternative I propose, doesn't create the instance, instead it
>>>> reads the fields from the stream, one by one and without instantiating
>>>> them, if they are newly read objects, stores them temporarily into
>>>> byte [] arrays in a Map with reference handle keys, otherwise it just
>>>> holds the reference handle.
>>>>
>>>> What it does next is wrap this information into a caller sensitive
>>>> api, GetFields or ReadSerial instance, that is passed as a constructor
>>>> parameter to the child class serial constructor.
>>>>
>>>> The child class checks invariants and reads each field it needs using
>>>> a static method prior to calling a superclass constructor, each class
>>>> in the inheritance hierarchy for the object then checks its invariants
>>>> until it gets to the first non serializable superclass.
>>>>
>>>> The benefit of this order is that each class is present in the thread
>>>> security context, so protection domain security and invariants are
>>>> enforced before instantiating an object.
>>>>
>>>> Hope this helps illuminate it a little better, regards,
>>>>
>>>> Peter.
>>>>
>>>> ----- Original message -----
>>>> > Peter F,
>>>> >
>>>> > I am still struggling with the basic concept of you proposal. Let me
>>>> see
>>>> > if I understand it correctly. Does the following describe a similar
>>>> > scenario as you envisage:
>>>> >
>>>> >    1) For each Serializable type, T, in the deserialized types
>>>> >          hierarchy, starting with the top most ( closest to
>>>> j.l.Object ),
>>>> >
>>>> >          1a) Read T's fields from the stream, fields
>>>> >
>>>> >          1b) validate(t, fields)  // t will be null first time
>>>> >
>>>> >          1c) allocate a new instance of T, and assign to t
>>>> >
>>>> >          1d) set fields in t
>>>> >
>>>> >    2) Return t;
>>>> >
>>>> > So for each level in the hierarchy, an instance of a type is created
>>>> > only after its invariants have been checked. This instance is then
>>>> > passed to the next level so it can participate in that levels
>>>> invariants
>>>> > validation.
>>>> >
>>>> > If this scenario is along the same lines as yours, then I just don't
>>>> see
>>>> > how 1c above will always be possible.
>>>> >
>>>> > If we could somehow make the object caller sensitive until after
>>>> > deserialization completes, then could avoid having to try to allocate
>>>> > multiple instance down the hierarchy.
>>>> >
>>>> > -Chris.
>>>> >
>>>> > On 13/01/15 10:24, Peter Firmstone wrote:
>>>> > > Could we use a static validator method and generate bytecode for
>>>> > > constructors dynamically?
>>>> > >
>>>> > > The developer can optionally implement the constructors.
>>>> > >
>>>> > > static GetField invariantCheck(GetField f);
>>>> > >
>>>> > > Create a caller sensitive GetField implementation and add a two new
>>>> > > methods to GetField:
>>>> > >
>>>> > > abstract Object createSuper(); // to access superclass objectmethods
>>>> > > for inavariant checking.
>>>> > >
>>>> > > abstract Class getType(String name);
>>>> > >
>>>> > > Set fields from within constructors.
>>>> > >
>>>> > > The generated constructors are straight forward:
>>>> > >
>>>> > > 1. Call static method.
>>>> > > 2. Call super class constructor with result from static method.
>>>> > > 3. Set final fields
>>>> > > 4. How to set transient fields, implement a private method called
>>>> from
>>>> > > within the constructor?
>>>> > >
>>>> > > Require a permission to extend GetField?
>>>> > >
>>>>
>>>
>

-- 
- DML



More information about the core-libs-dev mailing list