Skip to content

Commit 9562048

Browse files
nfullerandi34
authored andcommitted
Add additional field checks for deserialization.
Check that a field is not static when deserializing. Contains some additional tests to confirm and document behavior and prevent regressions for field deserialization. (cherry picked from commit f4d72bc) Bug: 17202597 Change-Id: I72456a8b45ca0de1d3dd2b0f9515548b02e0a7be
1 parent 39c70da commit 9562048

File tree

4 files changed

+129
-18
lines changed

4 files changed

+129
-18
lines changed

luni/src/main/java/java/io/ObjectInputStream.java

Lines changed: 4 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -23,7 +23,6 @@
2323
import java.lang.reflect.Field;
2424
import java.lang.reflect.InvocationTargetException;
2525
import java.lang.reflect.Method;
26-
import java.lang.reflect.Modifier;
2726
import java.lang.reflect.Proxy;
2827
import java.util.ArrayList;
2928
import java.util.Arrays;
@@ -1063,12 +1062,10 @@ private void readFieldValues(Object obj, ObjectStreamClass classDesc) throws Opt
10631062
}
10641063

10651064
for (ObjectStreamField fieldDesc : fields) {
1066-
Field field = classDesc.getReflectionField(fieldDesc);
1067-
if (field != null && Modifier.isTransient(field.getModifiers())) {
1068-
field = null; // No setting transient fields! (http://b/4471249)
1069-
}
1070-
// We may not have been able to find the field, or it may be transient, but we still
1071-
// need to read the value and do the other checking...
1065+
// checkAndGetReflectionField() can return null if it was not able to find the field or
1066+
// if it is transient or static. We still need to read the data and do the other
1067+
// checking...
1068+
Field field = classDesc.checkAndGetReflectionField(fieldDesc);
10721069
try {
10731070
Class<?> type = fieldDesc.getTypeInternal();
10741071
if (type == byte.class) {

luni/src/main/java/java/io/ObjectOutputStream.java

Lines changed: 5 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -950,9 +950,11 @@ private void writeFieldValues(Object obj, ObjectStreamClass classDesc) throws IO
950950
for (ObjectStreamField fieldDesc : classDesc.fields()) {
951951
try {
952952
Class<?> type = fieldDesc.getTypeInternal();
953-
Field field = classDesc.getReflectionField(fieldDesc);
953+
Field field = classDesc.checkAndGetReflectionField(fieldDesc);
954954
if (field == null) {
955-
throw new InvalidClassException(classDesc.getName() + " doesn't have a field " + fieldDesc.getName() + " of type " + type);
955+
throw new InvalidClassException(classDesc.getName()
956+
+ " doesn't have a serializable field " + fieldDesc.getName()
957+
+ " of type " + type);
956958
}
957959
if (type == byte.class) {
958960
output.writeByte(field.getByte(obj));
@@ -1750,7 +1752,7 @@ private int writeNewEnum(Object object, Class<?> theClass, boolean unshared) thr
17501752
// Only write field "name" for enum class, which is the second field of
17511753
// enum, that is fields[1]. Ignore all non-fields and fields.length < 2
17521754
if (fields != null && fields.length > 1) {
1753-
Field field = classDesc.getSuperclass().getReflectionField(fields[1]);
1755+
Field field = classDesc.getSuperclass().checkAndGetReflectionField(fields[1]);
17541756
if (field == null) {
17551757
throw new NoSuchFieldError();
17561758
}

luni/src/main/java/java/io/ObjectStreamClass.java

Lines changed: 28 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -185,26 +185,46 @@ long getConstructor() {
185185
return constructor;
186186
}
187187

188-
Field getReflectionField(ObjectStreamField osf) {
188+
/**
189+
* Returns the {@link Field} referred to by {@link ObjectStreamField} for the class described by
190+
* this {@link ObjectStreamClass}. A {@code null} value is returned if the local definition of
191+
* the field does not meet the criteria for a serializable / deserializable field, i.e. the
192+
* field must be non-static and non-transient. Caching of each field lookup is performed. The
193+
* first time a field is returned it is made accessible with a call to
194+
* {@link Field#setAccessible(boolean)}.
195+
*/
196+
Field checkAndGetReflectionField(ObjectStreamField osf) {
189197
synchronized (reflectionFields) {
190198
Field field = reflectionFields.get(osf);
191-
if (field != null) {
199+
// null might indicate a cache miss or a hit and a non-serializable field so we
200+
// check for a mapping.
201+
if (field != null || reflectionFields.containsKey(osf)) {
192202
return field;
193203
}
194204
}
195205

206+
Field field;
196207
try {
197208
Class<?> declaringClass = forClass();
198-
Field field = declaringClass.getDeclaredField(osf.getName());
199-
field.setAccessible(true);
200-
synchronized (reflectionFields) {
201-
reflectionFields.put(osf, field);
209+
field = declaringClass.getDeclaredField(osf.getName());
210+
211+
int modifiers = field.getModifiers();
212+
if (Modifier.isStatic(modifiers) || Modifier.isTransient(modifiers)) {
213+
// No serialization or deserialization of transient or static fields!
214+
// See http://b/4471249 and http://b/17202597.
215+
field = null;
216+
} else {
217+
field.setAccessible(true);
202218
}
203-
return reflectionFields.get(osf);
204219
} catch (NoSuchFieldException ex) {
205220
// The caller messed up. We'll return null and won't try to resolve this again.
206-
return null;
221+
field = null;
222+
}
223+
224+
synchronized (reflectionFields) {
225+
reflectionFields.put(osf, field);
207226
}
227+
return field;
208228
}
209229

210230
/*

luni/src/test/java/libcore/java/io/SerializationTest.java

Lines changed: 92 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -18,6 +18,7 @@
1818

1919
import java.io.IOException;
2020
import java.io.InvalidClassException;
21+
import java.io.NotSerializableException;
2122
import java.io.ObjectStreamClass;
2223
import java.io.ObjectStreamField;
2324
import java.io.Serializable;
@@ -49,6 +50,97 @@ static class FieldMadeTransient implements Serializable {
4950
private int nonTransientInt;
5051
}
5152

53+
public void testSerializeFieldMadeStatic() throws Exception {
54+
// Does ObjectStreamClass have the right idea?
55+
ObjectStreamClass osc = ObjectStreamClass.lookup(FieldMadeStatic.class);
56+
ObjectStreamField[] fields = osc.getFields();
57+
assertEquals(0, fields.length);
58+
59+
// This was created by serializing a FieldMadeStatic with a non-static staticInt
60+
String s = "aced0005737200316c6962636f72652e6a6176612e696f2e53657269616c697a6174696f6e54657"
61+
+ "374244669656c644d6164655374617469630000000000000000020001490009737461746963496e7"
62+
+ "47870000022b8";
63+
FieldMadeStatic deserialized = (FieldMadeStatic) SerializationTester.deserializeHex(s);
64+
// The field data is simply ignored if it is static.
65+
assertEquals(9999, deserialized.staticInt);
66+
}
67+
68+
static class FieldMadeStatic implements Serializable {
69+
private static final long serialVersionUID = 0L;
70+
// private int staticInt = 8888;
71+
private static int staticInt = 9999;
72+
}
73+
74+
// We can serialize an object that has an unserializable field providing it is null.
75+
public void testDeserializeNullUnserializableField() throws Exception {
76+
// This was created by creating a new SerializableContainer and not setting the
77+
// unserializable field. A canned serialized form is used so we can tell if the static
78+
// initializers were executed during deserialization.
79+
// SerializationTester.serializeHex(new SerializableContainer());
80+
String s = "aced0005737200376c6962636f72652e6a6176612e696f2e53657269616c697a6174696f6e54657"
81+
+ "3742453657269616c697a61626c65436f6e7461696e657200000000000000000200014c000e756e7"
82+
+ "3657269616c697a61626c657400334c6c6962636f72652f6a6176612f696f2f53657269616c697a6"
83+
+ "174696f6e546573742457617353657269616c697a61626c653b787070";
84+
85+
serializableContainerInitializedFlag = false;
86+
wasSerializableInitializedFlag = false;
87+
88+
SerializableContainer sc = (SerializableContainer) SerializationTester.deserializeHex(s);
89+
assertNull(sc.unserializable);
90+
91+
// Confirm the container was initialized, but the class for the null field was not.
92+
assertTrue(serializableContainerInitializedFlag);
93+
assertFalse(wasSerializableInitializedFlag);
94+
}
95+
96+
public static boolean serializableContainerInitializedFlag = false;
97+
98+
static class SerializableContainer implements Serializable {
99+
private static final long serialVersionUID = 0L;
100+
private Object unserializable = null;
101+
102+
static {
103+
serializableContainerInitializedFlag = true;
104+
}
105+
}
106+
107+
// We must not serialize an object that has a non-null unserializable field.
108+
public void testSerializeUnserializableField() throws Exception {
109+
SerializableContainer sc = new SerializableContainer();
110+
sc.unserializable = new WasSerializable();
111+
try {
112+
SerializationTester.serializeHex(sc);
113+
fail();
114+
} catch (NotSerializableException expected) {
115+
}
116+
}
117+
118+
// It must not be possible to deserialize an object if a field is no longer serializable.
119+
public void testDeserializeUnserializableField() throws Exception {
120+
// This was generated by creating a SerializableContainer and setting the unserializable
121+
// field to a WasSerializable when it was still Serializable. A canned serialized form is
122+
// used so we can tell if the static initializers were executed during deserialization.
123+
// SerializableContainer sc = new SerializableContainer();
124+
// sc.unserializable = new WasSerializable();
125+
// SerializationTester.serializeHex(sc);
126+
String s = "aced0005737200376c6962636f72652e6a6176612e696f2e53657269616c697a6174696f6e54657"
127+
+ "3742453657269616c697a61626c65436f6e7461696e657200000000000000000200014c000e756e7"
128+
+ "3657269616c697a61626c657400124c6a6176612f6c616e672f4f626a6563743b7870737200316c6"
129+
+ "962636f72652e6a6176612e696f2e53657269616c697a6174696f6e5465737424576173536572696"
130+
+ "16c697a61626c65000000000000000002000149000169787000000000";
131+
132+
serializableContainerInitializedFlag = false;
133+
wasSerializableInitializedFlag = false;
134+
try {
135+
SerializationTester.deserializeHex(s);
136+
fail();
137+
} catch (InvalidClassException expected) {
138+
}
139+
// Confirm neither the container nor the contained class was initialized.
140+
assertFalse(serializableContainerInitializedFlag);
141+
assertFalse(wasSerializableInitializedFlag);
142+
}
143+
52144
public void testSerialVersionUidChange() throws Exception {
53145
// this was created by serializing a SerialVersionUidChanged with serialVersionUID = 0L
54146
String s = "aced0005737200396c6962636f72652e6a6176612e696f2e53657269616c697a6174696f6e54657"

0 commit comments

Comments
 (0)