1 package android.content.pm; 2 3 import android.os.Parcel; 4 import android.os.Parcelable; 5 import android.support.test.filters.LargeTest; 6 7 import junit.framework.TestCase; 8 9 import java.util.ArrayList; 10 import java.util.Collections; 11 import java.util.List; 12 13 @LargeTest 14 public 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