summaryrefslogtreecommitdiff
path: root/services/backup
diff options
context:
space:
mode:
author Sarp Misoglu <sarpm@google.com> 2024-11-19 18:55:59 +0000
committer Sarp Misoglu <sarpm@google.com> 2024-11-20 16:40:14 +0000
commit64d73c4f0360c35e8a4b8180778ecf65aef5c2cc (patch)
tree49c3e16572c487c196e4c5d4dbd7415acdd2065d /services/backup
parent1ba241bebb4bd6ea766468f7568eb00f92d1dad4 (diff)
Ensure we bind to the correct app's agent
We have a 10s timeout in bindToAgentSynchronous and if we don't get an agentConnected callback by then the method returns null. There was a potential bug here because agentConnected doesn't check if the agent is the one we are currently expecting. It could happen with the following sequence of events: 1. bindToAgentSynchronous(A) 2. app A times out 3. bindToAgentSynchronous(B) 4. agentConnected(A) 5. the call from 3 returns the agent for app A instead of B. With this change we start tracking the state of the current connection better. Test: atest BackupAgentConnectionManagerTest & atest CtsBackupHostTestCases Bug: 328019933 Change-Id: I0556aff00fe8c2bbfe56fe10f15b7b15ff260590 Change-Id: I4c04e9279d9863716fc080c6ab218665e1591d51
Diffstat (limited to 'services/backup')
-rw-r--r--services/backup/java/com/android/server/backup/BackupAgentConnectionManager.java148
1 files changed, 97 insertions, 51 deletions
diff --git a/services/backup/java/com/android/server/backup/BackupAgentConnectionManager.java b/services/backup/java/com/android/server/backup/BackupAgentConnectionManager.java
index 5e4bab15952d..02f186557ca4 100644
--- a/services/backup/java/com/android/server/backup/BackupAgentConnectionManager.java
+++ b/services/backup/java/com/android/server/backup/BackupAgentConnectionManager.java
@@ -41,6 +41,7 @@ import android.os.UserHandle;
import android.util.ArraySet;
import android.util.Slog;
+import com.android.internal.annotations.GuardedBy;
import com.android.internal.annotations.VisibleForTesting;
import com.android.server.LocalServices;
import com.android.server.backup.internal.LifecycleOperationStorage;
@@ -71,8 +72,10 @@ public class BackupAgentConnectionManager {
// Activity Manager; use this lock object to signal when a requested binding has
// completed.
private final Object mAgentConnectLock = new Object();
- private IBackupAgent mConnectedAgent;
- private volatile boolean mConnecting;
+ @GuardedBy("mAgentConnectLock")
+ @Nullable
+ private BackupAgentConnection mCurrentConnection;
+
private final ArraySet<String> mRestoreNoRestrictedModePackages = new ArraySet<>();
private final ArraySet<String> mBackupNoRestrictedModePackages = new ArraySet<>();
@@ -96,8 +99,18 @@ public class BackupAgentConnectionManager {
mUserIdMsg = "[UserID:" + userId + "] ";
}
+ private static final class BackupAgentConnection {
+ public final ApplicationInfo appInfo;
+ public IBackupAgent backupAgent;
+ public boolean connecting = true; // Assume we are trying to connect on creation.
+
+ private BackupAgentConnection(ApplicationInfo appInfo) {
+ this.appInfo = appInfo;
+ }
+ }
+
/**
- * Fires off a backup agent, blocking until it attaches (and ActivityManager will call
+ * Fires off a backup agent, blocking until it attaches (i.e. ActivityManager calls
* {@link #agentConnected(String, IBinder)}) or until this operation times out.
*
* @param mode a {@code BACKUP_MODE} from {@link android.app.ApplicationThreadConstants}.
@@ -105,48 +118,56 @@ public class BackupAgentConnectionManager {
@Nullable
public IBackupAgent bindToAgentSynchronous(ApplicationInfo app, int mode,
@BackupAnnotations.BackupDestination int backupDestination) {
- IBackupAgent agent = null;
synchronized (mAgentConnectLock) {
- mConnecting = true;
- mConnectedAgent = null;
boolean useRestrictedMode = shouldUseRestrictedBackupModeForPackage(mode,
app.packageName);
+ if (mCurrentConnection != null) {
+ Slog.e(TAG, mUserIdMsg + "binding to new agent before unbinding from old one: "
+ + mCurrentConnection.appInfo.packageName);
+ }
+ mCurrentConnection = new BackupAgentConnection(app);
+
+ // bindBackupAgent() is an async API. It will kick off the app's process and call
+ // agentConnected() when it receives the agent from the app.
+ boolean startedBindSuccessfully = false;
try {
- if (mActivityManager.bindBackupAgent(app.packageName, mode, mUserId,
- backupDestination, useRestrictedMode)) {
- Slog.d(TAG, mUserIdMsg + "awaiting agent for " + app);
-
- // success; wait for the agent to arrive
- // only wait 10 seconds for the bind to happen
- long timeoutMark = System.currentTimeMillis() + 10 * 1000;
- while (mConnecting && mConnectedAgent == null && (System.currentTimeMillis()
- < timeoutMark)) {
- try {
- mAgentConnectLock.wait(5000);
- } catch (InterruptedException e) {
- // just bail
- Slog.w(TAG, mUserIdMsg + "Interrupted: " + e);
- mConnecting = false;
- mConnectedAgent = null;
- }
- }
+ startedBindSuccessfully = mActivityManager.bindBackupAgent(app.packageName, mode,
+ mUserId, backupDestination, useRestrictedMode);
+ } catch (RemoteException e) {
+ // can't happen - ActivityManager is local
+ }
- // if we timed out with no connect, abort and move on
- if (mConnecting) {
- Slog.w(TAG, mUserIdMsg + "Timeout waiting for agent " + app);
- mConnectedAgent = null;
+ if (!startedBindSuccessfully) {
+ Slog.w(TAG, mUserIdMsg + "bind request failed for " + app.packageName);
+ mCurrentConnection = null;
+ } else {
+ Slog.d(TAG, mUserIdMsg + "awaiting agent for " + app.packageName);
+
+ // Wait 10 seconds for the agent and then time out if we still haven't bound to it.
+ long timeoutMark = System.currentTimeMillis() + 10 * 1000;
+ while (mCurrentConnection != null && mCurrentConnection.connecting && (
+ System.currentTimeMillis() < timeoutMark)) {
+ try {
+ mAgentConnectLock.wait(5000);
+ } catch (InterruptedException e) {
+ Slog.w(TAG, mUserIdMsg + "Interrupted: " + e);
+ mCurrentConnection = null;
}
- Slog.i(TAG, mUserIdMsg + "got agent " + mConnectedAgent);
- agent = mConnectedAgent;
}
- } catch (RemoteException e) {
- // can't happen - ActivityManager is local
}
- }
- if (agent == null) {
+
+ if (mCurrentConnection != null) {
+ if (!mCurrentConnection.connecting) {
+ return mCurrentConnection.backupAgent;
+ }
+ // If we are still connecting, we've timed out.
+ Slog.w(TAG, mUserIdMsg + "Timeout waiting for agent " + app);
+ mCurrentConnection = null;
+ }
+
mActivityManagerInternal.clearPendingBackup(mUserId);
+ return null;
}
- return agent;
}
/**
@@ -154,30 +175,49 @@ public class BackupAgentConnectionManager {
* It will tell the app to destroy the agent.
*/
public void unbindAgent(ApplicationInfo app) {
- try {
- mActivityManager.unbindBackupAgent(app);
- } catch (RemoteException e) {
- // Can't happen - activity manager is local
+ synchronized (mAgentConnectLock) {
+ if (mCurrentConnection == null) {
+ Slog.w(TAG, mUserIdMsg + "unbindAgent but no current connection");
+ } else if (!mCurrentConnection.appInfo.packageName.equals(app.packageName)) {
+ Slog.w(TAG, mUserIdMsg + "unbindAgent for unexpected package: " + app.packageName
+ + " expected: " + mCurrentConnection.appInfo.packageName);
+ } else {
+ mCurrentConnection = null;
+ }
+
+ // Even if we weren't expecting to be bound to this agent, we should still call
+ // ActivityManager just in case. It will ignore the call if it also wasn't expecting it.
+ try {
+ mActivityManager.unbindBackupAgent(app);
+ } catch (RemoteException e) {
+ // Can't happen - activity manager is local
+ }
}
}
/**
* Callback: a requested backup agent has been instantiated. This should only be called from
- * the
- * {@link ActivityManager} when it's telling us that an agent is ready after a call to
+ * the {@link ActivityManager} when it's telling us that an agent is ready after a call to
* {@link #bindToAgentSynchronous(ApplicationInfo, int, int)}.
*/
public void agentConnected(String packageName, IBinder agentBinder) {
synchronized (mAgentConnectLock) {
- if (getCallingUid() == android.os.Process.SYSTEM_UID) {
- Slog.d(TAG,
- mUserIdMsg + "agentConnected pkg=" + packageName + " agent=" + agentBinder);
- mConnectedAgent = IBackupAgent.Stub.asInterface(agentBinder);
- mConnecting = false;
- } else {
+ if (getCallingUid() != Process.SYSTEM_UID) {
Slog.w(TAG, mUserIdMsg + "Non-system process uid=" + getCallingUid()
+ " claiming agent connected");
+ return;
}
+
+ Slog.d(TAG, mUserIdMsg + "agentConnected pkg=" + packageName + " agent=" + agentBinder);
+ if (mCurrentConnection == null) {
+ Slog.w(TAG, mUserIdMsg + "was not expecting connection");
+ } else if (!mCurrentConnection.appInfo.packageName.equals(packageName)) {
+ Slog.w(TAG, mUserIdMsg + "got agent for unexpected package=" + packageName);
+ } else {
+ mCurrentConnection.backupAgent = IBackupAgent.Stub.asInterface(agentBinder);
+ mCurrentConnection.connecting = false;
+ }
+
mAgentConnectLock.notifyAll();
}
}
@@ -189,16 +229,22 @@ public class BackupAgentConnectionManager {
*/
public void agentDisconnected(String packageName) {
synchronized (mAgentConnectLock) {
- if (getCallingUid() == Process.SYSTEM_UID) {
- mConnectedAgent = null;
- mConnecting = false;
- } else {
+ if (getCallingUid() != Process.SYSTEM_UID) {
Slog.w(TAG, mUserIdMsg + "Non-system process uid=" + getCallingUid()
+ " claiming agent disconnected");
+ return;
}
+
Slog.w(TAG, mUserIdMsg + "agentDisconnected: the backup agent for " + packageName
+ " died: cancel current operations");
+ // Only abort the current connection if the agent we were expecting or already
+ // connected to has disconnected.
+ if (mCurrentConnection != null && mCurrentConnection.appInfo.packageName.equals(
+ packageName)) {
+ mCurrentConnection = null;
+ }
+
// Offload operation cancellation off the main thread as the cancellation callbacks
// might call out to BackupTransport. Other operations started on the same package
// before the cancellation callback has executed will also be cancelled by the callback.