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