Home | History | Annotate | Download | only in pm
      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