Home | History | Annotate | Download | only in contacts
      1 /*
      2  * Copyright (C) 2011 The Android Open Source Project
      3  *
      4  * Licensed under the Apache License, Version 2.0 (the "License");
      5  * you may not use this file except in compliance with the License.
      6  * You may obtain a copy of the License at
      7  *
      8  *      http://www.apache.org/licenses/LICENSE-2.0
      9  *
     10  * Unless required by applicable law or agreed to in writing, software
     11  * distributed under the License is distributed on an "AS IS" BASIS,
     12  * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
     13  * See the License for the specific language governing permissions and
     14  * limitations under the License.
     15  */
     16 
     17 package com.android.providers.contacts;
     18 
     19 import static com.android.providers.contacts.EvenMoreAsserts.assertThrows;
     20 import static com.android.providers.contacts.TestUtils.cv;
     21 
     22 import android.database.Cursor;
     23 import android.net.Uri;
     24 import android.net.Uri.Builder;
     25 import android.provider.ContactsContract;
     26 import android.provider.ContactsContract.CommonDataKinds.Phone;
     27 import android.provider.ContactsContract.Contacts;
     28 import android.provider.ContactsContract.Data;
     29 import android.test.suitebuilder.annotation.MediumTest;
     30 
     31 import com.android.providers.contacts.testutil.RawContactUtil;
     32 
     33 /**
     34  * Unit tests for {@link ContactsProvider2}, to make sure the queries don't allow sql injection.
     35  *
     36  * Run the test like this:
     37  * <code>
     38  * adb shell am instrument -e class com.android.providers.contacts.SqlInjectionDetectionTest -w \
     39  *         com.android.providers.contacts.tests/android.test.InstrumentationTestRunner
     40  * </code>
     41  */
     42 @MediumTest
     43 public class SqlInjectionDetectionTest extends BaseContactsProvider2Test {
     44     private static final String[] PHONE_ID_PROJECTION = new String[] { Phone._ID };
     45 
     46     @Override
     47     protected void setUp() throws Exception {
     48         super.setUp();
     49     }
     50 
     51     public void testQueryValid() {
     52         assertQueryValid(Phone.CONTENT_URI, PHONE_ID_PROJECTION,
     53                 Phone.NUMBER + "='555-123-4567'", null);
     54 
     55         // The following tables are whitelisted.
     56         assertQueryValid(Data.CONTENT_URI, null,
     57                 "data._id in default_directory", null);
     58     }
     59 
     60     public void testPhoneQueryBadProjection() {
     61         assertQueryThrows(Phone.CONTENT_URI,
     62                 new String[] { "0 UNION SELECT _id FROM view_data--" }, null, null);
     63 
     64         // Invalid column names should be detected too.
     65         assertQueryThrows(Phone.CONTENT_URI, new String[] { "a" }, null, null);
     66         assertQueryThrows(Phone.CONTENT_URI, new String[] { " _id" }, null, null);
     67 
     68         // This is still invalid because we only allow exact column names in projections.
     69         assertQueryThrows(Phone.CONTENT_URI, new String[] { "[_id]" }, null, null);
     70     }
     71 
     72     public void testPhoneQueryBadSelection() {
     73         assertQueryThrows(Phone.CONTENT_URI, PHONE_ID_PROJECTION,
     74                 "0=1) UNION SELECT _id FROM view_data--", null);
     75         assertQueryThrows(Phone.CONTENT_URI, PHONE_ID_PROJECTION, ";delete from contacts", null);
     76         if (ContactsDatabaseHelper.DISALLOW_SUB_QUERIES) {
     77             assertQueryThrows(Phone.CONTENT_URI, PHONE_ID_PROJECTION,
     78                     "_id in data_usage_stat", null);
     79             assertQueryThrows(Phone.CONTENT_URI, PHONE_ID_PROJECTION,
     80                     "_id in (select _id from default_directory)", null);
     81         }
     82     }
     83 
     84     public void testPhoneQueryBadSortOrder() {
     85         assertQueryThrows(Phone.CONTENT_URI,
     86                 PHONE_ID_PROJECTION, null, "_id UNION SELECT _id FROM view_data--");
     87         assertQueryThrows(Phone.CONTENT_URI, PHONE_ID_PROJECTION, null, ";delete from contacts");
     88         if (ContactsDatabaseHelper.DISALLOW_SUB_QUERIES) {
     89             assertQueryThrows(Phone.CONTENT_URI, PHONE_ID_PROJECTION, null,
     90                     "_id in data_usage_stat");
     91             assertQueryThrows(Phone.CONTENT_URI, PHONE_ID_PROJECTION,
     92                     null, "exists (select _id from default_directory)");
     93         }
     94     }
     95 
     96     public void testPhoneQueryBadLimit() {
     97         // Non-numeric query parameters are ignored by the provider
     98         long rawContactId = RawContactUtil.createRawContactWithName(mResolver, "Hot", "Tamale");
     99         insertPhoneNumber(rawContactId, "555-123-4567");
    100 
    101         Builder builder = Contacts.CONTENT_FILTER_URI.buildUpon();
    102         builder.appendQueryParameter(ContactsContract.LIMIT_PARAM_KEY,
    103                 "0 UNION SELECT -50");
    104         assertQueryValid(Phone.CONTENT_URI,
    105                 PHONE_ID_PROJECTION, null, null);
    106 
    107         final Cursor c = mResolver.query(Phone.CONTENT_URI, PHONE_ID_PROJECTION, null, null, null);
    108         // the current implementation ignores every non-numeric limit. so we should see the
    109         // contact as the only result
    110         assertEquals(1, c.getCount());
    111         c.moveToFirst();
    112         assertNotSame(-50, c.getLong(0));
    113         c.close();
    114     }
    115 
    116     private void assertQueryValid(final Uri uri, final String[] projection,
    117             final String selection, final String sortOrder) {
    118         final Cursor c = mResolver.query(uri, projection, selection, null, sortOrder);
    119         c.close();
    120     }
    121 
    122     private <T extends Exception> void assertQueryThrows(final Uri uri,
    123             final String[] projection, final String selection, final String sortOrder) {
    124         assertThrows(IllegalArgumentException.class, () -> {
    125                 final Cursor c = mResolver.query(uri, projection, selection, null, sortOrder);
    126                 c.close();
    127         });
    128     }
    129 
    130     public void testBadDelete() {
    131         assertThrows(IllegalArgumentException.class, () -> {
    132             mResolver.delete(Contacts.CONTENT_URI, ";delete from contacts;--", null);
    133         });
    134         if (ContactsDatabaseHelper.DISALLOW_SUB_QUERIES) {
    135             assertThrows(IllegalArgumentException.class, () -> {
    136                 mResolver.delete(Contacts.CONTENT_URI, "_id in data_usage_stat", null);
    137             });
    138         }
    139     }
    140 
    141     public void testBadUpdate() {
    142         assertThrows(IllegalArgumentException.class, () -> {
    143             mResolver.update(Data.CONTENT_URI, cv(), ";delete from contacts;--", null);
    144         });
    145         if (ContactsDatabaseHelper.DISALLOW_SUB_QUERIES) {
    146             assertThrows(IllegalArgumentException.class, () -> {
    147                 mResolver.update(Data.CONTENT_URI, cv(), "_id in data_usage_stat", null);
    148             });
    149             assertThrows(IllegalArgumentException.class, () -> {
    150                 mResolver.update(Data.CONTENT_URI, cv("_id/**/", 1), null, null);
    151             });
    152 
    153             mResolver.update(Data.CONTENT_URI, cv("[data1]", 1), null, null);
    154         }
    155     }
    156 
    157     public void testBadInsert() {
    158         if (ContactsDatabaseHelper.DISALLOW_SUB_QUERIES) {
    159             assertThrows(IllegalArgumentException.class, () -> {
    160                 mResolver.insert(Data.CONTENT_URI, cv("_id/**/", 1));
    161             });
    162         }
    163     }
    164 }
    165