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 com.android.commands.monkey;
18 
19 import android.Manifest;
20 import android.content.pm.ApplicationInfo;
21 import android.content.pm.IPackageManager;
22 import android.content.pm.PackageInfo;
23 import android.content.pm.PackageManager;
24 import android.content.pm.PermissionInfo;
25 import android.os.Build;
26 import android.os.RemoteException;
27 import android.os.ServiceManager;
28 import android.os.UserHandle;
29 import android.permission.IPermissionManager;
30 
31 import java.util.ArrayList;
32 import java.util.HashMap;
33 import java.util.List;
34 import java.util.Map;
35 import java.util.Random;
36 
37 /**
38  * Utility class that encapsulates runtime permission related methods for monkey
39  *
40  */
41 public class MonkeyPermissionUtil {
42 
43     private static final String PERMISSION_PREFIX = "android.permission.";
44     private static final String PERMISSION_GROUP_PREFIX = "android.permission-group.";
45 
46     // from com.android.packageinstaller.permission.utils
47     private static final String[] MODERN_PERMISSION_GROUPS = {
48             Manifest.permission_group.CALENDAR, Manifest.permission_group.CAMERA,
49             Manifest.permission_group.CONTACTS, Manifest.permission_group.LOCATION,
50             Manifest.permission_group.SENSORS, Manifest.permission_group.SMS,
51             Manifest.permission_group.PHONE, Manifest.permission_group.MICROPHONE,
52             Manifest.permission_group.STORAGE
53     };
54 
55     // from com.android.packageinstaller.permission.utils
isModernPermissionGroup(String name)56     private static boolean isModernPermissionGroup(String name) {
57         for (String modernGroup : MODERN_PERMISSION_GROUPS) {
58             if (modernGroup.equals(name)) {
59                 return true;
60             }
61         }
62         return false;
63     }
64 
65     /**
66      * actual list of packages to target, with invalid packages excluded, and may optionally include
67      * system packages
68      */
69     private List<String> mTargetedPackages;
70     /** if we should target system packages regardless if they are listed */
71     private boolean mTargetSystemPackages;
72     private IPackageManager mPm;
73     private final IPermissionManager mPermManager;
74 
75     /** keep track of runtime permissions requested for each package targeted */
76     private Map<String, List<PermissionInfo>> mPermissionMap;
77 
MonkeyPermissionUtil()78     public MonkeyPermissionUtil() {
79         mPm = IPackageManager.Stub.asInterface(ServiceManager.getService("package"));
80         mPermManager =
81                 IPermissionManager.Stub.asInterface(ServiceManager.getService("permissionmgr"));
82     }
83 
setTargetSystemPackages(boolean targetSystemPackages)84     public void setTargetSystemPackages(boolean targetSystemPackages) {
85         mTargetSystemPackages = targetSystemPackages;
86     }
87 
88     /**
89      * Decide if a package should be targeted by permission monkey
90      * @param info
91      * @return
92      */
shouldTargetPackage(PackageInfo info)93     private boolean shouldTargetPackage(PackageInfo info) {
94         // target if permitted by white listing / black listing rules
95         if (MonkeyUtils.getPackageFilter().checkEnteringPackage(info.packageName)) {
96             return true;
97         }
98         if (mTargetSystemPackages
99                 // not explicitly black listed
100                 && !MonkeyUtils.getPackageFilter().isPackageInvalid(info.packageName)
101                 // is a system app
102                 && (info.applicationInfo.flags & ApplicationInfo.FLAG_SYSTEM) != 0) {
103             return true;
104         }
105         return false;
106     }
107 
shouldTargetPermission(String pkg, PermissionInfo pi)108     private boolean shouldTargetPermission(String pkg, PermissionInfo pi) throws RemoteException {
109         int flags = mPermManager.getPermissionFlags(pkg, pi.name, UserHandle.myUserId());
110         int fixedPermFlags = PackageManager.FLAG_PERMISSION_SYSTEM_FIXED
111                 | PackageManager.FLAG_PERMISSION_POLICY_FIXED;
112         return pi.group != null && pi.protectionLevel == PermissionInfo.PROTECTION_DANGEROUS
113                 && ((flags & fixedPermFlags) == 0)
114                 && isModernPermissionGroup(pi.group);
115     }
116 
populatePermissionsMapping()117     public boolean populatePermissionsMapping() {
118         mPermissionMap = new HashMap<>();
119         try {
120             List<?> pkgInfos = mPm.getInstalledPackages(
121                     PackageManager.GET_PERMISSIONS, UserHandle.myUserId()).getList();
122             for (Object o : pkgInfos) {
123                 PackageInfo info = (PackageInfo)o;
124                 if (!shouldTargetPackage(info)) {
125                     continue;
126                 }
127                 List<PermissionInfo> permissions = new ArrayList<>();
128                 if (info.applicationInfo.targetSdkVersion <= Build.VERSION_CODES.LOLLIPOP_MR1) {
129                     // skip apps targetting lower API level
130                     continue;
131                 }
132                 if (info.requestedPermissions == null) {
133                     continue;
134                 }
135                 for (String perm : info.requestedPermissions) {
136                     PermissionInfo pi = mPermManager.getPermissionInfo(perm, "shell", 0);
137                     if (pi != null && shouldTargetPermission(info.packageName, pi)) {
138                         permissions.add(pi);
139                     }
140                 }
141                 if (!permissions.isEmpty()) {
142                     mPermissionMap.put(info.packageName, permissions);
143                 }
144             }
145         } catch (RemoteException re) {
146             Logger.err.println("** Failed talking with package manager!");
147             return false;
148         }
149         if (!mPermissionMap.isEmpty()) {
150             mTargetedPackages = new ArrayList<>(mPermissionMap.keySet());
151         }
152         return true;
153     }
154 
dump()155     public void dump() {
156         Logger.out.println("// Targeted packages and permissions:");
157         for (Map.Entry<String, List<PermissionInfo>> e : mPermissionMap.entrySet()) {
158             Logger.out.println(String.format("//  + Using %s", e.getKey()));
159             for (PermissionInfo pi : e.getValue()) {
160                 String name = pi.name;
161                 if (name != null) {
162                     if (name.startsWith(PERMISSION_PREFIX)) {
163                         name = name.substring(PERMISSION_PREFIX.length());
164                     }
165                 }
166                 String group = pi.group;
167                 if (group != null) {
168                     if (group.startsWith(PERMISSION_GROUP_PREFIX)) {
169                         group = group.substring(PERMISSION_GROUP_PREFIX.length());
170                     }
171                 }
172                 Logger.out.println(String.format("//    Permission: %s [%s]", name, group));
173             }
174         }
175     }
176 
generateRandomPermissionEvent(Random random)177     public MonkeyPermissionEvent generateRandomPermissionEvent(Random random) {
178         String pkg = mTargetedPackages.get(random.nextInt(mTargetedPackages.size()));
179         List<PermissionInfo> infos = mPermissionMap.get(pkg);
180         return new MonkeyPermissionEvent(pkg, infos.get(random.nextInt(infos.size())));
181     }
182 }
183