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