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