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