blob: 1fe0271ac1ec5de679c50086aa0759ecb442ab8c [file] [log] [blame]
Jeff Sharkeyfe6f85c2017-01-20 10:42:57 -07001/*
2 * Copyright (C) 2017 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
17package com.android.server;
18
Todd Kennedy7c4c55d2017-11-02 10:01:39 -070019import static com.android.server.pm.PackageManagerServiceUtils.logCriticalInfo;
20
Jeff Sharkeyfe6f85c2017-01-20 10:42:57 -070021import android.content.ContentResolver;
22import android.content.Context;
Jeff Sharkey9f1fc2d2017-01-24 11:05:16 -070023import android.os.Build;
Jeff Sharkey82311462017-04-02 23:42:17 -060024import android.os.Environment;
Jeff Sharkey1bec4482017-02-23 12:40:54 -070025import android.os.FileUtils;
Jeff Sharkeyfe6f85c2017-01-20 10:42:57 -070026import android.os.RecoverySystem;
27import android.os.SystemClock;
28import android.os.SystemProperties;
29import android.os.UserHandle;
Jeff Sharkeyfe6f85c2017-01-20 10:42:57 -070030import android.provider.Settings;
31import android.text.format.DateUtils;
32import android.util.ExceptionUtils;
Jeff Sharkeybc9caa12017-03-11 20:38:21 -070033import android.util.Log;
Jeff Sharkeyfe6f85c2017-01-20 10:42:57 -070034import android.util.MathUtils;
35import android.util.Slog;
36import android.util.SparseArray;
Christian Brunschenf86039e2018-12-21 12:26:14 +000037import android.util.StatsLog;
Jeff Sharkeyfe6f85c2017-01-20 10:42:57 -070038
bpetrivsa7101952019-02-07 16:01:24 +000039import com.android.internal.annotations.VisibleForTesting;
Jeff Sharkeyfe6f85c2017-01-20 10:42:57 -070040import com.android.internal.util.ArrayUtils;
bpetrivs93075f42019-02-28 12:08:12 +000041import com.android.server.am.SettingsToPropertiesMapper;
bpetrivs62f15982019-02-13 17:18:16 +000042import com.android.server.utils.FlagNamespaceUtils;
Jeff Sharkeyfe6f85c2017-01-20 10:42:57 -070043
Jeff Sharkey1bec4482017-02-23 12:40:54 -070044import java.io.File;
bpetrivs93075f42019-02-28 12:08:12 +000045import java.util.Arrays;
Jeff Sharkeyd9574c72017-02-20 10:45:06 -070046
Jeff Sharkeyfe6f85c2017-01-20 10:42:57 -070047/**
48 * Utilities to help rescue the system from crash loops. Callers are expected to
49 * report boot events and persistent app crashes, and if they happen frequently
50 * enough this class will slowly escalate through several rescue operations
51 * before finally rebooting and prompting the user if they want to wipe data as
52 * a last resort.
53 *
54 * @hide
55 */
56public class RescueParty {
bpetrivsa7101952019-02-07 16:01:24 +000057 @VisibleForTesting
58 static final String PROP_ENABLE_RESCUE = "persist.sys.enable_rescue";
59 @VisibleForTesting
60 static final int TRIGGER_COUNT = 5;
61 @VisibleForTesting
62 static final String PROP_RESCUE_LEVEL = "sys.rescue_level";
63 @VisibleForTesting
64 static final int LEVEL_NONE = 0;
65 @VisibleForTesting
66 static final int LEVEL_RESET_SETTINGS_UNTRUSTED_DEFAULTS = 1;
67 @VisibleForTesting
68 static final int LEVEL_RESET_SETTINGS_UNTRUSTED_CHANGES = 2;
69 @VisibleForTesting
70 static final int LEVEL_RESET_SETTINGS_TRUSTED_DEFAULTS = 3;
71 @VisibleForTesting
72 static final int LEVEL_FACTORY_RESET = 4;
73 @VisibleForTesting
74 static final String PROP_RESCUE_BOOT_COUNT = "sys.rescue_boot_count";
75 @VisibleForTesting
76 static final long BOOT_TRIGGER_WINDOW_MILLIS = 300 * DateUtils.SECOND_IN_MILLIS;
77 @VisibleForTesting
78 static final long PERSISTENT_APP_CRASH_TRIGGER_WINDOW_MILLIS = 30 * DateUtils.SECOND_IN_MILLIS;
79 @VisibleForTesting
80 static final String TAG = "RescueParty";
Jeff Sharkeyfe6f85c2017-01-20 10:42:57 -070081
Jeff Sharkey9f1fc2d2017-01-24 11:05:16 -070082 private static final String PROP_DISABLE_RESCUE = "persist.sys.disable_rescue";
Jeff Sharkeyfe6f85c2017-01-20 10:42:57 -070083 private static final String PROP_RESCUE_BOOT_START = "sys.rescue_boot_start";
Jeff Sharkey9d640952017-06-26 19:57:16 -060084 private static final String PROP_VIRTUAL_DEVICE = "ro.hardware.virtual_device";
Jeff Sharkeyfe6f85c2017-01-20 10:42:57 -070085
Jeff Sharkeyfe6f85c2017-01-20 10:42:57 -070086 /** Threshold for boot loops */
87 private static final Threshold sBoot = new BootThreshold();
88 /** Threshold for app crash loops */
89 private static SparseArray<Threshold> sApps = new SparseArray<>();
90
Jeff Sharkey9f1fc2d2017-01-24 11:05:16 -070091 private static boolean isDisabled() {
Jeff Sharkeybc9caa12017-03-11 20:38:21 -070092 // Check if we're explicitly enabled for testing
Jeff Sharkey82311462017-04-02 23:42:17 -060093 if (SystemProperties.getBoolean(PROP_ENABLE_RESCUE, false)) {
Jeff Sharkeybc9caa12017-03-11 20:38:21 -070094 return false;
95 }
96
Jeff Sharkeycdee83a2017-01-26 15:29:16 -070097 // We're disabled on all engineering devices
Jeff Sharkeyd9574c72017-02-20 10:45:06 -070098 if (Build.IS_ENG) {
99 Slog.v(TAG, "Disabled because of eng build");
100 return true;
101 }
Jeff Sharkeycdee83a2017-01-26 15:29:16 -0700102
103 // We're disabled on userdebug devices connected over USB, since that's
104 // a decent signal that someone is actively trying to debug the device,
105 // or that it's in a lab environment.
Jeff Sharkeyd9574c72017-02-20 10:45:06 -0700106 if (Build.IS_USERDEBUG && isUsbActive()) {
107 Slog.v(TAG, "Disabled because of active USB connection");
108 return true;
Jeff Sharkeycdee83a2017-01-26 15:29:16 -0700109 }
110
111 // One last-ditch check
Jeff Sharkeyd9574c72017-02-20 10:45:06 -0700112 if (SystemProperties.getBoolean(PROP_DISABLE_RESCUE, false)) {
113 Slog.v(TAG, "Disabled because of manual property");
114 return true;
115 }
116
117 return false;
Jeff Sharkey9f1fc2d2017-01-24 11:05:16 -0700118 }
119
Jeff Sharkeyfe6f85c2017-01-20 10:42:57 -0700120 /**
121 * Take note of a boot event. If we notice too many of these events
122 * happening in rapid succession, we'll send out a rescue party.
123 */
124 public static void noteBoot(Context context) {
Jeff Sharkey9f1fc2d2017-01-24 11:05:16 -0700125 if (isDisabled()) return;
Jeff Sharkeyfe6f85c2017-01-20 10:42:57 -0700126 if (sBoot.incrementAndTest()) {
127 sBoot.reset();
128 incrementRescueLevel(sBoot.uid);
129 executeRescueLevel(context);
130 }
131 }
132
133 /**
bpetrivs0254ff62019-03-01 11:50:45 +0000134 * Take note of a persistent app or apex module crash. If we notice too many of these
Jeff Sharkeyfe6f85c2017-01-20 10:42:57 -0700135 * events happening in rapid succession, we'll send out a rescue party.
136 */
bpetrivs0254ff62019-03-01 11:50:45 +0000137 public static void noteAppCrash(Context context, int uid) {
Jeff Sharkey9f1fc2d2017-01-24 11:05:16 -0700138 if (isDisabled()) return;
Jeff Sharkeyfe6f85c2017-01-20 10:42:57 -0700139 Threshold t = sApps.get(uid);
140 if (t == null) {
141 t = new AppThreshold(uid);
142 sApps.put(uid, t);
143 }
144 if (t.incrementAndTest()) {
145 t.reset();
146 incrementRescueLevel(t.uid);
147 executeRescueLevel(context);
148 }
149 }
150
151 /**
152 * Check if we're currently attempting to reboot for a factory reset.
153 */
154 public static boolean isAttemptingFactoryReset() {
155 return SystemProperties.getInt(PROP_RESCUE_LEVEL, LEVEL_NONE) == LEVEL_FACTORY_RESET;
156 }
157
158 /**
bpetrivsa7101952019-02-07 16:01:24 +0000159 * Called when {@code SettingsProvider} has been published, which is a good
160 * opportunity to reset any settings depending on our rescue level.
161 */
162 public static void onSettingsProviderPublished(Context context) {
bpetrivs93075f42019-02-28 12:08:12 +0000163 handleNativeRescuePartyResets();
bpetrivsa7101952019-02-07 16:01:24 +0000164 executeRescueLevel(context);
165 }
166
167 @VisibleForTesting
168 static void resetAllThresholds() {
169 sBoot.reset();
170
171 for (int i = 0; i < sApps.size(); i++) {
172 Threshold appThreshold = sApps.get(sApps.keyAt(i));
173 appThreshold.reset();
174 }
175 }
176
177 @VisibleForTesting
178 static long getElapsedRealtime() {
179 return SystemClock.elapsedRealtime();
180 }
181
bpetrivs93075f42019-02-28 12:08:12 +0000182 private static void handleNativeRescuePartyResets() {
183 if (SettingsToPropertiesMapper.isNativeFlagsResetPerformed()) {
184 FlagNamespaceUtils.resetDeviceConfig(Settings.RESET_MODE_TRUSTED_DEFAULTS,
185 Arrays.asList(SettingsToPropertiesMapper.getResetNativeCategories()));
186 }
187 }
188
bpetrivsa7101952019-02-07 16:01:24 +0000189 /**
Jeff Sharkeyfe6f85c2017-01-20 10:42:57 -0700190 * Escalate to the next rescue level. After incrementing the level you'll
191 * probably want to call {@link #executeRescueLevel(Context)}.
192 */
193 private static void incrementRescueLevel(int triggerUid) {
194 final int level = MathUtils.constrain(
195 SystemProperties.getInt(PROP_RESCUE_LEVEL, LEVEL_NONE) + 1,
196 LEVEL_NONE, LEVEL_FACTORY_RESET);
197 SystemProperties.set(PROP_RESCUE_LEVEL, Integer.toString(level));
198
199 EventLogTags.writeRescueLevel(level, triggerUid);
Todd Kennedy7c4c55d2017-11-02 10:01:39 -0700200 logCriticalInfo(Log.WARN, "Incremented rescue level to "
Jeff Sharkeybc9caa12017-03-11 20:38:21 -0700201 + levelToString(level) + " triggered by UID " + triggerUid);
Jeff Sharkeyfe6f85c2017-01-20 10:42:57 -0700202 }
203
Jeff Sharkeyfe6f85c2017-01-20 10:42:57 -0700204 private static void executeRescueLevel(Context context) {
205 final int level = SystemProperties.getInt(PROP_RESCUE_LEVEL, LEVEL_NONE);
206 if (level == LEVEL_NONE) return;
207
208 Slog.w(TAG, "Attempting rescue level " + levelToString(level));
209 try {
210 executeRescueLevelInternal(context, level);
211 EventLogTags.writeRescueSuccess(level);
Todd Kennedy7c4c55d2017-11-02 10:01:39 -0700212 logCriticalInfo(Log.DEBUG,
Jeff Sharkeybc9caa12017-03-11 20:38:21 -0700213 "Finished rescue level " + levelToString(level));
Jeff Sharkeyfe6f85c2017-01-20 10:42:57 -0700214 } catch (Throwable t) {
Jeff Sharkeybc9caa12017-03-11 20:38:21 -0700215 final String msg = ExceptionUtils.getCompleteMessage(t);
216 EventLogTags.writeRescueFailure(level, msg);
Todd Kennedy7c4c55d2017-11-02 10:01:39 -0700217 logCriticalInfo(Log.ERROR,
Jeff Sharkeybc9caa12017-03-11 20:38:21 -0700218 "Failed rescue level " + levelToString(level) + ": " + msg);
Jeff Sharkeyfe6f85c2017-01-20 10:42:57 -0700219 }
220 }
221
222 private static void executeRescueLevelInternal(Context context, int level) throws Exception {
Christian Brunschenf86039e2018-12-21 12:26:14 +0000223 StatsLog.write(StatsLog.RESCUE_PARTY_RESET_REPORTED, level);
Jeff Sharkeyfe6f85c2017-01-20 10:42:57 -0700224 switch (level) {
225 case LEVEL_RESET_SETTINGS_UNTRUSTED_DEFAULTS:
226 resetAllSettings(context, Settings.RESET_MODE_UNTRUSTED_DEFAULTS);
227 break;
228 case LEVEL_RESET_SETTINGS_UNTRUSTED_CHANGES:
229 resetAllSettings(context, Settings.RESET_MODE_UNTRUSTED_CHANGES);
230 break;
231 case LEVEL_RESET_SETTINGS_TRUSTED_DEFAULTS:
232 resetAllSettings(context, Settings.RESET_MODE_TRUSTED_DEFAULTS);
233 break;
234 case LEVEL_FACTORY_RESET:
235 RecoverySystem.rebootPromptAndWipeUserData(context, TAG);
236 break;
237 }
bpetrivs62f15982019-02-13 17:18:16 +0000238 FlagNamespaceUtils.addToKnownResetNamespaces(
239 FlagNamespaceUtils.NAMESPACE_NO_PACKAGE);
Jeff Sharkeyfe6f85c2017-01-20 10:42:57 -0700240 }
241
242 private static void resetAllSettings(Context context, int mode) throws Exception {
Jeff Sharkeyfe6f85c2017-01-20 10:42:57 -0700243 // Try our best to reset all settings possible, and once finished
244 // rethrow any exception that we encountered
245 Exception res = null;
246 final ContentResolver resolver = context.getContentResolver();
247 try {
bpetrivs62f15982019-02-13 17:18:16 +0000248 FlagNamespaceUtils.resetDeviceConfig(mode);
249 } catch (Exception e) {
250 res = new RuntimeException("Failed to reset config settings", e);
Jeff Sharkeyfe6f85c2017-01-20 10:42:57 -0700251 }
bpetrivs0254ff62019-03-01 11:50:45 +0000252 try {
253 Settings.Global.resetToDefaultsAsUser(resolver, null, mode, UserHandle.USER_SYSTEM);
254 } catch (Exception e) {
255 res = new RuntimeException("Failed to reset global settings", e);
256 }
Jeff Sharkey82311462017-04-02 23:42:17 -0600257 for (int userId : getAllUserIds()) {
Jeff Sharkeyfe6f85c2017-01-20 10:42:57 -0700258 try {
259 Settings.Secure.resetToDefaultsAsUser(resolver, null, mode, userId);
bpetrivs62f15982019-02-13 17:18:16 +0000260 } catch (Exception e) {
261 res = new RuntimeException("Failed to reset secure settings for " + userId, e);
Jeff Sharkeyfe6f85c2017-01-20 10:42:57 -0700262 }
263 }
264 if (res != null) {
265 throw res;
266 }
267 }
268
269 /**
270 * Threshold that can be triggered if a number of events occur within a
271 * window of time.
272 */
273 private abstract static class Threshold {
274 public abstract int getCount();
275 public abstract void setCount(int count);
276 public abstract long getStart();
277 public abstract void setStart(long start);
278
279 private final int uid;
280 private final int triggerCount;
281 private final long triggerWindow;
282
283 public Threshold(int uid, int triggerCount, long triggerWindow) {
284 this.uid = uid;
285 this.triggerCount = triggerCount;
286 this.triggerWindow = triggerWindow;
287 }
288
289 public void reset() {
290 setCount(0);
291 setStart(0);
292 }
293
294 /**
295 * @return if this threshold has been triggered
296 */
297 public boolean incrementAndTest() {
bpetrivsa7101952019-02-07 16:01:24 +0000298 final long now = getElapsedRealtime();
Jeff Sharkeyfe6f85c2017-01-20 10:42:57 -0700299 final long window = now - getStart();
300 if (window > triggerWindow) {
301 setCount(1);
302 setStart(now);
303 return false;
304 } else {
305 int count = getCount() + 1;
306 setCount(count);
307 EventLogTags.writeRescueNote(uid, count, window);
308 Slog.w(TAG, "Noticed " + count + " events for UID " + uid + " in last "
309 + (window / 1000) + " sec");
310 return (count >= triggerCount);
311 }
312 }
313 }
314
315 /**
316 * Specialization of {@link Threshold} for monitoring boot events. It stores
317 * counters in system properties for robustness.
318 */
319 private static class BootThreshold extends Threshold {
320 public BootThreshold() {
bpetrivsa7101952019-02-07 16:01:24 +0000321 // We're interested in TRIGGER_COUNT events in any
322 // BOOT_TRIGGER_WINDOW_MILLIS second period; this window is super relaxed because
323 // booting can take a long time if forced to dexopt things.
324 super(android.os.Process.ROOT_UID, TRIGGER_COUNT, BOOT_TRIGGER_WINDOW_MILLIS);
Jeff Sharkeyfe6f85c2017-01-20 10:42:57 -0700325 }
326
327 @Override
328 public int getCount() {
329 return SystemProperties.getInt(PROP_RESCUE_BOOT_COUNT, 0);
330 }
331
332 @Override
333 public void setCount(int count) {
334 SystemProperties.set(PROP_RESCUE_BOOT_COUNT, Integer.toString(count));
335 }
336
337 @Override
338 public long getStart() {
339 return SystemProperties.getLong(PROP_RESCUE_BOOT_START, 0);
340 }
341
342 @Override
343 public void setStart(long start) {
344 SystemProperties.set(PROP_RESCUE_BOOT_START, Long.toString(start));
345 }
346 }
347
348 /**
349 * Specialization of {@link Threshold} for monitoring app crashes. It stores
350 * counters in memory.
351 */
352 private static class AppThreshold extends Threshold {
353 private int count;
354 private long start;
355
356 public AppThreshold(int uid) {
bpetrivsa7101952019-02-07 16:01:24 +0000357 // We're interested in TRIGGER_COUNT events in any
358 // PERSISTENT_APP_CRASH_TRIGGER_WINDOW_MILLIS second period; apps crash pretty quickly
359 // so we can keep a tight leash on them.
360 super(uid, TRIGGER_COUNT, PERSISTENT_APP_CRASH_TRIGGER_WINDOW_MILLIS);
Jeff Sharkeyfe6f85c2017-01-20 10:42:57 -0700361 }
362
363 @Override public int getCount() { return count; }
364 @Override public void setCount(int count) { this.count = count; }
365 @Override public long getStart() { return start; }
366 @Override public void setStart(long start) { this.start = start; }
367 }
368
Jeff Sharkey82311462017-04-02 23:42:17 -0600369 private static int[] getAllUserIds() {
Jeff Sharkeyfe6f85c2017-01-20 10:42:57 -0700370 int[] userIds = { UserHandle.USER_SYSTEM };
371 try {
Jeff Sharkey82311462017-04-02 23:42:17 -0600372 for (File file : FileUtils.listFilesOrEmpty(Environment.getDataSystemDeDirectory())) {
373 try {
374 final int userId = Integer.parseInt(file.getName());
375 if (userId != UserHandle.USER_SYSTEM) {
376 userIds = ArrayUtils.appendInt(userIds, userId);
377 }
378 } catch (NumberFormatException ignored) {
Jeff Sharkeyfe6f85c2017-01-20 10:42:57 -0700379 }
380 }
381 } catch (Throwable t) {
382 Slog.w(TAG, "Trouble discovering users", t);
383 }
384 return userIds;
385 }
386
Jeff Sharkeyd9574c72017-02-20 10:45:06 -0700387 /**
388 * Hacky test to check if the device has an active USB connection, which is
Jeff Sharkey1bec4482017-02-23 12:40:54 -0700389 * a good proxy for someone doing local development work.
Jeff Sharkeyd9574c72017-02-20 10:45:06 -0700390 */
391 private static boolean isUsbActive() {
Jeff Sharkey9d640952017-06-26 19:57:16 -0600392 if (SystemProperties.getBoolean(PROP_VIRTUAL_DEVICE, false)) {
393 Slog.v(TAG, "Assuming virtual device is connected over USB");
394 return true;
395 }
Jeff Sharkeyd9574c72017-02-20 10:45:06 -0700396 try {
Jeff Sharkey1bec4482017-02-23 12:40:54 -0700397 final String state = FileUtils
398 .readTextFile(new File("/sys/class/android_usb/android0/state"), 128, "");
399 return "CONFIGURED".equals(state.trim());
Jeff Sharkeyd9574c72017-02-20 10:45:06 -0700400 } catch (Throwable t) {
401 Slog.w(TAG, "Failed to determine if device was on USB", t);
402 return false;
403 }
404 }
405
Jeff Sharkeyfe6f85c2017-01-20 10:42:57 -0700406 private static String levelToString(int level) {
407 switch (level) {
408 case LEVEL_NONE: return "NONE";
409 case LEVEL_RESET_SETTINGS_UNTRUSTED_DEFAULTS: return "RESET_SETTINGS_UNTRUSTED_DEFAULTS";
410 case LEVEL_RESET_SETTINGS_UNTRUSTED_CHANGES: return "RESET_SETTINGS_UNTRUSTED_CHANGES";
411 case LEVEL_RESET_SETTINGS_TRUSTED_DEFAULTS: return "RESET_SETTINGS_TRUSTED_DEFAULTS";
412 case LEVEL_FACTORY_RESET: return "FACTORY_RESET";
413 default: return Integer.toString(level);
414 }
415 }
416}