1 /* 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 17 package com.android.server.backup.utils; 18 19 import static com.android.server.backup.BackupManagerService.MORE_DEBUG; 20 import static com.android.server.backup.BackupManagerService.SHARED_BACKUP_AGENT_PACKAGE; 21 import static com.android.server.backup.BackupManagerService.TAG; 22 23 import android.annotation.Nullable; 24 import android.app.backup.BackupTransport; 25 import android.content.pm.ApplicationInfo; 26 import android.content.pm.PackageInfo; 27 import android.content.pm.PackageManager; 28 import android.content.pm.PackageManagerInternal; 29 import android.content.pm.Signature; 30 import android.content.pm.SigningInfo; 31 import android.os.Process; 32 import android.util.Slog; 33 34 import com.android.internal.backup.IBackupTransport; 35 import com.android.internal.util.ArrayUtils; 36 import com.android.server.backup.transport.TransportClient; 37 38 /** 39 * Utility methods wrapping operations on ApplicationInfo and PackageInfo. 40 */ 41 public class AppBackupUtils { 42 43 private static final boolean DEBUG = false; 44 45 /** 46 * Returns whether app is eligible for backup. 47 * 48 * High level policy: apps are generally ineligible for backup if certain conditions apply. The 49 * conditions are: 50 * 51 * <ol> 52 * <li>their manifest states android:allowBackup="false" 53 * <li>they run as a system-level uid but do not supply their own backup agent 54 * <li>it is the special shared-storage backup package used for 'adb backup' 55 * </ol> 56 */ appIsEligibleForBackup(ApplicationInfo app, PackageManager pm)57 public static boolean appIsEligibleForBackup(ApplicationInfo app, PackageManager pm) { 58 // 1. their manifest states android:allowBackup="false" 59 if ((app.flags & ApplicationInfo.FLAG_ALLOW_BACKUP) == 0) { 60 return false; 61 } 62 63 // 2. they run as a system-level uid but do not supply their own backup agent 64 if ((app.uid < Process.FIRST_APPLICATION_UID) && (app.backupAgentName == null)) { 65 return false; 66 } 67 68 // 3. it is the special shared-storage backup package used for 'adb backup' 69 if (app.packageName.equals(SHARED_BACKUP_AGENT_PACKAGE)) { 70 return false; 71 } 72 73 // 4. it is an "instant" app 74 if (app.isInstantApp()) { 75 return false; 76 } 77 78 // Everything else checks out; the only remaining roadblock would be if the 79 // package were disabled 80 return !appIsDisabled(app, pm); 81 } 82 83 /** 84 * Returns whether an app is eligible for backup at runtime. That is, the app has to: 85 * <ol> 86 * <li>Return true for {@link #appIsEligibleForBackup(ApplicationInfo, PackageManager)} 87 * <li>Return false for {@link #appIsStopped(ApplicationInfo)} 88 * <li>Return false for {@link #appIsDisabled(ApplicationInfo, PackageManager)} 89 * <li>Be eligible for the transport via 90 * {@link BackupTransport#isAppEligibleForBackup(PackageInfo, boolean)} 91 * </ol> 92 */ appIsRunningAndEligibleForBackupWithTransport( @ullable TransportClient transportClient, String packageName, PackageManager pm)93 public static boolean appIsRunningAndEligibleForBackupWithTransport( 94 @Nullable TransportClient transportClient, String packageName, PackageManager pm) { 95 try { 96 PackageInfo packageInfo = pm.getPackageInfo(packageName, 97 PackageManager.GET_SIGNING_CERTIFICATES); 98 ApplicationInfo applicationInfo = packageInfo.applicationInfo; 99 if (!appIsEligibleForBackup(applicationInfo, pm) 100 || appIsStopped(applicationInfo) 101 || appIsDisabled(applicationInfo, pm)) { 102 return false; 103 } 104 if (transportClient != null) { 105 try { 106 IBackupTransport transport = 107 transportClient.connectOrThrow( 108 "AppBackupUtils.appIsEligibleForBackupAtRuntime"); 109 return transport.isAppEligibleForBackup( 110 packageInfo, AppBackupUtils.appGetsFullBackup(packageInfo)); 111 } catch (Exception e) { 112 Slog.e(TAG, "Unable to ask about eligibility: " + e.getMessage()); 113 } 114 } 115 // If transport is not present we couldn't tell that the package is not eligible. 116 return true; 117 } catch (PackageManager.NameNotFoundException e) { 118 return false; 119 } 120 } 121 122 /** Avoid backups of 'disabled' apps. */ appIsDisabled(ApplicationInfo app, PackageManager pm)123 public static boolean appIsDisabled(ApplicationInfo app, PackageManager pm) { 124 switch (pm.getApplicationEnabledSetting(app.packageName)) { 125 case PackageManager.COMPONENT_ENABLED_STATE_DISABLED: 126 case PackageManager.COMPONENT_ENABLED_STATE_DISABLED_USER: 127 case PackageManager.COMPONENT_ENABLED_STATE_DISABLED_UNTIL_USED: 128 return true; 129 130 default: 131 return false; 132 } 133 } 134 135 /** 136 * Checks if the app is in a stopped state. This is not part of the general "eligible for 137 * backup?" check because we *do* still need to restore data to apps in this state (e.g. 138 * newly-installing ones) 139 */ appIsStopped(ApplicationInfo app)140 public static boolean appIsStopped(ApplicationInfo app) { 141 return ((app.flags & ApplicationInfo.FLAG_STOPPED) != 0); 142 } 143 144 /** 145 * Returns whether the app can get full backup. Does *not* check overall backup eligibility 146 * policy! 147 */ appGetsFullBackup(PackageInfo pkg)148 public static boolean appGetsFullBackup(PackageInfo pkg) { 149 if (pkg.applicationInfo.backupAgentName != null) { 150 // If it has an agent, it gets full backups only if it says so 151 return (pkg.applicationInfo.flags & ApplicationInfo.FLAG_FULL_BACKUP_ONLY) != 0; 152 } 153 154 // No agent or fullBackupOnly="true" means we do indeed perform full-data backups for it 155 return true; 156 } 157 158 /** 159 * Returns whether the app is only capable of doing key/value. We say it's not if it allows full 160 * backup, and it is otherwise. 161 */ appIsKeyValueOnly(PackageInfo pkg)162 public static boolean appIsKeyValueOnly(PackageInfo pkg) { 163 return !appGetsFullBackup(pkg); 164 } 165 166 /** 167 * Returns whether the signatures stored {@param storedSigs}, coming from the source apk, match 168 * the signatures of the apk installed on the device, the target apk. If the target resides in 169 * the system partition we return true. Otherwise it's considered a match if both conditions 170 * hold: 171 * 172 * <ul> 173 * <li>Source and target have at least one signature each 174 * <li>Target contains all signatures in source, and nothing more 175 * </ul> 176 * 177 * or if both source and target have exactly one signature, and they don't match, we check 178 * if the app was ever signed with source signature (i.e. app has rotated key) 179 * Note: key rotation is only supported for apps ever signed with one key, and those apps will 180 * not be allowed to be signed by more certificates in the future 181 * 182 * Note that if {@param target} is null we return false. 183 */ signaturesMatch(Signature[] storedSigs, PackageInfo target, PackageManagerInternal pmi)184 public static boolean signaturesMatch(Signature[] storedSigs, PackageInfo target, 185 PackageManagerInternal pmi) { 186 if (target == null || target.packageName == null) { 187 return false; 188 } 189 190 // If the target resides on the system partition, we allow it to restore 191 // data from the like-named package in a restore set even if the signatures 192 // do not match. (Unlike general applications, those flashed to the system 193 // partition will be signed with the device's platform certificate, so on 194 // different phones the same system app will have different signatures.) 195 if ((target.applicationInfo.flags & ApplicationInfo.FLAG_SYSTEM) != 0) { 196 if (MORE_DEBUG) { 197 Slog.v(TAG, "System app " + target.packageName + " - skipping sig check"); 198 } 199 return true; 200 } 201 202 // Don't allow unsigned apps on either end 203 if (ArrayUtils.isEmpty(storedSigs)) { 204 return false; 205 } 206 207 SigningInfo signingInfo = target.signingInfo; 208 if (signingInfo == null) { 209 Slog.w(TAG, "signingInfo is empty, app was either unsigned or the flag" + 210 " PackageManager#GET_SIGNING_CERTIFICATES was not specified"); 211 return false; 212 } 213 214 if (DEBUG) { 215 Slog.v(TAG, "signaturesMatch(): stored=" + storedSigs + " device=" 216 + signingInfo.getApkContentsSigners()); 217 } 218 219 final int nStored = storedSigs.length; 220 if (nStored == 1) { 221 // if the app is only signed with one sig, it's possible it has rotated its key 222 // (the checks with signing history are delegated to PackageManager) 223 // TODO(b/73988180): address the case that app has declared restoreAnyVersion and is 224 // restoring from higher version to lower after having rotated the key (i.e. higher 225 // version has different sig than lower version that we want to restore to) 226 return pmi.isDataRestoreSafe(storedSigs[0], target.packageName); 227 } else { 228 // the app couldn't have rotated keys, since it was signed with multiple sigs - do 229 // a check to see if we find a match for all stored sigs 230 // since app hasn't rotated key, we only need to check with its current signers 231 Signature[] deviceSigs = signingInfo.getApkContentsSigners(); 232 int nDevice = deviceSigs.length; 233 234 // ensure that each stored sig matches an on-device sig 235 for (int i = 0; i < nStored; i++) { 236 boolean match = false; 237 for (int j = 0; j < nDevice; j++) { 238 if (storedSigs[i].equals(deviceSigs[j])) { 239 match = true; 240 break; 241 } 242 } 243 if (!match) { 244 return false; 245 } 246 } 247 // we have found a match for all stored sigs 248 return true; 249 } 250 } 251 } 252