Introduce a process crash count tolerence limit.

To prevent processes from consistently crashing over a large period of
time, introduce a new 12 crashes per 12 hours limit per process. This is
in addition to the MIN_CRASH_INTERVAL logic already in place to add apps
to a bad-apps list.

Bug: 159840270
Test: manual (consistently crash an app via 'adb shell am crash <pkg>'
in intervals larger than MIN_CRASH_INTERVAL until the limit is reached
and check 'dumpsys activity processes' to ensure the app is marked as
a bad app)
Test: dumpsys activity settings

Change-Id: Ic53dab2d0cee8512c9dde60ec73fb4b0558e7148
This commit is contained in:
Varun Shah
2020-11-04 10:28:21 -08:00
parent 55b5346c92
commit 75df622bb3
2 changed files with 140 additions and 36 deletions

View File

@@ -91,6 +91,9 @@ final class ActivityManagerConstants extends ContentObserver {
static final String KEY_TOP_TO_FGS_GRACE_DURATION = "top_to_fgs_grace_duration";
static final String KEY_PENDINGINTENT_WARNING_THRESHOLD = "pendingintent_warning_threshold";
static final String KEY_MIN_CRASH_INTERVAL = "min_crash_interval";
static final String KEY_PROCESS_CRASH_COUNT_RESET_INTERVAL =
"process_crash_count_reset_interval";
static final String KEY_PROCESS_CRASH_COUNT_LIMIT = "process_crash_count_limit";
private static final int DEFAULT_MAX_CACHED_PROCESSES = 32;
private static final long DEFAULT_BACKGROUND_SETTLE_TIME = 60*1000;
@@ -126,6 +129,8 @@ final class ActivityManagerConstants extends ContentObserver {
private static final int DEFAULT_PENDINGINTENT_WARNING_THRESHOLD = 2000;
private static final int DEFAULT_MIN_CRASH_INTERVAL = 2 * 60 * 1000;
private static final int DEFAULT_MAX_PHANTOM_PROCESSES = 32;
private static final int DEFAULT_PROCESS_CRASH_COUNT_RESET_INTERVAL = 12 * 60 * 60 * 1000;
private static final int DEFAULT_PROCESS_CRASH_COUNT_LIMIT = 12;
// Flag stored in the DeviceConfig API.
@@ -301,9 +306,27 @@ final class ActivityManagerConstants extends ContentObserver {
/**
* The minimum time we allow between crashes, for us to consider this
* application to be bad and stop its services and reject broadcasts.
* A reasonable interval here would be anything between 1-3 minutes.
*/
public static int MIN_CRASH_INTERVAL = DEFAULT_MIN_CRASH_INTERVAL;
/**
* We will allow for a maximum number of {@link PROCESS_CRASH_COUNT_LIMIT} crashes within this
* time period before we consider the application to be bad and stop services and reject
* broadcasts.
* A reasonable reset interval here would be anything between 10-20 hours along with a crash
* count limit of 10-20 crashes.
*/
static long PROCESS_CRASH_COUNT_RESET_INTERVAL = DEFAULT_PROCESS_CRASH_COUNT_RESET_INTERVAL;
/**
* The maximum number of crashes allowed within {@link PROCESS_CRASH_COUNT_RESET_INTERVAL_MS}
* before we consider the application to be bad and stop services and reject broadcasts.
* A reasonable crash count limit here would be anything between 10-20 crashes along with a
* reset interval of 10-20 hours.
*/
static int PROCESS_CRASH_COUNT_LIMIT = DEFAULT_PROCESS_CRASH_COUNT_LIMIT;
// Indicates whether the activity starts logging is enabled.
// Controlled by Settings.Global.ACTIVITY_STARTS_LOGGING_ENABLED
volatile boolean mFlagActivityStartsLoggingEnabled;
@@ -694,6 +717,11 @@ final class ActivityManagerConstants extends ContentObserver {
DEFAULT_MIN_CRASH_INTERVAL);
PENDINGINTENT_WARNING_THRESHOLD = mParser.getInt(KEY_PENDINGINTENT_WARNING_THRESHOLD,
DEFAULT_PENDINGINTENT_WARNING_THRESHOLD);
PROCESS_CRASH_COUNT_RESET_INTERVAL = mParser.getInt(
KEY_PROCESS_CRASH_COUNT_RESET_INTERVAL,
DEFAULT_PROCESS_CRASH_COUNT_RESET_INTERVAL);
PROCESS_CRASH_COUNT_LIMIT = mParser.getInt(KEY_PROCESS_CRASH_COUNT_LIMIT,
DEFAULT_PROCESS_CRASH_COUNT_LIMIT);
if (POWER_CHECK_INTERVAL != currentPowerCheckInterval) {
mService.mHandler.removeMessages(
@@ -934,6 +962,10 @@ final class ActivityManagerConstants extends ContentObserver {
pw.println(TOP_TO_FGS_GRACE_DURATION);
pw.print(" "); pw.print(KEY_MIN_CRASH_INTERVAL); pw.print("=");
pw.println(MIN_CRASH_INTERVAL);
pw.print(" "); pw.print(KEY_PROCESS_CRASH_COUNT_RESET_INTERVAL); pw.print("=");
pw.println(PROCESS_CRASH_COUNT_RESET_INTERVAL);
pw.print(" "); pw.print(KEY_PROCESS_CRASH_COUNT_LIMIT); pw.print("=");
pw.println(PROCESS_CRASH_COUNT_LIMIT);
pw.print(" "); pw.print(KEY_IMPERCEPTIBLE_KILL_EXEMPT_PROC_STATES); pw.print("=");
pw.println(Arrays.toString(IMPERCEPTIBLE_KILL_EXEMPT_PROC_STATES.toArray()));
pw.print(" "); pw.print(KEY_IMPERCEPTIBLE_KILL_EXEMPT_PACKAGES); pw.print("=");

View File

@@ -19,6 +19,8 @@ package com.android.server.am;
import static android.app.ActivityTaskManager.INVALID_TASK_ID;
import static android.content.pm.ApplicationInfo.FLAG_SYSTEM;
import static com.android.server.am.ActivityManagerConstants.PROCESS_CRASH_COUNT_LIMIT;
import static com.android.server.am.ActivityManagerConstants.PROCESS_CRASH_COUNT_RESET_INTERVAL;
import static com.android.server.am.ActivityManagerDebugConfig.TAG_AM;
import static com.android.server.am.ActivityManagerDebugConfig.TAG_WITH_CLASS_NAME;
import static com.android.server.am.ActivityManagerService.MY_PID;
@@ -44,6 +46,7 @@ import android.provider.Settings;
import android.util.ArrayMap;
import android.util.ArraySet;
import android.util.EventLog;
import android.util.Pair;
import android.util.Slog;
import android.util.SparseArray;
import android.util.TimeUtils;
@@ -89,6 +92,12 @@ class AppErrors {
*/
private final ProcessMap<Long> mProcessCrashShowDialogTimes = new ProcessMap<>();
/**
* A pairing between how many times various processes have crashed since a given time.
* Entry and exit conditions for this map are similar to mProcessCrashTimes.
*/
private final ProcessMap<Pair<Long, Integer>> mProcessCrashCounts = new ProcessMap<>();
/**
* Set of applications that we consider to be bad, and will reject
* incoming broadcasts from (which the user has no control over).
@@ -118,6 +127,7 @@ class AppErrors {
mProcessCrashTimes.clear();
mProcessCrashTimesPersistent.clear();
mProcessCrashShowDialogTimes.clear();
mProcessCrashCounts.clear();
synchronized (mBadProcesses) {
mBadProcesses.clear();
}
@@ -196,9 +206,9 @@ class AppErrors {
}
boolean dumpLocked(FileDescriptor fd, PrintWriter pw, boolean needSep, String dumpPackage) {
final long now = SystemClock.uptimeMillis();
if (!mProcessCrashTimes.getMap().isEmpty()) {
boolean printed = false;
final long now = SystemClock.uptimeMillis();
final ArrayMap<String, SparseArray<Long>> pmap = mProcessCrashTimes.getMap();
final int processCount = pmap.size();
for (int ip = 0; ip < processCount; ip++) {
@@ -227,6 +237,36 @@ class AppErrors {
}
}
if (!mProcessCrashCounts.getMap().isEmpty()) {
boolean printed = false;
final ArrayMap<String, SparseArray<Pair<Long, Integer>>> pmap =
mProcessCrashCounts.getMap();
final int processCount = pmap.size();
for (int ip = 0; ip < processCount; ip++) {
final String pname = pmap.keyAt(ip);
final SparseArray<Pair<Long, Integer>> uids = pmap.valueAt(ip);
final int uidCount = uids.size();
for (int i = 0; i < uidCount; i++) {
final int puid = uids.keyAt(i);
final ProcessRecord r = mService.getProcessNames().get(pname, puid);
if (dumpPackage != null && (r == null || !r.pkgList.containsKey(dumpPackage))) {
continue;
}
if (!printed) {
if (needSep) pw.println();
needSep = true;
pw.println(" First time processes crashed and counts:");
printed = true;
}
pw.print(" Process "); pw.print(pname);
pw.print(" uid "); pw.print(puid);
pw.print(": first crashed ");
TimeUtils.formatDuration(now - uids.valueAt(i).first, pw);
pw.print(" ago; crashes since then: "); pw.println(uids.valueAt(i).second);
}
}
}
if (!mBadProcesses.getMap().isEmpty()) {
boolean printed = false;
final ArrayMap<String, SparseArray<BadProcessInfo>> pmap = mBadProcesses.getMap();
@@ -295,34 +335,50 @@ class AppErrors {
void resetProcessCrashTimeLocked(final String processName, final int uid) {
mProcessCrashTimes.remove(processName, uid);
mProcessCrashCounts.remove(processName, uid);
}
void resetProcessCrashTimeLocked(boolean resetEntireUser, int appId, int userId) {
final ArrayMap<String, SparseArray<Long>> pmap = mProcessCrashTimes.getMap();
for (int ip = pmap.size() - 1; ip >= 0; ip--) {
SparseArray<Long> ba = pmap.valueAt(ip);
for (int i = ba.size() - 1; i >= 0; i--) {
boolean remove = false;
final int entUid = ba.keyAt(i);
if (!resetEntireUser) {
if (userId == UserHandle.USER_ALL) {
if (UserHandle.getAppId(entUid) == appId) {
remove = true;
}
} else {
if (entUid == UserHandle.getUid(userId, appId)) {
remove = true;
}
}
} else if (UserHandle.getUserId(entUid) == userId) {
remove = true;
}
if (remove) {
ba.removeAt(i);
}
}
final ArrayMap<String, SparseArray<Long>> pTimeMap = mProcessCrashTimes.getMap();
for (int ip = pTimeMap.size() - 1; ip >= 0; ip--) {
SparseArray<Long> ba = pTimeMap.valueAt(ip);
resetProcessCrashMapLocked(ba, resetEntireUser, appId, userId);
if (ba.size() == 0) {
pmap.removeAt(ip);
pTimeMap.removeAt(ip);
}
}
final ArrayMap<String, SparseArray<Pair<Long, Integer>>> pCountMap =
mProcessCrashCounts.getMap();
for (int ip = pCountMap.size() - 1; ip >= 0; ip--) {
SparseArray<Pair<Long, Integer>> ba = pCountMap.valueAt(ip);
resetProcessCrashMapLocked(ba, resetEntireUser, appId, userId);
if (ba.size() == 0) {
pCountMap.removeAt(ip);
}
}
}
private void resetProcessCrashMapLocked(SparseArray<?> ba, boolean resetEntireUser,
int appId, int userId) {
for (int i = ba.size() - 1; i >= 0; i--) {
boolean remove = false;
final int entUid = ba.keyAt(i);
if (!resetEntireUser) {
if (userId == UserHandle.USER_ALL) {
if (UserHandle.getAppId(entUid) == appId) {
remove = true;
}
} else {
if (entUid == UserHandle.getUid(userId, appId)) {
remove = true;
}
}
} else if (UserHandle.getUserId(entUid) == userId) {
remove = true;
}
if (remove) {
ba.removeAt(i);
}
}
}
@@ -567,12 +623,6 @@ class AppErrors {
if (res == AppErrorDialog.FORCE_QUIT_AND_REPORT) {
appErrorIntent = createAppErrorIntentLocked(r, timeMillis, crashInfo);
}
if (r != null && !r.isolated && res != AppErrorDialog.RESTART) {
// XXX Can't keep track of crash time for isolated processes,
// since they don't have a persistent identity.
mProcessCrashTimes.put(r.processName, r.uid,
SystemClock.uptimeMillis());
}
}
if (appErrorIntent != null) {
@@ -742,11 +792,14 @@ class AppErrors {
}
}
if (crashTime != null && now < crashTime + ActivityManagerConstants.MIN_CRASH_INTERVAL) {
// The process crashed again very quickly. If it was a bound foreground service, let's
// try to restart again in a while, otherwise the process loses!
Slog.w(TAG, "Process " + app.processName
+ " has crashed too many times: killing!");
final boolean quickCrash = crashTime != null
&& now < crashTime + ActivityManagerConstants.MIN_CRASH_INTERVAL;
if (quickCrash || isProcOverCrashLimit(app, now)) {
// The process either crashed again very quickly or has been crashing periodically in
// the last few hours. If it was a bound foreground service, let's try to restart again
// in a while, otherwise the process loses!
Slog.w(TAG, "Process " + app.processName + " has crashed too many times, killing!"
+ " Reason: " + (quickCrash ? "crashed quickly" : "over process crash limit"));
EventLog.writeEvent(EventLogTags.AM_PROCESS_CRASHED_TOO_MUCH,
app.userId, app.processName, app.uid);
mService.mAtmInternal.onHandleAppCrash(app.getWindowProcessController());
@@ -765,6 +818,7 @@ class AppErrors {
new BadProcessInfo(now, shortMsg, longMsg, stackTrace));
}
mProcessCrashTimes.remove(app.processName, app.uid);
mProcessCrashCounts.remove(app.processName, app.uid);
}
app.bad = true;
app.removed = true;
@@ -809,12 +863,30 @@ class AppErrors {
// because they don't have a persistent identity.
mProcessCrashTimes.put(app.processName, app.uid, now);
mProcessCrashTimesPersistent.put(app.processName, app.uid, now);
updateProcessCrashCount(app.processName, app.uid, now);
}
if (app.crashHandler != null) mService.mHandler.post(app.crashHandler);
return true;
}
private void updateProcessCrashCount(String processName, int uid, long now) {
Pair<Long, Integer> count = mProcessCrashCounts.get(processName, uid);
if (count == null || (count.first + PROCESS_CRASH_COUNT_RESET_INTERVAL) < now) {
count = new Pair<>(now, 1);
} else {
count = new Pair<>(count.first, count.second + 1);
}
mProcessCrashCounts.put(processName, uid, count);
}
private boolean isProcOverCrashLimit(ProcessRecord app, long now) {
final Pair<Long, Integer> crashCount = mProcessCrashCounts.get(app.processName, app.uid);
return !app.isolated && crashCount != null
&& now < (crashCount.first + PROCESS_CRASH_COUNT_RESET_INTERVAL)
&& crashCount.second >= PROCESS_CRASH_COUNT_LIMIT;
}
void handleShowAppErrorUi(Message msg) {
AppErrorDialog.Data data = (AppErrorDialog.Data) msg.obj;
boolean showBackground = Settings.Secure.getInt(mContext.getContentResolver(),