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