1package 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;
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
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
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     */
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     */
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
145        protected void writeToParcel(Parcel dest, int flags, int type) {
146            dest.writeInt(type);
147        }
148
149        @Override
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
183        public SmallObject(int a, int b) {
184            mFieldA = a;
185            mFieldB = b;
186        }
187
188        @Override
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
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
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
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
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