-
Enhancement
-
Resolution: Fixed
-
P4
-
1.4.0
-
None
-
beta
-
generic
-
generic
-
Verified
Currently it is possible to obtain references to (potentially private) objects
during deserialization by appending spurious back references to the stream of serialized data. Security-conscious programmers are encouraged to clone
sensitive internal objects after deserializing them, to ensure that outside
parties with access to the serialization stream cannot forge references to them.
This solution slows performance and wastes memory--two objects must be created
and a copy operation invoked in order to ensure a unique reference to a single
usable object.
A more efficient solution is possible with the addition of two new methods:
ObjectOutputStream.writeUnshared() and ObjectInputStream.readUnshared(). The
semantics of these methods are as follows (more or less--this is a rough cut):
1. ObjectOutputStream.writeUnshared() behaves the same way as
ObjectOutputStream.writeObject() except for two key differences:
- the wire handle table is not consulted when writing the object (i.e.,
writeUnshared() will never write a back reference, even if the
same object has been written to the stream previously via a call to
writeObject().
- the written object is not entered into the wire handle table. Instead,
a "null" (or equivalent, depending on implementation details yet to be
worked out) reference is entered into the wire handle table for the
next available wire handle, ensuring that no subsequent calls
to writeObject() will ever write a back reference to the wire handle
allocated for the object written with writeUnshared().
Note that writing an object with writeUnshared() does not affect the manner
in which objects referenced in turn by the "unshared" object are serialized.
In other words, if serializable object A contains a reference to serializable
object B and A is written with a call to writeUnshared(), B will still be
written as a normal, "shared" object with a call to writeObject().
Classes can cause object fields to be written with writeUnshared() instead of
writeObject() by:
- defining a class-specific writeObject() method which calls
writeUnshared() explicitly to write the given fields.
- defining serialPersistentFields so that it includes ObjectStreamField
entries for the unshared fields. Each of the "unshared" entries
should be constructed with a new ObjectStreamField constructor
which takes a boolean parameter indicating whether or not the
represented field is "shared".
2. ObjectInputStream.readUnshared() behaves the same way as
ObjectInputStream.readObject() except for two differences:
- calling readUnshared() to deserialize a back reference in the stream
will cause some form of an ObjectStreamException to be thrown.
- calling readUnshared() will invalidate the wire handle table entry
for the deserialized object, so that subsequent back references to the
wire handle table entry for the unshared object will be unresolvable
(attempting to read such a back reference should result in some form
of an ObjectStreamException).
3. A new constructor should be added to ObjectStreamField which allows
classes to mark specific fields as "unshared":
ObjectStreamField(String name, Class cl, boolean shared);
Storing an ObjectStreamField with shared set to false in the
serialPersistentFields field of a class would cause the field
(represented by the ObjectStreamField) to be serialized using
writeUnshared(), including cases in which the PutField API is used
to "write" the field value.
The additions proposed here for ObjectOutputStream and ObjectInputStream
are orthogonal to one another, and invisible with respect to the
underlying serialization protocol. An ObjectInputStream supporting
readUnshared() can read streams written by ObjectOutputStreams without writeUnshared() (and can even call readUnshared() on objects in the stream,
if the reader is sure that no later attempts will be made to read additional
references to the object). Similarly, serialization streams written by
an ObjectOutputStream with calls to writeUnshared() will still be deserializable
by earlier versions of ObjectInputStream. Hence, these changes do not
affect compatibility with previous versions of the JDK.
during deserialization by appending spurious back references to the stream of serialized data. Security-conscious programmers are encouraged to clone
sensitive internal objects after deserializing them, to ensure that outside
parties with access to the serialization stream cannot forge references to them.
This solution slows performance and wastes memory--two objects must be created
and a copy operation invoked in order to ensure a unique reference to a single
usable object.
A more efficient solution is possible with the addition of two new methods:
ObjectOutputStream.writeUnshared() and ObjectInputStream.readUnshared(). The
semantics of these methods are as follows (more or less--this is a rough cut):
1. ObjectOutputStream.writeUnshared() behaves the same way as
ObjectOutputStream.writeObject() except for two key differences:
- the wire handle table is not consulted when writing the object (i.e.,
writeUnshared() will never write a back reference, even if the
same object has been written to the stream previously via a call to
writeObject().
- the written object is not entered into the wire handle table. Instead,
a "null" (or equivalent, depending on implementation details yet to be
worked out) reference is entered into the wire handle table for the
next available wire handle, ensuring that no subsequent calls
to writeObject() will ever write a back reference to the wire handle
allocated for the object written with writeUnshared().
Note that writing an object with writeUnshared() does not affect the manner
in which objects referenced in turn by the "unshared" object are serialized.
In other words, if serializable object A contains a reference to serializable
object B and A is written with a call to writeUnshared(), B will still be
written as a normal, "shared" object with a call to writeObject().
Classes can cause object fields to be written with writeUnshared() instead of
writeObject() by:
- defining a class-specific writeObject() method which calls
writeUnshared() explicitly to write the given fields.
- defining serialPersistentFields so that it includes ObjectStreamField
entries for the unshared fields. Each of the "unshared" entries
should be constructed with a new ObjectStreamField constructor
which takes a boolean parameter indicating whether or not the
represented field is "shared".
2. ObjectInputStream.readUnshared() behaves the same way as
ObjectInputStream.readObject() except for two differences:
- calling readUnshared() to deserialize a back reference in the stream
will cause some form of an ObjectStreamException to be thrown.
- calling readUnshared() will invalidate the wire handle table entry
for the deserialized object, so that subsequent back references to the
wire handle table entry for the unshared object will be unresolvable
(attempting to read such a back reference should result in some form
of an ObjectStreamException).
3. A new constructor should be added to ObjectStreamField which allows
classes to mark specific fields as "unshared":
ObjectStreamField(String name, Class cl, boolean shared);
Storing an ObjectStreamField with shared set to false in the
serialPersistentFields field of a class would cause the field
(represented by the ObjectStreamField) to be serialized using
writeUnshared(), including cases in which the PutField API is used
to "write" the field value.
The additions proposed here for ObjectOutputStream and ObjectInputStream
are orthogonal to one another, and invisible with respect to the
underlying serialization protocol. An ObjectInputStream supporting
readUnshared() can read streams written by ObjectOutputStreams without writeUnshared() (and can even call readUnshared() on objects in the stream,
if the reader is sure that no later attempts will be made to read additional
references to the object). Similarly, serialization streams written by
an ObjectOutputStream with calls to writeUnshared() will still be deserializable
by earlier versions of ObjectInputStream. Hence, these changes do not
affect compatibility with previous versions of the JDK.