1 package android.content.pm;
2 
3 import android.os.Parcel;
4 import android.os.Parcelable;
5 import junit.framework.TestCase;
6 
7 import java.util.ArrayList;
8 import java.util.List;
9 
10 public class ParceledListSliceTest extends TestCase {
11 
testSmallList()12     public void testSmallList() throws Exception {
13         final int objectCount = 100;
14         List<SmallObject> list = new ArrayList<SmallObject>();
15         for (int i = 0; i < objectCount; i++) {
16             list.add(new SmallObject(i * 2, (i * 2) + 1));
17         }
18 
19         ParceledListSlice<SmallObject> slice;
20 
21         Parcel parcel = Parcel.obtain();
22         try {
23             parcel.writeParcelable(new ParceledListSlice<SmallObject>(list), 0);
24             parcel.setDataPosition(0);
25             slice = parcel.readParcelable(getClass().getClassLoader());
26         } finally {
27             parcel.recycle();
28         }
29 
30         assertNotNull(slice);
31         assertNotNull(slice.getList());
32         assertEquals(objectCount, slice.getList().size());
33 
34         for (int i = 0; i < objectCount; i++) {
35             assertEquals(i * 2, slice.getList().get(i).mFieldA);
36             assertEquals((i * 2) + 1, slice.getList().get(i).mFieldB);
37         }
38     }
39 
measureLargeObject()40     private static int measureLargeObject() {
41         Parcel p = Parcel.obtain();
42         try {
43             new LargeObject(0, 0, 0, 0, 0).writeToParcel(p, 0);
44             return p.dataPosition();
45         } finally {
46             p.recycle();
47         }
48     }
49 
50     /**
51      * Test that when the list is large, the data is successfully parceled
52      * and unparceled (the implementation will send pieces of the list in
53      * separate round-trips to avoid the IPC limit).
54      */
testLargeList()55     public void testLargeList() throws Exception {
56         final int thresholdBytes = 256 * 1024;
57         final int objectCount = thresholdBytes / measureLargeObject();
58 
59         List<LargeObject> list = new ArrayList<LargeObject>();
60         for (int i = 0; i < objectCount; i++) {
61             list.add(new LargeObject(
62                     i * 5,
63                     (i * 5) + 1,
64                     (i * 5) + 2,
65                     (i * 5) + 3,
66                     (i * 5) + 4
67             ));
68         }
69 
70         ParceledListSlice<LargeObject> slice;
71 
72         Parcel parcel = Parcel.obtain();
73         try {
74             parcel.writeParcelable(new ParceledListSlice<LargeObject>(list), 0);
75             parcel.setDataPosition(0);
76             slice = parcel.readParcelable(getClass().getClassLoader());
77         } finally {
78             parcel.recycle();
79         }
80 
81         assertNotNull(slice);
82         assertNotNull(slice.getList());
83         assertEquals(objectCount, slice.getList().size());
84 
85         for (int i = 0; i < objectCount; i++) {
86             assertEquals(i * 5, slice.getList().get(i).mFieldA);
87             assertEquals((i * 5) + 1, slice.getList().get(i).mFieldB);
88             assertEquals((i * 5) + 2, slice.getList().get(i).mFieldC);
89             assertEquals((i * 5) + 3, slice.getList().get(i).mFieldD);
90             assertEquals((i * 5) + 4, slice.getList().get(i).mFieldE);
91         }
92     }
93 
94     /**
95      * Test that only homogeneous elements may be unparceled.
96      */
testHomogeneousElements()97     public void testHomogeneousElements() throws Exception {
98         List<BaseObject> list = new ArrayList<BaseObject>();
99         list.add(new LargeObject(0, 1, 2, 3, 4));
100         list.add(new SmallObject(5, 6));
101         list.add(new SmallObject(7, 8));
102 
103         Parcel parcel = Parcel.obtain();
104         try {
105             writeEvilParceledListSlice(parcel, list);
106             parcel.setDataPosition(0);
107             try {
108                 ParceledListSlice.CREATOR.createFromParcel(parcel, getClass().getClassLoader());
109                 assertTrue("Unparceled heterogeneous ParceledListSlice", false);
110             } catch (IllegalArgumentException e) {
111                 // Success, we're not allowed to process heterogeneous
112                 // elements in a ParceledListSlice.
113             }
114         } finally {
115             parcel.recycle();
116         }
117     }
118 
119     /**
120      * Write a ParcelableListSlice that uses the BaseObject base class as the Creator.
121      * This is dangerous, as it may affect how the data is unparceled, then later parceled
122      * by the system, leading to a self-modifying data security vulnerability.
123      */
writeEvilParceledListSlice(Parcel dest, List<T> list)124     private static <T extends BaseObject> void writeEvilParceledListSlice(Parcel dest, List<T> list) {
125         final int listCount = list.size();
126 
127         // Number of items.
128         dest.writeInt(listCount);
129 
130         // The type/creator to use when unparceling. Here we use the base class
131         // to simulate an attack on ParceledListSlice.
132         dest.writeString(BaseObject.class.getName());
133 
134         for (int i = 0; i < listCount; i++) {
135             // 1 means the item is present.
136             dest.writeInt(1);
137             list.get(i).writeToParcel(dest, 0);
138         }
139     }
140 
141     public abstract static class BaseObject implements Parcelable {
142         protected static final int TYPE_SMALL = 0;
143         protected static final int TYPE_LARGE = 1;
144 
writeToParcel(Parcel dest, int flags, int type)145         protected void writeToParcel(Parcel dest, int flags, int type) {
146             dest.writeInt(type);
147         }
148 
149         @Override
describeContents()150         public int describeContents() {
151             return 0;
152         }
153 
154         /**
155          * This is *REALLY* bad, but we're doing it in the test to ensure that we handle
156          * the possible exploit when unparceling an object with the BaseObject written as
157          * Creator.
158          */
159         public static final Creator<BaseObject> CREATOR = new Creator<BaseObject>() {
160             @Override
161             public BaseObject createFromParcel(Parcel source) {
162                 switch (source.readInt()) {
163                     case TYPE_SMALL:
164                         return SmallObject.createFromParcelBody(source);
165                     case TYPE_LARGE:
166                         return LargeObject.createFromParcelBody(source);
167                     default:
168                         throw new IllegalArgumentException("Unknown type");
169                 }
170             }
171 
172             @Override
173             public BaseObject[] newArray(int size) {
174                 return new BaseObject[size];
175             }
176         };
177     }
178 
179     public static class SmallObject extends BaseObject {
180         public int mFieldA;
181         public int mFieldB;
182 
SmallObject(int a, int b)183         public SmallObject(int a, int b) {
184             mFieldA = a;
185             mFieldB = b;
186         }
187 
188         @Override
writeToParcel(Parcel dest, int flags)189         public void writeToParcel(Parcel dest, int flags) {
190             super.writeToParcel(dest, flags, TYPE_SMALL);
191             dest.writeInt(mFieldA);
192             dest.writeInt(mFieldB);
193         }
194 
createFromParcelBody(Parcel source)195         public static SmallObject createFromParcelBody(Parcel source) {
196             return new SmallObject(source.readInt(), source.readInt());
197         }
198 
199         public static final Creator<SmallObject> CREATOR = new Creator<SmallObject>() {
200             @Override
201             public SmallObject createFromParcel(Parcel source) {
202                 // Consume the type (as it is always written out).
203                 source.readInt();
204                 return createFromParcelBody(source);
205             }
206 
207             @Override
208             public SmallObject[] newArray(int size) {
209                 return new SmallObject[size];
210             }
211         };
212     }
213 
214     public static class LargeObject extends BaseObject {
215         public int mFieldA;
216         public int mFieldB;
217         public int mFieldC;
218         public int mFieldD;
219         public int mFieldE;
220 
LargeObject(int a, int b, int c, int d, int e)221         public LargeObject(int a, int b, int c, int d, int e) {
222             mFieldA = a;
223             mFieldB = b;
224             mFieldC = c;
225             mFieldD = d;
226             mFieldE = e;
227         }
228 
229         @Override
writeToParcel(Parcel dest, int flags)230         public void writeToParcel(Parcel dest, int flags) {
231             super.writeToParcel(dest, flags, TYPE_LARGE);
232             dest.writeInt(mFieldA);
233             dest.writeInt(mFieldB);
234             dest.writeInt(mFieldC);
235             dest.writeInt(mFieldD);
236             dest.writeInt(mFieldE);
237         }
238 
createFromParcelBody(Parcel source)239         public static LargeObject createFromParcelBody(Parcel source) {
240             return new LargeObject(
241                     source.readInt(),
242                     source.readInt(),
243                     source.readInt(),
244                     source.readInt(),
245                     source.readInt()
246             );
247         }
248 
249         public static final Creator<LargeObject> CREATOR = new Creator<LargeObject>() {
250             @Override
251             public LargeObject createFromParcel(Parcel source) {
252                 // Consume the type (as it is always written out).
253                 source.readInt();
254                 return createFromParcelBody(source);
255             }
256 
257             @Override
258             public LargeObject[] newArray(int size) {
259                 return new LargeObject[size];
260             }
261         };
262     }
263 }
264