Wednesday, December 03, 2008

Calendar bug

Java 6 Update 11 fixes "calendar security bug" among others.

Altough some minor adjustments were made to the Calendar class, the actual fix deals with how deserialization calls non-serializable superclass constructor. But I like to call it the Calendar bug, anyway, because in my original evaluation of the problem I put the blame on the java.util.Calendar class.

The problem, which is present in Java 6 Update 10 and earlier (and in the corresponding Java 5 and Java 1.4.2 releases), goes something like this:

java.util.Calendar is a serializable class. Presumably due to reverse compatibility, its serialization logic is non-trivial. One of the non-trivial things that it does, is that the readObject method (which is used for customizing object deserialization) calls ObjectInputStream.readObject() inside a doPrivileged block to deserialize a ZoneInfo object which might or might not be present.

Something like this:
// If there's a ZoneInfo object, use it for zone.
try {
ZoneInfo zi = (ZoneInfo) AccessController.doPrivileged(
new PrivilegedExceptionAction() {
public Object run() throws Exception {
return input.readObject();
}
});
if (zi != null) {
zone = zi;
}
} catch (Exception e) {
}


doPrivileged blocks are used when the caller of trusted code doesn't necessarily have the privileges to execute some operation. In this specific case, the ZoneInfo object being deserialized resides in the sun.util.calendar package and applet code normally doesn't have access to this package (or any package which starts with "sun.").

Without the doPrivileged block, if an unsigned applet were to try to deserialize a Calendar object, it would raise a SecurityException. With doPrivileged, the code executes fine even when the caller (unsigned applet code) doesn't have the privileges. There is an old bug related to deserializing calendar objects and the doPrivileged block was presumably the fix.

The problem is that there is absolutely no guarantee that the stream being read contains a ZoneInfo object. It might contain, for example, a serializable ClassLoader subclass. One would have to manouver a bit to create such a stream, but with basic knowledge of the Java serialization protocol, this is a relatively trivial task.

So what happens if there should to be a Serializable ClassLoader subclass in the stream in the place of the ZoneInfo object? What's the big deal?

The input.readObject() method would execute just fine, because it has no knowledge of what type of an object it is supposed to deserialize. It reads the stream, gets the details of the next object in the stream, instantiates the object (without calling the constructor) and returns the object.

At this point the Calendar code tries to do a cast of the object to the ZoneInfo type and this will cause a ClassCastException, as the types aren't compatible. But the object was deserialized just fine. The implication of this is that applet code can deserialize objects in a privileged context, just by constructing an input stream (ByteArrayInputStream, for example) which contains a special serialized Calendar object which contains some other object instead of a ZoneInfo.

You might think that the reference to the newly created object is forever lost, only to be collected by the garbage collector later on. However, it is possible to implement a readObject method in the serializable class which stores the reference to the created object in some static context.

So what?

Here's where the ClassLoader comes into play. java.lang.ClassLoader is not Serializable and thus can't be serialized. But it is a non-final class, so it can be extended.

Subclasses of non-serializable classes can be serializable just by implementing the java.io.Serializable interface. When the object is deserialized, the JVM first calls the constructor of the non-serializable class, and then it deserializes the fields of the serializable subclass. The fancy part is that there will be no subclass code on the stack when the constructor of the non-serializable class is called (or rather, this was true until update 11). Thus, if the deserialization happens in a privileged context, the constructor also gets called in a privileged context.

Why is this relevant?

Because calling a ClassLoader constructor requires special privileges. Privileges that an unsigned applet normally doesn't have. So this way an applet could (past tense, because of the fix in update 11) obtain it's very own ClassLoader. If you have access to your own ClassLoader subclass, you can load new classes with any privileges. You can load a class which has the privileges to do anything that the user running the browser running the applet can do, including, but not limited to, removing files, opening connections and executing external programs.

So how did Sun fix the bug?

Well, first of all, they left the doPrivileged block in the Calendar class. I'm not a 100% percent sure that was a correct decision, but more about that later on.

What they did fix, and on this I fully agree is that now when a serializable subclass of a non-serializable class is deserialized, a generated sun.reflect.GeneratedSerializationConstructorAccessorxxx instance is put on the call stack before calling the superclass constructor, thus making it lose the privileged context in the case where the subclass itself isn't privileged.

I had to correct myself on this one.

4 comments:

Anonymous said...

Extremely interesting bug, thanks for sharing the details!

For the exploitation, you can avoid playing with serialization manually by using replaceObject(), it is way more convenient.

Sami Koivu said...

Ah, nice (about replaceObject). I hadn't thought of that. That *is* way more convenient.

Thanks for the tip. :)

John Cowan said...

Moral: Java serialization is eeeeevil. Don't use it, don't implement it. If you must serialize things, use XML or JSON. If your framework demands serialization, get a better framework.

Corbin Simpson said...

Amazing. Absolutely genius. I still hate Java, but that is a terrific exploit.