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