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
This commit is contained in:
Hongyi Zhang
2020-10-30 17:57:56 -07:00
parent 090fab9753
commit b9a46dfe0d
2 changed files with 85 additions and 15 deletions

View File

@@ -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);
}

View File

@@ -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));