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