Add a V->U restore allowlist/denylist
A package is eligible for V to U downgrade restore if either:
- The package has restoreAnyVersion set to false and is part of the V to U allowst
- The package has restoreAnyVersion set to true and is not part of the denylist
Bug : 324233962
Test: atest PerformUnifiedRestoreTaskTest
Manual:check that a package with restoreanyversion=true
is not restored if in the denylist,
check that a package with restoreanyversion=false
is restored if in the allowlist
Change-Id: Id1cf031c593730132ebe24ecab0ffc4a3f87920e
diff --git a/core/java/android/provider/Settings.java b/core/java/android/provider/Settings.java
index ec4d587..50adc40 100644
--- a/core/java/android/provider/Settings.java
+++ b/core/java/android/provider/Settings.java
@@ -12481,6 +12481,24 @@
public static void setLocationProviderEnabled(ContentResolver cr,
String provider, boolean enabled) {
}
+
+ /**
+ * List of system components that support restore in a V-> U OS downgrade but do not have
+ * RestoreAnyVersion set to true. Value set before system restore.
+ * This setting is not B&Rd
+ * List is stored as a comma-separated string of package names e.g. "a,b,c"
+ * @hide
+ */
+ public static final String V_TO_U_RESTORE_ALLOWLIST = "v_to_u_restore_allowlist";
+
+ /**
+ * List of system components that have RestoreAnyVersion set to true but do not support
+ * restore in a V-> U OS downgrade. Value set before system restore.
+ * This setting is not B&Rd
+ * List is stored as a comma-separated string of package names e.g. "a,b,c"
+ * @hide
+ */
+ public static final String V_TO_U_RESTORE_DENYLIST = "v_to_u_restore_denylist";
}
/**
diff --git a/packages/SettingsProvider/test/src/android/provider/SettingsBackupTest.java b/packages/SettingsProvider/test/src/android/provider/SettingsBackupTest.java
index b58187d..28cdc6d 100644
--- a/packages/SettingsProvider/test/src/android/provider/SettingsBackupTest.java
+++ b/packages/SettingsProvider/test/src/android/provider/SettingsBackupTest.java
@@ -807,7 +807,9 @@
Settings.Secure.UI_TRANSLATION_ENABLED,
Settings.Secure.ACCESSIBILITY_DISPLAY_MAGNIFICATION_EDGE_HAPTIC_ENABLED,
Settings.Secure.DND_CONFIGS_MIGRATED,
- Settings.Secure.NAVIGATION_MODE_RESTORE);
+ Settings.Secure.NAVIGATION_MODE_RESTORE,
+ Settings.Secure.V_TO_U_RESTORE_ALLOWLIST,
+ Settings.Secure.V_TO_U_RESTORE_DENYLIST);
@Test
public void systemSettingsBackedUpOrDenied() {
diff --git a/services/backup/flags.aconfig b/services/backup/flags.aconfig
index 71f2b9e..e9f959f 100644
--- a/services/backup/flags.aconfig
+++ b/services/backup/flags.aconfig
@@ -35,6 +35,15 @@
}
flag {
+ name: "enable_v_to_u_restore_for_system_components_in_allowlist"
+ namespace: "onboarding"
+ description: "Enables system components to opt in to support restore in V to U downgrade "
+ "scenario without opting in for restoreAnyVersion."
+ bug: "324233962"
+ is_fixed_read_only: true
+}
+
+flag {
name: "enable_increase_datatypes_for_agent_logging"
namespace: "onboarding"
description: "Increase the number of a supported datatypes that an agent can define for its "
diff --git a/services/backup/java/com/android/server/backup/PackageManagerBackupAgent.java b/services/backup/java/com/android/server/backup/PackageManagerBackupAgent.java
index 9f0deea..6e98e68 100644
--- a/services/backup/java/com/android/server/backup/PackageManagerBackupAgent.java
+++ b/services/backup/java/com/android/server/backup/PackageManagerBackupAgent.java
@@ -177,6 +177,10 @@
return mHasMetadata;
}
+ public int getSourceSdk() {
+ return mStoredSdkVersion;
+ }
+
public Metadata getRestoredMetadata(String packageName) {
if (mRestoredSignatures == null) {
Slog.w(TAG, "getRestoredMetadata() before metadata read!");
diff --git a/services/backup/java/com/android/server/backup/restore/PerformUnifiedRestoreTask.java b/services/backup/java/com/android/server/backup/restore/PerformUnifiedRestoreTask.java
index d85dd87..e666442 100644
--- a/services/backup/java/com/android/server/backup/restore/PerformUnifiedRestoreTask.java
+++ b/services/backup/java/com/android/server/backup/restore/PerformUnifiedRestoreTask.java
@@ -44,6 +44,7 @@
import android.content.pm.PackageManager;
import android.content.pm.PackageManager.NameNotFoundException;
import android.content.pm.PackageManagerInternal;
+import android.os.Build;
import android.os.Bundle;
import android.os.Message;
import android.os.ParcelFileDescriptor;
@@ -51,6 +52,7 @@
import android.os.RemoteException;
import android.os.SystemClock;
import android.os.UserHandle;
+import android.provider.Settings;
import android.util.EventLog;
import android.util.Slog;
@@ -82,6 +84,7 @@
import java.io.FileOutputStream;
import java.io.IOException;
import java.util.ArrayList;
+import java.util.Arrays;
import java.util.List;
import java.util.Objects;
import java.util.Set;
@@ -158,6 +161,12 @@
// When finished call listener
private final OnTaskFinishedListener mListener;
+ // List of packages that support V-> U downgrade but do not have RestoreAnyVersion set to true.
+ private List<String> mVToUAllowlist;
+
+ // List of packages that have RestoreAnyVersion set to true but do not support V-> U downgrade.
+ private List<String> mVToUDenylist;
+
// Key/value: bookkeeping about staged data and files for agent access
private File mBackupDataName;
private File mStageName;
@@ -172,7 +181,8 @@
@VisibleForTesting
PerformUnifiedRestoreTask(
UserBackupManagerService backupManagerService,
- TransportConnection transportConnection) {
+ TransportConnection transportConnection,
+ String vToUAllowlist, String vToUDenyList) {
mListener = null;
mAgentTimeoutParameters = null;
mOperationStorage = null;
@@ -183,6 +193,8 @@
mBackupEligibilityRules = null;
this.backupManagerService = backupManagerService;
mBackupManagerMonitorEventSender = new BackupManagerMonitorEventSender(/* monitor= */ null);
+ mVToUAllowlist = createVToUList(vToUAllowlist);
+ mVToUDenylist = createVToUList(vToUDenyList);
}
// This task can assume that the wakelock is properly held for it and doesn't have to worry
@@ -223,6 +235,18 @@
backupManagerService.getAgentTimeoutParameters(),
"Timeout parameters cannot be null");
mBackupEligibilityRules = backupEligibilityRules;
+ mVToUAllowlist =
+ createVToUList(
+ Settings.Secure.getStringForUser(
+ backupManagerService.getContext().getContentResolver(),
+ Settings.Secure.V_TO_U_RESTORE_ALLOWLIST,
+ mUserId));
+ mVToUDenylist =
+ createVToUList(
+ Settings.Secure.getStringForUser(
+ backupManagerService.getContext().getContentResolver(),
+ Settings.Secure.V_TO_U_RESTORE_DENYLIST,
+ mUserId));
if (targetPackage != null) {
// Single package restore
@@ -636,60 +660,29 @@
// Data is from a "newer" version of the app than we have currently
// installed. If the app has not declared that it is prepared to
// handle this case, we do not attempt the restore.
- if ((mCurrentPackage.applicationInfo.flags
- & ApplicationInfo.FLAG_RESTORE_ANY_VERSION)
- == 0) {
- String message =
- "Source version "
- + metaInfo.versionCode
- + " > installed version "
- + mCurrentPackage.getLongVersionCode();
- Slog.w(TAG, "Package " + pkgName + ": " + message);
- Bundle monitoringExtras =
- mBackupManagerMonitorEventSender.putMonitoringExtra(
- null,
- BackupManagerMonitor.EXTRA_LOG_RESTORE_VERSION,
- metaInfo.versionCode);
- monitoringExtras =
- mBackupManagerMonitorEventSender.putMonitoringExtra(
- monitoringExtras,
- BackupManagerMonitor.EXTRA_LOG_RESTORE_ANYWAY,
- false);
- monitoringExtras = addRestoreOperationTypeToEvent(monitoringExtras);
- mBackupManagerMonitorEventSender.monitorEvent(
- BackupManagerMonitor.LOG_EVENT_ID_RESTORE_VERSION_HIGHER,
- mCurrentPackage,
- BackupManagerMonitor.LOG_EVENT_CATEGORY_BACKUP_MANAGER_POLICY,
- monitoringExtras);
- EventLog.writeEvent(EventLogTags.RESTORE_AGENT_FAILURE, pkgName, message);
- nextState = UnifiedRestoreState.RUNNING_QUEUE;
- return;
- } else {
- if (DEBUG) {
- Slog.v(
- TAG,
- "Source version "
- + metaInfo.versionCode
- + " > installed version "
- + mCurrentPackage.getLongVersionCode()
- + " but restoreAnyVersion");
+ if (mIsSystemRestore
+ && isVToUDowngrade(mPmAgent.getSourceSdk(), android.os.Build.VERSION.SDK_INT)) {
+ if (isPackageEligibleForVToURestore(mCurrentPackage)) {
+ Slog.i(TAG, "Package " + pkgName
+ + " is eligible for V to U downgrade scenario");
+ } else {
+ String message = "Package not eligible for V to U downgrade scenario";
+ Slog.i(TAG, pkgName + " : " + message);
+ EventLog.writeEvent(EventLogTags.RESTORE_AGENT_FAILURE, pkgName, message);
+ nextState = UnifiedRestoreState.RUNNING_QUEUE;
+ return;
}
- Bundle monitoringExtras =
- mBackupManagerMonitorEventSender.putMonitoringExtra(
- null,
- BackupManagerMonitor.EXTRA_LOG_RESTORE_VERSION,
- metaInfo.versionCode);
- monitoringExtras =
- mBackupManagerMonitorEventSender.putMonitoringExtra(
- monitoringExtras,
- BackupManagerMonitor.EXTRA_LOG_RESTORE_ANYWAY,
- true);
- monitoringExtras = addRestoreOperationTypeToEvent(monitoringExtras);
- mBackupManagerMonitorEventSender.monitorEvent(
- BackupManagerMonitor.LOG_EVENT_ID_RESTORE_VERSION_HIGHER,
- mCurrentPackage,
- BackupManagerMonitor.LOG_EVENT_CATEGORY_BACKUP_MANAGER_POLICY,
- monitoringExtras);
+ } else {
+ if ((mCurrentPackage.applicationInfo.flags
+ & ApplicationInfo.FLAG_RESTORE_ANY_VERSION)
+ == 0) {
+ // Downgrade scenario with RestoreAnyVersion flag off
+ logDowngradeScenario(/* isRestoreAnyVersion */ false, metaInfo);
+ nextState = UnifiedRestoreState.RUNNING_QUEUE;
+ return;
+ } else {
+ logDowngradeScenario(/* isRestoreAnyVersion */ true, metaInfo);
+ }
}
}
@@ -1673,4 +1666,86 @@
return mBackupManagerMonitorEventSender.putMonitoringExtra(
extras, BackupManagerMonitor.EXTRA_LOG_OPERATION_TYPE, RESTORE);
}
+
+ // checks the sdk of the target/source device for a B&R operation.
+ // system components can opt in/out of V->U restore via allowlists. All other apps are
+ // not impacted
+ @SuppressWarnings("AndroidFrameworkCompatChange")
+ @VisibleForTesting
+ protected boolean isVToUDowngrade(int sourceSdk, int targetSdk) {
+ // We assume that if the source sdk is greater than U then the source is V.
+ return Flags.enableVToURestoreForSystemComponentsInAllowlist()
+ && (sourceSdk > Build.VERSION_CODES.UPSIDE_DOWN_CAKE)
+ && (targetSdk == Build.VERSION_CODES.UPSIDE_DOWN_CAKE);
+ }
+
+ @VisibleForTesting
+ protected List<String> createVToUList(@Nullable String listString) {
+ // The allowlist/denylist is stored as a comma-separated list of package names
+ List<String> list = new ArrayList<>();
+ if (listString != null) {
+ list = Arrays.asList(listString.split(","));
+ }
+ return list;
+ }
+
+ @VisibleForTesting
+ protected boolean isPackageEligibleForVToURestore(PackageInfo mCurrentPackage) {
+ // A package is eligible for V to U downgrade restore if either:
+ // - The package has restoreAnyVersion set to false and is part of the V to U allowlist
+ // (and not in the denylist)
+ // - The package has restoreAnyVersion set to true and is not part of the denylist
+ if (mVToUDenylist.contains(mCurrentPackage.packageName)){
+ return false;
+ } else if ((mCurrentPackage.applicationInfo.flags
+ & ApplicationInfo.FLAG_RESTORE_ANY_VERSION)
+ == 0) {
+ // package has restoreAnyVersion set to false
+ return mVToUAllowlist.contains(mCurrentPackage.packageName);
+ } else {
+ // package has restoreAnyVersion set to true and is nor in denylist
+ return true;
+ }
+ }
+
+ private void logDowngradeScenario(boolean isRestoreAnyVersion, Metadata metaInfo) {
+ Bundle monitoringExtras =
+ mBackupManagerMonitorEventSender.putMonitoringExtra(
+ null,
+ BackupManagerMonitor.EXTRA_LOG_RESTORE_VERSION,
+ metaInfo.versionCode);
+ String message;
+ if (isRestoreAnyVersion) {
+ monitoringExtras =
+ mBackupManagerMonitorEventSender.putMonitoringExtra(
+ monitoringExtras,
+ BackupManagerMonitor.EXTRA_LOG_RESTORE_ANYWAY,
+ true);
+ message = "Source version "
+ + metaInfo.versionCode
+ + " > installed version "
+ + mCurrentPackage.getLongVersionCode()
+ + " but restoreAnyVersion";
+ } else {
+ monitoringExtras =
+ mBackupManagerMonitorEventSender.putMonitoringExtra(
+ monitoringExtras,
+ BackupManagerMonitor.EXTRA_LOG_RESTORE_ANYWAY,
+ false);
+ message = "Source version "
+ + metaInfo.versionCode
+ + " > installed version "
+ + mCurrentPackage.getLongVersionCode();
+ EventLog.writeEvent(EventLogTags.RESTORE_AGENT_FAILURE, mCurrentPackage.packageName,
+ message);
+ }
+ Slog.i(TAG, "Package " + mCurrentPackage.packageName + ": " + message);
+ monitoringExtras = addRestoreOperationTypeToEvent(monitoringExtras);
+ mBackupManagerMonitorEventSender.monitorEvent(
+ BackupManagerMonitor.LOG_EVENT_ID_RESTORE_VERSION_HIGHER,
+ mCurrentPackage,
+ BackupManagerMonitor.LOG_EVENT_CATEGORY_BACKUP_MANAGER_POLICY,
+ monitoringExtras);
+ }
+
}
diff --git a/services/tests/mockingservicestests/src/com/android/server/backup/restore/PerformUnifiedRestoreTaskTest.java b/services/tests/mockingservicestests/src/com/android/server/backup/restore/PerformUnifiedRestoreTaskTest.java
index 940469f..414532b 100644
--- a/services/tests/mockingservicestests/src/com/android/server/backup/restore/PerformUnifiedRestoreTaskTest.java
+++ b/services/tests/mockingservicestests/src/com/android/server/backup/restore/PerformUnifiedRestoreTaskTest.java
@@ -29,16 +29,20 @@
import android.app.backup.BackupDataOutput;
import android.app.backup.BackupTransport;
import android.content.Context;
+import android.content.pm.ApplicationInfo;
import android.content.pm.PackageInfo;
+import android.os.Build;
import android.os.Message;
import android.os.RemoteException;
import android.platform.test.annotations.Presubmit;
+import android.platform.test.flag.junit.SetFlagsRule;
import android.provider.DeviceConfig;
import androidx.test.InstrumentationRegistry;
import androidx.test.runner.AndroidJUnit4;
import com.android.modules.utils.testing.TestableDeviceConfig;
+import com.android.server.backup.Flags;
import com.android.server.backup.UserBackupManagerService;
import com.android.server.backup.internal.BackupHandler;
import com.android.server.backup.transport.BackupTransportClient;
@@ -56,10 +60,12 @@
import org.mockito.stubbing.Answer;
import java.util.ArrayDeque;
+import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collections;
import java.util.HashMap;
import java.util.HashSet;
+import java.util.List;
import java.util.Map;
import java.util.Queue;
import java.util.Set;
@@ -74,10 +80,17 @@
private static final String SYSTEM_PACKAGE_NAME = "android";
private static final String NON_SYSTEM_PACKAGE_NAME = "package";
- @Mock private BackupDataInput mBackupDataInput;
- @Mock private BackupDataOutput mBackupDataOutput;
- @Mock private UserBackupManagerService mBackupManagerService;
- @Mock private TransportConnection mTransportConnection;
+ private static final String V_TO_U_ALLOWLIST = "pkg1";
+ private static final String V_TO_U_DENYLIST = "pkg2";
+
+ @Mock
+ private BackupDataInput mBackupDataInput;
+ @Mock
+ private BackupDataOutput mBackupDataOutput;
+ @Mock
+ private UserBackupManagerService mBackupManagerService;
+ @Mock
+ private TransportConnection mTransportConnection;
private Set<String> mExcludedkeys = new HashSet<>();
private Map<String, String> mBackupData = new HashMap<>();
@@ -91,6 +104,10 @@
public TestableDeviceConfig.TestableDeviceConfigRule mDeviceConfigRule =
new TestableDeviceConfig.TestableDeviceConfigRule();
+ @Rule
+ public final SetFlagsRule mSetFlagsRule = new SetFlagsRule();
+
+
private Context mContext;
@Before
@@ -118,7 +135,8 @@
return null;
});
- mRestoreTask = new PerformUnifiedRestoreTask(mBackupManagerService, mTransportConnection);
+ mRestoreTask = new PerformUnifiedRestoreTask(mBackupManagerService, mTransportConnection,
+ V_TO_U_ALLOWLIST, V_TO_U_DENYLIST);
}
private void populateTestData() {
@@ -235,6 +253,122 @@
== UnifiedRestoreState.FINAL);
}
+ @Test
+ public void testCreateVToUList_listSettingIsNull_returnEmptyList() {
+ List<String> expectedEmptyList = new ArrayList<>();
+
+ List<String> list = mRestoreTask.createVToUList(null);
+
+ assertEquals(list, expectedEmptyList);
+ }
+
+ @Test
+ public void testCreateVToUList_listIsNotNull_returnCorrectList() {
+ List<String> expectedList = Arrays.asList("a", "b", "c");
+ String listString = "a,b,c";
+
+ List<String> list = mRestoreTask.createVToUList(listString);
+
+ assertEquals(list, expectedList);
+ }
+
+ @Test
+ public void testIsVToUDowngrade_vToUFlagIsOffAndTargetIsUSourceIsV_returnFalse() {
+ mSetFlagsRule.disableFlags(
+ Flags.FLAG_ENABLE_V_TO_U_RESTORE_FOR_SYSTEM_COMPONENTS_IN_ALLOWLIST);
+
+ boolean isVToUDowngrade = mRestoreTask.isVToUDowngrade(
+ Build.VERSION_CODES.VANILLA_ICE_CREAM, Build.VERSION_CODES.UPSIDE_DOWN_CAKE);
+
+ assertFalse(isVToUDowngrade);
+ }
+
+ @Test
+ public void testIsVToUDowngrade_vToUFlagIsOnAndTargetIsUSourceIsV_returnTrue() {
+ mSetFlagsRule.enableFlags(
+ Flags.FLAG_ENABLE_V_TO_U_RESTORE_FOR_SYSTEM_COMPONENTS_IN_ALLOWLIST);
+
+ boolean isVToUDowngrade = mRestoreTask.isVToUDowngrade(
+ Build.VERSION_CODES.VANILLA_ICE_CREAM, Build.VERSION_CODES.UPSIDE_DOWN_CAKE);
+
+ assertTrue(isVToUDowngrade);
+ }
+
+ @Test
+ public void testIsVToUDowngrade_vToUFlagIsOnAndSourceIsNotV_returnFalse() {
+ mSetFlagsRule.enableFlags(
+ Flags.FLAG_ENABLE_V_TO_U_RESTORE_FOR_SYSTEM_COMPONENTS_IN_ALLOWLIST);
+
+ boolean isVToUDowngrade = mRestoreTask.isVToUDowngrade(Build.VERSION_CODES.UPSIDE_DOWN_CAKE,
+ Build.VERSION_CODES.UPSIDE_DOWN_CAKE);
+
+ assertFalse(isVToUDowngrade);
+ }
+
+ @Test
+ public void testIsVToUDowngrade_vToUFlagIsOnAndTargetIsNotU_returnFalse() {
+ mSetFlagsRule.enableFlags(
+ Flags.FLAG_ENABLE_V_TO_U_RESTORE_FOR_SYSTEM_COMPONENTS_IN_ALLOWLIST);
+
+ boolean isVToUDowngrade = mRestoreTask.isVToUDowngrade(
+ Build.VERSION_CODES.VANILLA_ICE_CREAM, Build.VERSION_CODES.VANILLA_ICE_CREAM);
+
+ assertFalse(isVToUDowngrade);
+ }
+
+
+ @Test
+ public void testIsEligibleForVToUDowngrade_pkgIsNotOnAllowlist_returnFalse() {
+ PackageInfo testPackageInfo = new PackageInfo();
+ testPackageInfo.packageName = "pkg";
+ testPackageInfo.applicationInfo = new ApplicationInfo();
+ // restoreAnyVersion flag is off
+ testPackageInfo.applicationInfo.flags = 0;
+
+ boolean eligibilityCriteria = mRestoreTask.isPackageEligibleForVToURestore(testPackageInfo);
+
+ assertFalse(eligibilityCriteria);
+ }
+
+ @Test
+ public void testIsEligibleForVToUDowngrade_pkgIsOnAllowlist_returnTrue() {
+ PackageInfo testPackageInfo = new PackageInfo();
+ testPackageInfo.packageName = "pkg1";
+ testPackageInfo.applicationInfo = new ApplicationInfo();
+ // restoreAnyVersion flag is off
+ testPackageInfo.applicationInfo.flags = 0;
+
+ boolean eligibilityCriteria = mRestoreTask.isPackageEligibleForVToURestore(testPackageInfo);
+
+ assertTrue(eligibilityCriteria);
+ }
+
+ @Test
+ public void testIsEligibleForVToUDowngrade_pkgIsNotOnDenyList_returnTrue() {
+ PackageInfo testPackageInfo = new PackageInfo();
+ testPackageInfo.packageName = "pkg";
+ testPackageInfo.applicationInfo = new ApplicationInfo();
+ // restoreAnyVersion flag is on
+ testPackageInfo.applicationInfo.flags = ApplicationInfo.FLAG_RESTORE_ANY_VERSION;
+
+ boolean eligibilityCriteria = mRestoreTask.isPackageEligibleForVToURestore(testPackageInfo);
+
+ assertTrue(eligibilityCriteria);
+ }
+
+ @Test
+ public void testIsEligibleForVToUDowngrade_pkgIsOnDenyList_returnFalse() {
+ PackageInfo testPackageInfo = new PackageInfo();
+ testPackageInfo.packageName = "pkg2";
+ testPackageInfo.applicationInfo = new ApplicationInfo();
+ // restoreAnyVersion flag is on
+ testPackageInfo.applicationInfo.flags = ApplicationInfo.FLAG_RESTORE_ANY_VERSION;
+
+ boolean eligibilityCriteria = mRestoreTask.isPackageEligibleForVToURestore(testPackageInfo);
+
+ assertFalse(eligibilityCriteria);
+ }
+
private void setupForRestoreKeyValueState(int transportStatus)
throws RemoteException, TransportNotAvailableException {
// Mock BackupHandler to do nothing when executeNextState() is called