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 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 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 116 /** 117 * Test that only homogeneous elements may be unparceled. 118 */ 119 public void testHomogeneousElements() throws Exception { 120 List<BaseObject> list = new ArrayList<BaseObject>(); 121 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 } 286