diff options
author | Gavin Corkery <gavincorkery@google.com> | 2021-09-01 16:28:45 +0100 |
---|---|---|
committer | Android Build Coastguard Worker <android-build-coastguard-worker@google.com> | 2021-09-28 02:01:04 +0000 |
commit | 8a4fcf6f04b068195a306ecbd87246b4a6f0dfe5 (patch) | |
tree | a106db65373fdd6c6bafb2ace514408685d32972 | |
parent | 2a4120e78bde52c00a48fc4e44bb6f54401dcede (diff) | |
download | base-8a4fcf6f04b068195a306ecbd87246b4a6f0dfe5.tar.gz |
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 6d72bede84bc08c3588798fc2a7dc3835981f97d)
(cherry picked from commit 3fdf5c31f865ee353726d41470a6069d3c3c885c)
-rw-r--r-- | services/core/java/com/android/server/RescueParty.java | 58 | ||||
-rw-r--r-- | services/tests/mockingservicestests/src/com/android/server/RescuePartyTest.java | 64 |
2 files changed, 95 insertions, 27 deletions
diff --git a/services/core/java/com/android/server/RescueParty.java b/services/core/java/com/android/server/RescueParty.java index c3543e7ba368..c1c9fbb121a8 100644 --- a/services/core/java/com/android/server/RescueParty.java +++ b/services/core/java/com/android/server/RescueParty.java @@ -327,18 +327,23 @@ public class RescueParty { } } - private static int getMaxRescueLevel() { - return SystemProperties.getBoolean(PROP_DISABLE_FACTORY_RESET_FLAG, false) - ? LEVEL_RESET_SETTINGS_TRUSTED_DEFAULTS : LEVEL_FACTORY_RESET; + private static int getMaxRescueLevel(boolean mayPerformFactoryReset) { + if (!mayPerformFactoryReset + || 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. * * @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. */ - private static int getRescueLevel(int mitigationCount) { + private static int getRescueLevel(int mitigationCount, boolean mayPerformFactoryReset) { if (mitigationCount == 1) { return LEVEL_RESET_SETTINGS_UNTRUSTED_DEFAULTS; } else if (mitigationCount == 2) { @@ -346,9 +351,9 @@ public class RescueParty { } else if (mitigationCount == 3) { return LEVEL_RESET_SETTINGS_TRUSTED_DEFAULTS; } else if (mitigationCount == 4) { - return Math.min(getMaxRescueLevel(), LEVEL_WARM_REBOOT); + return Math.min(getMaxRescueLevel(mayPerformFactoryReset), LEVEL_WARM_REBOOT); } else if (mitigationCount >= 5) { - return Math.min(getMaxRescueLevel(), LEVEL_FACTORY_RESET); + return Math.min(getMaxRescueLevel(mayPerformFactoryReset), LEVEL_FACTORY_RESET); } else { Slog.w(TAG, "Expected positive mitigation count, was " + mitigationCount); return LEVEL_NONE; @@ -614,7 +619,8 @@ public class RescueParty { @FailureReasons int failureReason, int mitigationCount) { if (!isDisabled() && (failureReason == PackageWatchdog.FAILURE_REASON_APP_CRASH || failureReason == PackageWatchdog.FAILURE_REASON_APP_NOT_RESPONDING)) { - return mapRescueLevelToUserImpact(getRescueLevel(mitigationCount)); + return mapRescueLevelToUserImpact(getRescueLevel(mitigationCount, + mayPerformFactoryReset(failedPackage))); } else { return PackageHealthObserverImpact.USER_IMPACT_NONE; } @@ -628,7 +634,8 @@ public class RescueParty { } if (failureReason == PackageWatchdog.FAILURE_REASON_APP_CRASH || failureReason == PackageWatchdog.FAILURE_REASON_APP_NOT_RESPONDING) { - final int level = getRescueLevel(mitigationCount); + final int level = getRescueLevel(mitigationCount, + mayPerformFactoryReset(failedPackage)); executeRescueLevel(mContext, failedPackage == null ? null : failedPackage.getPackageName(), level); return true; @@ -653,12 +660,7 @@ public class RescueParty { } catch (PackageManager.NameNotFoundException ignore) { } - try { - ApplicationInfo info = pm.getApplicationInfo(packageName, 0); - return (info.flags & PERSISTENT_MASK) == PERSISTENT_MASK; - } catch (PackageManager.NameNotFoundException e) { - return false; - } + return isPersistentSystemApp(packageName); } @Override @@ -666,7 +668,7 @@ public class RescueParty { if (isDisabled()) { return PackageHealthObserverImpact.USER_IMPACT_NONE; } - return mapRescueLevelToUserImpact(getRescueLevel(mitigationCount)); + return mapRescueLevelToUserImpact(getRescueLevel(mitigationCount, true)); } @Override @@ -674,7 +676,8 @@ public class RescueParty { if (isDisabled()) { return false; } - executeRescueLevel(mContext, /*failedPackage=*/ null, getRescueLevel(mitigationCount)); + executeRescueLevel(mContext, /*failedPackage=*/ null, + getRescueLevel(mitigationCount, true)); return true; } @@ -683,6 +686,29 @@ public class RescueParty { 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, @NonNull String namespace) { // Record it in calling packages to namespace map diff --git a/services/tests/mockingservicestests/src/com/android/server/RescuePartyTest.java b/services/tests/mockingservicestests/src/com/android/server/RescuePartyTest.java index fd364ae77240..51fa8517e45f 100644 --- a/services/tests/mockingservicestests/src/com/android/server/RescuePartyTest.java +++ b/services/tests/mockingservicestests/src/com/android/server/RescuePartyTest.java @@ -37,6 +37,8 @@ import static org.mockito.Mockito.times; import android.content.ContentResolver; import android.content.Context; +import android.content.pm.ApplicationInfo; +import android.content.pm.PackageManager; import android.content.pm.VersionedPackage; import android.os.Bundle; 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_PACKAGE2 = "com.package.name2"; 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 NAMESPACE2 = "namespace2"; private static final String NAMESPACE3 = "namespace3"; @@ -103,6 +107,8 @@ public class RescuePartyTest { private PackageWatchdog mMockPackageWatchdog; @Mock(answer = Answers.RETURNS_DEEP_STUBS) private ContentResolver mMockContentResolver; + @Mock(answer = Answers.RETURNS_DEEP_STUBS) + private PackageManager mPackageManager; @Captor private ArgumentCaptor<RemoteCallback> mMonitorCallbackCaptor; @@ -129,6 +135,17 @@ public class RescuePartyTest { mNamespacesWiped = new HashSet<>(); 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 RescuePartyObserver.reset(); @@ -241,29 +258,53 @@ public class RescuePartyTest { @Test public void testPersistentAppCrashDetectionWithExecutionForAllRescueLevels() { - notePersistentAppCrash(1); + noteAppCrash(1, true); verifySettingsResets(Settings.RESET_MODE_UNTRUSTED_DEFAULTS, /*resetNamespaces=*/ null, /*configResetVerifiedTimesMap=*/ null); - notePersistentAppCrash(2); + noteAppCrash(2, true); verifySettingsResets(Settings.RESET_MODE_UNTRUSTED_CHANGES, /*resetNamespaces=*/ null, /*configResetVerifiedTimesMap=*/ null); - notePersistentAppCrash(3); + noteAppCrash(3, true); verifySettingsResets(Settings.RESET_MODE_TRUSTED_DEFAULTS, /*resetNamespaces=*/ null, /*configResetVerifiedTimesMap=*/ null); - notePersistentAppCrash(4); + noteAppCrash(4, true); assertTrue(RescueParty.isRebootPropertySet()); - notePersistentAppCrash(5); + noteAppCrash(5, true); 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 public void testNonPersistentAppCrashDetectionWithScopedResets() { RescueParty.onSettingsProviderPublished(mMockContext); verify(() -> Settings.Config.registerMonitorCallback(eq(mMockContentResolver), @@ -311,11 +352,11 @@ public class RescuePartyTest { observer.execute(new VersionedPackage( CALLING_PACKAGE1, 1), PackageWatchdog.FAILURE_REASON_APP_NOT_RESPONDING, 4); - assertTrue(RescueParty.isRebootPropertySet()); + assertFalse(RescueParty.isRebootPropertySet()); observer.execute(new VersionedPackage( CALLING_PACKAGE1, 1), PackageWatchdog.FAILURE_REASON_APP_NOT_RESPONDING, 5); - assertTrue(RescueParty.isFactoryResetPropertySet()); + assertFalse(RescueParty.isFactoryResetPropertySet()); } @Test @@ -376,11 +417,11 @@ public class RescuePartyTest { observer.execute(new VersionedPackage( CALLING_PACKAGE1, 1), PackageWatchdog.FAILURE_REASON_APP_NOT_RESPONDING, 4); - assertTrue(RescueParty.isRebootPropertySet()); + assertFalse(RescueParty.isRebootPropertySet()); observer.execute(new VersionedPackage( CALLING_PACKAGE1, 1), PackageWatchdog.FAILURE_REASON_APP_NOT_RESPONDING, 5); - assertTrue(RescueParty.isFactoryResetPropertySet()); + assertFalse(RescueParty.isFactoryResetPropertySet()); } @Test @@ -627,9 +668,10 @@ public class RescuePartyTest { 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( - "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) { |