diff options
author | Neil Fuller <nfuller@google.com> | 2014-07-21 11:14:14 +0000 |
---|---|---|
committer | Android Git Automerger <android-git-automerger@android.com> | 2014-07-21 11:14:14 +0000 |
commit | a99ec913e8ee64a7e94ddce779f616571fc90443 (patch) | |
tree | f161997aa159dc0d4b3de6e3db059df21111eebe /luni | |
parent | f5efc79f4cdd3aab545718de6cdc6f6d95f48f37 (diff) | |
parent | be5fed6ac8f49aafd30b1881f70e12f5d59d3222 (diff) | |
download | libcore-a99ec913e8ee64a7e94ddce779f616571fc90443.zip libcore-a99ec913e8ee64a7e94ddce779f616571fc90443.tar.gz libcore-a99ec913e8ee64a7e94ddce779f616571fc90443.tar.bz2 |
am be5fed6a: am 4d73cf3c: am 81feaa00: am 889e1d5a: am 1fe89b7b: am e0c6824f: resolved conflicts for merge of 47f0ec56 to jb-dev-plus-aosp
* commit 'be5fed6ac8f49aafd30b1881f70e12f5d59d3222':
Add additional checks in ObjectInputStream
Diffstat (limited to 'luni')
-rw-r--r-- | luni/src/main/java/java/io/ObjectInputStream.java | 25 | ||||
-rw-r--r-- | luni/src/main/java/java/io/ObjectStreamClass.java | 69 | ||||
-rw-r--r-- | luni/src/main/java/java/io/ObjectStreamConstants.java | 16 | ||||
-rw-r--r-- | luni/src/test/java/libcore/java/io/SerializationTest.java | 260 |
4 files changed, 349 insertions, 21 deletions
diff --git a/luni/src/main/java/java/io/ObjectInputStream.java b/luni/src/main/java/java/io/ObjectInputStream.java index 35f6fc1..f26549d 100644 --- a/luni/src/main/java/java/io/ObjectInputStream.java +++ b/luni/src/main/java/java/io/ObjectInputStream.java @@ -1053,7 +1053,8 @@ public class ObjectInputStream extends InputStream implements ObjectInput, Objec * @see #readFields * @see #readObject() */ - private void readFieldValues(Object obj, ObjectStreamClass classDesc) throws OptionalDataException, ClassNotFoundException, IOException { + private void readFieldValues(Object obj, ObjectStreamClass classDesc) + throws OptionalDataException, ClassNotFoundException, IOException { // Now we must read all fields and assign them to the receiver ObjectStreamField[] fields = classDesc.getLoadFields(); fields = (fields == null) ? ObjectStreamClass.NO_FIELDS : fields; @@ -1577,6 +1578,9 @@ public class ObjectInputStream extends InputStream implements ObjectInput, Objec ClassNotFoundException, IOException { // read classdesc for Enum first ObjectStreamClass classDesc = readEnumDesc(); + + Class enumType = classDesc.checkAndGetTcEnumClass(); + int newHandle = nextHandle(); // read name after class desc String name; @@ -1598,9 +1602,11 @@ public class ObjectInputStream extends InputStream implements ObjectInput, Objec Enum<?> result; try { - result = Enum.valueOf((Class) classDesc.forClass(), name); + result = Enum.valueOf(enumType, name); } catch (IllegalArgumentException e) { - throw new InvalidObjectException(e.getMessage()); + InvalidObjectException ioe = new InvalidObjectException(e.getMessage()); + ioe.initCause(e); + throw ioe; } registerObjectRead(result, newHandle, unshared); return result; @@ -1784,9 +1790,10 @@ public class ObjectInputStream extends InputStream implements ObjectInput, Objec throw missingClassDescriptor(); } + Class<?> objectClass = classDesc.checkAndGetTcObjectClass(); + int newHandle = nextHandle(); - Class<?> objectClass = classDesc.forClass(); - Object result = null; + Object result; Object registeredResult = null; if (objectClass != null) { // Now we know which class to instantiate and which constructor to @@ -2058,8 +2065,7 @@ public class ObjectInputStream extends InputStream implements ObjectInput, Objec * if the source stream does not contain readable serialized * objects. */ - protected void readStreamHeader() throws IOException, - StreamCorruptedException { + protected void readStreamHeader() throws IOException, StreamCorruptedException { if (input.readShort() == STREAM_MAGIC && input.readShort() == STREAM_VERSION) { return; @@ -2259,7 +2265,7 @@ public class ObjectInputStream extends InputStream implements ObjectInput, Objec // not primitive class // Use the first non-null ClassLoader on the stack. If null, use // the system class loader - cls = Class.forName(className, true, callerClassLoader); + cls = Class.forName(className, false, callerClassLoader); } } return cls; @@ -2333,8 +2339,7 @@ public class ObjectInputStream extends InputStream implements ObjectInput, Objec throws InvalidClassException { Class<?> localClass = loadedStreamClass.forClass(); - ObjectStreamClass localStreamClass = ObjectStreamClass - .lookupStreamClass(localClass); + ObjectStreamClass localStreamClass = ObjectStreamClass.lookupStreamClass(localClass); if (loadedStreamClass.getSerialVersionUID() != localStreamClass .getSerialVersionUID()) { diff --git a/luni/src/main/java/java/io/ObjectStreamClass.java b/luni/src/main/java/java/io/ObjectStreamClass.java index 1bde314..9b7f2c9 100644 --- a/luni/src/main/java/java/io/ObjectStreamClass.java +++ b/luni/src/main/java/java/io/ObjectStreamClass.java @@ -1068,7 +1068,6 @@ public class ObjectStreamClass implements Serializable { tlc.put(cl, cachedValue); } return cachedValue; - } /** @@ -1298,4 +1297,72 @@ public class ObjectStreamClass implements Serializable { public String toString() { return getName() + ": static final long serialVersionUID =" + getSerialVersionUID() + "L;"; } + + /** + * Checks the local class to make sure it is valid for {@link ObjectStreamConstants#TC_OBJECT} + * deserialization. Also performs some sanity checks of the stream data. This method is used + * during deserialization to confirm the local class is likely to be compatible with the coming + * stream data, but before an instance is instantiated. + * + * @hide used internally during deserialization + */ + public Class<?> checkAndGetTcObjectClass() throws InvalidClassException { + // We check some error possibilities that might cause problems later. + boolean wasSerializable = (flags & ObjectStreamConstants.SC_SERIALIZABLE) != 0; + boolean wasExternalizable = (flags & ObjectStreamConstants.SC_EXTERNALIZABLE) != 0; + if (wasSerializable == wasExternalizable) { + throw new InvalidClassException( + getName() + " stream data is corrupt: SC_SERIALIZABLE=" + wasSerializable + + " SC_EXTERNALIZABLE=" + wasExternalizable + + ", classDescFlags must have one or the other"); + } + + // TC_ENUM is handled elsewhere. See checkAndGetTcEnumClass(). + if (isEnum()) { + throw new InvalidClassException( + getName() + " local class is incompatible: Local class is an enum, streamed" + + " data is tagged with TC_OBJECT"); + } + + // isSerializable() is true if the local class implements Serializable. Externalizable + // classes are also Serializable via inheritance. + if (!isSerializable()) { + throw new InvalidClassException(getName() + " local class is incompatible: Not" + + " Serializable"); + } + + // The stream class was externalizable, but is only serializable locally. + if (wasExternalizable != isExternalizable()) { + throw new InvalidClassException( + getName() + " local class is incompatible: Local class is Serializable, stream" + + " data requires Externalizable"); + } + + // The following are left unchecked and thus are treated leniently at this point. + // SC_BLOCK_DATA may be set iff SC_EXTERNALIZABLE is set AND version 2 of the protocol is in + // use. + // SC_ENUM should not be set. + + return forClass(); + } + + /** + * Checks the local class to make sure it is valid for {@link ObjectStreamConstants#TC_ENUM} + * deserialization. This method is used during deserialization to confirm the local class is + * likely to be compatible with the coming stream data, but before an instance is instantiated. + * + * @hide used internally during deserialization + */ + public Class<?> checkAndGetTcEnumClass() throws InvalidClassException { + if (!isEnum()) { + throw new InvalidClassException( + getName() + " local class is incompatible: Local class is not an enum," + + " streamed data is tagged with TC_ENUM"); + } + + // The stream flags are expected to be SC_SERIALIZABLE | SC_ENUM but these and the + // other flags are not used when reading enum data so they are treated leniently. + + return forClass(); + } } diff --git a/luni/src/main/java/java/io/ObjectStreamConstants.java b/luni/src/main/java/java/io/ObjectStreamConstants.java index 8228b33..95f8b03 100644 --- a/luni/src/main/java/java/io/ObjectStreamConstants.java +++ b/luni/src/main/java/java/io/ObjectStreamConstants.java @@ -149,25 +149,25 @@ public abstract interface ObjectStreamConstants { // Flags that indicate if the object was serializable, externalizable // and had a writeObject method when dumped. /** - * Bit mask for the {@code flag} field in ObjectStreamClass. Indicates - * that a serializable class has its own {@code writeObject} method. + * Bit mask for the {@code flag} field in {@link ObjectStreamClass}. Indicates + * that a {@link Serializable} class has its own {@code writeObject} method. */ public static final byte SC_WRITE_METHOD = 0x01; // If SC_SERIALIZABLE /** - * Bit mask for the {@code flag} field in ObjectStreamClass. Indicates - * that a class is serializable. + * Bit mask for the {@code flag} field in {@link ObjectStreamClass}. Indicates + * that a class implements {@link Serializable} but not {@link Externalizable}. */ public static final byte SC_SERIALIZABLE = 0x02; /** - * Bit mask for the {@code flag} field in ObjectStreamClass. Indicates - * that a class is externalizable. + * Bit mask for the {@code flag} field in {@link ObjectStreamClass}. Indicates + * that a class implements {@link Externalizable}. */ public static final byte SC_EXTERNALIZABLE = 0x04; /** - * Bit mask for the {@code flag} field in ObjectStreamClass. Indicates + * Bit mask for the {@code flag} field in {@link ObjectStreamClass}. Indicates * that an externalizable class is written in block data mode. */ public static final byte SC_BLOCK_DATA = 0x08; // If SC_EXTERNALIZABLE @@ -178,7 +178,7 @@ public abstract interface ObjectStreamConstants { public static final byte TC_ENUM = 0x7E; /** - * Bit mask for the {@code flag} field in ObjectStreamClass. Indicates + * Bit mask for the {@code flag} field in {@link ObjectStreamClass}. Indicates * that a class is an enum type. */ public static final byte SC_ENUM = 0x10; diff --git a/luni/src/test/java/libcore/java/io/SerializationTest.java b/luni/src/test/java/libcore/java/io/SerializationTest.java index d452c11..1002cf1 100644 --- a/luni/src/test/java/libcore/java/io/SerializationTest.java +++ b/luni/src/test/java/libcore/java/io/SerializationTest.java @@ -16,12 +16,15 @@ package libcore.java.io; -import java.io.IOException; +import junit.framework.TestCase; + import java.io.InvalidClassException; +import java.io.InvalidObjectException; import java.io.ObjectStreamClass; import java.io.ObjectStreamField; import java.io.Serializable; -import junit.framework.TestCase; +import java.lang.reflect.InvocationHandler; +import java.lang.reflect.Method; import libcore.util.SerializationTester; public final class SerializationTest extends TestCase { @@ -61,6 +64,7 @@ public final class SerializationTest extends TestCase { } } + @SuppressWarnings("unused") // Required for deserialization test static class SerialVersionUidChanged implements Serializable { private static final long serialVersionUID = 1L; // was 0L private int a; @@ -77,7 +81,259 @@ public final class SerializationTest extends TestCase { } } + @SuppressWarnings("unused") // Required for deserialization test static class FieldsChanged implements Serializable { private int b; // was 'a' } + + public static boolean wasSerializableInitializedFlag = false; + + @SuppressWarnings("unused") // Required for deserialization test. + public static class WasSerializable /* implements java.io.Serializable */ { + static final long serialVersionUID = 0L; + static { + SerializationTest.wasSerializableInitializedFlag = true; + } + private int i; + } + + public void testDeserializeWasSerializableClass() throws Exception { + // This was created by serializing a WasSerializable when it was serializable. + // String s = SerializationTester.serializeHex(new WasSerializable()); + final String s = "aced0005737200316c6962636f72652e6a6176612e696f2e53657269616c697a6174696f6" + + "e546573742457617353657269616c697a61626c65000000000000000002000149000169787000000" + + "000"; + + wasSerializableInitializedFlag = false; + try { + SerializationTester.deserializeHex(s); + fail(); + } catch (InvalidClassException expected) { + } + assertFalse(wasSerializableInitializedFlag); + } + + // The WasExternalizable class before it was modified. + /* + public static class WasExternalizable implements Externalizable { + static final long serialVersionUID = 0L; + + @Override + public void readExternal(ObjectInput input) throws IOException, ClassNotFoundException { + + } + + @Override + public void writeExternal(ObjectOutput output) throws IOException { + + } + } + */ + + public static boolean wasExternalizableInitializedFlag = false; + + @SuppressWarnings("unused") // Required for deserialization test + public static class WasExternalizable implements Serializable { + static final long serialVersionUID = 0L; + static { + SerializationTest.wasExternalizableInitializedFlag = true; + } + + } + + public void testDeserializeWasExternalizableClass() throws Exception { + // This was created by serializing a WasExternalizable when it was externalizable. + // String s = SerializationTester.serializeHex(new WasExternalizable()); + final String s = "aced0005737200336c6962636f72652e6a6176612e696f2e53657269616c697a6174696f6" + + "e546573742457617345787465726e616c697a61626c6500000000000000000c0000787078"; + + wasExternalizableInitializedFlag = false; + try { + SerializationTester.deserializeHex(s); + fail(); + } catch (InvalidClassException expected) { + } + // Unlike other similar tests static initialization will take place if the local class is + // Serializable or Externalizable because serialVersionUID field is accessed. + // The RI appears to do the same. + assertTrue(wasExternalizableInitializedFlag); + } + + // The WasEnum class before it was modified. + /* + public enum WasEnum { + VALUE + } + */ + + public static boolean wasEnumInitializedFlag = false; + + @SuppressWarnings("unused") // Required for deserialization test + public static class WasEnum { + static final long serialVersionUID = 0L; + static { + SerializationTest.wasEnumInitializedFlag = true; + } + } + + public void testDeserializeWasEnum() throws Exception { + // This was created by serializing a WasEnum when it was an enum. + // String s = SerializationTester.serializeHex(WasEnum.VALUE); + final String s = "aced00057e7200296c6962636f72652e6a6176612e696f2e53657269616c697a6174696f6" + + "e5465737424576173456e756d00000000000000001200007872000e6a6176612e6c616e672e456e7" + + "56d0000000000000000120000787074000556414c5545"; + + wasEnumInitializedFlag = false; + try { + SerializationTester.deserializeHex(s); + fail(); + } catch (InvalidClassException expected) { + } + assertFalse(wasEnumInitializedFlag); + } + + // The WasObject class before it was modified. + /* + public static class WasObject implements java.io.Serializable { + static final long serialVersionUID = 0L; + private int i; + } + */ + + public static boolean wasObjectInitializedFlag; + + @SuppressWarnings("unused") // Required for deserialization test + public enum WasObject { + VALUE; + + static { + SerializationTest.wasObjectInitializedFlag = true; + } + } + + public void testDeserializeWasObject() throws Exception { + // This was created by serializing a WasObject when it wasn't yet an enum. + // String s = SerializationTester.serializeHex(new WasObject()); + final String s = "aced00057372002b6c6962636f72652e6a6176612e696f2e53657269616c697a6174696f6" + + "e54657374245761734f626a656374000000000000000002000149000169787000000000"; + + wasObjectInitializedFlag = false; + try { + SerializationTester.deserializeHex(s); + fail(); + } catch (InvalidClassException expected) { + } + assertFalse(wasObjectInitializedFlag); + } + + @SuppressWarnings("unused") // Required for deserialization test + public enum EnumMissingValue { + /*MISSING_VALUE*/ + } + + public void testDeserializeEnumMissingValue() throws Exception { + // This was created by serializing a EnumMissingValue when it had MISSING_VALUE. + // String s = SerializationTester.serializeHex(EnumMissingValue.MISSING_VALUE); + final String s = "aced00057e7200326c6962636f72652e6a6176612e696f2e53657269616c697a6174696f6" + + "e5465737424456e756d4d697373696e6756616c756500000000000000001200007872000e6a61766" + + "12e6c616e672e456e756d0000000000000000120000787074000d4d495353494e475f56414c5545"; + + try { + SerializationTester.deserializeHex(s); + fail(); + } catch (InvalidObjectException expected) { + } + } + + + public static Object hasStaticInitializerObject; + + public static class HasStaticInitializer implements Serializable { + static { + SerializationTest.hasStaticInitializerObject = new Object(); + } + } + + public void testDeserializeStaticInitializerIsRunEventually() throws Exception { + // This was created by serializing a HasStaticInitializer + // String s = SerializationTester.serializeHex(new HasStaticInitializer()); + final String s = "aced0005737200366c6962636f72652e6a6176612e696f2e53657269616c697a6174696f6" + + "e5465737424486173537461746963496e697469616c697a6572138aa8ed9e9b660a0200007870"; + + // Confirm the ClassLoader behaves as it should. + Class.forName( + HasStaticInitializer.class.getName(), + false /* shouldInitialize */, + Thread.currentThread().getContextClassLoader()); + assertNull(hasStaticInitializerObject); + + SerializationTester.deserializeHex(s); + + assertNotNull(hasStaticInitializerObject); + } + + @SuppressWarnings("unused") // Required for deserialization test + public static /*interface*/ class WasInterface { + } + + @SuppressWarnings("unused") // Required for deserialization test + public static class SerializableInvocationHandler implements InvocationHandler, Serializable { + @Override + public Object invoke(Object proxy, Method method, Object[] args) { + return null; + } + } + + public void testDeserializeProxyWasInterface() throws Exception { + // This was created by serializing a proxy referencing WasInterface when it was an + // interface. + // Object o = Proxy.newProxyInstance( + // Thread.currentThread().getContextClassLoader(), + // new Class[] { WasInterface.class }, + // new SerializableInvocationHandler()); + // String s = SerializationTester.serializeHex(o); + final String s = "aced0005737d00000001002e6c6962636f72652e6a6176612e696f2e53657269616c697a6" + + "174696f6e5465737424576173496e74657266616365787200176a6176612e6c616e672e7265666c6" + + "563742e50726f7879e127da20cc1043cb0200014c0001687400254c6a6176612f6c616e672f72656" + + "66c6563742f496e766f636174696f6e48616e646c65723b78707372003f6c6962636f72652e6a617" + + "6612e696f2e53657269616c697a6174696f6e546573742453657269616c697a61626c65496e766f6" + + "36174696f6e48616e646c6572e6ceffa2941ee3210200007870"; + try { + SerializationTester.deserializeHex(s); + fail(); + } catch (ClassNotFoundException expected) { + } + } + + @SuppressWarnings("unused") // Required for deserialization test + public static class WasSerializableInvocationHandler + implements InvocationHandler /*, Serializable*/ { + static final long serialVersionUID = 0L; + + @Override + public Object invoke(Object proxy, Method method, Object[] args) { + return null; + } + } + + public void testDeserializeProxyInvocationHandlerWasSerializable() throws Exception { + // This was created by serializing a proxy referencing WasSerializableInvocationHandler when + // it was Serializable. + // Object o = Proxy.newProxyInstance( + // Thread.currentThread().getContextClassLoader(), + // new Class[] { Comparable.class }, + // new WasSerializableInvocationHandler()); + // String s = SerializationTester.serializeHex(o); + final String s = "aced0005737d0000000100146a6176612e6c616e672e436f6d70617261626c65787200176" + + "a6176612e6c616e672e7265666c6563742e50726f7879e127da20cc1043cb0200014c00016874002" + + "54c6a6176612f6c616e672f7265666c6563742f496e766f636174696f6e48616e646c65723b78707" + + "37200426c6962636f72652e6a6176612e696f2e53657269616c697a6174696f6e546573742457617" + + "353657269616c697a61626c65496e766f636174696f6e48616e646c6572000000000000000002000" + + "07870"; + try { + SerializationTester.deserializeHex(s); + fail(); + } catch (InvalidClassException expected) { + } + } } |