From b9a46dfe0dc8b726b746fddd009961a195f78866 Mon Sep 17 00:00:00 2001 From: Hongyi Zhang Date: Fri, 30 Oct 2020 17:57:56 -0700 Subject: [PATCH] Factory Reset flag & minor refinement over flag reset scopes 1. At the step of wiping all namespaces, only wipe namespaces with recorded access by a package. If a namespace is not even accessed by any package, then resetting it most likely won't make any changes. Restricting the scope can slightly make the reset less aggressive. 2. In performScopedReset step, if the package hasn't been recorded to access any namespace, reset nothing rather than reset all namespaces. "reset all" will happen on the next level any way. 3. Allowlist namespace "configuration" from RescueParty resets, so that we can use it to contain flags for RescueParty itself. 4. Add a flag which can disable factory reset(but by default still enabling it) into namespace "configuration". Test: atest RescuePartyTest Bug: 162425385 Change-Id: Ie2fbd531535fcaed31c72331b01453e48777eacf --- .../java/com/android/server/RescueParty.java | 50 ++++++++++++++++--- .../com/android/server/RescuePartyTest.java | 50 +++++++++++++++---- 2 files changed, 85 insertions(+), 15 deletions(-) diff --git a/services/core/java/com/android/server/RescueParty.java b/services/core/java/com/android/server/RescueParty.java index 9fc8f0b5a3c37..ef6dab5a45fbb 100644 --- a/services/core/java/com/android/server/RescueParty.java +++ b/services/core/java/com/android/server/RescueParty.java @@ -56,6 +56,7 @@ import java.io.File; import java.util.ArrayList; import java.util.Arrays; import java.util.HashMap; +import java.util.HashSet; import java.util.Iterator; import java.util.List; import java.util.Map; @@ -100,6 +101,10 @@ public class RescueParty { private static final String PROP_VIRTUAL_DEVICE = "ro.hardware.virtual_device"; private static final String PROP_DEVICE_CONFIG_DISABLE_FLAG = "persist.device_config.configuration.disable_rescue_party"; + private static final String PROP_DISABLE_FACTORY_RESET_FLAG = + "persist.device_config.configuration.disable_rescue_party_factory_reset"; + // The DeviceConfig namespace containing all RescueParty switches. + private static final String NAMESPACE_CONFIGURATION = "configuration"; private static final int PERSISTENT_MASK = ApplicationInfo.FLAG_PERSISTENT | ApplicationInfo.FLAG_SYSTEM; @@ -215,6 +220,10 @@ public class RescueParty { if (SettingsToPropertiesMapper.isNativeFlagsResetPerformed()) { String[] resetNativeCategories = SettingsToPropertiesMapper.getResetNativeCategories(); for (int i = 0; i < resetNativeCategories.length; i++) { + // Don't let RescueParty reset the namespace for RescueParty switches. + if (NAMESPACE_CONFIGURATION.equals(resetNativeCategories[i])) { + continue; + } DeviceConfig.resetToDefaults(Settings.RESET_MODE_TRUSTED_DEFAULTS, resetNativeCategories[i]); } @@ -225,8 +234,10 @@ public class RescueParty { * Get the next rescue level. This indicates the next level of mitigation that may be taken. */ private static int getNextRescueLevel() { + int maxRescueLevel = SystemProperties.getBoolean(PROP_DISABLE_FACTORY_RESET_FLAG, false) + ? LEVEL_RESET_SETTINGS_TRUSTED_DEFAULTS : LEVEL_FACTORY_RESET; return MathUtils.constrain(SystemProperties.getInt(PROP_RESCUE_LEVEL, LEVEL_NONE) + 1, - LEVEL_NONE, LEVEL_FACTORY_RESET); + LEVEL_NONE, maxRescueLevel); } /** @@ -349,12 +360,30 @@ public class RescueParty { private static void resetDeviceConfig(Context context, int resetMode, @Nullable String failedPackage) { if (!shouldPerformScopedResets() || failedPackage == null) { - DeviceConfig.resetToDefaults(resetMode, /*namespace=*/ null); + resetAllAffectedNamespaces(context, resetMode); } else { performScopedReset(context, resetMode, failedPackage); } } + private static void resetAllAffectedNamespaces(Context context, int resetMode) { + RescuePartyObserver rescuePartyObserver = RescuePartyObserver.getInstance(context); + Set allAffectedNamespaces = rescuePartyObserver.getAllAffectedNamespaceSet(); + + Slog.w(TAG, + "Performing reset for all affected namespaces: " + + Arrays.toString(allAffectedNamespaces.toArray())); + Iterator it = allAffectedNamespaces.iterator(); + while (it.hasNext()) { + String namespace = it.next(); + // Don't let RescueParty reset the namespace for RescueParty switches. + if (NAMESPACE_CONFIGURATION.equals(namespace)) { + continue; + } + DeviceConfig.resetToDefaults(resetMode, namespace); + } + } + private static boolean shouldPerformScopedResets() { int rescueLevel = MathUtils.constrain( SystemProperties.getInt(PROP_RESCUE_LEVEL, LEVEL_NONE), @@ -367,16 +396,21 @@ public class RescueParty { RescuePartyObserver rescuePartyObserver = RescuePartyObserver.getInstance(context); Set affectedNamespaces = rescuePartyObserver.getAffectedNamespaceSet( failedPackage); - if (affectedNamespaces == null) { - DeviceConfig.resetToDefaults(resetMode, /*namespace=*/ null); - } else { + // If we can't find namespaces affected for current package, + // skip this round of reset. + if (affectedNamespaces != null) { Slog.w(TAG, "Performing scoped reset for package: " + failedPackage + ", affected namespaces: " + Arrays.toString(affectedNamespaces.toArray())); Iterator it = affectedNamespaces.iterator(); while (it.hasNext()) { - DeviceConfig.resetToDefaults(resetMode, it.next()); + String namespace = it.next(); + // Don't let RescueParty reset the namespace for RescueParty switches. + if (NAMESPACE_CONFIGURATION.equals(namespace)) { + continue; + } + DeviceConfig.resetToDefaults(resetMode, namespace); } } } @@ -514,6 +548,10 @@ public class RescueParty { return mCallingPackageNamespaceSetMap.get(failedPackage); } + private synchronized Set getAllAffectedNamespaceSet() { + return new HashSet(mNamespaceCallingPackageSetMap.keySet()); + } + private synchronized Set getCallingPackagesSet(String namespace) { return mNamespaceCallingPackageSetMap.get(namespace); } diff --git a/services/tests/mockingservicestests/src/com/android/server/RescuePartyTest.java b/services/tests/mockingservicestests/src/com/android/server/RescuePartyTest.java index 736a7be5e39ec..2c92ae44d63a5 100644 --- a/services/tests/mockingservicestests/src/com/android/server/RescuePartyTest.java +++ b/services/tests/mockingservicestests/src/com/android/server/RescuePartyTest.java @@ -27,9 +27,11 @@ import static com.android.dx.mockito.inline.extended.ExtendedMockito.when; import static com.android.server.RescueParty.LEVEL_FACTORY_RESET; import static org.junit.Assert.assertEquals; +import static org.junit.Assert.assertFalse; import static org.junit.Assert.assertTrue; import static org.mockito.ArgumentMatchers.eq; import static org.mockito.ArgumentMatchers.isNull; +import static org.mockito.Mockito.never; import static org.mockito.Mockito.times; import android.content.ContentResolver; @@ -79,8 +81,11 @@ public class RescuePartyTest { private static final String CALLING_PACKAGE2 = "com.package.name2"; private static final String NAMESPACE1 = "namespace1"; private static final String NAMESPACE2 = "namespace2"; + private static final String NAMESPACE3 = "namespace3"; private static final String PROP_DEVICE_CONFIG_DISABLE_FLAG = "persist.device_config.configuration.disable_rescue_party"; + private static final String PROP_DISABLE_FACTORY_RESET_FLAG = + "persist.device_config.configuration.disable_rescue_party_factory_reset"; private MockitoSession mSession; private HashMap mSystemSettingsMap; @@ -183,27 +188,38 @@ public class RescuePartyTest { @Test public void testBootLoopDetectionWithExecutionForAllRescueLevels() { + RescueParty.onSettingsProviderPublished(mMockContext); + verify(() -> Settings.Config.registerMonitorCallback(eq(mMockContentResolver), + mMonitorCallbackCaptor.capture())); + noteBoot(); verifySettingsResets(Settings.RESET_MODE_UNTRUSTED_DEFAULTS, /*resetNamespaces=*/ null); assertEquals(RescueParty.LEVEL_RESET_SETTINGS_UNTRUSTED_DEFAULTS, SystemProperties.getInt(RescueParty.PROP_RESCUE_LEVEL, RescueParty.LEVEL_NONE)); + // Record DeviceConfig accesses + RescuePartyObserver observer = RescuePartyObserver.getInstance(mMockContext); + RemoteCallback monitorCallback = mMonitorCallbackCaptor.getValue(); + monitorCallback.sendResult(getConfigAccessBundle(CALLING_PACKAGE1, NAMESPACE1)); + monitorCallback.sendResult(getConfigAccessBundle(CALLING_PACKAGE1, NAMESPACE2)); + + final String[] expectedAllResetNamespaces = new String[]{NAMESPACE1, NAMESPACE2}; + noteBoot(); - verifySettingsResets(Settings.RESET_MODE_UNTRUSTED_CHANGES, /*resetNamespaces=*/ null); + verifySettingsResets(Settings.RESET_MODE_UNTRUSTED_CHANGES, expectedAllResetNamespaces); assertEquals(RescueParty.LEVEL_RESET_SETTINGS_UNTRUSTED_CHANGES, SystemProperties.getInt(RescueParty.PROP_RESCUE_LEVEL, RescueParty.LEVEL_NONE)); noteBoot(); - verifySettingsResets(Settings.RESET_MODE_TRUSTED_DEFAULTS, /*resetNamespaces=*/ null); + verifySettingsResets(Settings.RESET_MODE_TRUSTED_DEFAULTS, expectedAllResetNamespaces); assertEquals(RescueParty.LEVEL_RESET_SETTINGS_TRUSTED_DEFAULTS, SystemProperties.getInt(RescueParty.PROP_RESCUE_LEVEL, RescueParty.LEVEL_NONE)); noteBoot(); - verify(() -> RecoverySystem.rebootPromptAndWipeUserData(mMockContext, RescueParty.TAG)); assertEquals(LEVEL_FACTORY_RESET, SystemProperties.getInt(RescueParty.PROP_RESCUE_LEVEL, RescueParty.LEVEL_NONE)); } @@ -230,7 +246,6 @@ public class RescuePartyTest { notePersistentAppCrash(); - verify(() -> RecoverySystem.rebootPromptAndWipeUserData(mMockContext, RescueParty.TAG)); assertEquals(LEVEL_FACTORY_RESET, SystemProperties.getInt(RescueParty.PROP_RESCUE_LEVEL, RescueParty.LEVEL_NONE)); } @@ -247,6 +262,7 @@ public class RescuePartyTest { monitorCallback.sendResult(getConfigAccessBundle(CALLING_PACKAGE1, NAMESPACE1)); monitorCallback.sendResult(getConfigAccessBundle(CALLING_PACKAGE1, NAMESPACE2)); monitorCallback.sendResult(getConfigAccessBundle(CALLING_PACKAGE2, NAMESPACE2)); + monitorCallback.sendResult(getConfigAccessBundle(CALLING_PACKAGE2, NAMESPACE3)); // Fake DeviceConfig value changes monitorCallback.sendResult(getConfigNamespaceUpdateBundle(NAMESPACE1)); verify(mMockPackageWatchdog).startObservingHealth(observer, @@ -255,10 +271,15 @@ public class RescuePartyTest { verify(mMockPackageWatchdog, times(2)).startObservingHealth(eq(observer), mPackageListCaptor.capture(), eq(RescueParty.DEFAULT_OBSERVING_DURATION_MS)); + monitorCallback.sendResult(getConfigNamespaceUpdateBundle(NAMESPACE3)); + verify(mMockPackageWatchdog).startObservingHealth(observer, + Arrays.asList(CALLING_PACKAGE2), RescueParty.DEFAULT_OBSERVING_DURATION_MS); assertTrue(mPackageListCaptor.getValue().containsAll( Arrays.asList(CALLING_PACKAGE1, CALLING_PACKAGE2))); // Perform and verify scoped resets final String[] expectedResetNamespaces = new String[]{NAMESPACE1, NAMESPACE2}; + final String[] expectedAllResetNamespaces = + new String[]{NAMESPACE1, NAMESPACE2, NAMESPACE3}; observer.execute(new VersionedPackage( CALLING_PACKAGE1, 1), PackageWatchdog.FAILURE_REASON_APP_CRASH); verifySettingsResets(Settings.RESET_MODE_UNTRUSTED_DEFAULTS, expectedResetNamespaces); @@ -273,13 +294,12 @@ public class RescuePartyTest { observer.execute(new VersionedPackage( CALLING_PACKAGE1, 1), PackageWatchdog.FAILURE_REASON_APP_NOT_RESPONDING); - verifySettingsResets(Settings.RESET_MODE_TRUSTED_DEFAULTS, /*resetNamespaces=*/null); + verifySettingsResets(Settings.RESET_MODE_TRUSTED_DEFAULTS, expectedAllResetNamespaces); assertEquals(RescueParty.LEVEL_RESET_SETTINGS_TRUSTED_DEFAULTS, SystemProperties.getInt(RescueParty.PROP_RESCUE_LEVEL, RescueParty.LEVEL_NONE)); observer.execute(new VersionedPackage( CALLING_PACKAGE1, 1), PackageWatchdog.FAILURE_REASON_APP_CRASH); - verify(() -> RecoverySystem.rebootPromptAndWipeUserData(mMockContext, RescueParty.TAG)); assertTrue(RescueParty.isAttemptingFactoryReset()); } @@ -288,7 +308,6 @@ public class RescuePartyTest { for (int i = 0; i < LEVEL_FACTORY_RESET; i++) { noteBoot(); } - verify(() -> RecoverySystem.rebootPromptAndWipeUserData(mMockContext, RescueParty.TAG)); assertTrue(RescueParty.isAttemptingFactoryReset()); } @@ -337,11 +356,24 @@ public class RescuePartyTest { assertEquals(RescuePartyObserver.getInstance(mMockContext).execute(sFailingPackage, PackageWatchdog.FAILURE_REASON_APP_NOT_RESPONDING), false); - // Restore the property value initalized in SetUp() + // Restore the property value initialized in SetUp() SystemProperties.set(RescueParty.PROP_ENABLE_RESCUE, Boolean.toString(true)); SystemProperties.set(PROP_DEVICE_CONFIG_DISABLE_FLAG, Boolean.toString(false)); } + @Test + public void testDisablingFactoryResetByDeviceConfigFlag() { + SystemProperties.set(PROP_DISABLE_FACTORY_RESET_FLAG, Boolean.toString(true)); + + for (int i = 0; i < LEVEL_FACTORY_RESET; i++) { + noteBoot(); + } + assertFalse(RescueParty.isAttemptingFactoryReset()); + + // Restore the property value initialized in SetUp() + SystemProperties.set(PROP_DISABLE_FACTORY_RESET_FLAG, ""); + } + @Test public void testHealthCheckLevels() { RescuePartyObserver observer = RescuePartyObserver.getInstance(mMockContext); @@ -437,7 +469,7 @@ public class RescuePartyTest { eq(resetMode), anyInt())); // Verify DeviceConfig resets if (resetNamespaces == null) { - verify(() -> DeviceConfig.resetToDefaults(resetMode, /*namespace=*/ null)); + verify(() -> DeviceConfig.resetToDefaults(anyInt(), anyString()), never()); } else { for (String namespace : resetNamespaces) { verify(() -> DeviceConfig.resetToDefaults(resetMode, namespace));