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

Commit 3fdf5c31 authored by Gavin Corkery's avatar Gavin Corkery
Browse files

Restrict Rescue Party factory reset packages

Only allow Rescue Party to escalate to a factory reset
for boot loops, and consistent crashes of persistent
system packages.

Amends RescuePartyTest to test the rescue steps that
are taken for both persistent and non-persistent
packages.

Test: atest RescuePartyTest
Bug: 198264578
Change-Id: I12dc019826a07231e1ac5cbd57afc5f3ea2dd39b
Merged-In: I12dc019826a07231e1ac5cbd57afc5f3ea2dd39b
(cherry picked from commit 6d72bede)
parent bc9015e2
Loading
Loading
Loading
Loading
+42 −16
Original line number Original line Diff line number Diff line
@@ -327,18 +327,23 @@ public class RescueParty {
        }
        }
    }
    }


    private static int getMaxRescueLevel() {
    private static int getMaxRescueLevel(boolean mayPerformFactoryReset) {
        return SystemProperties.getBoolean(PROP_DISABLE_FACTORY_RESET_FLAG, false)
        if (!mayPerformFactoryReset
                ? LEVEL_RESET_SETTINGS_TRUSTED_DEFAULTS : LEVEL_FACTORY_RESET;
                || SystemProperties.getBoolean(PROP_DISABLE_FACTORY_RESET_FLAG, false)) {
            return LEVEL_RESET_SETTINGS_TRUSTED_DEFAULTS;
        }
        return LEVEL_FACTORY_RESET;
    }
    }


    /**
    /**
     * Get the rescue level to perform if this is the n-th attempt at mitigating failure.
     * Get the rescue level to perform if this is the n-th attempt at mitigating failure.
     *
     *
     * @param mitigationCount: the mitigation attempt number (1 = first attempt etc.)
     * @param mitigationCount: the mitigation attempt number (1 = first attempt etc.)
     * @param mayPerformFactoryReset: whether or not a factory reset may be performed for the given
     *                              failure.
     * @return the rescue level for the n-th mitigation attempt.
     * @return the rescue level for the n-th mitigation attempt.
     */
     */
    private static int getRescueLevel(int mitigationCount) {
    private static int getRescueLevel(int mitigationCount, boolean mayPerformFactoryReset) {
        if (mitigationCount == 1) {
        if (mitigationCount == 1) {
            return LEVEL_RESET_SETTINGS_UNTRUSTED_DEFAULTS;
            return LEVEL_RESET_SETTINGS_UNTRUSTED_DEFAULTS;
        } else if (mitigationCount == 2) {
        } else if (mitigationCount == 2) {
@@ -346,9 +351,9 @@ public class RescueParty {
        } else if (mitigationCount == 3) {
        } else if (mitigationCount == 3) {
            return LEVEL_RESET_SETTINGS_TRUSTED_DEFAULTS;
            return LEVEL_RESET_SETTINGS_TRUSTED_DEFAULTS;
        } else if (mitigationCount == 4) {
        } else if (mitigationCount == 4) {
            return Math.min(getMaxRescueLevel(), LEVEL_WARM_REBOOT);
            return Math.min(getMaxRescueLevel(mayPerformFactoryReset), LEVEL_WARM_REBOOT);
        } else if (mitigationCount >= 5) {
        } else if (mitigationCount >= 5) {
            return Math.min(getMaxRescueLevel(), LEVEL_FACTORY_RESET);
            return Math.min(getMaxRescueLevel(mayPerformFactoryReset), LEVEL_FACTORY_RESET);
        } else {
        } else {
            Slog.w(TAG, "Expected positive mitigation count, was " + mitigationCount);
            Slog.w(TAG, "Expected positive mitigation count, was " + mitigationCount);
            return LEVEL_NONE;
            return LEVEL_NONE;
@@ -614,7 +619,8 @@ public class RescueParty {
                @FailureReasons int failureReason, int mitigationCount) {
                @FailureReasons int failureReason, int mitigationCount) {
            if (!isDisabled() && (failureReason == PackageWatchdog.FAILURE_REASON_APP_CRASH
            if (!isDisabled() && (failureReason == PackageWatchdog.FAILURE_REASON_APP_CRASH
                    || failureReason == PackageWatchdog.FAILURE_REASON_APP_NOT_RESPONDING)) {
                    || failureReason == PackageWatchdog.FAILURE_REASON_APP_NOT_RESPONDING)) {
                return mapRescueLevelToUserImpact(getRescueLevel(mitigationCount));
                return mapRescueLevelToUserImpact(getRescueLevel(mitigationCount,
                        mayPerformFactoryReset(failedPackage)));
            } else {
            } else {
                return PackageHealthObserverImpact.USER_IMPACT_NONE;
                return PackageHealthObserverImpact.USER_IMPACT_NONE;
            }
            }
@@ -628,7 +634,8 @@ public class RescueParty {
            }
            }
            if (failureReason == PackageWatchdog.FAILURE_REASON_APP_CRASH
            if (failureReason == PackageWatchdog.FAILURE_REASON_APP_CRASH
                    || failureReason == PackageWatchdog.FAILURE_REASON_APP_NOT_RESPONDING) {
                    || failureReason == PackageWatchdog.FAILURE_REASON_APP_NOT_RESPONDING) {
                final int level = getRescueLevel(mitigationCount);
                final int level = getRescueLevel(mitigationCount,
                        mayPerformFactoryReset(failedPackage));
                executeRescueLevel(mContext,
                executeRescueLevel(mContext,
                        failedPackage == null ? null : failedPackage.getPackageName(), level);
                        failedPackage == null ? null : failedPackage.getPackageName(), level);
                return true;
                return true;
@@ -653,12 +660,7 @@ public class RescueParty {
            } catch (PackageManager.NameNotFoundException ignore) {
            } catch (PackageManager.NameNotFoundException ignore) {
            }
            }


            try {
            return isPersistentSystemApp(packageName);
                ApplicationInfo info = pm.getApplicationInfo(packageName, 0);
                return (info.flags & PERSISTENT_MASK) == PERSISTENT_MASK;
            } catch (PackageManager.NameNotFoundException e) {
                return false;
            }
        }
        }


        @Override
        @Override
@@ -666,7 +668,7 @@ public class RescueParty {
            if (isDisabled()) {
            if (isDisabled()) {
                return PackageHealthObserverImpact.USER_IMPACT_NONE;
                return PackageHealthObserverImpact.USER_IMPACT_NONE;
            }
            }
            return mapRescueLevelToUserImpact(getRescueLevel(mitigationCount));
            return mapRescueLevelToUserImpact(getRescueLevel(mitigationCount, true));
        }
        }


        @Override
        @Override
@@ -674,7 +676,8 @@ public class RescueParty {
            if (isDisabled()) {
            if (isDisabled()) {
                return false;
                return false;
            }
            }
            executeRescueLevel(mContext, /*failedPackage=*/ null, getRescueLevel(mitigationCount));
            executeRescueLevel(mContext, /*failedPackage=*/ null,
                    getRescueLevel(mitigationCount, true));
            return true;
            return true;
        }
        }


@@ -683,6 +686,29 @@ public class RescueParty {
            return NAME;
            return NAME;
        }
        }


        /**
         * Returns {@code true} if the failing package is non-null and performing a reboot or
         * prompting a factory reset is an acceptable mitigation strategy for the package's
         * failure, {@code false} otherwise.
         */
        private boolean mayPerformFactoryReset(@Nullable VersionedPackage failingPackage) {
            if (failingPackage == null) {
                return false;
            }

            return isPersistentSystemApp(failingPackage.getPackageName());
        }

        private boolean isPersistentSystemApp(@NonNull String packageName) {
            PackageManager pm = mContext.getPackageManager();
            try {
                ApplicationInfo info = pm.getApplicationInfo(packageName, 0);
                return (info.flags & PERSISTENT_MASK) == PERSISTENT_MASK;
            } catch (PackageManager.NameNotFoundException e) {
                return false;
            }
        }

        private synchronized void recordDeviceConfigAccess(@NonNull String callingPackage,
        private synchronized void recordDeviceConfigAccess(@NonNull String callingPackage,
                @NonNull String namespace) {
                @NonNull String namespace) {
            // Record it in calling packages to namespace map
            // Record it in calling packages to namespace map
+53 −11
Original line number Original line Diff line number Diff line
@@ -37,6 +37,8 @@ import static org.mockito.Mockito.times;


import android.content.ContentResolver;
import android.content.ContentResolver;
import android.content.Context;
import android.content.Context;
import android.content.pm.ApplicationInfo;
import android.content.pm.PackageManager;
import android.content.pm.VersionedPackage;
import android.content.pm.VersionedPackage;
import android.os.Bundle;
import android.os.Bundle;
import android.os.RecoverySystem;
import android.os.RecoverySystem;
@@ -83,6 +85,8 @@ public class RescuePartyTest {
    private static final String CALLING_PACKAGE1 = "com.package.name1";
    private static final String CALLING_PACKAGE1 = "com.package.name1";
    private static final String CALLING_PACKAGE2 = "com.package.name2";
    private static final String CALLING_PACKAGE2 = "com.package.name2";
    private static final String CALLING_PACKAGE3 = "com.package.name3";
    private static final String CALLING_PACKAGE3 = "com.package.name3";
    private static final String PERSISTENT_PACKAGE = "com.persistent.package";
    private static final String NON_PERSISTENT_PACKAGE = "com.nonpersistent.package";
    private static final String NAMESPACE1 = "namespace1";
    private static final String NAMESPACE1 = "namespace1";
    private static final String NAMESPACE2 = "namespace2";
    private static final String NAMESPACE2 = "namespace2";
    private static final String NAMESPACE3 = "namespace3";
    private static final String NAMESPACE3 = "namespace3";
@@ -103,6 +107,8 @@ public class RescuePartyTest {
    private PackageWatchdog mMockPackageWatchdog;
    private PackageWatchdog mMockPackageWatchdog;
    @Mock(answer = Answers.RETURNS_DEEP_STUBS)
    @Mock(answer = Answers.RETURNS_DEEP_STUBS)
    private ContentResolver mMockContentResolver;
    private ContentResolver mMockContentResolver;
    @Mock(answer = Answers.RETURNS_DEEP_STUBS)
    private PackageManager mPackageManager;


    @Captor
    @Captor
    private ArgumentCaptor<RemoteCallback> mMonitorCallbackCaptor;
    private ArgumentCaptor<RemoteCallback> mMonitorCallbackCaptor;
@@ -129,6 +135,17 @@ public class RescuePartyTest {
        mNamespacesWiped = new HashSet<>();
        mNamespacesWiped = new HashSet<>();


        when(mMockContext.getContentResolver()).thenReturn(mMockContentResolver);
        when(mMockContext.getContentResolver()).thenReturn(mMockContentResolver);
        when(mMockContext.getPackageManager()).thenReturn(mPackageManager);
        ApplicationInfo persistentApplicationInfo = new ApplicationInfo();
        persistentApplicationInfo.flags |=
                ApplicationInfo.FLAG_SYSTEM | ApplicationInfo.FLAG_PERSISTENT;

        // If the package name is PERSISTENT_PACKAGE, then set the flags to be persistent and
        // system. Don't set any flags otherwise.
        when(mPackageManager.getApplicationInfo(eq(PERSISTENT_PACKAGE),
                anyInt())).thenReturn(persistentApplicationInfo);
        when(mPackageManager.getApplicationInfo(eq(NON_PERSISTENT_PACKAGE),
                anyInt())).thenReturn(new ApplicationInfo());
        // Reset observer instance to get new mock context on every run
        // Reset observer instance to get new mock context on every run
        RescuePartyObserver.reset();
        RescuePartyObserver.reset();


@@ -241,28 +258,52 @@ public class RescuePartyTest {


    @Test
    @Test
    public void testPersistentAppCrashDetectionWithExecutionForAllRescueLevels() {
    public void testPersistentAppCrashDetectionWithExecutionForAllRescueLevels() {
        notePersistentAppCrash(1);
        noteAppCrash(1, true);


        verifySettingsResets(Settings.RESET_MODE_UNTRUSTED_DEFAULTS, /*resetNamespaces=*/ null,
        verifySettingsResets(Settings.RESET_MODE_UNTRUSTED_DEFAULTS, /*resetNamespaces=*/ null,
                /*configResetVerifiedTimesMap=*/ null);
                /*configResetVerifiedTimesMap=*/ null);


        notePersistentAppCrash(2);
        noteAppCrash(2, true);


        verifySettingsResets(Settings.RESET_MODE_UNTRUSTED_CHANGES, /*resetNamespaces=*/ null,
        verifySettingsResets(Settings.RESET_MODE_UNTRUSTED_CHANGES, /*resetNamespaces=*/ null,
                /*configResetVerifiedTimesMap=*/ null);
                /*configResetVerifiedTimesMap=*/ null);


        notePersistentAppCrash(3);
        noteAppCrash(3, true);


        verifySettingsResets(Settings.RESET_MODE_TRUSTED_DEFAULTS, /*resetNamespaces=*/ null,
        verifySettingsResets(Settings.RESET_MODE_TRUSTED_DEFAULTS, /*resetNamespaces=*/ null,
                /*configResetVerifiedTimesMap=*/ null);
                /*configResetVerifiedTimesMap=*/ null);


        notePersistentAppCrash(4);
        noteAppCrash(4, true);
        assertTrue(RescueParty.isRebootPropertySet());
        assertTrue(RescueParty.isRebootPropertySet());


        notePersistentAppCrash(5);
        noteAppCrash(5, true);
        assertTrue(RescueParty.isFactoryResetPropertySet());
        assertTrue(RescueParty.isFactoryResetPropertySet());
    }
    }


    @Test
    public void testNonPersistentAppOnlyPerformsFlagResets() {
        noteAppCrash(1, false);

        verifySettingsResets(Settings.RESET_MODE_UNTRUSTED_DEFAULTS, /*resetNamespaces=*/ null,
                /*configResetVerifiedTimesMap=*/ null);

        noteAppCrash(2, false);

        verifySettingsResets(Settings.RESET_MODE_UNTRUSTED_CHANGES, /*resetNamespaces=*/ null,
                /*configResetVerifiedTimesMap=*/ null);

        noteAppCrash(3, false);

        verifySettingsResets(Settings.RESET_MODE_TRUSTED_DEFAULTS, /*resetNamespaces=*/ null,
                /*configResetVerifiedTimesMap=*/ null);

        noteAppCrash(4, false);
        assertFalse(RescueParty.isRebootPropertySet());

        noteAppCrash(5, false);
        assertFalse(RescueParty.isFactoryResetPropertySet());
    }

    @Test
    @Test
    public void testNonPersistentAppCrashDetectionWithScopedResets() {
    public void testNonPersistentAppCrashDetectionWithScopedResets() {
        RescueParty.onSettingsProviderPublished(mMockContext);
        RescueParty.onSettingsProviderPublished(mMockContext);
@@ -311,11 +352,11 @@ public class RescuePartyTest {


        observer.execute(new VersionedPackage(
        observer.execute(new VersionedPackage(
                CALLING_PACKAGE1, 1), PackageWatchdog.FAILURE_REASON_APP_NOT_RESPONDING, 4);
                CALLING_PACKAGE1, 1), PackageWatchdog.FAILURE_REASON_APP_NOT_RESPONDING, 4);
        assertTrue(RescueParty.isRebootPropertySet());
        assertFalse(RescueParty.isRebootPropertySet());


        observer.execute(new VersionedPackage(
        observer.execute(new VersionedPackage(
                CALLING_PACKAGE1, 1), PackageWatchdog.FAILURE_REASON_APP_NOT_RESPONDING, 5);
                CALLING_PACKAGE1, 1), PackageWatchdog.FAILURE_REASON_APP_NOT_RESPONDING, 5);
        assertTrue(RescueParty.isFactoryResetPropertySet());
        assertFalse(RescueParty.isFactoryResetPropertySet());
    }
    }


    @Test
    @Test
@@ -376,11 +417,11 @@ public class RescuePartyTest {


        observer.execute(new VersionedPackage(
        observer.execute(new VersionedPackage(
                CALLING_PACKAGE1, 1), PackageWatchdog.FAILURE_REASON_APP_NOT_RESPONDING, 4);
                CALLING_PACKAGE1, 1), PackageWatchdog.FAILURE_REASON_APP_NOT_RESPONDING, 4);
        assertTrue(RescueParty.isRebootPropertySet());
        assertFalse(RescueParty.isRebootPropertySet());


        observer.execute(new VersionedPackage(
        observer.execute(new VersionedPackage(
                CALLING_PACKAGE1, 1), PackageWatchdog.FAILURE_REASON_APP_NOT_RESPONDING, 5);
                CALLING_PACKAGE1, 1), PackageWatchdog.FAILURE_REASON_APP_NOT_RESPONDING, 5);
        assertTrue(RescueParty.isFactoryResetPropertySet());
        assertFalse(RescueParty.isFactoryResetPropertySet());
    }
    }


    @Test
    @Test
@@ -627,9 +668,10 @@ public class RescuePartyTest {
        RescuePartyObserver.getInstance(mMockContext).executeBootLoopMitigation(mitigationCount);
        RescuePartyObserver.getInstance(mMockContext).executeBootLoopMitigation(mitigationCount);
    }
    }


    private void notePersistentAppCrash(int mitigationCount) {
    private void noteAppCrash(int mitigationCount, boolean isPersistent) {
        String packageName = isPersistent ? PERSISTENT_PACKAGE : NON_PERSISTENT_PACKAGE;
        RescuePartyObserver.getInstance(mMockContext).execute(new VersionedPackage(
        RescuePartyObserver.getInstance(mMockContext).execute(new VersionedPackage(
                "com.package.name", 1), PackageWatchdog.FAILURE_REASON_APP_CRASH, mitigationCount);
                packageName, 1), PackageWatchdog.FAILURE_REASON_APP_CRASH, mitigationCount);
    }
    }


    private Bundle getConfigAccessBundle(String callingPackage, String namespace) {
    private Bundle getConfigAccessBundle(String callingPackage, String namespace) {