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