blob: a9f190c84cca22f5ac69800f10c785e9918725f4 [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;
37
38import com.android.internal.util.ArrayUtils;
Jeff Sharkeybc9caa12017-03-11 20:38:21 -070039import com.android.server.pm.PackageManagerService;
Jeff Sharkeyfe6f85c2017-01-20 10:42:57 -070040
Jeff Sharkey1bec4482017-02-23 12:40:54 -070041import java.io.File;
Jeff Sharkeyd9574c72017-02-20 10:45:06 -070042
Jeff Sharkeyfe6f85c2017-01-20 10:42:57 -070043/**
44 * Utilities to help rescue the system from crash loops. Callers are expected to
45 * report boot events and persistent app crashes, and if they happen frequently
46 * enough this class will slowly escalate through several rescue operations
47 * before finally rebooting and prompting the user if they want to wipe data as
48 * a last resort.
49 *
50 * @hide
51 */
52public class RescueParty {
53 private static final String TAG = "RescueParty";
54
Jeff Sharkeybc9caa12017-03-11 20:38:21 -070055 private static final String PROP_ENABLE_RESCUE = "persist.sys.enable_rescue";
Jeff Sharkey9f1fc2d2017-01-24 11:05:16 -070056 private static final String PROP_DISABLE_RESCUE = "persist.sys.disable_rescue";
Jeff Sharkeyfe6f85c2017-01-20 10:42:57 -070057 private static final String PROP_RESCUE_LEVEL = "sys.rescue_level";
58 private static final String PROP_RESCUE_BOOT_COUNT = "sys.rescue_boot_count";
59 private static final String PROP_RESCUE_BOOT_START = "sys.rescue_boot_start";
Jeff Sharkey9d640952017-06-26 19:57:16 -060060 private static final String PROP_VIRTUAL_DEVICE = "ro.hardware.virtual_device";
Jeff Sharkeyfe6f85c2017-01-20 10:42:57 -070061
62 private static final int LEVEL_NONE = 0;
63 private static final int LEVEL_RESET_SETTINGS_UNTRUSTED_DEFAULTS = 1;
64 private static final int LEVEL_RESET_SETTINGS_UNTRUSTED_CHANGES = 2;
65 private static final int LEVEL_RESET_SETTINGS_TRUSTED_DEFAULTS = 3;
66 private static final int LEVEL_FACTORY_RESET = 4;
67
68 /** Threshold for boot loops */
69 private static final Threshold sBoot = new BootThreshold();
70 /** Threshold for app crash loops */
71 private static SparseArray<Threshold> sApps = new SparseArray<>();
72
Jeff Sharkey9f1fc2d2017-01-24 11:05:16 -070073 private static boolean isDisabled() {
Jeff Sharkeybc9caa12017-03-11 20:38:21 -070074 // Check if we're explicitly enabled for testing
Jeff Sharkey82311462017-04-02 23:42:17 -060075 if (SystemProperties.getBoolean(PROP_ENABLE_RESCUE, false)) {
Jeff Sharkeybc9caa12017-03-11 20:38:21 -070076 return false;
77 }
78
Jeff Sharkeycdee83a2017-01-26 15:29:16 -070079 // We're disabled on all engineering devices
Jeff Sharkeyd9574c72017-02-20 10:45:06 -070080 if (Build.IS_ENG) {
81 Slog.v(TAG, "Disabled because of eng build");
82 return true;
83 }
Jeff Sharkeycdee83a2017-01-26 15:29:16 -070084
85 // We're disabled on userdebug devices connected over USB, since that's
86 // a decent signal that someone is actively trying to debug the device,
87 // or that it's in a lab environment.
Jeff Sharkeyd9574c72017-02-20 10:45:06 -070088 if (Build.IS_USERDEBUG && isUsbActive()) {
89 Slog.v(TAG, "Disabled because of active USB connection");
90 return true;
Jeff Sharkeycdee83a2017-01-26 15:29:16 -070091 }
92
93 // One last-ditch check
Jeff Sharkeyd9574c72017-02-20 10:45:06 -070094 if (SystemProperties.getBoolean(PROP_DISABLE_RESCUE, false)) {
95 Slog.v(TAG, "Disabled because of manual property");
96 return true;
97 }
98
99 return false;
Jeff Sharkey9f1fc2d2017-01-24 11:05:16 -0700100 }
101
Jeff Sharkeyfe6f85c2017-01-20 10:42:57 -0700102 /**
103 * Take note of a boot event. If we notice too many of these events
104 * happening in rapid succession, we'll send out a rescue party.
105 */
106 public static void noteBoot(Context context) {
Jeff Sharkey9f1fc2d2017-01-24 11:05:16 -0700107 if (isDisabled()) return;
Jeff Sharkeyfe6f85c2017-01-20 10:42:57 -0700108 if (sBoot.incrementAndTest()) {
109 sBoot.reset();
110 incrementRescueLevel(sBoot.uid);
111 executeRescueLevel(context);
112 }
113 }
114
115 /**
116 * Take note of a persistent app crash. If we notice too many of these
117 * events happening in rapid succession, we'll send out a rescue party.
118 */
119 public static void notePersistentAppCrash(Context context, int uid) {
Jeff Sharkey9f1fc2d2017-01-24 11:05:16 -0700120 if (isDisabled()) return;
Jeff Sharkeyfe6f85c2017-01-20 10:42:57 -0700121 Threshold t = sApps.get(uid);
122 if (t == null) {
123 t = new AppThreshold(uid);
124 sApps.put(uid, t);
125 }
126 if (t.incrementAndTest()) {
127 t.reset();
128 incrementRescueLevel(t.uid);
129 executeRescueLevel(context);
130 }
131 }
132
133 /**
134 * Check if we're currently attempting to reboot for a factory reset.
135 */
136 public static boolean isAttemptingFactoryReset() {
137 return SystemProperties.getInt(PROP_RESCUE_LEVEL, LEVEL_NONE) == LEVEL_FACTORY_RESET;
138 }
139
140 /**
141 * Escalate to the next rescue level. After incrementing the level you'll
142 * probably want to call {@link #executeRescueLevel(Context)}.
143 */
144 private static void incrementRescueLevel(int triggerUid) {
145 final int level = MathUtils.constrain(
146 SystemProperties.getInt(PROP_RESCUE_LEVEL, LEVEL_NONE) + 1,
147 LEVEL_NONE, LEVEL_FACTORY_RESET);
148 SystemProperties.set(PROP_RESCUE_LEVEL, Integer.toString(level));
149
150 EventLogTags.writeRescueLevel(level, triggerUid);
Todd Kennedy7c4c55d2017-11-02 10:01:39 -0700151 logCriticalInfo(Log.WARN, "Incremented rescue level to "
Jeff Sharkeybc9caa12017-03-11 20:38:21 -0700152 + levelToString(level) + " triggered by UID " + triggerUid);
Jeff Sharkeyfe6f85c2017-01-20 10:42:57 -0700153 }
154
155 /**
156 * Called when {@code SettingsProvider} has been published, which is a good
157 * opportunity to reset any settings depending on our rescue level.
158 */
159 public static void onSettingsProviderPublished(Context context) {
160 executeRescueLevel(context);
161 }
162
163 private static void executeRescueLevel(Context context) {
164 final int level = SystemProperties.getInt(PROP_RESCUE_LEVEL, LEVEL_NONE);
165 if (level == LEVEL_NONE) return;
166
167 Slog.w(TAG, "Attempting rescue level " + levelToString(level));
168 try {
169 executeRescueLevelInternal(context, level);
170 EventLogTags.writeRescueSuccess(level);
Todd Kennedy7c4c55d2017-11-02 10:01:39 -0700171 logCriticalInfo(Log.DEBUG,
Jeff Sharkeybc9caa12017-03-11 20:38:21 -0700172 "Finished rescue level " + levelToString(level));
Jeff Sharkeyfe6f85c2017-01-20 10:42:57 -0700173 } catch (Throwable t) {
Jeff Sharkeybc9caa12017-03-11 20:38:21 -0700174 final String msg = ExceptionUtils.getCompleteMessage(t);
175 EventLogTags.writeRescueFailure(level, msg);
Todd Kennedy7c4c55d2017-11-02 10:01:39 -0700176 logCriticalInfo(Log.ERROR,
Jeff Sharkeybc9caa12017-03-11 20:38:21 -0700177 "Failed rescue level " + levelToString(level) + ": " + msg);
Jeff Sharkeyfe6f85c2017-01-20 10:42:57 -0700178 }
179 }
180
181 private static void executeRescueLevelInternal(Context context, int level) throws Exception {
182 switch (level) {
183 case LEVEL_RESET_SETTINGS_UNTRUSTED_DEFAULTS:
184 resetAllSettings(context, Settings.RESET_MODE_UNTRUSTED_DEFAULTS);
185 break;
186 case LEVEL_RESET_SETTINGS_UNTRUSTED_CHANGES:
187 resetAllSettings(context, Settings.RESET_MODE_UNTRUSTED_CHANGES);
188 break;
189 case LEVEL_RESET_SETTINGS_TRUSTED_DEFAULTS:
190 resetAllSettings(context, Settings.RESET_MODE_TRUSTED_DEFAULTS);
191 break;
192 case LEVEL_FACTORY_RESET:
193 RecoverySystem.rebootPromptAndWipeUserData(context, TAG);
194 break;
195 }
196 }
197
198 private static void resetAllSettings(Context context, int mode) throws Exception {
Jeff Sharkeyfe6f85c2017-01-20 10:42:57 -0700199 // Try our best to reset all settings possible, and once finished
200 // rethrow any exception that we encountered
201 Exception res = null;
202 final ContentResolver resolver = context.getContentResolver();
203 try {
204 Settings.Global.resetToDefaultsAsUser(resolver, null, mode, UserHandle.USER_SYSTEM);
Jeff Sharkeyd9574c72017-02-20 10:45:06 -0700205 } catch (Throwable t) {
206 res = new RuntimeException("Failed to reset global settings", t);
Jeff Sharkeyfe6f85c2017-01-20 10:42:57 -0700207 }
Jeff Sharkey82311462017-04-02 23:42:17 -0600208 for (int userId : getAllUserIds()) {
Jeff Sharkeyfe6f85c2017-01-20 10:42:57 -0700209 try {
210 Settings.Secure.resetToDefaultsAsUser(resolver, null, mode, userId);
Jeff Sharkeyd9574c72017-02-20 10:45:06 -0700211 } catch (Throwable t) {
212 res = new RuntimeException("Failed to reset secure settings for " + userId, t);
Jeff Sharkeyfe6f85c2017-01-20 10:42:57 -0700213 }
214 }
215 if (res != null) {
216 throw res;
217 }
218 }
219
220 /**
221 * Threshold that can be triggered if a number of events occur within a
222 * window of time.
223 */
224 private abstract static class Threshold {
225 public abstract int getCount();
226 public abstract void setCount(int count);
227 public abstract long getStart();
228 public abstract void setStart(long start);
229
230 private final int uid;
231 private final int triggerCount;
232 private final long triggerWindow;
233
234 public Threshold(int uid, int triggerCount, long triggerWindow) {
235 this.uid = uid;
236 this.triggerCount = triggerCount;
237 this.triggerWindow = triggerWindow;
238 }
239
240 public void reset() {
241 setCount(0);
242 setStart(0);
243 }
244
245 /**
246 * @return if this threshold has been triggered
247 */
248 public boolean incrementAndTest() {
249 final long now = SystemClock.elapsedRealtime();
250 final long window = now - getStart();
251 if (window > triggerWindow) {
252 setCount(1);
253 setStart(now);
254 return false;
255 } else {
256 int count = getCount() + 1;
257 setCount(count);
258 EventLogTags.writeRescueNote(uid, count, window);
259 Slog.w(TAG, "Noticed " + count + " events for UID " + uid + " in last "
260 + (window / 1000) + " sec");
261 return (count >= triggerCount);
262 }
263 }
264 }
265
266 /**
267 * Specialization of {@link Threshold} for monitoring boot events. It stores
268 * counters in system properties for robustness.
269 */
270 private static class BootThreshold extends Threshold {
271 public BootThreshold() {
272 // We're interested in 5 events in any 300 second period; this
273 // window is super relaxed because booting can take a long time if
274 // forced to dexopt things.
275 super(android.os.Process.ROOT_UID, 5, 300 * DateUtils.SECOND_IN_MILLIS);
276 }
277
278 @Override
279 public int getCount() {
280 return SystemProperties.getInt(PROP_RESCUE_BOOT_COUNT, 0);
281 }
282
283 @Override
284 public void setCount(int count) {
285 SystemProperties.set(PROP_RESCUE_BOOT_COUNT, Integer.toString(count));
286 }
287
288 @Override
289 public long getStart() {
290 return SystemProperties.getLong(PROP_RESCUE_BOOT_START, 0);
291 }
292
293 @Override
294 public void setStart(long start) {
295 SystemProperties.set(PROP_RESCUE_BOOT_START, Long.toString(start));
296 }
297 }
298
299 /**
300 * Specialization of {@link Threshold} for monitoring app crashes. It stores
301 * counters in memory.
302 */
303 private static class AppThreshold extends Threshold {
304 private int count;
305 private long start;
306
307 public AppThreshold(int uid) {
308 // We're interested in 5 events in any 30 second period; apps crash
309 // pretty quickly so we can keep a tight leash on them.
310 super(uid, 5, 30 * DateUtils.SECOND_IN_MILLIS);
311 }
312
313 @Override public int getCount() { return count; }
314 @Override public void setCount(int count) { this.count = count; }
315 @Override public long getStart() { return start; }
316 @Override public void setStart(long start) { this.start = start; }
317 }
318
Jeff Sharkey82311462017-04-02 23:42:17 -0600319 private static int[] getAllUserIds() {
Jeff Sharkeyfe6f85c2017-01-20 10:42:57 -0700320 int[] userIds = { UserHandle.USER_SYSTEM };
321 try {
Jeff Sharkey82311462017-04-02 23:42:17 -0600322 for (File file : FileUtils.listFilesOrEmpty(Environment.getDataSystemDeDirectory())) {
323 try {
324 final int userId = Integer.parseInt(file.getName());
325 if (userId != UserHandle.USER_SYSTEM) {
326 userIds = ArrayUtils.appendInt(userIds, userId);
327 }
328 } catch (NumberFormatException ignored) {
Jeff Sharkeyfe6f85c2017-01-20 10:42:57 -0700329 }
330 }
331 } catch (Throwable t) {
332 Slog.w(TAG, "Trouble discovering users", t);
333 }
334 return userIds;
335 }
336
Jeff Sharkeyd9574c72017-02-20 10:45:06 -0700337 /**
338 * Hacky test to check if the device has an active USB connection, which is
Jeff Sharkey1bec4482017-02-23 12:40:54 -0700339 * a good proxy for someone doing local development work.
Jeff Sharkeyd9574c72017-02-20 10:45:06 -0700340 */
341 private static boolean isUsbActive() {
Jeff Sharkey9d640952017-06-26 19:57:16 -0600342 if (SystemProperties.getBoolean(PROP_VIRTUAL_DEVICE, false)) {
343 Slog.v(TAG, "Assuming virtual device is connected over USB");
344 return true;
345 }
Jeff Sharkeyd9574c72017-02-20 10:45:06 -0700346 try {
Jeff Sharkey1bec4482017-02-23 12:40:54 -0700347 final String state = FileUtils
348 .readTextFile(new File("/sys/class/android_usb/android0/state"), 128, "");
349 return "CONFIGURED".equals(state.trim());
Jeff Sharkeyd9574c72017-02-20 10:45:06 -0700350 } catch (Throwable t) {
351 Slog.w(TAG, "Failed to determine if device was on USB", t);
352 return false;
353 }
354 }
355
Jeff Sharkeyfe6f85c2017-01-20 10:42:57 -0700356 private static String levelToString(int level) {
357 switch (level) {
358 case LEVEL_NONE: return "NONE";
359 case LEVEL_RESET_SETTINGS_UNTRUSTED_DEFAULTS: return "RESET_SETTINGS_UNTRUSTED_DEFAULTS";
360 case LEVEL_RESET_SETTINGS_UNTRUSTED_CHANGES: return "RESET_SETTINGS_UNTRUSTED_CHANGES";
361 case LEVEL_RESET_SETTINGS_TRUSTED_DEFAULTS: return "RESET_SETTINGS_TRUSTED_DEFAULTS";
362 case LEVEL_FACTORY_RESET: return "FACTORY_RESET";
363 default: return Integer.toString(level);
364 }
365 }
366}