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