Donate to e Foundation | Murena handsets with /e/OS | Own a part of Murena! Learn more

Commit b9a46dfe authored by Hongyi Zhang's avatar Hongyi Zhang
Browse files

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
parent 090fab97
Loading
Loading
Loading
Loading
+44 −6
Original line number Diff line number Diff line
@@ -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<String> allAffectedNamespaces = rescuePartyObserver.getAllAffectedNamespaceSet();

        Slog.w(TAG,
                "Performing reset for all affected namespaces: "
                        + Arrays.toString(allAffectedNamespaces.toArray()));
        Iterator<String> 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<String> 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<String> 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<String> getAllAffectedNamespaceSet() {
            return new HashSet<String>(mNamespaceCallingPackageSetMap.keySet());
        }

        private synchronized Set<String> getCallingPackagesSet(String namespace) {
            return mNamespaceCallingPackageSetMap.get(namespace);
        }
+41 −9
Original line number Diff line number Diff line
@@ -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<String, String> 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));