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