• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
1 /*
2  * Copyright (C) 2024 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;
20 import static com.android.server.crashrecovery.proto.CrashRecoveryStatsLog.WATCHDOG_ROLLBACK_OCCURRED__ROLLBACK_REASON__REASON_NATIVE_CRASH_DURING_BOOT;
21 import static com.android.server.crashrecovery.proto.CrashRecoveryStatsLog.WATCHDOG_ROLLBACK_OCCURRED__ROLLBACK_TYPE__ROLLBACK_SUCCESS;
22 
23 import android.annotation.NonNull;
24 import android.annotation.Nullable;
25 import android.content.Context;
26 import android.content.pm.ApplicationInfo;
27 import android.content.pm.PackageManager;
28 import android.content.pm.VersionedPackage;
29 import android.os.SystemProperties;
30 import android.util.ArraySet;
31 import android.util.Slog;
32 
33 import com.android.internal.annotations.VisibleForTesting;
34 import com.android.server.crashrecovery.proto.CrashRecoveryStatsLog;
35 
36 import java.util.List;
37 import java.util.Set;
38 
39 /**
40  * This class handles the logic for logging Apexd-triggered rollback events.
41  * TODO: b/354112511 Refactor to have a separate metric for ApexdReverts
42  */
43 public final class ApexdRevertLogger {
44     private static final String TAG = "WatchdogRollbackLogger";
45 
46     private static final String LOGGING_PARENT_KEY = "android.content.pm.LOGGING_PARENT";
47 
48     /**
49      * Logs that one or more apexd reverts have occurred, along with the crashing native process
50      * that caused apexd to revert during boot.
51      *
52      * @param context the context to use when determining the log packages
53      * @param failedPackageNames a list of names of packages which were reverted
54      * @param failingNativeProcess the crashing native process which caused a revert
55      */
logApexdRevert(Context context, @NonNull List<String> failedPackageNames, @NonNull String failingNativeProcess)56     public static void logApexdRevert(Context context, @NonNull List<String> failedPackageNames,
57             @NonNull String failingNativeProcess) {
58         Set<VersionedPackage> logPackages = getLogPackages(context, failedPackageNames);
59         for (VersionedPackage logPackage: logPackages) {
60             logEvent(logPackage,
61                     failingNativeProcess);
62         }
63     }
64 
65     /**
66      * Gets the set of parent packages for a given set of failed package names. In the case that
67      * multiple sessions have failed, we want to log failure for each of the parent packages.
68      * Even if multiple failed packages have the same parent, we only log the parent package once.
69      */
getLogPackages(Context context, @NonNull List<String> failedPackageNames)70     private static Set<VersionedPackage> getLogPackages(Context context,
71             @NonNull List<String> failedPackageNames) {
72         Set<VersionedPackage> parentPackages = new ArraySet<>();
73         for (String failedPackageName: failedPackageNames) {
74             parentPackages.add(getLogPackage(context, new VersionedPackage(failedPackageName, 0)));
75         }
76         return parentPackages;
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     private 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     @Nullable
getLoggingParentName(Context context, @NonNull String packageName)105     private static String getLoggingParentName(Context context, @NonNull String packageName) {
106         PackageManager packageManager = context.getPackageManager();
107         try {
108             int flags = PackageManager.MATCH_APEX | PackageManager.GET_META_DATA;
109             ApplicationInfo ai = packageManager.getPackageInfo(packageName, flags).applicationInfo;
110             if (ai == null || ai.metaData == null) {
111                 return null;
112             }
113             return ai.metaData.getString(LOGGING_PARENT_KEY);
114         } catch (Exception e) {
115             Slog.w(TAG, "Unable to discover logging parent package: " + packageName, e);
116             return null;
117         }
118     }
119 
120     /**
121      * Log a Apexd rollback event to statsd.
122      *
123      * @param logPackage         the package to associate the rollback with.
124      * @param failingPackageName the failing package or process which triggered the rollback.
125      */
logEvent(@ullable VersionedPackage logPackage, @NonNull String failingPackageName)126     private static void logEvent(@Nullable VersionedPackage logPackage,
127             @NonNull String failingPackageName) {
128         Slog.i(TAG, "Watchdog event occurred with type: ROLLBACK_SUCCESS"
129                 + " logPackage: " + logPackage
130                 + " rollbackReason: REASON_NATIVE_CRASH_DURING_BOOT"
131                 + " failedPackageName: " + failingPackageName);
132         CrashRecoveryStatsLog.write(
133                 WATCHDOG_ROLLBACK_OCCURRED,
134                 WATCHDOG_ROLLBACK_OCCURRED__ROLLBACK_TYPE__ROLLBACK_SUCCESS,
135                 (logPackage != null) ? logPackage.getPackageName() : "",
136                 (logPackage != null) ? logPackage.getVersionCode() : 0,
137                 WATCHDOG_ROLLBACK_OCCURRED__ROLLBACK_REASON__REASON_NATIVE_CRASH_DURING_BOOT,
138                 failingPackageName,
139                 new byte[]{});
140 
141         logTestProperties(logPackage, failingPackageName);
142     }
143 
144     /**
145      * Writes properties which will be used by rollback tests to check if rollback has occurred
146      * have occurred.
147      *
148      * persist.sys.rollbacktest.enabled: true if rollback tests are running
149      * persist.sys.rollbacktest.ROLLBACK_SUCCESS.logPackage: the package to associate the rollback
150      * persist.sys.rollbacktest.ROLLBACK_SUCCESS.rollbackReason: the reason Apexd triggered it
151      * persist.sys.rollbacktest.ROLLBACK_SUCCESS.failedPackageName: the failing package or process
152      * which triggered the rollback
153      */
logTestProperties(@ullable VersionedPackage logPackage, @NonNull String failingPackageName)154     private static void logTestProperties(@Nullable VersionedPackage logPackage,
155             @NonNull String failingPackageName) {
156         // This property should be on only during the tests
157         final String prefix = "persist.sys.rollbacktest.";
158         if (!SystemProperties.getBoolean(prefix + "enabled", false)) {
159             return;
160         }
161         String key = prefix +  "ROLLBACK_SUCCESS";
162         SystemProperties.set(key, String.valueOf(true));
163         SystemProperties.set(key + ".logPackage", logPackage != null ? logPackage.toString() : "");
164         SystemProperties.set(key + ".rollbackReason", "REASON_NATIVE_CRASH_DURING_BOOT");
165         SystemProperties.set(key + ".failedPackageName", failingPackageName);
166     }
167 }
168