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 package com.android.compatibility.common.deviceinfo; 17 18 import android.Manifest; 19 import android.annotation.TargetApi; 20 import android.app.admin.DevicePolicyManager; 21 import android.app.role.RoleManager; 22 import android.content.ComponentName; 23 import android.content.Context; 24 import android.content.pm.ApplicationInfo; 25 import android.content.pm.PackageInfo; 26 import android.content.pm.PackageManager; 27 import android.content.pm.PermissionInfo; 28 import android.os.Build; 29 import android.os.Process; 30 31 import com.android.compatibility.common.util.DeviceInfoStore; 32 import com.android.compatibility.common.util.PackageUtil; 33 34 import static com.android.compatibility.common.util.SystemUtil.callWithShellPermissionIdentity; 35 36 import java.io.IOException; 37 import java.util.ArrayList; 38 import java.util.Arrays; 39 import java.util.HashMap; 40 import java.util.HashSet; 41 import java.util.List; 42 import java.util.Set; 43 44 /** 45 * PackageDeviceInfo collector. 46 */ 47 @TargetApi(Build.VERSION_CODES.N) 48 public class PackageDeviceInfo extends DeviceInfo { 49 50 private static final String PLATFORM = "android"; 51 private static final String PLATFORM_ANDROID_PERMISSION_PREFIX = "android.permission."; 52 private static final String PLATFORM_MANIFEST_PERMISSION_PREFIX = "android.Manifest.permission."; 53 54 private static final String PACKAGE = "package"; 55 private static final String NAME = "name"; 56 private static final String VERSION_NAME = "version_name"; 57 private static final String DIR = "dir"; 58 private static final String SYSTEM_PRIV = "system_priv"; 59 private static final String MIN_SDK = "min_sdk"; 60 private static final String TARGET_SDK = "target_sdk"; 61 62 private static final String REQUESTED_PERMISSIONS = "requested_permissions"; 63 private static final String DEFINED_PERMISSIONS = "defined_permissions"; 64 private static final String PERMISSION_NAME = "name"; 65 private static final String PERMISSION_FLAGS = "flags"; 66 private static final String PERMISSION_GROUP = "permission_group"; 67 private static final String PERMISSION_PROTECTION = "protection_level"; 68 private static final String PERMISSION_PROTECTION_FLAGS = "protection_level_flags"; 69 private static final String PERMISSION_IS_GRANTED = "is_granted"; 70 71 72 private static final String PERMISSION_TYPE = "type"; 73 private static final int PERMISSION_TYPE_SYSTEM = 1; 74 private static final int PERMISSION_TYPE_OEM = 2; 75 private static final int PERMISSION_TYPE_CUSTOM = 3; 76 77 private static final String REQUESTED_ROLES = "requested_roles"; 78 private static final String ROLE_NAME = "name"; 79 80 private static final String HAS_SYSTEM_UID = "has_system_uid"; 81 82 private static final String SHARES_INSTALL_PERMISSION = "shares_install_packages_permission"; 83 private static final String INSTALL_PACKAGES_PERMISSION = "android.permission.INSTALL_PACKAGES"; 84 85 private static final String SHA256_CERT = "sha256_cert"; 86 87 private static final String SHA256_FILE = "sha256_file"; 88 89 private static final String CONFIG_NOTIFICATION_ACCESS = "config_defaultListenerAccessPackages"; 90 private static final String HAS_DEFAULT_NOTIFICATION_ACCESS = "has_default_notification_access"; 91 92 private static final String UID = "uid"; 93 private static final String IS_ACTIVE_ADMIN = "is_active_admin"; 94 95 private static final String CONFIG_ACCESSIBILITY_SERVICE = "config_defaultAccessibilityService"; 96 private static final String DEFAULT_ACCESSIBILITY_SERVICE = "is_default_accessibility_service"; 97 98 private static final HashSet<String> ADDITIONAL_ANDROID_PERMISSIONS = new HashSet<>(Arrays.asList(new String[] { 99 "com.android.voicemail.permission.ADD_VOICEMAIL", 100 "com.android.voicemail.permission.WRITE_VOICEMAIL", 101 "com.android.voicemail.permission.READ_VOICEMAIL", 102 "com.android.browser.permission.READ_HISTORY_BOOKMARKS", 103 "com.android.browser.permission.WRITE_HISTORY_BOOKMARKS", 104 "com.android.alarm.permission.SET_ALARM", 105 "com.android.launcher.permission.INSTALL_SHORTCUT", 106 "com.android.launcher.permission.UNINSTALL_SHORTCUT", 107 "com.android.permission.INSTALL_EXISTING_PACKAGES", 108 "com.android.permission.USE_INSTALLER_V2", 109 "com.android.permission.USE_SYSTEM_DATA_LOADERS", 110 "android.intent.category.MASTER_CLEAR.permission.C2D_MESSAGE" 111 })); 112 113 114 @Override collectDeviceInfo(DeviceInfoStore store)115 protected void collectDeviceInfo(DeviceInfoStore store) throws Exception { 116 final PackageManager pm = getContext().getPackageManager(); 117 118 final List<PackageInfo> allPackages = 119 pm.getInstalledPackages(PackageManager.GET_PERMISSIONS); 120 final Set<String> defaultNotificationListeners = 121 getColonSeparatedPackageList(CONFIG_NOTIFICATION_ACCESS); 122 123 final Set<String> deviceAdminPackages = getActiveDeviceAdminPackages(); 124 125 final ComponentName defaultAccessibilityComponent = getDefaultAccessibilityComponent(); 126 127 final HashMap<String, List<String>> packageRolesData = getPackageRolesData(); 128 129 // Platform permission data used to tag permissions information with sourcing information 130 final PackageInfo platformInfo = pm.getPackageInfo(PLATFORM , PackageManager.GET_PERMISSIONS); 131 final Set<String> platformPermissions = new HashSet<String>(); 132 for (PermissionInfo permission : platformInfo.permissions) { 133 platformPermissions.add(permission.name); 134 } 135 136 store.startArray(PACKAGE); 137 for (PackageInfo pkg : allPackages) { 138 store.startGroup(); 139 store.addResult(NAME, pkg.packageName); 140 store.addResult(VERSION_NAME, pkg.versionName); 141 142 collectRequestedPermissions(store, pm, platformPermissions, pkg); 143 collectDefinedPermissions(store, platformPermissions, pkg); 144 145 collectionApplicationInfo(store, pm, pkg); 146 147 store.addResult(HAS_DEFAULT_NOTIFICATION_ACCESS, 148 defaultNotificationListeners.contains(pkg.packageName)); 149 150 store.addResult(IS_ACTIVE_ADMIN, deviceAdminPackages.contains(pkg.packageName)); 151 152 boolean isDefaultAccessibilityComponent = false; 153 if (defaultAccessibilityComponent != null) { 154 isDefaultAccessibilityComponent = pkg.packageName.equals( 155 defaultAccessibilityComponent.getPackageName() 156 ); 157 } 158 store.addResult(DEFAULT_ACCESSIBILITY_SERVICE, isDefaultAccessibilityComponent); 159 160 String sha256_cert = PackageUtil.computePackageSignatureDigest(pkg.packageName); 161 store.addResult(SHA256_CERT, sha256_cert); 162 163 String sha256_file = PackageUtil.computePackageFileDigest(pkg); 164 store.addResult(SHA256_FILE, sha256_file); 165 166 collectRoles(store, packageRolesData, pkg); 167 168 store.endGroup(); 169 } 170 store.endArray(); // "package" 171 } 172 collectRequestedPermissions(DeviceInfoStore store, PackageManager pm, Set<String> systemPermissions, PackageInfo pkg)173 private static void collectRequestedPermissions(DeviceInfoStore store, 174 PackageManager pm, 175 Set<String> systemPermissions, 176 PackageInfo pkg) throws IOException 177 { 178 store.startArray(REQUESTED_PERMISSIONS); 179 if (pkg.requestedPermissions != null && pkg.requestedPermissions.length > 0) { 180 for (String permission : pkg.requestedPermissions) { 181 if (permission == null) continue; 182 183 try { 184 final PermissionInfo pi = pm.getPermissionInfo(permission, 0); 185 186 store.startGroup(); 187 writePermissionsDetails(pi, store, systemPermissions); 188 189 boolean isGranted = pm.checkPermission( 190 permission, pkg.packageName) == pm.PERMISSION_GRANTED; 191 store.addResult(PERMISSION_IS_GRANTED, isGranted); 192 193 store.endGroup(); 194 } catch (PackageManager.NameNotFoundException e) { 195 // ignore unrecognized permission and continue 196 } 197 } 198 } 199 store.endArray(); 200 } 201 collectDefinedPermissions(DeviceInfoStore store, Set<String> systemPermissions, PackageInfo pkg)202 private static void collectDefinedPermissions(DeviceInfoStore store, 203 Set<String> systemPermissions, 204 PackageInfo pkg) throws IOException { 205 if (pkg.permissions != null && pkg.permissions.length > 0) { 206 store.startArray(DEFINED_PERMISSIONS); 207 for (PermissionInfo permission : pkg.permissions) { 208 if (permission == null) continue; 209 // Ignore "android" package defined AOSP permissions. 210 if (pkg.packageName.equals(PLATFORM) 211 && isAndroidPermission(permission.name)) 212 continue; 213 214 store.startGroup(); 215 writePermissionsDetails(permission, store, systemPermissions); 216 store.endGroup(); 217 218 } 219 store.endArray(); 220 } 221 } 222 collectionApplicationInfo(DeviceInfoStore store, PackageManager pm, PackageInfo pkg)223 private static void collectionApplicationInfo(DeviceInfoStore store, 224 PackageManager pm, 225 PackageInfo pkg) throws IOException { 226 final ApplicationInfo appInfo = pkg.applicationInfo; 227 if (appInfo != null) { 228 store.addResult(DIR, appInfo.sourceDir); 229 store.addResult(SYSTEM_PRIV, appInfo.isPrivilegedApp()); 230 231 store.addResult(MIN_SDK, appInfo.minSdkVersion); 232 store.addResult(TARGET_SDK, appInfo.targetSdkVersion); 233 234 store.addResult(HAS_SYSTEM_UID, appInfo.uid < Process.FIRST_APPLICATION_UID); 235 236 final boolean canInstall = sharesUidWithInstallerPackage(pm, appInfo.uid); 237 store.addResult(SHARES_INSTALL_PERMISSION, canInstall); 238 239 store.addResult(UID, appInfo.uid); 240 } 241 } 242 sharesUidWithInstallerPackage(PackageManager pm, int uid)243 private static boolean sharesUidWithInstallerPackage(PackageManager pm, int uid) { 244 final String[] sharesUidWith = pm.getPackagesForUid(uid); 245 246 if (sharesUidWith == null) { 247 return false; 248 } 249 250 // Approx 20 permissions per package for rough estimate of sizing 251 final int capacity = sharesUidWith.length * 20; 252 final List<String> sharedPermissions = new ArrayList<>(capacity); 253 for (String pkg :sharesUidWith){ 254 try { 255 final PackageInfo info = pm.getPackageInfo(pkg, PackageManager.GET_PERMISSIONS); 256 257 if (info.requestedPermissions == null) { 258 continue; 259 } 260 261 for (String p : info.requestedPermissions) { 262 if (p != null) { 263 sharedPermissions.add(p); 264 } 265 } 266 } catch (PackageManager.NameNotFoundException e) { 267 // ignore, continue 268 } 269 } 270 271 return sharedPermissions.contains(PackageDeviceInfo.INSTALL_PACKAGES_PERMISSION); 272 } 273 writePermissionsDetails(PermissionInfo pi, DeviceInfoStore store, Set<String> systemPermissions)274 private static void writePermissionsDetails(PermissionInfo pi, 275 DeviceInfoStore store, 276 Set<String> systemPermissions) throws IOException { 277 final String permissionName = pi.name; 278 store.addResult(PERMISSION_NAME, permissionName); 279 280 if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.JELLY_BEAN_MR1) { 281 store.addResult(PERMISSION_FLAGS, pi.flags); 282 } else { 283 store.addResult(PERMISSION_FLAGS, 0); 284 } 285 286 store.addResult(PERMISSION_GROUP, pi.group); 287 288 if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.P) { 289 store.addResult(PERMISSION_PROTECTION, pi.getProtection()); 290 store.addResult(PERMISSION_PROTECTION_FLAGS, pi.getProtectionFlags()); 291 } else { 292 store.addResult(PERMISSION_PROTECTION, 293 pi.protectionLevel & PermissionInfo.PROTECTION_MASK_BASE); 294 store.addResult(PERMISSION_PROTECTION_FLAGS, 295 pi.protectionLevel & ~PermissionInfo.PROTECTION_MASK_BASE); 296 } 297 298 final boolean isPlatformPermission = systemPermissions.contains(permissionName); 299 if (isPlatformPermission) { 300 final boolean isAndroidPermission = isAndroidPermission(permissionName); 301 if (isAndroidPermission) { 302 store.addResult(PERMISSION_TYPE, PERMISSION_TYPE_SYSTEM); 303 } else { 304 store.addResult(PERMISSION_TYPE, PERMISSION_TYPE_OEM); 305 } 306 } else { 307 store.addResult(PERMISSION_TYPE, PERMISSION_TYPE_CUSTOM); 308 } 309 } 310 getActiveDeviceAdminPackages()311 private Set<String> getActiveDeviceAdminPackages() { 312 final DevicePolicyManager dpm = (DevicePolicyManager) 313 getContext().getSystemService(Context.DEVICE_POLICY_SERVICE); 314 315 final List<ComponentName> components = dpm.getActiveAdmins(); 316 if (components == null) { 317 return new HashSet<>(0); 318 } 319 320 final HashSet<String> packages = new HashSet<>(components.size()); 321 for (ComponentName component : components) { 322 packages.add(component.getPackageName()); 323 } 324 325 return packages; 326 } 327 getDefaultAccessibilityComponent()328 private ComponentName getDefaultAccessibilityComponent() { 329 final String defaultAccessibilityServiceComponent = 330 getRawDeviceConfig(CONFIG_ACCESSIBILITY_SERVICE); 331 return ComponentName.unflattenFromString(defaultAccessibilityServiceComponent); 332 } 333 334 /** 335 * Parses and returns a set of package ids from a configuration value 336 * e.g config_defaultListenerAccessPackages 337 **/ getColonSeparatedPackageList(String name)338 private Set<String> getColonSeparatedPackageList(String name) { 339 String raw = getRawDeviceConfig(name); 340 String[] packages = raw.split(":"); 341 return new HashSet<>(Arrays.asList(packages)); 342 } 343 344 /** Returns the value of a device configuration setting available in android.internal.R.* **/ getRawDeviceConfig(String name)345 private String getRawDeviceConfig(String name) { 346 return getContext() 347 .getResources() 348 .getString(getDeviceResourcesIdentifier(name, "string")); 349 } 350 getDeviceResourcesIdentifier(String name, String type)351 private int getDeviceResourcesIdentifier(String name, String type) { 352 return getContext() 353 .getResources() 354 .getIdentifier(name, type, "android"); 355 } 356 357 /** Return a boolean value to whether the permission is an android permission defined by android package */ isAndroidPermission(String permissionName)358 private static boolean isAndroidPermission(String permissionName) { 359 if(permissionName.startsWith(PLATFORM_ANDROID_PERMISSION_PREFIX) 360 || permissionName.startsWith(PLATFORM_MANIFEST_PERMISSION_PREFIX) 361 || ADDITIONAL_ANDROID_PERMISSIONS.contains(permissionName)) 362 return true; 363 return false; 364 } 365 collectRoles(DeviceInfoStore store, HashMap<String, List<String>> packageRolesData, PackageInfo pkg)366 private static void collectRoles(DeviceInfoStore store, 367 HashMap<String, List<String>> packageRolesData, 368 PackageInfo pkg) throws IOException { 369 String packageName = pkg.packageName; 370 if(packageRolesData.containsKey(packageName)) { 371 List<String> roleNames = packageRolesData.get(packageName); 372 373 store.startArray(REQUESTED_ROLES); 374 for(String roleName: roleNames) { 375 store.startGroup(); 376 store.addResult(ROLE_NAME, roleName); 377 store.endGroup(); 378 } 379 store.endArray(); 380 } 381 } 382 383 /* 384 Return a map of PackageName -> List of RoleNames held by that package 385 */ getPackageRolesData()386 private HashMap<String, List<String>> getPackageRolesData() throws Exception { 387 final RoleManager roleManager = getContext().getSystemService(RoleManager.class); 388 HashMap<String, List<String>> packageRolesData = new HashMap<>(); 389 390 for(String roleName: RolesUtil.ROLE_NAMES) { 391 List<String> packageNames = getRoleHolders(roleName, roleManager); 392 393 for(String packageName: packageNames) { 394 packageRolesData.putIfAbsent(packageName, new ArrayList<>()); 395 packageRolesData.get(packageName).add(roleName); 396 } 397 } 398 return packageRolesData; 399 } 400 getRoleHolders(String roleName, RoleManager roleManager)401 public static List<String> getRoleHolders(String roleName, RoleManager roleManager) throws Exception { 402 return callWithShellPermissionIdentity( 403 () -> roleManager.getRoleHolders(roleName), 404 Manifest.permission.MANAGE_ROLE_HOLDERS); 405 } 406 } 407 408