blob: 5dd3c2ce6b5d165eb4b58629b992dba87626c1be [file] [log] [blame]
Adam Lesinskif741c372014-11-07 11:26:14 -08001package android.content.pm;
2
3import android.os.Parcel;
4import android.os.Parcelable;
5import junit.framework.TestCase;
6
7import java.util.ArrayList;
8import java.util.List;
9
10public class ParceledListSliceTest extends TestCase {
11
12 public void testSmallList() throws Exception {
13 final int objectCount = 100;
Jon Larimer48c95c42015-01-29 15:54:43 -050014 List<SmallObject> list = new ArrayList<SmallObject>();
Adam Lesinskif741c372014-11-07 11:26:14 -080015 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 {
Jon Larimer48c95c42015-01-29 15:54:43 -050023 parcel.writeParcelable(new ParceledListSlice<SmallObject>(list), 0);
Adam Lesinskif741c372014-11-07 11:26:14 -080024 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
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 */
55 public void testLargeList() throws Exception {
56 final int thresholdBytes = 256 * 1024;
57 final int objectCount = thresholdBytes / measureLargeObject();
58
Jon Larimer48c95c42015-01-29 15:54:43 -050059 List<LargeObject> list = new ArrayList<LargeObject>();
Adam Lesinskif741c372014-11-07 11:26:14 -080060 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 {
Jon Larimer48c95c42015-01-29 15:54:43 -050074 parcel.writeParcelable(new ParceledListSlice<LargeObject>(list), 0);
Adam Lesinskif741c372014-11-07 11:26:14 -080075 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
Robin Leeabaa0692017-02-20 20:54:22 +000094 public void testStringList() throws Exception {
95 final int objectCount = 400;
96 List<String> list = new ArrayList<String>();
97 for (long i = 0; i < objectCount; i++) {
98 list.add(Long.toString(i * (6 - i)));
99 }
100
101 StringParceledListSlice slice;
102 Parcel parcel = Parcel.obtain();
103 try {
104 parcel.writeParcelable(new StringParceledListSlice(list), 0);
105 parcel.setDataPosition(0);
106 slice = parcel.readParcelable(getClass().getClassLoader());
107 } finally {
108 parcel.recycle();
109 }
110
111 assertNotNull(slice);
112 assertNotNull(slice.getList());
113 assertEquals(list, slice.getList());
114 }
115
Adam Lesinskif741c372014-11-07 11:26:14 -0800116 /**
117 * Test that only homogeneous elements may be unparceled.
118 */
119 public void testHomogeneousElements() throws Exception {
Jon Larimer48c95c42015-01-29 15:54:43 -0500120 List<BaseObject> list = new ArrayList<BaseObject>();
Adam Lesinskif741c372014-11-07 11:26:14 -0800121 list.add(new LargeObject(0, 1, 2, 3, 4));
122 list.add(new SmallObject(5, 6));
123 list.add(new SmallObject(7, 8));
124
125 Parcel parcel = Parcel.obtain();
126 try {
127 writeEvilParceledListSlice(parcel, list);
128 parcel.setDataPosition(0);
129 try {
130 ParceledListSlice.CREATOR.createFromParcel(parcel, getClass().getClassLoader());
131 assertTrue("Unparceled heterogeneous ParceledListSlice", false);
132 } catch (IllegalArgumentException e) {
133 // Success, we're not allowed to process heterogeneous
134 // elements in a ParceledListSlice.
135 }
136 } finally {
137 parcel.recycle();
138 }
139 }
140
141 /**
142 * Write a ParcelableListSlice that uses the BaseObject base class as the Creator.
143 * This is dangerous, as it may affect how the data is unparceled, then later parceled
144 * by the system, leading to a self-modifying data security vulnerability.
145 */
146 private static <T extends BaseObject> void writeEvilParceledListSlice(Parcel dest, List<T> list) {
147 final int listCount = list.size();
148
149 // Number of items.
150 dest.writeInt(listCount);
151
152 // The type/creator to use when unparceling. Here we use the base class
153 // to simulate an attack on ParceledListSlice.
154 dest.writeString(BaseObject.class.getName());
155
156 for (int i = 0; i < listCount; i++) {
157 // 1 means the item is present.
158 dest.writeInt(1);
159 list.get(i).writeToParcel(dest, 0);
160 }
161 }
162
163 public abstract static class BaseObject implements Parcelable {
164 protected static final int TYPE_SMALL = 0;
165 protected static final int TYPE_LARGE = 1;
166
167 protected void writeToParcel(Parcel dest, int flags, int type) {
168 dest.writeInt(type);
169 }
170
171 @Override
172 public int describeContents() {
173 return 0;
174 }
175
176 /**
177 * This is *REALLY* bad, but we're doing it in the test to ensure that we handle
178 * the possible exploit when unparceling an object with the BaseObject written as
179 * Creator.
180 */
181 public static final Creator<BaseObject> CREATOR = new Creator<BaseObject>() {
182 @Override
183 public BaseObject createFromParcel(Parcel source) {
184 switch (source.readInt()) {
185 case TYPE_SMALL:
186 return SmallObject.createFromParcelBody(source);
187 case TYPE_LARGE:
188 return LargeObject.createFromParcelBody(source);
189 default:
190 throw new IllegalArgumentException("Unknown type");
191 }
192 }
193
194 @Override
195 public BaseObject[] newArray(int size) {
196 return new BaseObject[size];
197 }
198 };
199 }
200
201 public static class SmallObject extends BaseObject {
202 public int mFieldA;
203 public int mFieldB;
204
205 public SmallObject(int a, int b) {
206 mFieldA = a;
207 mFieldB = b;
208 }
209
210 @Override
211 public void writeToParcel(Parcel dest, int flags) {
212 super.writeToParcel(dest, flags, TYPE_SMALL);
213 dest.writeInt(mFieldA);
214 dest.writeInt(mFieldB);
215 }
216
217 public static SmallObject createFromParcelBody(Parcel source) {
218 return new SmallObject(source.readInt(), source.readInt());
219 }
220
221 public static final Creator<SmallObject> CREATOR = new Creator<SmallObject>() {
222 @Override
223 public SmallObject createFromParcel(Parcel source) {
224 // Consume the type (as it is always written out).
225 source.readInt();
226 return createFromParcelBody(source);
227 }
228
229 @Override
230 public SmallObject[] newArray(int size) {
231 return new SmallObject[size];
232 }
233 };
234 }
235
236 public static class LargeObject extends BaseObject {
237 public int mFieldA;
238 public int mFieldB;
239 public int mFieldC;
240 public int mFieldD;
241 public int mFieldE;
242
243 public LargeObject(int a, int b, int c, int d, int e) {
244 mFieldA = a;
245 mFieldB = b;
246 mFieldC = c;
247 mFieldD = d;
248 mFieldE = e;
249 }
250
251 @Override
252 public void writeToParcel(Parcel dest, int flags) {
253 super.writeToParcel(dest, flags, TYPE_LARGE);
254 dest.writeInt(mFieldA);
255 dest.writeInt(mFieldB);
256 dest.writeInt(mFieldC);
257 dest.writeInt(mFieldD);
258 dest.writeInt(mFieldE);
259 }
260
261 public static LargeObject createFromParcelBody(Parcel source) {
262 return new LargeObject(
263 source.readInt(),
264 source.readInt(),
265 source.readInt(),
266 source.readInt(),
267 source.readInt()
268 );
269 }
270
271 public static final Creator<LargeObject> CREATOR = new Creator<LargeObject>() {
272 @Override
273 public LargeObject createFromParcel(Parcel source) {
274 // Consume the type (as it is always written out).
275 source.readInt();
276 return createFromParcelBody(source);
277 }
278
279 @Override
280 public LargeObject[] newArray(int size) {
281 return new LargeObject[size];
282 }
283 };
284 }
285}