diff --git a/core/java/android/app/backup/BackupManager.java b/core/java/android/app/backup/BackupManager.java index 44a4b78e1afaf..673de8fa7c8c1 100644 --- a/core/java/android/app/backup/BackupManager.java +++ b/core/java/android/app/backup/BackupManager.java @@ -205,13 +205,16 @@ public class BackupManager { @Retention(RetentionPolicy.SOURCE) @IntDef({ OperationType.BACKUP, - OperationType.MIGRATION + OperationType.MIGRATION, + OperationType.ADB_BACKUP, }) public @interface OperationType { - // A regular backup / restore operation. + // A backup / restore to / from an off-device location, e.g. cloud. int BACKUP = 0; - // A full migration: all app data for non-system apps is eligible. + // A direct transfer to another device. int MIGRATION = 1; + // Backup via adb, data saved on the host machine. + int ADB_BACKUP = 3; } private Context mContext; diff --git a/core/java/android/content/pm/PackageManager.java b/core/java/android/content/pm/PackageManager.java index 19e4d148aee8d..d550c7c680333 100644 --- a/core/java/android/content/pm/PackageManager.java +++ b/core/java/android/content/pm/PackageManager.java @@ -4131,6 +4131,14 @@ public abstract class PackageManager { */ public static final int UNSTARTABLE_REASON_INSUFFICIENT_STORAGE = 2; + /** + * A manifest property to control app's participation in {@code adb backup}. Should only + * be used by system / privileged apps. + * + * @hide + */ + public static final String PROPERTY_ALLOW_ADB_BACKUP = "android.backup.ALLOW_ADB_BACKUP"; + /** {@hide} */ public int getUserId() { return UserHandle.myUserId(); diff --git a/services/backup/Android.bp b/services/backup/Android.bp index b5444f485a1c4..68376c5ad1783 100644 --- a/services/backup/Android.bp +++ b/services/backup/Android.bp @@ -10,5 +10,5 @@ java_library_static { defaults: ["platform_service_defaults"], srcs: [":services.backup-sources"], libs: ["services.core"], - static_libs: ["backuplib"], + static_libs: ["backuplib", "app-compat-annotations"], } diff --git a/services/backup/java/com/android/server/backup/UserBackupManagerService.java b/services/backup/java/com/android/server/backup/UserBackupManagerService.java index 2ff66b564ec96..136cd22fad83f 100644 --- a/services/backup/java/com/android/server/backup/UserBackupManagerService.java +++ b/services/backup/java/com/android/server/backup/UserBackupManagerService.java @@ -3030,9 +3030,11 @@ public class UserBackupManagerService { } Slog.i(TAG, addUserIdToLogMessage(mUserId, "Beginning adb backup...")); + BackupEligibilityRules eligibilityRules = getEligibilityRulesForOperation( + OperationType.ADB_BACKUP); AdbBackupParams params = new AdbBackupParams(fd, includeApks, includeObbs, includeShared, doWidgets, doAllApps, includeSystem, compress, doKeyValue, - pkgList, mScheduledBackupEligibility); + pkgList, eligibilityRules); final int token = generateRandomIntegerToken(); synchronized (mAdbBackupRestoreConfirmations) { mAdbBackupRestoreConfirmations.put(token, params); diff --git a/services/backup/java/com/android/server/backup/restore/PerformAdbRestoreTask.java b/services/backup/java/com/android/server/backup/restore/PerformAdbRestoreTask.java index c94286ffcffac..e03150eb824f0 100644 --- a/services/backup/java/com/android/server/backup/restore/PerformAdbRestoreTask.java +++ b/services/backup/java/com/android/server/backup/restore/PerformAdbRestoreTask.java @@ -24,6 +24,7 @@ import static com.android.server.backup.BackupPasswordManager.PBKDF_FALLBACK; import static com.android.server.backup.UserBackupManagerService.BACKUP_FILE_HEADER_MAGIC; import static com.android.server.backup.UserBackupManagerService.BACKUP_FILE_VERSION; +import android.app.backup.BackupManager; import android.app.backup.IFullBackupRestoreObserver; import android.content.pm.PackageManagerInternal; import android.os.ParcelFileDescriptor; @@ -104,11 +105,13 @@ public class PerformAdbRestoreTask implements Runnable { return; } + BackupEligibilityRules eligibilityRules = new BackupEligibilityRules( + mBackupManagerService.getPackageManager(), + LocalServices.getService(PackageManagerInternal.class), + mBackupManagerService.getUserId(), BackupManager.OperationType.ADB_BACKUP); FullRestoreEngine mEngine = new FullRestoreEngine(mBackupManagerService, null, mObserver, null, null, true, 0 /*unused*/, true, - BackupEligibilityRules.forBackup(mBackupManagerService.getPackageManager(), - LocalServices.getService(PackageManagerInternal.class), - mBackupManagerService.getUserId())); + eligibilityRules); FullRestoreEngineThread mEngineThread = new FullRestoreEngineThread(mEngine, tarInputStream); mEngineThread.run(); diff --git a/services/backup/java/com/android/server/backup/utils/BackupEligibilityRules.java b/services/backup/java/com/android/server/backup/utils/BackupEligibilityRules.java index 73ba1f19c0920..2078492e67ab9 100644 --- a/services/backup/java/com/android/server/backup/utils/BackupEligibilityRules.java +++ b/services/backup/java/com/android/server/backup/utils/BackupEligibilityRules.java @@ -25,12 +25,16 @@ import static com.android.server.pm.PackageManagerService.PLATFORM_PACKAGE_NAME; import android.annotation.Nullable; import android.app.backup.BackupManager.OperationType; import android.app.backup.BackupTransport; +import android.compat.annotation.ChangeId; +import android.compat.annotation.EnabledSince; +import android.app.compat.CompatChanges; import android.content.pm.ApplicationInfo; import android.content.pm.PackageInfo; import android.content.pm.PackageManager; import android.content.pm.PackageManagerInternal; import android.content.pm.Signature; import android.content.pm.SigningInfo; +import android.os.Build; import android.os.UserHandle; import android.util.Slog; @@ -41,6 +45,7 @@ import com.android.server.backup.transport.TransportClient; import com.google.android.collect.Sets; +import java.util.Objects; import java.util.Set; /** @@ -57,6 +62,15 @@ public class BackupEligibilityRules { private final int mUserId; @OperationType private final int mOperationType; + /** + * When this change is enabled, {@code adb backup} is automatically turned on for apps + * running as debuggable ({@code android:debuggable} set to {@code true}) and unavailable to + * any other apps. + */ + @ChangeId + @EnabledSince(targetSdkVersion = Build.VERSION_CODES.S) + static final long RESTRICT_ADB_BACKUP = 171032338L; + public static BackupEligibilityRules forBackup(PackageManager packageManager, PackageManagerInternal packageManagerInternal, int userId) { @@ -134,12 +148,53 @@ public class BackupEligibilityRules { * @return boolean indicating whether backup is allowed. */ public boolean isAppBackupAllowed(ApplicationInfo app) { - if (mOperationType == OperationType.MIGRATION && !UserHandle.isCore(app.uid)) { - // Backup / restore of all apps is force allowed during device-to-device migration. - return true; - } + boolean isSystemApp = UserHandle.isCore(app.uid); + boolean allowBackup = (app.flags & ApplicationInfo.FLAG_ALLOW_BACKUP) != 0; + switch (mOperationType) { + case OperationType.MIGRATION: + // Backup / restore of all non-system apps is force allowed during + // device-to-device migration. + return !isSystemApp || allowBackup; + case OperationType.ADB_BACKUP: + String packageName = app.packageName; + if (packageName == null) { + Slog.w(TAG, "Invalid ApplicationInfo object"); + return false; + } - return (app.flags & ApplicationInfo.FLAG_ALLOW_BACKUP) != 0; + if (!CompatChanges.isChangeEnabled(RESTRICT_ADB_BACKUP, packageName, + UserHandle.of(mUserId))) { + return allowBackup; + } + + if (PLATFORM_PACKAGE_NAME.equals(packageName)) { + // Always enable adb backup for SystemBackupAgent in "android" package (this is + // done to avoid breaking existing integration tests and might change in the + // future). + return true; + } + + boolean isPrivileged = (app.flags & ApplicationInfo.PRIVATE_FLAG_PRIVILEGED) != 0; + boolean isDebuggable = (app.flags & ApplicationInfo.FLAG_DEBUGGABLE) != 0; + if (isSystemApp || isPrivileged) { + try { + return mPackageManager.getProperty(PackageManager.PROPERTY_ALLOW_ADB_BACKUP, + packageName).getBoolean(); + } catch (PackageManager.NameNotFoundException e) { + Slog.w(TAG, "Failed to read allowAdbBackup property for + " + + packageName); + return false; + } + } else { + // All other apps can use adb backup only when running in debuggable mode. + return isDebuggable; + } + case OperationType.BACKUP: + return allowBackup; + default: + Slog.w(TAG, "Unknown operation type:" + mOperationType); + return false; + } } /** diff --git a/services/tests/servicestests/Android.bp b/services/tests/servicestests/Android.bp index 343b156e443a3..78b471b7d66cf 100644 --- a/services/tests/servicestests/Android.bp +++ b/services/tests/servicestests/Android.bp @@ -33,6 +33,7 @@ android_test { "androidx.test.ext.truth", "androidx.test.runner", "androidx.test.rules", + "platform-compat-test-rules", "mockito-target-minus-junit4", "platform-test-annotations", "ShortcutManagerTestUtils", diff --git a/services/tests/servicestests/src/com/android/server/backup/utils/BackupEligibilityRulesTest.java b/services/tests/servicestests/src/com/android/server/backup/utils/BackupEligibilityRulesTest.java index 444155d12b3fa..738527e1df0bb 100644 --- a/services/tests/servicestests/src/com/android/server/backup/utils/BackupEligibilityRulesTest.java +++ b/services/tests/servicestests/src/com/android/server/backup/utils/BackupEligibilityRulesTest.java @@ -18,14 +18,17 @@ package com.android.server.backup.utils; import static com.google.common.truth.Truth.assertThat; +import static org.mockito.ArgumentMatchers.eq; import static org.mockito.Mockito.doReturn; import static org.mockito.Mockito.mock; import static org.mockito.Mockito.when; import android.app.backup.BackupManager.OperationType; +import android.compat.testing.PlatformCompatChangeRule; import android.content.pm.ApplicationInfo; import android.content.pm.PackageInfo; import android.content.pm.PackageManager; +import android.content.pm.PackageManager.Property; import android.content.pm.PackageManagerInternal; import android.content.pm.PackageParser; import android.content.pm.Signature; @@ -38,9 +41,15 @@ import androidx.test.filters.SmallTest; import androidx.test.runner.AndroidJUnit4; import com.android.server.backup.UserBackupManagerService; +import com.android.server.pm.parsing.pkg.AndroidPackage; + +import libcore.junit.util.compat.CoreCompatChangeRule.DisableCompatChanges; +import libcore.junit.util.compat.CoreCompatChangeRule.EnableCompatChanges; import org.junit.Before; +import org.junit.Rule; import org.junit.Test; +import org.junit.rules.TestRule; import org.junit.runner.RunWith; import org.mockito.Mock; import org.mockito.MockitoAnnotations; @@ -50,17 +59,19 @@ import org.mockito.MockitoAnnotations; @RunWith(AndroidJUnit4.class) public class BackupEligibilityRulesTest { private static final String CUSTOM_BACKUP_AGENT_NAME = "custom.backup.agent"; - private static final String TEST_PACKAGE_NAME = "test_package"; + private static final String TEST_PACKAGE_NAME = "com.android.frameworks.servicestests"; private static final Signature SIGNATURE_1 = generateSignature((byte) 1); private static final Signature SIGNATURE_2 = generateSignature((byte) 2); private static final Signature SIGNATURE_3 = generateSignature((byte) 3); private static final Signature SIGNATURE_4 = generateSignature((byte) 4); + @Rule public TestRule compatChangeRule = new PlatformCompatChangeRule(); + @Mock private PackageManagerInternal mMockPackageManagerInternal; @Mock private PackageManager mPackageManager; - private BackupEligibilityRules mBackupEligibilityRules; + private BackupEligibilityRules mBackupEligibilityRules; private int mUserId; @Before @@ -225,7 +236,6 @@ public class BackupEligibilityRulesTest { throws Exception { ApplicationInfo applicationInfo = getApplicationInfo(Process.SYSTEM_UID, /* flags */ 0, CUSTOM_BACKUP_AGENT_NAME); - BackupEligibilityRules eligibilityRules = getBackupEligibilityRules( OperationType.MIGRATION); boolean isEligible = eligibilityRules.appIsEligibleForBackup(applicationInfo); @@ -233,6 +243,82 @@ public class BackupEligibilityRulesTest { assertThat(isEligible).isFalse(); } + @Test + @EnableCompatChanges({BackupEligibilityRules.RESTRICT_ADB_BACKUP}) + public void appIsEligibleForBackup_adbBackupNotAllowed_returnsFalseForAdbBackup() + throws Exception { + ApplicationInfo applicationInfo = getApplicationInfo(Process.FIRST_APPLICATION_UID, + /* flags */ ApplicationInfo.PRIVATE_FLAG_PRIVILEGED, CUSTOM_BACKUP_AGENT_NAME); + BackupEligibilityRules eligibilityRules = getBackupEligibilityRules( + OperationType.ADB_BACKUP); + when(mPackageManager.getProperty(eq(PackageManager.PROPERTY_ALLOW_ADB_BACKUP), + eq(TEST_PACKAGE_NAME))).thenReturn(getAdbBackupProperty( + /* allowAdbBackup */ false)); + + boolean isEligible = eligibilityRules.appIsEligibleForBackup(applicationInfo); + + assertThat(isEligible).isFalse(); + } + + @Test + @EnableCompatChanges({BackupEligibilityRules.RESTRICT_ADB_BACKUP}) + public void appIsEligibleForBackup_adbBackupAllowed_returnsTrueForAdbBackup() + throws Exception { + ApplicationInfo applicationInfo = getApplicationInfo(Process.FIRST_APPLICATION_UID, + /* flags */ ApplicationInfo.PRIVATE_FLAG_PRIVILEGED, CUSTOM_BACKUP_AGENT_NAME); + BackupEligibilityRules eligibilityRules = getBackupEligibilityRules( + OperationType.ADB_BACKUP); + when(mPackageManager.getProperty(eq(PackageManager.PROPERTY_ALLOW_ADB_BACKUP), + eq(TEST_PACKAGE_NAME))).thenReturn(getAdbBackupProperty( + /* allowAdbBackup */ true)); + + boolean isEligible = eligibilityRules.appIsEligibleForBackup(applicationInfo); + + assertThat(isEligible).isTrue(); + } + + @Test + @EnableCompatChanges({BackupEligibilityRules.RESTRICT_ADB_BACKUP}) + public void appIsEligibleForBackup_debuggableNonPrivilegedApp_returnsTrueForAdbBackup() + throws Exception { + ApplicationInfo applicationInfo = getApplicationInfo(Process.FIRST_APPLICATION_UID, + /* flags */ ApplicationInfo.FLAG_DEBUGGABLE, CUSTOM_BACKUP_AGENT_NAME); + BackupEligibilityRules eligibilityRules = getBackupEligibilityRules( + OperationType.ADB_BACKUP); + + boolean isEligible = eligibilityRules.appIsEligibleForBackup(applicationInfo); + + assertThat(isEligible).isTrue(); + } + + @Test + @DisableCompatChanges({BackupEligibilityRules.RESTRICT_ADB_BACKUP}) + public void appIsEligibleForBackup_allowBackupTrueBeforeS_returnsTrueForAdbBackup() + throws Exception { + ApplicationInfo applicationInfo = getApplicationInfo(Process.FIRST_APPLICATION_UID, + ApplicationInfo.FLAG_ALLOW_BACKUP, CUSTOM_BACKUP_AGENT_NAME); + BackupEligibilityRules eligibilityRules = getBackupEligibilityRules( + OperationType.ADB_BACKUP); + + boolean isEligible = eligibilityRules.appIsEligibleForBackup(applicationInfo); + + assertThat(isEligible).isTrue(); + } + + @Test + @DisableCompatChanges({BackupEligibilityRules.RESTRICT_ADB_BACKUP}) + public void appIsEligibleForBackup_allowBackupFalseBeforeS_returnsFalseForAdbBackup() + throws Exception { + ApplicationInfo applicationInfo = getApplicationInfo(Process.FIRST_APPLICATION_UID, + /* flags */ 0, CUSTOM_BACKUP_AGENT_NAME); + BackupEligibilityRules eligibilityRules = getBackupEligibilityRules( + OperationType.ADB_BACKUP); + + boolean isEligible = eligibilityRules.appIsEligibleForBackup(applicationInfo); + + assertThat(isEligible).isFalse(); + } + @Test public void appIsDisabled_stateDefaultManifestEnabled_returnsFalse() throws Exception { ApplicationInfo applicationInfo = new ApplicationInfo(); @@ -789,10 +875,15 @@ public class BackupEligibilityRulesTest { private static ApplicationInfo getApplicationInfo(int appUid, int flags, String backupAgentName) { ApplicationInfo applicationInfo = new ApplicationInfo(); - applicationInfo.flags = 0; + applicationInfo.flags = flags; applicationInfo.packageName = TEST_PACKAGE_NAME; applicationInfo.uid = appUid; applicationInfo.backupAgentName = backupAgentName; return applicationInfo; } + + private static Property getAdbBackupProperty(boolean allowAdbBackup) { + return new Property(PackageManager.PROPERTY_ALLOW_ADB_BACKUP, allowAdbBackup, + TEST_PACKAGE_NAME, /* className */ ""); + } }