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.TAG;
21 import static com.android.server.backup.UserBackupManagerService.PACKAGE_MANAGER_SENTINEL;
22 import static com.android.server.backup.UserBackupManagerService.SETTINGS_PACKAGE;
23 import static com.android.server.backup.UserBackupManagerService.SHARED_BACKUP_AGENT_PACKAGE;
24 import static com.android.server.backup.UserBackupManagerService.WALLPAPER_PACKAGE;
25 import static com.android.server.pm.PackageManagerService.PLATFORM_PACKAGE_NAME;
26 
27 import android.annotation.Nullable;
28 import android.app.backup.BackupAnnotations.BackupDestination;
29 import android.app.backup.BackupTransport;
30 import android.app.compat.CompatChanges;
31 import android.compat.annotation.ChangeId;
32 import android.compat.annotation.EnabledSince;
33 import android.compat.annotation.Overridable;
34 import android.content.Context;
35 import android.content.pm.ApplicationInfo;
36 import android.content.pm.PackageInfo;
37 import android.content.pm.PackageManager;
38 import android.content.pm.PackageManagerInternal;
39 import android.content.pm.Signature;
40 import android.content.pm.SigningInfo;
41 import android.os.Build;
42 import android.os.UserHandle;
43 import android.os.UserManager;
44 import android.util.Slog;
45 
46 import com.android.internal.annotations.VisibleForTesting;
47 import com.android.internal.util.ArrayUtils;
48 import com.android.server.backup.SetUtils;
49 import com.android.server.backup.transport.BackupTransportClient;
50 import com.android.server.backup.transport.TransportConnection;
51 
52 import com.google.android.collect.Sets;
53 
54 import java.util.Arrays;
55 import java.util.Set;
56 
57 /**
58  * Utility methods wrapping operations on ApplicationInfo and PackageInfo.
59  */
60 public class BackupEligibilityRules {
61     private static final boolean DEBUG = false;
62 
63     /**
64      * List of system packages that are eligible for backup in "profile" users (such as work
65      * profile). See {@link UserManager#isProfile()}. This is a subset of {@link
66      * #systemPackagesAllowedForNonSystemUsers}
67      */
68     private static final Set<String> systemPackagesAllowedForProfileUser =
69             Sets.newArraySet(PACKAGE_MANAGER_SENTINEL, PLATFORM_PACKAGE_NAME);
70 
71     /**
72      * List of system packages that are eligible for backup in non-system users.
73      */
74     private static final Set<String> systemPackagesAllowedForNonSystemUsers = SetUtils.union(
75             systemPackagesAllowedForProfileUser,
76             Sets.newArraySet(WALLPAPER_PACKAGE, SETTINGS_PACKAGE));
77 
78     private final PackageManager mPackageManager;
79     private final PackageManagerInternal mPackageManagerInternal;
80     private final int mUserId;
81     private boolean mIsProfileUser = false;
82     @BackupDestination  private final int mBackupDestination;
83     private final boolean mSkipRestoreForLaunchedApps;
84 
85     /**
86      * When  this change is enabled, {@code adb backup}  is automatically turned on for apps
87      * running as debuggable ({@code android:debuggable} set to {@code true}) and unavailable to
88      * any other apps.
89      */
90     @ChangeId
91     @EnabledSince(targetSdkVersion = Build.VERSION_CODES.S)
92     static final long RESTRICT_ADB_BACKUP = 171032338L;
93 
94     /**
95      * When  this change is enabled, {@code android:allowBackup}  is ignored for apps during D2D
96      * (device-to-device) migrations.
97      */
98     @ChangeId
99     @Overridable
100     @EnabledSince(targetSdkVersion = Build.VERSION_CODES.S)
101     static final long IGNORE_ALLOW_BACKUP_IN_D2D = 183147249L;
102 
forBackup(PackageManager packageManager, PackageManagerInternal packageManagerInternal, int userId, Context context)103     public static BackupEligibilityRules forBackup(PackageManager packageManager,
104             PackageManagerInternal packageManagerInternal,
105             int userId,
106             Context context) {
107         return new BackupEligibilityRules(packageManager, packageManagerInternal, userId, context,
108                 BackupDestination.CLOUD);
109     }
110 
BackupEligibilityRules(PackageManager packageManager, PackageManagerInternal packageManagerInternal, int userId, Context context, @BackupDestination int backupDestination)111     public BackupEligibilityRules(PackageManager packageManager,
112             PackageManagerInternal packageManagerInternal,
113             int userId,
114             Context context,
115             @BackupDestination int backupDestination) {
116         this(packageManager, packageManagerInternal, userId, context, backupDestination,
117                 /* skipRestoreForLaunchedApps */ false);
118     }
119 
BackupEligibilityRules(PackageManager packageManager, PackageManagerInternal packageManagerInternal, int userId, Context context, @BackupDestination int backupDestination, boolean skipRestoreForLaunchedApps)120     public BackupEligibilityRules(PackageManager packageManager,
121             PackageManagerInternal packageManagerInternal,
122             int userId,
123             Context context,
124             @BackupDestination int backupDestination,
125             boolean skipRestoreForLaunchedApps) {
126         mPackageManager = packageManager;
127         mPackageManagerInternal = packageManagerInternal;
128         mUserId = userId;
129         mBackupDestination = backupDestination;
130         UserManager userManager = context.getSystemService(UserManager.class);
131         mIsProfileUser = userManager.isProfile();
132         mSkipRestoreForLaunchedApps = skipRestoreForLaunchedApps;
133     }
134 
135     /**
136      * Returns whether app is eligible for backup.
137      *
138      * High level policy: apps are generally ineligible for backup if certain conditions apply. The
139      * conditions are:
140      *
141      * <ol>
142      *     <li>their manifest states android:allowBackup="false"
143      *     <li>they run as a system-level uid but do not supply their own backup agent
144      *     <li>it is the special shared-storage backup package used for 'adb backup'
145      * </ol>
146      *
147      * These eligibility conditions are also checked before restore, in case the backup happened on
148      * a device / from the version of the app where these rules were not enforced.
149      *
150      * However, the above eligibility rules are ignored for non-system apps in in case of
151      * device-to-device migration, see {@link BackupDestination}.
152      */
153     @VisibleForTesting
appIsEligibleForBackup(ApplicationInfo app)154     public boolean appIsEligibleForBackup(ApplicationInfo app) {
155         // 1. their manifest states android:allowBackup="false" and this is not a device-to-device
156         // migration
157         if (!isAppBackupAllowed(app)) {
158             return false;
159         }
160 
161         // 2. they run as a system-level uid
162         if (UserHandle.isCore(app.uid)) {
163             // and the backup is happening for a non-system user or profile on a package that is
164             // not explicitly allowed.
165             if (mUserId != UserHandle.USER_SYSTEM) {
166                 if (mIsProfileUser && !systemPackagesAllowedForProfileUser.contains(
167                         app.packageName)) {
168                     return false;
169                 }
170                 if (!mIsProfileUser && !systemPackagesAllowedForNonSystemUsers.contains(
171                         app.packageName)) {
172                     return false;
173                 }
174             }
175 
176             // or do not supply their own backup agent
177             if (app.backupAgentName == null) {
178                 return false;
179             }
180         }
181 
182         // 3. it is the special shared-storage backup package used for 'adb backup'
183         if (app.packageName.equals(SHARED_BACKUP_AGENT_PACKAGE)) {
184             return false;
185         }
186 
187         // 4. it is an "instant" app
188         if (app.isInstantApp()) {
189             return false;
190         }
191 
192         return !appIsDisabled(app);
193     }
194 
195     /**
196     * Check if this app allows backup. Apps can opt out of backup by stating
197     * android:allowBackup="false" in their manifest. However, this flag is ignored for non-system
198     * apps during device-to-device migrations, see {@link BackupDestination}.
199     *
200     * @param app The app under check.
201     * @return boolean indicating whether backup is allowed.
202     */
isAppBackupAllowed(ApplicationInfo app)203     public boolean isAppBackupAllowed(ApplicationInfo app) {
204         boolean allowBackup = (app.flags & ApplicationInfo.FLAG_ALLOW_BACKUP) != 0;
205         switch (mBackupDestination) {
206             case BackupDestination.DEVICE_TRANSFER:
207                 // Backup / restore of all non-system apps is force allowed during
208                 // device-to-device migration.
209                 boolean isSystemApp = (app.flags & ApplicationInfo.FLAG_SYSTEM) != 0;
210                 boolean ignoreAllowBackup = !isSystemApp && CompatChanges.isChangeEnabled(
211                         IGNORE_ALLOW_BACKUP_IN_D2D, app.packageName, UserHandle.of(mUserId));
212                 return ignoreAllowBackup || allowBackup;
213             case BackupDestination.ADB_BACKUP:
214                 String packageName = app.packageName;
215                 if (packageName == null) {
216                     Slog.w(TAG, "Invalid ApplicationInfo object");
217                     return false;
218                 }
219 
220                 if (!CompatChanges.isChangeEnabled(RESTRICT_ADB_BACKUP, packageName,
221                         UserHandle.of(mUserId))) {
222                     return allowBackup;
223                 }
224 
225                 if (PLATFORM_PACKAGE_NAME.equals(packageName)) {
226                     // Always enable adb backup for SystemBackupAgent in "android" package (this is
227                     // done to avoid breaking existing integration tests and might change in the
228                     // future).
229                     return true;
230                 }
231 
232                 boolean isPrivileged = (app.flags & ApplicationInfo.PRIVATE_FLAG_PRIVILEGED) != 0;
233                 boolean isDebuggable = (app.flags & ApplicationInfo.FLAG_DEBUGGABLE) != 0;
234                 if (UserHandle.isCore(app.uid) || isPrivileged) {
235                     try {
236                         return mPackageManager.getPropertyAsUser(
237                                 PackageManager.PROPERTY_ALLOW_ADB_BACKUP, packageName,
238                                 null /* className */, mUserId).getBoolean();
239                     } catch (PackageManager.NameNotFoundException e) {
240                         Slog.w(TAG, "Failed to read allowAdbBackup property for + "
241                                 + packageName);
242 
243                         // This temporarily falls back to the legacy allowBackup flag to
244                         // avoid breaking existing users of adb backup. Once they're able to use
245                         // the new ALLOW_ADB_BACKUP property, we'll return false here.
246                         // TODO(b/176088499): Return false here.
247                         return allowBackup;
248                     }
249                 } else {
250                     // All other apps can use adb backup only when running in debuggable mode.
251                     return isDebuggable;
252                 }
253             case BackupDestination.CLOUD:
254                 return allowBackup;
255             default:
256                 Slog.w(TAG, "Unknown operation type:" + mBackupDestination);
257                 return false;
258         }
259     }
260 
261     /**
262      * Returns whether an app is eligible for backup at runtime. That is, the app has to:
263      * <ol>
264      *     <li>Return true for {@link #appIsEligibleForBackup(ApplicationInfo, int)}
265      *     <li>Return false for {@link #appIsStopped(ApplicationInfo)}
266      *     <li>Return false for {@link #appIsDisabled(ApplicationInfo, int)}
267      *     <li>Be eligible for the transport via
268      *         {@link BackupTransport#isAppEligibleForBackup(PackageInfo, boolean)}
269      * </ol>
270      */
appIsRunningAndEligibleForBackupWithTransport( @ullable TransportConnection transportConnection, String packageName)271     public boolean appIsRunningAndEligibleForBackupWithTransport(
272             @Nullable TransportConnection transportConnection,
273             String packageName) {
274         try {
275             PackageInfo packageInfo = mPackageManager.getPackageInfoAsUser(packageName,
276                     PackageManager.GET_SIGNING_CERTIFICATES, mUserId);
277             ApplicationInfo applicationInfo = packageInfo.applicationInfo;
278             if (!appIsEligibleForBackup(applicationInfo)
279                     || appIsStopped(applicationInfo)
280                     || appIsDisabled(applicationInfo)) {
281                 return false;
282             }
283             if (transportConnection != null) {
284                 try {
285                     BackupTransportClient transport =
286                             transportConnection.connectOrThrow(
287                                     "AppBackupUtils.appIsRunningAndEligibleForBackupWithTransport");
288                     return transport.isAppEligibleForBackup(
289                             packageInfo, appGetsFullBackup(packageInfo));
290                 } catch (Exception e) {
291                     Slog.e(TAG, "Unable to ask about eligibility: " + e.getMessage());
292                 }
293             }
294             // If transport is not present we couldn't tell that the package is not eligible.
295             return true;
296         } catch (PackageManager.NameNotFoundException e) {
297             return false;
298         }
299     }
300 
301     /**
302      * Determine if data restore should be run for the given package.
303      *
304      * <p>This is used in combination with {@link #appIsEligibleForBackup(ApplicationInfo)} that
305      * checks whether the backup being restored should have happened in the first place.</p>
306      */
isAppEligibleForRestore(ApplicationInfo app)307     public boolean isAppEligibleForRestore(ApplicationInfo app) {
308         if (!mSkipRestoreForLaunchedApps) {
309             return true;
310         }
311 
312         // If an app implemented a BackupAgent, they are expected to handle being restored even
313         // after first launch and avoid conflicts between existing app data and restored data.
314         if (app.backupAgentName != null) {
315             return true;
316         }
317 
318         // Otherwise only restore an app if it hasn't been launched before.
319         return !mPackageManagerInternal.wasPackageEverLaunched(app.packageName, mUserId);
320     }
321 
322     /** Avoid backups of 'disabled' apps. */
323     @VisibleForTesting
appIsDisabled( ApplicationInfo app)324     boolean appIsDisabled(
325             ApplicationInfo app) {
326         int enabledSetting = mPackageManagerInternal.getApplicationEnabledState(app.packageName,
327                 mUserId);
328 
329         switch (enabledSetting) {
330             case PackageManager.COMPONENT_ENABLED_STATE_DISABLED:
331             case PackageManager.COMPONENT_ENABLED_STATE_DISABLED_USER:
332             case PackageManager.COMPONENT_ENABLED_STATE_DISABLED_UNTIL_USED:
333                 return true;
334             case PackageManager.COMPONENT_ENABLED_STATE_DEFAULT:
335                 return !app.enabled;
336             default:
337                 return false;
338         }
339     }
340 
341     /**
342      * Checks if the app is in a stopped state.  This is not part of the general "eligible for
343      * backup?" check because we *do* still need to restore data to apps in this state (e.g.
344      * newly-installing ones).
345      *
346      * <p>Reasons for such state:
347      * <ul>
348      *     <li>The app has been force-stopped.
349      *     <li>The app has been cleared.
350      *     <li>The app has just been installed.
351      * </ul>
352      */
appIsStopped(ApplicationInfo app)353     public boolean appIsStopped(ApplicationInfo app) {
354         return ((app.flags & ApplicationInfo.FLAG_STOPPED) != 0);
355     }
356 
357     /**
358      * Returns whether the app can get full backup. Does *not* check overall backup eligibility
359      * policy!
360      */
361     @VisibleForTesting
appGetsFullBackup(PackageInfo pkg)362     public boolean appGetsFullBackup(PackageInfo pkg) {
363         if (pkg.applicationInfo.backupAgentName != null) {
364             // If it has an agent, it gets full backups only if it says so
365             return (pkg.applicationInfo.flags & ApplicationInfo.FLAG_FULL_BACKUP_ONLY) != 0;
366         }
367 
368         // No agent or fullBackupOnly="true" means we do indeed perform full-data backups for it
369         return true;
370     }
371 
372     /**
373      * Returns whether the app is only capable of doing key/value. We say it's not if it allows full
374      * backup, and it is otherwise.
375      */
appIsKeyValueOnly(PackageInfo pkg)376     public boolean appIsKeyValueOnly(PackageInfo pkg) {
377         return !appGetsFullBackup(pkg);
378     }
379 
380     /**
381      * Returns whether the signatures stored {@param storedSigs}, coming from the source apk, match
382      * the signatures of the apk installed on the device, the target apk. If the target resides in
383      * the system partition we return true. Otherwise it's considered a match if both conditions
384      * hold:
385      *
386      * <ul>
387      *   <li>Source and target have at least one signature each
388      *   <li>Target contains all signatures in source, and nothing more
389      * </ul>
390      *
391      * or if both source and target have exactly one signature, and they don't match, we check
392      * if the app was ever signed with source signature (i.e. app has rotated key)
393      * Note: key rotation is only supported for apps ever signed with one key, and those apps will
394      * not be allowed to be signed by more certificates in the future
395      *
396      * Note that if {@param target} is null we return false.
397      */
signaturesMatch(Signature[] storedSigs, PackageInfo target)398     public boolean signaturesMatch(Signature[] storedSigs, PackageInfo target) {
399         if (target == null || target.packageName == null) {
400             return false;
401         }
402 
403         // If the target resides on the system partition, we allow it to restore
404         // data from the like-named package in a restore set even if the signatures
405         // do not match.  (Unlike general applications, those flashed to the system
406         // partition will be signed with the device's platform certificate, so on
407         // different phones the same system app will have different signatures.)
408         if ((target.applicationInfo.flags & ApplicationInfo.FLAG_SYSTEM) != 0) {
409             if (MORE_DEBUG) {
410                 Slog.v(TAG, "System app " + target.packageName + " - skipping sig check");
411             }
412             return true;
413         }
414 
415         // Don't allow unsigned apps on either end
416         if (ArrayUtils.isEmpty(storedSigs)) {
417             return false;
418         }
419 
420         SigningInfo signingInfo = target.signingInfo;
421         if (signingInfo == null) {
422             Slog.w(TAG, "signingInfo is empty, app was either unsigned or the flag" +
423                     " PackageManager#GET_SIGNING_CERTIFICATES was not specified");
424             return false;
425         }
426 
427         if (DEBUG) {
428             Slog.v(TAG, "signaturesMatch(): stored=" + Arrays.toString(storedSigs)
429                     + " device=" + Arrays.toString(signingInfo.getApkContentsSigners()));
430         }
431 
432         final int nStored = storedSigs.length;
433         if (nStored == 1) {
434             // if the app is only signed with one sig, it's possible it has rotated its key
435             // (the checks with signing history are delegated to PackageManager)
436             // TODO(b/73988180): address the case that app has declared restoreAnyVersion and is
437             // restoring from higher version to lower after having rotated the key (i.e. higher
438             // version has different sig than lower version that we want to restore to)
439             return mPackageManagerInternal.isDataRestoreSafe(storedSigs[0], target.packageName);
440         } else {
441             // the app couldn't have rotated keys, since it was signed with multiple sigs - do
442             // a check to see if we find a match for all stored sigs
443             // since app hasn't rotated key, we only need to check with its current signers
444             Signature[] deviceSigs = signingInfo.getApkContentsSigners();
445             int nDevice = deviceSigs.length;
446 
447             // ensure that each stored sig matches an on-device sig
448             for (int i = 0; i < nStored; i++) {
449                 boolean match = false;
450                 for (int j = 0; j < nDevice; j++) {
451                     if (storedSigs[i].equals(deviceSigs[j])) {
452                         match = true;
453                         break;
454                     }
455                 }
456                 if (!match) {
457                     return false;
458                 }
459             }
460             // we have found a match for all stored sigs
461             return true;
462         }
463     }
464 
getBackupDestination()465     public int getBackupDestination() {
466         return mBackupDestination;
467     }
468 }
469