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     /**
     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