• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
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;
18 
19 import static com.android.server.backup.BackupManagerService.DEBUG;
20 import static com.android.server.backup.BackupManagerService.TAG;
21 
22 import android.annotation.Nullable;
23 import android.app.ActivityManager;
24 import android.app.ActivityManagerInternal;
25 import android.app.ApplicationThreadConstants;
26 import android.app.IActivityManager;
27 import android.app.IBackupAgent;
28 import android.app.backup.BackupAnnotations;
29 import android.app.compat.CompatChanges;
30 import android.compat.annotation.ChangeId;
31 import android.compat.annotation.EnabledSince;
32 import android.content.pm.ApplicationInfo;
33 import android.content.pm.PackageManager;
34 import android.content.pm.PackageManager.NameNotFoundException;
35 import android.os.Binder;
36 import android.os.Build;
37 import android.os.IBinder;
38 import android.os.Process;
39 import android.os.RemoteException;
40 import android.os.UserHandle;
41 import android.util.ArraySet;
42 import android.util.Slog;
43 
44 import com.android.internal.annotations.GuardedBy;
45 import com.android.internal.annotations.VisibleForTesting;
46 import com.android.server.LocalServices;
47 import com.android.server.backup.BackupRestoreTask.CancellationReason;
48 import com.android.server.backup.internal.LifecycleOperationStorage;
49 
50 import java.util.Set;
51 
52 /**
53  * Handles the lifecycle of {@link IBackupAgent}s that the {@link UserBackupManagerService}
54  * communicates with.
55  *
56  * <p>There can only be one agent that's connected to at a time.
57  *
58  * <p>There should be only one instance of this class per {@link UserBackupManagerService}.
59  */
60 public class BackupAgentConnectionManager {
61 
62     /**
63      * Enables the OS making a decision on whether backup restricted mode should be used for apps
64      * that haven't explicitly opted in or out. See
65      * {@link android.content.pm.PackageManager#PROPERTY_USE_RESTRICTED_BACKUP_MODE} for details.
66      */
67     @ChangeId
68     @EnabledSince(targetSdkVersion = Build.VERSION_CODES.BAKLAVA)
69     public static final long OS_DECIDES_BACKUP_RESTRICTED_MODE = 376661510;
70 
71     // The thread performing the sequence of queued backups binds to each app's agent
72     // in succession.  Bind notifications are asynchronously delivered through the
73     // Activity Manager; use this lock object to signal when a requested binding has
74     // completed.
75     private final Object mAgentConnectLock = new Object();
76     @GuardedBy("mAgentConnectLock")
77     @Nullable
78     private BackupAgentConnection mCurrentConnection;
79 
80     @GuardedBy("mAgentConnectLock")
81     private final ArraySet<String> mRestoreNoRestrictedModePackages = new ArraySet<>();
82     @GuardedBy("mAgentConnectLock")
83     private final ArraySet<String> mBackupNoRestrictedModePackages = new ArraySet<>();
84 
85     private final IActivityManager mActivityManager;
86     private final ActivityManagerInternal mActivityManagerInternal;
87     private final LifecycleOperationStorage mOperationStorage;
88     private final PackageManager mPackageManager;
89     private final UserBackupManagerService mUserBackupManagerService;
90     private final int mUserId;
91     private final String mUserIdMsg;
92 
BackupAgentConnectionManager(LifecycleOperationStorage operationStorage, PackageManager packageManager, UserBackupManagerService userBackupManagerService, int userId)93     BackupAgentConnectionManager(LifecycleOperationStorage operationStorage,
94             PackageManager packageManager, UserBackupManagerService userBackupManagerService,
95             int userId) {
96         mActivityManager = ActivityManager.getService();
97         mActivityManagerInternal = LocalServices.getService(ActivityManagerInternal.class);
98         mOperationStorage = operationStorage;
99         mPackageManager = packageManager;
100         mUserBackupManagerService = userBackupManagerService;
101         mUserId = userId;
102         mUserIdMsg = "[UserID:" + userId + "] ";
103     }
104 
105     private static final class BackupAgentConnection {
106         public final ApplicationInfo appInfo;
107         public final int backupMode;
108         public final boolean inRestrictedMode;
109         public IBackupAgent backupAgent;
110         public boolean connecting = true; // Assume we are trying to connect on creation.
111 
BackupAgentConnection(ApplicationInfo appInfo, int backupMode, boolean inRestrictedMode)112         private BackupAgentConnection(ApplicationInfo appInfo, int backupMode,
113                 boolean inRestrictedMode) {
114             this.appInfo = appInfo;
115             this.backupMode = backupMode;
116             this.inRestrictedMode = inRestrictedMode;
117         }
118     }
119 
120     /**
121      * Fires off a backup agent, blocking until it attaches (i.e. ActivityManager calls
122      * {@link #agentConnected(String, IBinder)}) or until this operation times out.
123      *
124      * @param backupMode a {@code BACKUP_MODE} from {@link android.app.ApplicationThreadConstants}.
125      */
126     @Nullable
bindToAgentSynchronous(ApplicationInfo app, int backupMode, @BackupAnnotations.BackupDestination int backupDestination)127     public IBackupAgent bindToAgentSynchronous(ApplicationInfo app, int backupMode,
128             @BackupAnnotations.BackupDestination int backupDestination) {
129         if (app == null) {
130             Slog.w(TAG, mUserIdMsg + "bindToAgentSynchronous for null app");
131             return null;
132         }
133 
134         synchronized (mAgentConnectLock) {
135             boolean useRestrictedMode = shouldUseRestrictedBackupModeForPackage(backupMode,
136                     app.packageName);
137             if (mCurrentConnection != null) {
138                 Slog.e(TAG, mUserIdMsg + "binding to new agent before unbinding from old one: "
139                         + mCurrentConnection.appInfo.packageName);
140             }
141             mCurrentConnection = new BackupAgentConnection(app, backupMode, useRestrictedMode);
142 
143             // bindBackupAgent() is an async API. It will kick off the app's process and call
144             // agentConnected() when it receives the agent from the app.
145             boolean startedBindSuccessfully = false;
146             try {
147                 startedBindSuccessfully = mActivityManager.bindBackupAgent(app.packageName,
148                         backupMode, mUserId, backupDestination, useRestrictedMode);
149             } catch (RemoteException e) {
150                 // can't happen - ActivityManager is local
151             }
152 
153             if (!startedBindSuccessfully) {
154                 Slog.w(TAG, mUserIdMsg + "bind request failed for " + app.packageName);
155                 mCurrentConnection = null;
156             } else {
157                 Slog.d(TAG, mUserIdMsg + "awaiting agent for " + app.packageName);
158 
159                 // Wait 10 seconds for the agent and then time out if we still haven't bound to it.
160                 long timeoutMark = System.currentTimeMillis() + 10 * 1000;
161                 while (mCurrentConnection != null && mCurrentConnection.connecting && (
162                         System.currentTimeMillis() < timeoutMark)) {
163                     try {
164                         mAgentConnectLock.wait(5000);
165                     } catch (InterruptedException e) {
166                         Slog.w(TAG, mUserIdMsg + "Interrupted: " + e);
167                         mCurrentConnection = null;
168                     }
169                 }
170             }
171 
172             if (mCurrentConnection != null) {
173                 if (!mCurrentConnection.connecting) {
174                     return mCurrentConnection.backupAgent;
175                 }
176                 // If we are still connecting, we've timed out.
177                 Slog.w(TAG, mUserIdMsg + "Timeout waiting for agent " + app);
178                 mCurrentConnection = null;
179             }
180 
181             mActivityManagerInternal.clearPendingBackup(mUserId);
182             return null;
183         }
184     }
185 
186     /**
187      * Tell the ActivityManager that we are done with the {@link IBackupAgent} of this {@code app}.
188      * It will tell the app to destroy the agent.
189      *
190      * <p>If {@code allowKill} is set, this will kill the app's process if the app is in restricted
191      * mode or if it was started for restore and specified {@code android:killAfterRestore} in its
192      * manifest.
193      *
194      * @see #shouldUseRestrictedBackupModeForPackage(int, String)
195      */
unbindAgent(ApplicationInfo app, boolean allowKill)196     public void unbindAgent(ApplicationInfo app, boolean allowKill) {
197         if (app == null) {
198             Slog.w(TAG, mUserIdMsg + "unbindAgent for null app");
199             return;
200         }
201 
202         synchronized (mAgentConnectLock) {
203             // Even if we weren't expecting to be bound to this agent, we should still call
204             // ActivityManager just in case. It will ignore the call if it also wasn't expecting it.
205             try {
206                 mActivityManager.unbindBackupAgent(app);
207 
208                 // Evaluate this before potentially setting mCurrentConnection = null.
209                 boolean willKill = allowKill && shouldKillAppOnUnbind(app);
210 
211                 if (mCurrentConnection == null) {
212                     Slog.w(TAG, mUserIdMsg + "unbindAgent but no current connection");
213                 } else if (!mCurrentConnection.appInfo.packageName.equals(app.packageName)) {
214                     Slog.w(TAG,
215                             mUserIdMsg + "unbindAgent for unexpected package: " + app.packageName
216                                     + " expected: " + mCurrentConnection.appInfo.packageName);
217                 } else {
218                     mCurrentConnection = null;
219                 }
220 
221                 if (willKill) {
222                     Slog.i(TAG, mUserIdMsg + "Killing agent host process");
223                     mActivityManager.killApplicationProcess(app.processName, app.uid);
224                 }
225             } catch (RemoteException e) {
226                 // Can't happen - activity manager is local
227             }
228         }
229     }
230 
231     @GuardedBy("mAgentConnectLock")
shouldKillAppOnUnbind(ApplicationInfo app)232     private boolean shouldKillAppOnUnbind(ApplicationInfo app) {
233         // We don't ask system UID processes to be killed.
234         if (UserHandle.isCore(app.uid)) {
235             return false;
236         }
237 
238         // If the app is in restricted mode or if we're not sure if it is because our internal
239         // state is messed up, we need to avoid it being stuck in it.
240         if (mCurrentConnection == null || mCurrentConnection.inRestrictedMode) {
241             return true;
242         }
243 
244         // App was doing restore and asked to be killed afterwards.
245         return isBackupModeRestore(mCurrentConnection.backupMode)
246                 && (app.flags & ApplicationInfo.FLAG_KILL_AFTER_RESTORE) != 0;
247     }
248 
249     /**
250      * Callback: a requested backup agent has been instantiated. This should only be called from
251      * the {@link ActivityManager} when it's telling us that an agent is ready after a call to
252      * {@link #bindToAgentSynchronous(ApplicationInfo, int, int)}.
253      */
agentConnected(String packageName, IBinder agentBinder)254     public void agentConnected(String packageName, IBinder agentBinder) {
255         synchronized (mAgentConnectLock) {
256             if (getCallingUid() != Process.SYSTEM_UID) {
257                 Slog.w(TAG, mUserIdMsg + "Non-system process uid=" + getCallingUid()
258                         + " claiming agent connected");
259                 return;
260             }
261 
262             Slog.d(TAG, mUserIdMsg + "agentConnected pkg=" + packageName + " agent=" + agentBinder);
263             if (mCurrentConnection == null) {
264                 Slog.w(TAG, mUserIdMsg + "was not expecting connection");
265             } else if (!mCurrentConnection.appInfo.packageName.equals(packageName)) {
266                 Slog.w(TAG, mUserIdMsg + "got agent for unexpected package=" + packageName);
267             } else {
268                 mCurrentConnection.backupAgent = IBackupAgent.Stub.asInterface(agentBinder);
269                 mCurrentConnection.connecting = false;
270             }
271 
272             mAgentConnectLock.notifyAll();
273         }
274     }
275 
276     /**
277      * Callback: a backup agent has failed to come up, or has unexpectedly quit. If the agent failed
278      * to come up in the first place, the agentBinder argument will be {@code null}. This should
279      * only be called from the {@link ActivityManager}.
280      */
agentDisconnected(String packageName)281     public void agentDisconnected(String packageName) {
282         synchronized (mAgentConnectLock) {
283             if (getCallingUid() != Process.SYSTEM_UID) {
284                 Slog.w(TAG, mUserIdMsg + "Non-system process uid=" + getCallingUid()
285                         + " claiming agent disconnected");
286                 return;
287             }
288 
289             Slog.w(TAG, mUserIdMsg + "agentDisconnected: the backup agent for " + packageName
290                     + " died: cancel current operations");
291 
292             // Only abort the current connection if the agent we were expecting or already
293             // connected to has disconnected.
294             if (mCurrentConnection != null && mCurrentConnection.appInfo.packageName.equals(
295                     packageName)) {
296                 mCurrentConnection = null;
297             }
298 
299             // Offload operation cancellation off the main thread as the cancellation callbacks
300             // might call out to BackupTransport. Other operations started on the same package
301             // before the cancellation callback has executed will also be cancelled by the callback.
302             Runnable cancellationRunnable =
303                     () -> {
304                         // On handleCancel(), the operation will call unbindAgent() which will make
305                         // sure the app doesn't get stuck in restricted mode.
306                         for (int token : mOperationStorage.operationTokensForPackage(packageName)) {
307                             if (DEBUG) {
308                                 Slog.d(
309                                         TAG,
310                                         mUserIdMsg
311                                                 + "agentDisconnected: cancelling for token:"
312                                                 + Integer.toHexString(token));
313                             }
314                             mUserBackupManagerService.handleCancel(
315                                     token, CancellationReason.AGENT_DISCONNECTED);
316                         }
317                     };
318             getThreadForCancellation(cancellationRunnable).start();
319 
320             mAgentConnectLock.notifyAll();
321         }
322     }
323 
324     /**
325      * Marks the given set of packages as packages that should not be put into restricted mode if
326      * they are started for the given {@link BackupAnnotations.OperationType}.
327      */
setNoRestrictedModePackages(Set<String> packageNames, @BackupAnnotations.OperationType int opType)328     public void setNoRestrictedModePackages(Set<String> packageNames,
329             @BackupAnnotations.OperationType int opType) {
330         synchronized (mAgentConnectLock) {
331             if (opType == BackupAnnotations.OperationType.BACKUP) {
332                 mBackupNoRestrictedModePackages.clear();
333                 mBackupNoRestrictedModePackages.addAll(packageNames);
334             } else if (opType == BackupAnnotations.OperationType.RESTORE) {
335                 mRestoreNoRestrictedModePackages.clear();
336                 mRestoreNoRestrictedModePackages.addAll(packageNames);
337             } else {
338                 throw new IllegalArgumentException("opType must be BACKUP or RESTORE");
339             }
340         }
341     }
342 
343     /**
344      * Clears the list of packages that should not be put into restricted mode for either backup or
345      * restore.
346      */
clearNoRestrictedModePackages()347     public void clearNoRestrictedModePackages() {
348         synchronized (mAgentConnectLock) {
349             mBackupNoRestrictedModePackages.clear();
350             mRestoreNoRestrictedModePackages.clear();
351         }
352     }
353 
354     /**
355      * If the app has specified {@link PackageManager#PROPERTY_USE_RESTRICTED_BACKUP_MODE}, then
356      * its value is returned. If it hasn't and it targets an SDK below
357      * {@link Build.VERSION_CODES#BAKLAVA} then returns true. If it targets a newer SDK, then
358      * returns the decision made by the {@link android.app.backup.BackupTransport}.
359      *
360      * <p>When this method is called, we should have already asked the transport and cached its
361      * response in {@link #mBackupNoRestrictedModePackages} or
362      * {@link #mRestoreNoRestrictedModePackages} so this method will immediately return without
363      * any IPC to the transport.
364      */
365     @GuardedBy("mAgentConnectLock")
shouldUseRestrictedBackupModeForPackage( @ackupAnnotations.OperationType int mode, String packageName)366     private boolean shouldUseRestrictedBackupModeForPackage(
367             @BackupAnnotations.OperationType int mode, String packageName) {
368         // Key/Value apps are never put in restricted mode.
369         if (mode == ApplicationThreadConstants.BACKUP_MODE_INCREMENTAL
370                 || mode == ApplicationThreadConstants.BACKUP_MODE_RESTORE) {
371             return false;
372         }
373 
374         if (!Flags.enableRestrictedModeChanges()) {
375             return true;
376         }
377 
378         try {
379             PackageManager.Property property = mPackageManager.getPropertyAsUser(
380                     PackageManager.PROPERTY_USE_RESTRICTED_BACKUP_MODE,
381                     packageName, /* className= */ null, mUserId);
382             if (property.isBoolean()) {
383                 // If the package has explicitly specified, we won't ask the transport.
384                 return property.getBoolean();
385             } else {
386                 Slog.w(TAG,
387                         PackageManager.PROPERTY_USE_RESTRICTED_BACKUP_MODE + "must be a boolean.");
388             }
389         } catch (NameNotFoundException e) {
390             // This is expected when the package has not defined the property in its manifest.
391         }
392 
393         // The package has not specified the property. The behavior depends on the package's
394         // targetSdk.
395         // <36 gets the old behavior of always using restricted mode.
396         if (!CompatChanges.isChangeEnabled(OS_DECIDES_BACKUP_RESTRICTED_MODE, packageName,
397                 UserHandle.of(mUserId))) {
398             return true;
399         }
400 
401         // Apps targeting >=36 get the behavior decided by the transport.
402         // By this point, we should have asked the transport and cached its decision.
403         if ((mode == ApplicationThreadConstants.BACKUP_MODE_FULL
404                 && mBackupNoRestrictedModePackages.contains(packageName)) || (
405                 mode == ApplicationThreadConstants.BACKUP_MODE_RESTORE_FULL
406                         && mRestoreNoRestrictedModePackages.contains(packageName))) {
407             Slog.d(TAG, "Transport requested no restricted mode for: " + packageName);
408             return false;
409         }
410         return true;
411     }
412 
isBackupModeRestore(int backupMode)413     private static boolean isBackupModeRestore(int backupMode) {
414         return backupMode == ApplicationThreadConstants.BACKUP_MODE_RESTORE
415                 || backupMode == ApplicationThreadConstants.BACKUP_MODE_RESTORE_FULL;
416     }
417 
418     @VisibleForTesting
getThreadForCancellation(Runnable operation)419     Thread getThreadForCancellation(Runnable operation) {
420         return new Thread(operation, /* operationName */ "agent-disconnected");
421     }
422 
423     @VisibleForTesting
getCallingUid()424     int getCallingUid() {
425         return Binder.getCallingUid();
426     }
427 }
428