1 /* 2 * Copyright (C) 2020 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.server.rollback; 18 19 import static com.android.server.crashrecovery.proto.CrashRecoveryStatsLog.WATCHDOG_ROLLBACK_OCCURRED__ROLLBACK_REASON__REASON_APP_CRASH; 20 import static com.android.server.crashrecovery.proto.CrashRecoveryStatsLog.WATCHDOG_ROLLBACK_OCCURRED__ROLLBACK_REASON__REASON_APP_NOT_RESPONDING; 21 import static com.android.server.crashrecovery.proto.CrashRecoveryStatsLog.WATCHDOG_ROLLBACK_OCCURRED__ROLLBACK_REASON__REASON_BOOT_LOOPING; 22 import static com.android.server.crashrecovery.proto.CrashRecoveryStatsLog.WATCHDOG_ROLLBACK_OCCURRED__ROLLBACK_REASON__REASON_EXPLICIT_HEALTH_CHECK; 23 import static com.android.server.crashrecovery.proto.CrashRecoveryStatsLog.WATCHDOG_ROLLBACK_OCCURRED__ROLLBACK_REASON__REASON_NATIVE_CRASH; 24 import static com.android.server.crashrecovery.proto.CrashRecoveryStatsLog.WATCHDOG_ROLLBACK_OCCURRED__ROLLBACK_REASON__REASON_NATIVE_CRASH_DURING_BOOT; 25 import static com.android.server.crashrecovery.proto.CrashRecoveryStatsLog.WATCHDOG_ROLLBACK_OCCURRED__ROLLBACK_REASON__REASON_UNKNOWN; 26 import static com.android.server.crashrecovery.proto.CrashRecoveryStatsLog.WATCHDOG_ROLLBACK_OCCURRED__ROLLBACK_TYPE__ROLLBACK_BOOT_TRIGGERED; 27 import static com.android.server.crashrecovery.proto.CrashRecoveryStatsLog.WATCHDOG_ROLLBACK_OCCURRED__ROLLBACK_TYPE__ROLLBACK_FAILURE; 28 import static com.android.server.crashrecovery.proto.CrashRecoveryStatsLog.WATCHDOG_ROLLBACK_OCCURRED__ROLLBACK_TYPE__ROLLBACK_INITIATE; 29 import static com.android.server.crashrecovery.proto.CrashRecoveryStatsLog.WATCHDOG_ROLLBACK_OCCURRED__ROLLBACK_TYPE__ROLLBACK_SUCCESS; 30 31 import android.annotation.NonNull; 32 import android.annotation.Nullable; 33 import android.content.Context; 34 import android.content.pm.ApplicationInfo; 35 import android.content.pm.PackageInstaller; 36 import android.content.pm.PackageManager; 37 import android.content.pm.VersionedPackage; 38 import android.content.rollback.PackageRollbackInfo; 39 import android.content.rollback.RollbackInfo; 40 import android.os.SystemProperties; 41 import android.text.TextUtils; 42 import android.util.ArraySet; 43 import android.util.Slog; 44 45 import com.android.internal.annotations.VisibleForTesting; 46 import com.android.server.PackageWatchdog; 47 import com.android.server.crashrecovery.proto.CrashRecoveryStatsLog; 48 49 import java.util.List; 50 import java.util.Set; 51 52 /** 53 * This class handles the logic for logging Watchdog-triggered rollback events. 54 */ 55 public final class WatchdogRollbackLogger { 56 private static final String TAG = "WatchdogRollbackLogger"; 57 58 private static final String LOGGING_PARENT_KEY = "android.content.pm.LOGGING_PARENT"; 59 WatchdogRollbackLogger()60 private WatchdogRollbackLogger() { 61 } 62 63 @Nullable getLoggingParentName(Context context, @NonNull String packageName)64 private static String getLoggingParentName(Context context, @NonNull String packageName) { 65 PackageManager packageManager = context.getPackageManager(); 66 try { 67 int flags = PackageManager.MATCH_APEX | PackageManager.GET_META_DATA; 68 ApplicationInfo ai = packageManager.getPackageInfo(packageName, flags).applicationInfo; 69 if (ai.metaData == null) { 70 return null; 71 } 72 return ai.metaData.getString(LOGGING_PARENT_KEY); 73 } catch (Exception e) { 74 Slog.w(TAG, "Unable to discover logging parent package: " + packageName, e); 75 return null; 76 } 77 } 78 79 /** 80 * Returns the logging parent of a given package if it exists, {@code null} otherwise. 81 * 82 * The logging parent is defined by the {@code android.content.pm.LOGGING_PARENT} field in the 83 * metadata of a package's AndroidManifest.xml. 84 */ 85 @VisibleForTesting 86 @Nullable getLogPackage(Context context, @NonNull VersionedPackage failingPackage)87 static VersionedPackage getLogPackage(Context context, 88 @NonNull VersionedPackage failingPackage) { 89 String logPackageName; 90 VersionedPackage loggingParent; 91 logPackageName = getLoggingParentName(context, failingPackage.getPackageName()); 92 if (logPackageName == null) { 93 return null; 94 } 95 try { 96 loggingParent = new VersionedPackage(logPackageName, context.getPackageManager() 97 .getPackageInfo(logPackageName, 0 /* flags */).getLongVersionCode()); 98 } catch (PackageManager.NameNotFoundException e) { 99 return null; 100 } 101 return loggingParent; 102 } 103 104 105 /** 106 * Gets the set of parent packages for a given set of failed package names. In the case that 107 * multiple sessions have failed, we want to log failure for each of the parent packages. 108 * Even if multiple failed packages have the same parent, we only log the parent package once. 109 */ getLogPackages(Context context, @NonNull List<String> failedPackageNames)110 private static Set<VersionedPackage> getLogPackages(Context context, 111 @NonNull List<String> failedPackageNames) { 112 Set<VersionedPackage> parentPackages = new ArraySet<>(); 113 for (String failedPackageName: failedPackageNames) { 114 parentPackages.add(getLogPackage(context, new VersionedPackage(failedPackageName, 0))); 115 } 116 return parentPackages; 117 } 118 119 logRollbackStatusOnBoot(Context context, int rollbackId, String logPackageName, List<RollbackInfo> recentlyCommittedRollbacks)120 static void logRollbackStatusOnBoot(Context context, int rollbackId, String logPackageName, 121 List<RollbackInfo> recentlyCommittedRollbacks) { 122 PackageInstaller packageInstaller = context.getPackageManager().getPackageInstaller(); 123 124 RollbackInfo rollback = null; 125 for (RollbackInfo info : recentlyCommittedRollbacks) { 126 if (rollbackId == info.getRollbackId()) { 127 rollback = info; 128 break; 129 } 130 } 131 132 if (rollback == null) { 133 Slog.e(TAG, "rollback info not found for last staged rollback: " + rollbackId); 134 return; 135 } 136 137 // Use the version of the logging parent that was installed before 138 // we rolled back for logging purposes. 139 VersionedPackage oldLoggingPackage = null; 140 if (!TextUtils.isEmpty(logPackageName)) { 141 for (PackageRollbackInfo packageRollback : rollback.getPackages()) { 142 if (logPackageName.equals(packageRollback.getPackageName())) { 143 oldLoggingPackage = packageRollback.getVersionRolledBackFrom(); 144 break; 145 } 146 } 147 } 148 149 int sessionId = rollback.getCommittedSessionId(); 150 PackageInstaller.SessionInfo sessionInfo = packageInstaller.getSessionInfo(sessionId); 151 if (sessionInfo == null) { 152 Slog.e(TAG, "On boot completed, could not load session id " + sessionId); 153 return; 154 } 155 156 if (sessionInfo.isStagedSessionApplied()) { 157 logEvent(oldLoggingPackage, 158 WATCHDOG_ROLLBACK_OCCURRED__ROLLBACK_TYPE__ROLLBACK_SUCCESS, 159 WATCHDOG_ROLLBACK_OCCURRED__ROLLBACK_REASON__REASON_UNKNOWN, ""); 160 } else if (sessionInfo.isStagedSessionFailed()) { 161 logEvent(oldLoggingPackage, 162 WATCHDOG_ROLLBACK_OCCURRED__ROLLBACK_TYPE__ROLLBACK_FAILURE, 163 WATCHDOG_ROLLBACK_OCCURRED__ROLLBACK_REASON__REASON_UNKNOWN, ""); 164 } 165 } 166 167 /** 168 * Logs that one or more apexd reverts have occurred, along with the crashing native process 169 * that caused apexd to revert during boot. 170 * 171 * @param context the context to use when determining the log packages 172 * @param failedPackageNames a list of names of packages which were reverted 173 * @param failingNativeProcess the crashing native process which caused a revert 174 */ logApexdRevert(Context context, @NonNull List<String> failedPackageNames, @NonNull String failingNativeProcess)175 public static void logApexdRevert(Context context, @NonNull List<String> failedPackageNames, 176 @NonNull String failingNativeProcess) { 177 Set<VersionedPackage> logPackages = getLogPackages(context, failedPackageNames); 178 for (VersionedPackage logPackage: logPackages) { 179 logEvent(logPackage, 180 WATCHDOG_ROLLBACK_OCCURRED__ROLLBACK_TYPE__ROLLBACK_SUCCESS, 181 WATCHDOG_ROLLBACK_OCCURRED__ROLLBACK_REASON__REASON_NATIVE_CRASH_DURING_BOOT, 182 failingNativeProcess); 183 } 184 } 185 186 /** 187 * Log a Watchdog rollback event to statsd. 188 * 189 * @param logPackage the package to associate the rollback with. 190 * @param type the state of the rollback. 191 * @param rollbackReason the reason Watchdog triggered a rollback, if known. 192 * @param failingPackageName the failing package or process which triggered the rollback. 193 */ logEvent(@ullable VersionedPackage logPackage, int type, int rollbackReason, @NonNull String failingPackageName)194 public static void logEvent(@Nullable VersionedPackage logPackage, int type, 195 int rollbackReason, @NonNull String failingPackageName) { 196 Slog.i(TAG, "Watchdog event occurred with type: " + rollbackTypeToString(type) 197 + " logPackage: " + logPackage 198 + " rollbackReason: " + rollbackReasonToString(rollbackReason) 199 + " failedPackageName: " + failingPackageName); 200 if (logPackage != null) { 201 CrashRecoveryStatsLog.write( 202 CrashRecoveryStatsLog.WATCHDOG_ROLLBACK_OCCURRED, 203 type, 204 logPackage.getPackageName(), 205 logPackage.getVersionCode(), 206 rollbackReason, 207 failingPackageName, 208 new byte[]{}); 209 } else { 210 // In the case that the log package is null, still log an empty string as an 211 // indication that retrieving the logging parent failed. 212 CrashRecoveryStatsLog.write( 213 CrashRecoveryStatsLog.WATCHDOG_ROLLBACK_OCCURRED, 214 type, 215 "", 216 0, 217 rollbackReason, 218 failingPackageName, 219 new byte[]{}); 220 } 221 222 logTestProperties(logPackage, type, rollbackReason, failingPackageName); 223 } 224 225 /** 226 * Writes properties which will be used by rollback tests to check if particular rollback 227 * events have occurred. 228 * 229 * persist.sys.rollbacktest.enabled: true if rollback tests are running 230 * persist.sys.rollbacktest.EVENT_TYPE: true if a particular rollback event has occurred 231 * ex: persist.sys.rollbacktest.ROLLBACK_INITIATE is true if ROLLBACK_INITIATE has happened 232 * persist.sys.rollbacktest.EVENT_TYPE.logPackage: the package to associate the rollback with 233 * persist.sys.rollbacktest.EVENT_TYPE.rollbackReason: the reason Watchdog triggered a rollback 234 * persist.sys.rollbacktest.EVENT_TYPE.failedPackageName: the failing package or process which 235 * triggered the rollback 236 */ logTestProperties(@ullable VersionedPackage logPackage, int type, int rollbackReason, @NonNull String failingPackageName)237 private static void logTestProperties(@Nullable VersionedPackage logPackage, int type, 238 int rollbackReason, @NonNull String failingPackageName) { 239 // This property should be on only during the tests 240 final String prefix = "persist.sys.rollbacktest."; 241 if (!SystemProperties.getBoolean(prefix + "enabled", false)) { 242 return; 243 } 244 String key = prefix + rollbackTypeToString(type); 245 SystemProperties.set(key, String.valueOf(true)); 246 SystemProperties.set(key + ".logPackage", logPackage != null ? logPackage.toString() : ""); 247 SystemProperties.set(key + ".rollbackReason", rollbackReasonToString(rollbackReason)); 248 SystemProperties.set(key + ".failedPackageName", failingPackageName); 249 } 250 251 @VisibleForTesting mapFailureReasonToMetric(@ackageWatchdog.FailureReasons int failureReason)252 static int mapFailureReasonToMetric(@PackageWatchdog.FailureReasons int failureReason) { 253 switch (failureReason) { 254 case PackageWatchdog.FAILURE_REASON_NATIVE_CRASH: 255 return WATCHDOG_ROLLBACK_OCCURRED__ROLLBACK_REASON__REASON_NATIVE_CRASH; 256 case PackageWatchdog.FAILURE_REASON_EXPLICIT_HEALTH_CHECK: 257 return WATCHDOG_ROLLBACK_OCCURRED__ROLLBACK_REASON__REASON_EXPLICIT_HEALTH_CHECK; 258 case PackageWatchdog.FAILURE_REASON_APP_CRASH: 259 return WATCHDOG_ROLLBACK_OCCURRED__ROLLBACK_REASON__REASON_APP_CRASH; 260 case PackageWatchdog.FAILURE_REASON_APP_NOT_RESPONDING: 261 return WATCHDOG_ROLLBACK_OCCURRED__ROLLBACK_REASON__REASON_APP_NOT_RESPONDING; 262 case PackageWatchdog.FAILURE_REASON_BOOT_LOOP: 263 return WATCHDOG_ROLLBACK_OCCURRED__ROLLBACK_REASON__REASON_BOOT_LOOPING; 264 default: 265 return WATCHDOG_ROLLBACK_OCCURRED__ROLLBACK_REASON__REASON_UNKNOWN; 266 } 267 } 268 rollbackTypeToString(int type)269 private static String rollbackTypeToString(int type) { 270 switch (type) { 271 case WATCHDOG_ROLLBACK_OCCURRED__ROLLBACK_TYPE__ROLLBACK_INITIATE: 272 return "ROLLBACK_INITIATE"; 273 case WATCHDOG_ROLLBACK_OCCURRED__ROLLBACK_TYPE__ROLLBACK_SUCCESS: 274 return "ROLLBACK_SUCCESS"; 275 case WATCHDOG_ROLLBACK_OCCURRED__ROLLBACK_TYPE__ROLLBACK_FAILURE: 276 return "ROLLBACK_FAILURE"; 277 case WATCHDOG_ROLLBACK_OCCURRED__ROLLBACK_TYPE__ROLLBACK_BOOT_TRIGGERED: 278 return "ROLLBACK_BOOT_TRIGGERED"; 279 default: 280 return "UNKNOWN"; 281 } 282 } 283 rollbackReasonToString(int reason)284 private static String rollbackReasonToString(int reason) { 285 switch (reason) { 286 case WATCHDOG_ROLLBACK_OCCURRED__ROLLBACK_REASON__REASON_NATIVE_CRASH: 287 return "REASON_NATIVE_CRASH"; 288 case WATCHDOG_ROLLBACK_OCCURRED__ROLLBACK_REASON__REASON_EXPLICIT_HEALTH_CHECK: 289 return "REASON_EXPLICIT_HEALTH_CHECK"; 290 case WATCHDOG_ROLLBACK_OCCURRED__ROLLBACK_REASON__REASON_APP_CRASH: 291 return "REASON_APP_CRASH"; 292 case WATCHDOG_ROLLBACK_OCCURRED__ROLLBACK_REASON__REASON_APP_NOT_RESPONDING: 293 return "REASON_APP_NOT_RESPONDING"; 294 case WATCHDOG_ROLLBACK_OCCURRED__ROLLBACK_REASON__REASON_NATIVE_CRASH_DURING_BOOT: 295 return "REASON_NATIVE_CRASH_DURING_BOOT"; 296 case WATCHDOG_ROLLBACK_OCCURRED__ROLLBACK_REASON__REASON_BOOT_LOOPING: 297 return "REASON_BOOT_LOOP"; 298 default: 299 return "UNKNOWN"; 300 } 301 } 302 } 303