Home | History | Annotate | Download | only in cts
      1 /*
      2 * Copyright (C) 2015 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 android.permission2.cts;
     18 
     19 import static android.content.pm.PermissionInfo.FLAG_INSTALLED;
     20 import static android.content.pm.PermissionInfo.PROTECTION_MASK_BASE;
     21 import static android.os.Build.VERSION.SECURITY_PATCH;
     22 
     23 import static com.google.common.truth.Truth.assertThat;
     24 
     25 import android.content.Context;
     26 import android.content.pm.PackageInfo;
     27 import android.content.pm.PackageManager;
     28 import android.content.pm.PackageManager.NameNotFoundException;
     29 import android.content.pm.PermissionGroupInfo;
     30 import android.content.pm.PermissionInfo;
     31 import android.os.storage.StorageManager;
     32 import android.platform.test.annotations.AppModeFull;
     33 import android.util.ArrayMap;
     34 import android.util.ArraySet;
     35 import android.util.Log;
     36 import android.util.Xml;
     37 
     38 import androidx.annotation.NonNull;
     39 import androidx.annotation.Nullable;
     40 import androidx.test.ext.junit.runners.AndroidJUnit4;
     41 import androidx.test.platform.app.InstrumentationRegistry;
     42 
     43 import org.junit.Test;
     44 import org.junit.runner.RunWith;
     45 import org.xmlpull.v1.XmlPullParser;
     46 
     47 import java.io.InputStream;
     48 import java.text.ParseException;
     49 import java.text.SimpleDateFormat;
     50 import java.util.ArrayList;
     51 import java.util.Date;
     52 import java.util.Iterator;
     53 import java.util.List;
     54 import java.util.Map;
     55 import java.util.Objects;
     56 import java.util.Set;
     57 
     58 /**
     59  * Tests for permission policy on the platform.
     60  */
     61 @AppModeFull(reason = "Instant apps cannot read the system servers permission")
     62 @RunWith(AndroidJUnit4.class)
     63 public class PermissionPolicyTest {
     64     private static final Date HIDE_NON_SYSTEM_OVERLAY_WINDOWS_PATCH_DATE = parseDate("2017-11-01");
     65     private static final String HIDE_NON_SYSTEM_OVERLAY_WINDOWS_PERMISSION
     66             = "android.permission.HIDE_NON_SYSTEM_OVERLAY_WINDOWS";
     67 
     68     private static final String LOG_TAG = "PermissionProtectionTest";
     69 
     70     private static final String PLATFORM_PACKAGE_NAME = "android";
     71 
     72     private static final String PLATFORM_ROOT_NAMESPACE = "android.";
     73 
     74     private static final String AUTOMOTIVE_SERVICE_PACKAGE_NAME = "com.android.car";
     75 
     76     private static final String TAG_PERMISSION = "permission";
     77     private static final String TAG_PERMISSION_GROUP = "permission-group";
     78 
     79     private static final String ATTR_NAME = "name";
     80     private static final String ATTR_PERMISSION_GROUP = "permissionGroup";
     81     private static final String ATTR_PERMISSION_FLAGS = "permissionFlags";
     82     private static final String ATTR_PROTECTION_LEVEL = "protectionLevel";
     83     private static final String ATTR_BACKGROUND_PERMISSION = "backgroundPermission";
     84 
     85     private static final Context sContext =
     86             InstrumentationRegistry.getInstrumentation().getTargetContext();
     87 
     88     @Test
     89     public void platformPermissionPolicyIsUnaltered() throws Exception {
     90         Map<String, PermissionInfo> declaredPermissionsMap =
     91                 getPermissionsForPackage(sContext, PLATFORM_PACKAGE_NAME);
     92 
     93         List<String> offendingList = new ArrayList<>();
     94 
     95         List<PermissionGroupInfo> declaredGroups = sContext.getPackageManager()
     96                 .getAllPermissionGroups(0);
     97         Set<String> declaredGroupsSet = new ArraySet<>();
     98         for (PermissionGroupInfo declaredGroup : declaredGroups) {
     99             declaredGroupsSet.add(declaredGroup.name);
    100         }
    101 
    102         Set<String> expectedPermissionGroups = loadExpectedPermissionGroupNames(
    103                 R.raw.android_manifest);
    104         List<ExpectedPermissionInfo> expectedPermissions = loadExpectedPermissions(
    105                 R.raw.android_manifest);
    106 
    107         if (sContext.getPackageManager().hasSystemFeature(PackageManager.FEATURE_AUTOMOTIVE)) {
    108             expectedPermissions.addAll(loadExpectedPermissions(R.raw.automotive_android_manifest));
    109             declaredPermissionsMap.putAll(
    110                     getPermissionsForPackage(sContext, AUTOMOTIVE_SERVICE_PACKAGE_NAME));
    111         }
    112 
    113         for (ExpectedPermissionInfo expectedPermission : expectedPermissions) {
    114             String expectedPermissionName = expectedPermission.name;
    115             if (shouldSkipPermission(expectedPermissionName)) {
    116                 continue;
    117             }
    118 
    119             // OEMs cannot remove permissions
    120             PermissionInfo declaredPermission = declaredPermissionsMap.get(expectedPermissionName);
    121             if (declaredPermission == null) {
    122                 offendingList.add("Permission " + expectedPermissionName + " must be declared");
    123                 continue;
    124             }
    125 
    126             // We want to end up with OEM defined permissions and groups to check their namespace
    127             declaredPermissionsMap.remove(expectedPermissionName);
    128 
    129             // OEMs cannot change permission protection
    130             final int expectedProtection = expectedPermission.protectionLevel
    131                     & PROTECTION_MASK_BASE;
    132             final int declaredProtection = declaredPermission.protectionLevel
    133                     & PROTECTION_MASK_BASE;
    134             if (expectedProtection != declaredProtection) {
    135                 offendingList.add(
    136                         String.format(
    137                                 "Permission %s invalid protection level %x, expected %x",
    138                                 expectedPermissionName, declaredProtection, expectedProtection));
    139             }
    140 
    141             // OEMs cannot change permission flags
    142             final int expectedFlags = expectedPermission.flags;
    143             final int declaredFlags = (declaredPermission.flags & ~FLAG_INSTALLED);
    144             if (expectedFlags != declaredFlags) {
    145                 offendingList.add(
    146                         String.format(
    147                                 "Permission %s invalid flags %x, expected %x",
    148                                 expectedPermissionName,
    149                                 declaredFlags,
    150                                 expectedFlags));
    151             }
    152 
    153             // OEMs cannot change permission protection flags
    154             final int expectedProtectionFlags =
    155                     expectedPermission.protectionLevel & ~PROTECTION_MASK_BASE;
    156             final int declaredProtectionFlags = declaredPermission.getProtectionFlags();
    157             if (expectedProtectionFlags != declaredProtectionFlags) {
    158                 offendingList.add(
    159                         String.format(
    160                                 "Permission %s invalid enforced protection %x, expected %x",
    161                                 expectedPermissionName,
    162                                 declaredProtectionFlags,
    163                                 expectedProtectionFlags));
    164             }
    165 
    166             // OEMs cannot change permission grouping
    167             if ((declaredPermission.protectionLevel & PermissionInfo.PROTECTION_DANGEROUS) != 0) {
    168                 if (!Objects.equals(expectedPermission.group, declaredPermission.group)) {
    169                     offendingList.add(
    170                             "Permission " + expectedPermissionName + " not in correct group "
    171                             + "(expected=" + expectedPermission.group + " actual="
    172                                     + declaredPermission.group);
    173                 }
    174 
    175                 if (declaredPermission.group != null
    176                         && !declaredGroupsSet.contains(declaredPermission.group)) {
    177                     offendingList.add(
    178                             "Permission group " + expectedPermission.group + " must be defined");
    179                 }
    180             }
    181 
    182             // OEMs cannot change background permission mapping
    183             if (!Objects.equals(expectedPermission.backgroundPermission,
    184                     declaredPermission.backgroundPermission)) {
    185                 offendingList.add(
    186                         String.format(
    187                                 "Permission %s invalid background permission %s, expected %s",
    188                                 expectedPermissionName,
    189                                 declaredPermission.backgroundPermission,
    190                                 expectedPermission.backgroundPermission));
    191             }
    192         }
    193 
    194         // OEMs cannot define permissions in the platform namespace
    195         for (String permission : declaredPermissionsMap.keySet()) {
    196             if (permission.startsWith(PLATFORM_ROOT_NAMESPACE)) {
    197                 final PermissionInfo permInfo = declaredPermissionsMap.get(permission);
    198                 offendingList.add(
    199                         "Cannot define permission " + permission
    200                         + ", package " + permInfo.packageName
    201                         + " in android namespace");
    202             }
    203         }
    204 
    205         // OEMs cannot define groups in the platform namespace
    206         for (PermissionGroupInfo declaredGroup : declaredGroups) {
    207             if (!expectedPermissionGroups.contains(declaredGroup.name)) {
    208                 if (declaredGroup.name != null) {
    209                     if (declaredGroup.packageName.equals(PLATFORM_PACKAGE_NAME)
    210                             && declaredGroup.name.startsWith(PLATFORM_ROOT_NAMESPACE)) {
    211                         offendingList.add(
    212                                 "Cannot define group " + declaredGroup.name
    213                                 + ", package " + declaredGroup.packageName
    214                                 + " in android namespace");
    215                     }
    216                 }
    217             }
    218         }
    219 
    220         // OEMs cannot define new ephemeral permissions
    221         for (String permission : declaredPermissionsMap.keySet()) {
    222             PermissionInfo info = declaredPermissionsMap.get(permission);
    223             if ((info.protectionLevel & PermissionInfo.PROTECTION_FLAG_INSTANT) != 0) {
    224                 offendingList.add("Cannot define new instant permission " + permission);
    225             }
    226         }
    227 
    228         // Fail on any offending item
    229         assertThat(offendingList).named("list of offending permissions").isEmpty();
    230     }
    231 
    232     private List<ExpectedPermissionInfo> loadExpectedPermissions(int resourceId) throws Exception {
    233         List<ExpectedPermissionInfo> permissions = new ArrayList<>();
    234         try (InputStream in = sContext.getResources().openRawResource(resourceId)) {
    235             XmlPullParser parser = Xml.newPullParser();
    236             parser.setInput(in, null);
    237 
    238             final int outerDepth = parser.getDepth();
    239             int type;
    240             while ((type = parser.next()) != XmlPullParser.END_DOCUMENT
    241                     && (type != XmlPullParser.END_TAG || parser.getDepth() > outerDepth)) {
    242                 if (type == XmlPullParser.END_TAG || type == XmlPullParser.TEXT) {
    243                     continue;
    244                 }
    245                 if (TAG_PERMISSION.equals(parser.getName())) {
    246                     ExpectedPermissionInfo permissionInfo = new ExpectedPermissionInfo(
    247                             parser.getAttributeValue(null, ATTR_NAME),
    248                             parser.getAttributeValue(null, ATTR_PERMISSION_GROUP),
    249                             parser.getAttributeValue(null, ATTR_BACKGROUND_PERMISSION),
    250                             parsePermissionFlags(
    251                                     parser.getAttributeValue(null, ATTR_PERMISSION_FLAGS)),
    252                             parseProtectionLevel(
    253                                     parser.getAttributeValue(null, ATTR_PROTECTION_LEVEL)));
    254                     permissions.add(permissionInfo);
    255                 } else {
    256                     Log.e(LOG_TAG, "Unknown tag " + parser.getName());
    257                 }
    258             }
    259         }
    260 
    261         // STOPSHIP: remove this once isolated storage is always enabled
    262         if (!StorageManager.hasIsolatedStorage()) {
    263             Iterator<ExpectedPermissionInfo> it = permissions.iterator();
    264             while (it.hasNext()) {
    265                 final ExpectedPermissionInfo pi = it.next();
    266                 switch (pi.name) {
    267                     case android.Manifest.permission.ACCESS_MEDIA_LOCATION:
    268                     case android.Manifest.permission.WRITE_OBB:
    269                         it.remove();
    270                         break;
    271                 }
    272             }
    273         }
    274 
    275         return permissions;
    276     }
    277 
    278     private Set<String> loadExpectedPermissionGroupNames(int resourceId) throws Exception {
    279         ArraySet<String> permissionGroups = new ArraySet<>();
    280         try (InputStream in = sContext.getResources().openRawResource(resourceId)) {
    281             XmlPullParser parser = Xml.newPullParser();
    282             parser.setInput(in, null);
    283 
    284             final int outerDepth = parser.getDepth();
    285             int type;
    286             while ((type = parser.next()) != XmlPullParser.END_DOCUMENT
    287                     && (type != XmlPullParser.END_TAG || parser.getDepth() > outerDepth)) {
    288                 if (type == XmlPullParser.END_TAG || type == XmlPullParser.TEXT) {
    289                     continue;
    290                 }
    291                 if (TAG_PERMISSION_GROUP.equals(parser.getName())) {
    292                     permissionGroups.add(parser.getAttributeValue(null, ATTR_NAME));
    293                 } else {
    294                     Log.e(LOG_TAG, "Unknown tag " + parser.getName());
    295                 }
    296             }
    297         }
    298         return permissionGroups;
    299     }
    300 
    301     private static int parsePermissionFlags(@Nullable String permissionFlagsString) {
    302         if (permissionFlagsString == null) {
    303             return 0;
    304         }
    305 
    306         int protectionFlags = 0;
    307         String[] fragments = permissionFlagsString.split("\\|");
    308         for (String fragment : fragments) {
    309             switch (fragment.trim()) {
    310                 case "removed": {
    311                     protectionFlags |= PermissionInfo.FLAG_REMOVED;
    312                 } break;
    313                 case "costsMoney": {
    314                     protectionFlags |= PermissionInfo.FLAG_COSTS_MONEY;
    315                 } break;
    316                 case "hardRestricted": {
    317                     protectionFlags |= PermissionInfo.FLAG_HARD_RESTRICTED;
    318                 } break;
    319                 case "immutablyRestricted": {
    320                     protectionFlags |= PermissionInfo.FLAG_IMMUTABLY_RESTRICTED;
    321                 } break;
    322                 case "softRestricted": {
    323                     protectionFlags |= PermissionInfo.FLAG_SOFT_RESTRICTED;
    324                 } break;
    325             }
    326         }
    327         return protectionFlags;
    328     }
    329 
    330     private static int parseProtectionLevel(String protectionLevelString) {
    331         int protectionLevel = 0;
    332         String[] fragments = protectionLevelString.split("\\|");
    333         for (String fragment : fragments) {
    334             switch (fragment.trim()) {
    335                 case "normal": {
    336                     protectionLevel |= PermissionInfo.PROTECTION_NORMAL;
    337                 } break;
    338                 case "dangerous": {
    339                     protectionLevel |= PermissionInfo.PROTECTION_DANGEROUS;
    340                 } break;
    341                 case "signature": {
    342                     protectionLevel |= PermissionInfo.PROTECTION_SIGNATURE;
    343                 } break;
    344                 case "signatureOrSystem": {
    345                     protectionLevel |= PermissionInfo.PROTECTION_SIGNATURE;
    346                     protectionLevel |= PermissionInfo.PROTECTION_FLAG_SYSTEM;
    347                 } break;
    348                 case "system": {
    349                     protectionLevel |= PermissionInfo.PROTECTION_FLAG_SYSTEM;
    350                 } break;
    351                 case "installer": {
    352                     protectionLevel |= PermissionInfo.PROTECTION_FLAG_INSTALLER;
    353                 } break;
    354                 case "verifier": {
    355                     protectionLevel |= PermissionInfo.PROTECTION_FLAG_VERIFIER;
    356                 } break;
    357                 case "preinstalled": {
    358                     protectionLevel |= PermissionInfo.PROTECTION_FLAG_PREINSTALLED;
    359                 } break;
    360                 case "pre23": {
    361                     protectionLevel |= PermissionInfo.PROTECTION_FLAG_PRE23;
    362                 } break;
    363                 case "appop": {
    364                     protectionLevel |= PermissionInfo.PROTECTION_FLAG_APPOP;
    365                 } break;
    366                 case "development": {
    367                     protectionLevel |= PermissionInfo.PROTECTION_FLAG_DEVELOPMENT;
    368                 } break;
    369                 case "privileged": {
    370                     protectionLevel |= PermissionInfo.PROTECTION_FLAG_PRIVILEGED;
    371                 } break;
    372                 case "oem": {
    373                     protectionLevel |= PermissionInfo.PROTECTION_FLAG_OEM;
    374                 } break;
    375                 case "vendorPrivileged": {
    376                     protectionLevel |= PermissionInfo.PROTECTION_FLAG_VENDOR_PRIVILEGED;
    377                 } break;
    378                 case "setup": {
    379                     protectionLevel |= PermissionInfo.PROTECTION_FLAG_SETUP;
    380                 } break;
    381                 case "textClassifier": {
    382                     protectionLevel |= PermissionInfo.PROTECTION_FLAG_SYSTEM_TEXT_CLASSIFIER;
    383                 } break;
    384                 case "wellbeing": {
    385                     protectionLevel |= PermissionInfo.PROTECTION_FLAG_WELLBEING;
    386                 } break;
    387                 case "configurator": {
    388                     protectionLevel |= PermissionInfo.PROTECTION_FLAG_CONFIGURATOR;
    389                 } break;
    390                 case "incidentReportApprover": {
    391                     protectionLevel |= PermissionInfo.PROTECTION_FLAG_INCIDENT_REPORT_APPROVER;
    392                 } break;
    393                 case "documenter": {
    394                     protectionLevel |= PermissionInfo.PROTECTION_FLAG_DOCUMENTER;
    395                 } break;
    396                 case "appPredictor": {
    397                     protectionLevel |= PermissionInfo.PROTECTION_FLAG_APP_PREDICTOR;
    398                 } break;
    399                 case "instant": {
    400                     protectionLevel |= PermissionInfo.PROTECTION_FLAG_INSTANT;
    401                 } break;
    402                 case "runtime": {
    403                     protectionLevel |= PermissionInfo.PROTECTION_FLAG_RUNTIME_ONLY;
    404                 } break;
    405             }
    406         }
    407         return protectionLevel;
    408     }
    409 
    410     private static Map<String, PermissionInfo> getPermissionsForPackage(Context context, String pkg)
    411             throws NameNotFoundException {
    412         PackageInfo packageInfo = context.getPackageManager()
    413                 .getPackageInfo(pkg, PackageManager.GET_PERMISSIONS);
    414         Map<String, PermissionInfo> declaredPermissionsMap = new ArrayMap<>();
    415 
    416         for (PermissionInfo declaredPermission : packageInfo.permissions) {
    417             declaredPermissionsMap.put(declaredPermission.name, declaredPermission);
    418         }
    419         return declaredPermissionsMap;
    420     }
    421 
    422     private static Date parseDate(String date) {
    423         Date patchDate = new Date();
    424         try {
    425             SimpleDateFormat template = new SimpleDateFormat("yyyy-MM-dd");
    426             patchDate = template.parse(date);
    427         } catch (ParseException e) {
    428         }
    429 
    430         return patchDate;
    431     }
    432 
    433     private boolean shouldSkipPermission(String permissionName) {
    434         return parseDate(SECURITY_PATCH).before(HIDE_NON_SYSTEM_OVERLAY_WINDOWS_PATCH_DATE) &&
    435                 HIDE_NON_SYSTEM_OVERLAY_WINDOWS_PERMISSION.equals(permissionName);
    436 
    437     }
    438 
    439     private class ExpectedPermissionInfo {
    440         final @NonNull String name;
    441         final @Nullable String group;
    442         final @Nullable String backgroundPermission;
    443         final int flags;
    444         final int protectionLevel;
    445 
    446         private ExpectedPermissionInfo(@NonNull String name, @Nullable String group,
    447                 @Nullable String backgroundPermission, int flags, int protectionLevel) {
    448             this.name = name;
    449             this.group = group;
    450             this.backgroundPermission = backgroundPermission;
    451             this.flags = flags;
    452             this.protectionLevel = protectionLevel;
    453         }
    454     }
    455 }
    456