teach monkey to flip permissions on apps

1. added a new MonkeyEvent type for permission events
2. added monkey permission utility for randomized permission
   events generation against targeted packages
3. refactored package whitelist/blacklist into MonkeyUtils class

Change-Id: I8f7998d74c3e28d02f5bcd47a0f9cc6167b93c93
This commit is contained in:
Guang Zhu
2015-06-16 21:14:08 -07:00
parent 5050956d6f
commit 403be1157f
6 changed files with 363 additions and 59 deletions

View File

@@ -31,15 +31,12 @@ import android.os.RemoteException;
import android.os.ServiceManager;
import android.os.StrictMode;
import android.os.SystemClock;
import android.os.SystemProperties;
import android.os.UserHandle;
import android.view.IWindowManager;
import android.view.Surface;
import java.io.BufferedReader;
import java.io.BufferedWriter;
import java.io.ByteArrayInputStream;
import java.io.DataInputStream;
import java.io.File;
import java.io.FileReader;
import java.io.FileWriter;
@@ -47,12 +44,12 @@ import java.io.IOException;
import java.io.InputStream;
import java.io.InputStreamReader;
import java.io.Writer;
import java.security.SecureRandom;
import java.util.ArrayList;
import java.util.HashSet;
import java.util.Iterator;
import java.util.List;
import java.util.Random;
import java.util.Set;
/**
* Application that injects random key events and other actions into the system.
@@ -181,12 +178,6 @@ public class Monkey {
/** Package whitelist file. */
private String mPkgWhitelistFile;
/** Packages we are allowed to run, or empty if no restriction. */
private HashSet<String> mValidPackages = new HashSet<String>();
/** Packages we are not allowed to run. */
private HashSet<String> mInvalidPackages = new HashSet<String>();
/** Categories we are allowed to launch **/
private ArrayList<String> mMainCategories = new ArrayList<String>();
@@ -251,36 +242,20 @@ public class Monkey {
private MonkeyNetworkMonitor mNetworkMonitor = new MonkeyNetworkMonitor();
private boolean mPermissionTargetSystem = false;
// information on the current activity.
public static Intent currentIntent;
public static String currentPackage;
/**
* Check whether we should run against the givn package.
*
* @param pkg The package name.
* @return Returns true if we should run against pkg.
*/
private boolean checkEnteringPackage(String pkg) {
if (mInvalidPackages.size() > 0) {
if (mInvalidPackages.contains(pkg)) {
return false;
}
} else if (mValidPackages.size() > 0) {
if (!mValidPackages.contains(pkg)) {
return false;
}
}
return true;
}
/**
* Monitor operations happening in the system.
*/
private class ActivityController extends IActivityController.Stub {
public boolean activityStarting(Intent intent, String pkg) {
boolean allow = checkEnteringPackage(pkg) || (DEBUG_ALLOW_ANY_STARTS != 0);
boolean allow = MonkeyUtils.getPackageFilter().checkEnteringPackage(pkg)
|| (DEBUG_ALLOW_ANY_STARTS != 0);
if (mVerbose > 0) {
// StrictMode's disk checks end up catching this on
// userdebug/eng builds due to PrintStream going to a
@@ -301,7 +276,8 @@ public class Monkey {
public boolean activityResuming(String pkg) {
StrictMode.ThreadPolicy savedPolicy = StrictMode.allowThreadDiskWrites();
System.out.println(" // activityResuming(" + pkg + ")");
boolean allow = checkEnteringPackage(pkg) || (DEBUG_ALLOW_ANY_RESTARTS != 0);
boolean allow = MonkeyUtils.getPackageFilter().checkEnteringPackage(pkg)
|| (DEBUG_ALLOW_ANY_RESTARTS != 0);
if (!allow) {
if (mVerbose > 0) {
System.out.println(" // " + (allow ? "Allowing" : "Rejecting")
@@ -559,18 +535,7 @@ public class Monkey {
if (mVerbose > 0) {
System.out.println(":Monkey: seed=" + mSeed + " count=" + mCount);
if (mValidPackages.size() > 0) {
Iterator<String> it = mValidPackages.iterator();
while (it.hasNext()) {
System.out.println(":AllowPackage: " + it.next());
}
}
if (mInvalidPackages.size() > 0) {
Iterator<String> it = mInvalidPackages.iterator();
while (it.hasNext()) {
System.out.println(":DisallowPackage: " + it.next());
}
}
MonkeyUtils.getPackageFilter().dump();
if (mMainCategories.size() != 0) {
Iterator<String> it = mMainCategories.iterator();
while (it.hasNext()) {
@@ -626,7 +591,8 @@ public class Monkey {
if (mVerbose >= 2) { // check seeding performance
System.out.println("// Seeded: " + mSeed);
}
mEventSource = new MonkeySourceRandom(mRandom, mMainApps, mThrottle, mRandomizeThrottle);
mEventSource = new MonkeySourceRandom(mRandom, mMainApps,
mThrottle, mRandomizeThrottle, mPermissionTargetSystem);
mEventSource.setVerbose(mVerbose);
// set any of the factors that has been set
for (int i = 0; i < MonkeySourceRandom.FACTORZ_COUNT; i++) {
@@ -756,11 +722,12 @@ public class Monkey {
try {
String opt;
Set<String> validPackages = new HashSet<>();
while ((opt = nextOption()) != null) {
if (opt.equals("-s")) {
mSeed = nextOptionLong("Seed");
} else if (opt.equals("-p")) {
mValidPackages.add(nextOptionData());
validPackages.add(nextOptionData());
} else if (opt.equals("-c")) {
mMainCategories.add(nextOptionData());
} else if (opt.equals("-v")) {
@@ -812,6 +779,9 @@ public class Monkey {
} else if (opt.equals("--pct-pinchzoom")) {
int i = MonkeySourceRandom.FACTOR_PINCHZOOM;
mFactors[i] = -nextOptionLong("pinch zoom events percentage");
} else if (opt.equals("--pct-permission")) {
int i = MonkeySourceRandom.FACTOR_PERMISSION;
mFactors[i] = -nextOptionLong("runtime permission toggle events percentage");
} else if (opt.equals("--pkg-blacklist-file")) {
mPkgBlacklistFile = nextOptionData();
} else if (opt.equals("--pkg-whitelist-file")) {
@@ -845,6 +815,8 @@ public class Monkey {
} else if (opt.equals("--periodic-bugreport")){
mGetPeriodicBugreport = true;
mBugreportFrequency = nextOptionLong("Number of iterations");
} else if (opt.equals("--permission-target-system")){
mPermissionTargetSystem = true;
} else if (opt.equals("-h")) {
showUsage();
return false;
@@ -854,6 +826,7 @@ public class Monkey {
return false;
}
}
MonkeyUtils.getPackageFilter().addValidPackages(validPackages);
} catch (RuntimeException ex) {
System.err.println("** Error: " + ex.toString());
showUsage();
@@ -889,7 +862,7 @@ public class Monkey {
* @param list The destination list.
* @return Returns false if any error occurs.
*/
private static boolean loadPackageListFromFile(String fileName, HashSet<String> list) {
private static boolean loadPackageListFromFile(String fileName, Set<String> list) {
BufferedReader reader = null;
try {
reader = new BufferedReader(new FileReader(fileName));
@@ -921,20 +894,24 @@ public class Monkey {
* @return Returns false if any error occurs.
*/
private boolean loadPackageLists() {
if (((mPkgWhitelistFile != null) || (mValidPackages.size() > 0))
if (((mPkgWhitelistFile != null) || (MonkeyUtils.getPackageFilter().hasValidPackages()))
&& (mPkgBlacklistFile != null)) {
System.err.println("** Error: you can not specify a package blacklist "
+ "together with a whitelist or individual packages (via -p).");
return false;
}
Set<String> validPackages = new HashSet<>();
if ((mPkgWhitelistFile != null)
&& (!loadPackageListFromFile(mPkgWhitelistFile, mValidPackages))) {
&& (!loadPackageListFromFile(mPkgWhitelistFile, validPackages))) {
return false;
}
MonkeyUtils.getPackageFilter().addValidPackages(validPackages);
Set<String> invalidPackages = new HashSet<>();
if ((mPkgBlacklistFile != null)
&& (!loadPackageListFromFile(mPkgBlacklistFile, mInvalidPackages))) {
&& (!loadPackageListFromFile(mPkgBlacklistFile, invalidPackages))) {
return false;
}
MonkeyUtils.getPackageFilter().addInvalidPackages(invalidPackages);
return true;
}
@@ -1014,7 +991,7 @@ public class Monkey {
for (int a = 0; a < NA; a++) {
ResolveInfo r = mainApps.get(a);
String packageName = r.activityInfo.applicationInfo.packageName;
if (checkEnteringPackage(packageName)) {
if (MonkeyUtils.getPackageFilter().checkEnteringPackage(packageName)) {
if (mVerbose >= 2) { // very verbose
System.out.println("// + Using main activity " + r.activityInfo.name
+ " (from package " + packageName + ")");
@@ -1352,6 +1329,7 @@ public class Monkey {
usage.append(" [--pct-nav PERCENT] [--pct-majornav PERCENT]\n");
usage.append(" [--pct-appswitch PERCENT] [--pct-flip PERCENT]\n");
usage.append(" [--pct-anyevent PERCENT] [--pct-pinchzoom PERCENT]\n");
usage.append(" [--pct-permission PERCENT]\n");
usage.append(" [--pkg-blacklist-file PACKAGE_BLACKLIST_FILE]\n");
usage.append(" [--pkg-whitelist-file PACKAGE_WHITELIST_FILE]\n");
usage.append(" [--wait-dbg] [--dbg-no-events]\n");
@@ -1365,6 +1343,7 @@ public class Monkey {
usage.append(" [--script-log]\n");
usage.append(" [--bugreport]\n");
usage.append(" [--periodic-bugreport]\n");
usage.append(" [--permission-target-system]\n");
usage.append(" COUNT\n");
System.err.println(usage.toString());
}

View File

@@ -31,7 +31,8 @@ public abstract class MonkeyEvent {
public static final int EVENT_TYPE_ACTIVITY = 4;
public static final int EVENT_TYPE_FLIP = 5; // Keyboard flip
public static final int EVENT_TYPE_THROTTLE = 6;
public static final int EVENT_TYPE_NOOP = 7;
public static final int EVENT_TYPE_PERMISSION = 7;
public static final int EVENT_TYPE_NOOP = 8;
public static final int INJECT_SUCCESS = 1;
public static final int INJECT_FAIL = 0;

View File

@@ -0,0 +1,58 @@
/*
* Copyright (C) 2015 The Android Open Source Project
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package com.android.commands.monkey;
import android.app.IActivityManager;
import android.content.pm.IPackageManager;
import android.content.pm.PackageManager;
import android.content.pm.PermissionInfo;
import android.os.RemoteException;
import android.os.ServiceManager;
import android.os.UserHandle;
import android.view.IWindowManager;
public class MonkeyPermissionEvent extends MonkeyEvent {
private String mPkg;
private PermissionInfo mPermissionInfo;
public MonkeyPermissionEvent(String pkg, PermissionInfo permissionInfo) {
super(EVENT_TYPE_PERMISSION);
mPkg = pkg;
mPermissionInfo = permissionInfo;
}
@Override
public int injectEvent(IWindowManager iwm, IActivityManager iam, int verbose) {
IPackageManager pm = IPackageManager.Stub.asInterface(ServiceManager.getService("package"));
try {
// determine if we should grant or revoke permission
int perm = pm.checkPermission(mPermissionInfo.name, mPkg, UserHandle.myUserId());
boolean grant = perm == PackageManager.PERMISSION_DENIED;
// log before calling pm in case we hit an error
System.out.println(String.format(":Permission %s %s to package %s",
grant ? "grant" : "revoke", mPermissionInfo.name, mPkg));
if (grant) {
pm.grantRuntimePermission(mPkg, mPermissionInfo.name, UserHandle.myUserId());
} else {
pm.revokeRuntimePermission(mPkg, mPermissionInfo.name, UserHandle.myUserId());
}
return MonkeyEvent.INJECT_SUCCESS;
} catch (RemoteException re) {
return MonkeyEvent.INJECT_ERROR_REMOTE_EXCEPTION;
}
}
}

View File

@@ -0,0 +1,178 @@
/*
* Copyright (C) 2015 The Android Open Source Project
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package com.android.commands.monkey;
import android.Manifest;
import android.content.pm.ApplicationInfo;
import android.content.pm.IPackageManager;
import android.content.pm.PackageInfo;
import android.content.pm.PackageManager;
import android.content.pm.PermissionInfo;
import android.os.Build;
import android.os.RemoteException;
import android.os.ServiceManager;
import android.os.UserHandle;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.Random;
/**
* Utility class that encapsulates runtime permission related methods for monkey
*
*/
public class MonkeyPermissionUtil {
private static final String PERMISSION_PREFIX = "android.permission.";
private static final String PERMISSION_GROUP_PREFIX = "android.permission-group.";
// from com.android.packageinstaller.permission.utils
private static final String[] MODERN_PERMISSION_GROUPS = {
Manifest.permission_group.CALENDAR, Manifest.permission_group.CAMERA,
Manifest.permission_group.CONTACTS, Manifest.permission_group.LOCATION,
Manifest.permission_group.SENSORS, Manifest.permission_group.SMS,
Manifest.permission_group.PHONE, Manifest.permission_group.MICROPHONE,
Manifest.permission_group.STORAGE
};
// from com.android.packageinstaller.permission.utils
private static boolean isModernPermissionGroup(String name) {
for (String modernGroup : MODERN_PERMISSION_GROUPS) {
if (modernGroup.equals(name)) {
return true;
}
}
return false;
}
/**
* actual list of packages to target, with invalid packages excluded, and may optionally include
* system packages
*/
private List<String> mTargetedPackages;
/** if we should target system packages regardless if they are listed */
private boolean mTargetSystemPackages;
private IPackageManager mPm;
/** keep track of runtime permissions requested for each package targeted */
private Map<String, List<PermissionInfo>> mPermissionMap;
public MonkeyPermissionUtil() {
mPm = IPackageManager.Stub.asInterface(ServiceManager.getService("package"));
}
public void setTargetSystemPackages(boolean targetSystemPackages) {
mTargetSystemPackages = targetSystemPackages;
}
/**
* Decide if a package should be targeted by permission monkey
* @param info
* @return
*/
private boolean shouldTargetPackage(PackageInfo info) {
// target if permitted by white listing / black listing rules
if (MonkeyUtils.getPackageFilter().checkEnteringPackage(info.packageName)) {
return true;
}
if (mTargetSystemPackages
// not explicitly black listed
&& !MonkeyUtils.getPackageFilter().isPackageInvalid(info.packageName)
// is a system app
&& (info.applicationInfo.flags & ApplicationInfo.FLAG_SYSTEM) != 0) {
return true;
}
return false;
}
private boolean shouldTargetPermission(String pkg, PermissionInfo pi) throws RemoteException {
int flags = mPm.getPermissionFlags(pi.name, pkg, UserHandle.myUserId());
int fixedPermFlags = PackageManager.FLAG_PERMISSION_SYSTEM_FIXED
| PackageManager.FLAG_PERMISSION_POLICY_FIXED;
return pi.group != null && pi.protectionLevel == PermissionInfo.PROTECTION_DANGEROUS
&& ((flags & fixedPermFlags) == 0)
&& isModernPermissionGroup(pi.group);
}
public boolean populatePermissionsMapping() {
mPermissionMap = new HashMap<>();
try {
List<?> pkgInfos = mPm.getInstalledPackages(
PackageManager.GET_PERMISSIONS, UserHandle.myUserId()).getList();
for (Object o : pkgInfos) {
PackageInfo info = (PackageInfo)o;
if (!shouldTargetPackage(info)) {
continue;
}
List<PermissionInfo> permissions = new ArrayList<>();
if (info.applicationInfo.targetSdkVersion <= Build.VERSION_CODES.LOLLIPOP_MR1) {
// skip apps targetting lower API level
continue;
}
if (info.requestedPermissions == null) {
continue;
}
for (String perm : info.requestedPermissions) {
PermissionInfo pi = mPm.getPermissionInfo(perm, 0);
if (pi != null && shouldTargetPermission(info.packageName, pi)) {
permissions.add(pi);
}
}
if (!permissions.isEmpty()) {
mPermissionMap.put(info.packageName, permissions);
}
}
} catch (RemoteException re) {
System.err.println("** Failed talking with package manager!");
return false;
}
if (!mPermissionMap.isEmpty()) {
mTargetedPackages = new ArrayList<>(mPermissionMap.keySet());
}
return true;
}
public void dump() {
System.out.println("// Targeted packages and permissions:");
for (Map.Entry<String, List<PermissionInfo>> e : mPermissionMap.entrySet()) {
System.out.println(String.format("// + Using %s", e.getKey()));
for (PermissionInfo pi : e.getValue()) {
String name = pi.name;
if (name != null) {
if (name.startsWith(PERMISSION_PREFIX)) {
name = name.substring(PERMISSION_PREFIX.length());
}
}
String group = pi.group;
if (group != null) {
if (group.startsWith(PERMISSION_GROUP_PREFIX)) {
group = group.substring(PERMISSION_GROUP_PREFIX.length());
}
}
System.out.println(String.format("// Permission: %s [%s]", name, group));
}
}
}
public MonkeyPermissionEvent generateRandomPermissionEvent(Random random) {
String pkg = mTargetedPackages.get(random.nextInt(mTargetedPackages.size()));
List<PermissionInfo> infos = mPermissionMap.get(pkg);
return new MonkeyPermissionEvent(pkg, infos.get(random.nextInt(infos.size())));
}
}

View File

@@ -26,7 +26,7 @@ import android.view.KeyEvent;
import android.view.MotionEvent;
import android.view.Surface;
import java.util.ArrayList;
import java.util.List;
import java.util.Random;
/**
@@ -82,8 +82,9 @@ public class MonkeySourceRandom implements MonkeyEventSource {
public static final int FACTOR_SYSOPS = 7;
public static final int FACTOR_APPSWITCH = 8;
public static final int FACTOR_FLIP = 9;
public static final int FACTOR_ANYTHING = 10;
public static final int FACTORZ_COUNT = 11; // should be last+1
public static final int FACTOR_PERMISSION = 10;
public static final int FACTOR_ANYTHING = 11;
public static final int FACTORZ_COUNT = 12; // should be last+1
private static final int GESTURE_TAP = 0;
private static final int GESTURE_DRAG = 1;
@@ -93,12 +94,13 @@ public class MonkeySourceRandom implements MonkeyEventSource {
* values after we read any optional values.
**/
private float[] mFactors = new float[FACTORZ_COUNT];
private ArrayList<ComponentName> mMainApps;
private List<ComponentName> mMainApps;
private int mEventCount = 0; //total number of events generated so far
private MonkeyEventQueue mQ;
private Random mRandom;
private int mVerbose = 0;
private long mThrottle = 0;
private MonkeyPermissionUtil mPermissionUtil;
private boolean mKeyboardOpen = false;
@@ -117,8 +119,8 @@ public class MonkeySourceRandom implements MonkeyEventSource {
return KeyEvent.keyCodeFromString(keyName);
}
public MonkeySourceRandom(Random random, ArrayList<ComponentName> MainApps,
long throttle, boolean randomizeThrottle) {
public MonkeySourceRandom(Random random, List<ComponentName> MainApps,
long throttle, boolean randomizeThrottle, boolean permissionTargetSystem) {
// default values for random distributions
// note, these are straight percentages, to match user input (cmd line args)
// but they will be converted to 0..1 values before the main loop runs.
@@ -132,12 +134,16 @@ public class MonkeySourceRandom implements MonkeyEventSource {
mFactors[FACTOR_SYSOPS] = 2.0f;
mFactors[FACTOR_APPSWITCH] = 2.0f;
mFactors[FACTOR_FLIP] = 1.0f;
// disbale permission by default
mFactors[FACTOR_PERMISSION] = 0.0f;
mFactors[FACTOR_ANYTHING] = 13.0f;
mFactors[FACTOR_PINCHZOOM] = 2.0f;
mRandom = random;
mMainApps = MainApps;
mQ = new MonkeyEventQueue(random, throttle, randomizeThrottle);
mPermissionUtil = new MonkeyPermissionUtil();
mPermissionUtil.setTargetSystemPackages(permissionTargetSystem);
}
/**
@@ -410,6 +416,9 @@ public class MonkeySourceRandom implements MonkeyEventSource {
} else if (cls < mFactors[FACTOR_ROTATION]) {
generateRotationEvent(mRandom);
return;
} else if (cls < mFactors[FACTOR_PERMISSION]) {
mQ.add(mPermissionUtil.generateRandomPermissionEvent(mRandom));
return;
}
// The remaining event categories are injected as key events
@@ -450,8 +459,15 @@ public class MonkeySourceRandom implements MonkeyEventSource {
}
public boolean validate() {
//check factors
return adjustEventFactors();
boolean ret = true;
// only populate & dump permissions if enabled
if (mFactors[FACTOR_PERMISSION] != 0.0f) {
ret &= mPermissionUtil.populatePermissionsMapping();
if (ret && mVerbose >= 2) {
mPermissionUtil.dump();
}
}
return ret & adjustEventFactors();
}
public void setVerbose(int verbose) {

View File

@@ -17,6 +17,9 @@
package com.android.commands.monkey;
import java.text.SimpleDateFormat;
import java.util.HashSet;
import java.util.Iterator;
import java.util.Set;
/**
* Misc utilities.
@@ -26,6 +29,7 @@ public abstract class MonkeyUtils {
private static final java.util.Date DATE = new java.util.Date();
private static final SimpleDateFormat DATE_FORMATTER = new SimpleDateFormat(
"yyyy-MM-dd HH:mm:ss.SSS ");
private static PackageFilter sFilter;
private MonkeyUtils() {
}
@@ -38,4 +42,72 @@ public abstract class MonkeyUtils {
return DATE_FORMATTER.format(DATE);
}
public static PackageFilter getPackageFilter() {
if (sFilter == null) {
sFilter = new PackageFilter();
}
return sFilter;
}
public static class PackageFilter {
private Set<String> mValidPackages = new HashSet<>();
private Set<String> mInvalidPackages = new HashSet<>();
private PackageFilter() {
}
public void addValidPackages(Set<String> validPackages) {
mValidPackages.addAll(validPackages);
}
public void addInvalidPackages(Set<String> invalidPackages) {
mInvalidPackages.addAll(invalidPackages);
}
public boolean hasValidPackages() {
return mValidPackages.size() > 0;
}
public boolean isPackageValid(String pkg) {
return mValidPackages.contains(pkg);
}
public boolean isPackageInvalid(String pkg) {
return mInvalidPackages.contains(pkg);
}
/**
* Check whether we should run against the given package.
*
* @param pkg The package name.
* @return Returns true if we should run against pkg.
*/
public boolean checkEnteringPackage(String pkg) {
if (mInvalidPackages.size() > 0) {
if (mInvalidPackages.contains(pkg)) {
return false;
}
} else if (mValidPackages.size() > 0) {
if (!mValidPackages.contains(pkg)) {
return false;
}
}
return true;
}
public void dump() {
if (mValidPackages.size() > 0) {
Iterator<String> it = mValidPackages.iterator();
while (it.hasNext()) {
System.out.println(":AllowPackage: " + it.next());
}
}
if (mInvalidPackages.size() > 0) {
Iterator<String> it = mInvalidPackages.iterator();
while (it.hasNext()) {
System.out.println(":DisallowPackage: " + it.next());
}
}
}
}
}