Add a method for wiping flags per package in RescueParty

Provide a method for RollbackManager to clear flags when performing
rollback.
Mapping of packages to namespaces comes from both the runtime records
already maintained by RescueParty and a preset DeviceConfig flag of
namespace <-> package for Mainline modules(will be pushed from GMScore).

Test: atest RescuePartyTest & manually trigger the action on local build
Bug: 173120414
Change-Id: I4773a6124b07e3291cacf71dc7b702f84b69008c
This commit is contained in:
Hongyi Zhang
2020-11-23 13:25:30 -08:00
parent 2f4a7ff808
commit 89dfb988d1
2 changed files with 226 additions and 2 deletions

View File

@@ -16,6 +16,8 @@
package com.android.server;
import static android.provider.DeviceConfig.Properties;
import static com.android.server.pm.PackageManagerServiceUtils.logCriticalInfo;
import android.annotation.NonNull;
@@ -36,6 +38,7 @@ import android.os.SystemProperties;
import android.os.UserHandle;
import android.provider.DeviceConfig;
import android.provider.Settings;
import android.text.TextUtils;
import android.util.ArraySet;
import android.util.ExceptionUtils;
import android.util.Log;
@@ -93,6 +96,12 @@ public class RescueParty {
static final long DEFAULT_OBSERVING_DURATION_MS = TimeUnit.DAYS.toMillis(2);
@VisibleForTesting
static final int DEVICE_CONFIG_RESET_MODE = Settings.RESET_MODE_TRUSTED_DEFAULTS;
// The DeviceConfig namespace containing all RescueParty switches.
@VisibleForTesting
static final String NAMESPACE_CONFIGURATION = "configuration";
@VisibleForTesting
static final String NAMESPACE_TO_PACKAGE_MAPPING_FLAG =
"namespace_to_package_mapping";
private static final String NAME = "rescue-party-observer";
@@ -103,8 +112,6 @@ public class RescueParty {
"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;
@@ -170,6 +177,81 @@ public class RescueParty {
}));
}
/**
* Called when {@code RollbackManager} performs Mainline module rollbacks,
* to avoid rolled back modules consuming flag values only expected to work
* on modules of newer versions.
*/
public static void resetDeviceConfigForPackages(List<String> packageNames) {
if (packageNames == null) {
return;
}
Set<String> namespacesToReset = new ArraySet<String>();
Iterator<String> it = packageNames.iterator();
RescuePartyObserver rescuePartyObserver = RescuePartyObserver.getInstanceIfCreated();
// Get runtime package to namespace mapping if created.
if (rescuePartyObserver != null) {
while (it.hasNext()) {
String packageName = it.next();
Set<String> runtimeAffectedNamespaces =
rescuePartyObserver.getAffectedNamespaceSet(packageName);
if (runtimeAffectedNamespaces != null) {
namespacesToReset.addAll(runtimeAffectedNamespaces);
}
}
}
// Get preset package to namespace mapping if created.
Set<String> presetAffectedNamespaces = getPresetNamespacesForPackages(
packageNames);
if (presetAffectedNamespaces != null) {
namespacesToReset.addAll(presetAffectedNamespaces);
}
// Clear flags under the namespaces mapped to these packages.
// Using setProperties since DeviceConfig.resetToDefaults bans the current flag set.
Iterator<String> namespaceIt = namespacesToReset.iterator();
while (namespaceIt.hasNext()) {
String namespaceToReset = namespaceIt.next();
Properties properties = new Properties.Builder(namespaceToReset).build();
try {
DeviceConfig.setProperties(properties);
} catch (DeviceConfig.BadConfigException exception) {
logCriticalInfo(Log.WARN, "namespace " + namespaceToReset
+ " is already banned, skip reset.");
}
}
}
private static Set<String> getPresetNamespacesForPackages(List<String> packageNames) {
Set<String> resultSet = new ArraySet<String>();
try {
String flagVal = DeviceConfig.getString(NAMESPACE_CONFIGURATION,
NAMESPACE_TO_PACKAGE_MAPPING_FLAG, "");
String[] mappingEntries = flagVal.split(",");
for (int i = 0; i < mappingEntries.length; i++) {
if (TextUtils.isEmpty(mappingEntries[i])) {
continue;
}
String[] splittedEntry = mappingEntries[i].split(":");
if (splittedEntry.length != 2) {
throw new RuntimeException("Invalid mapping entry: " + mappingEntries[i]);
}
String namespace = splittedEntry[0];
String packageName = splittedEntry[1];
if (packageNames.contains(packageName)) {
resultSet.add(namespace);
}
}
} catch (Exception e) {
resultSet.clear();
Slog.e(TAG, "Failed to read preset package to namespaces mapping.", e);
} finally {
return resultSet;
}
}
@VisibleForTesting
static long getElapsedRealtime() {
return SystemClock.elapsedRealtime();
@@ -469,6 +551,14 @@ public class RescueParty {
}
}
/** Gets singleton instance. It returns null if the instance is not created yet.*/
@Nullable
public static RescuePartyObserver getInstanceIfCreated() {
synchronized (RescuePartyObserver.class) {
return sRescuePartyObserver;
}
}
@VisibleForTesting
static void reset() {
synchronized (RescuePartyObserver.class) {

View File

@@ -16,6 +16,7 @@
package com.android.server;
import static com.android.dx.mockito.inline.extended.ExtendedMockito.any;
import static com.android.dx.mockito.inline.extended.ExtendedMockito.anyBoolean;
import static com.android.dx.mockito.inline.extended.ExtendedMockito.anyInt;
import static com.android.dx.mockito.inline.extended.ExtendedMockito.anyLong;
@@ -44,6 +45,7 @@ import android.os.SystemProperties;
import android.os.UserHandle;
import android.provider.DeviceConfig;
import android.provider.Settings;
import android.util.ArraySet;
import com.android.dx.mockito.inline.extended.ExtendedMockito;
import com.android.server.PackageWatchdog.PackageHealthObserverImpact;
@@ -63,6 +65,7 @@ import org.mockito.stubbing.Answer;
import java.util.Arrays;
import java.util.HashMap;
import java.util.HashSet;
import java.util.List;
/**
@@ -79,9 +82,11 @@ public class RescuePartyTest {
private static final String PROP_DISABLE_RESCUE = "persist.sys.disable_rescue";
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 NAMESPACE1 = "namespace1";
private static final String NAMESPACE2 = "namespace2";
private static final String NAMESPACE3 = "namespace3";
private static final String NAMESPACE4 = "namespace4";
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 =
@@ -89,6 +94,8 @@ public class RescuePartyTest {
private MockitoSession mSession;
private HashMap<String, String> mSystemSettingsMap;
//Records the namespaces wiped by setProperties().
private HashSet<String> mNamespacesWiped;
@Mock(answer = Answers.RETURNS_DEEP_STUBS)
private Context mMockContext;
@@ -119,6 +126,7 @@ public class RescuePartyTest {
.spyStatic(PackageWatchdog.class)
.startMocking();
mSystemSettingsMap = new HashMap<>();
mNamespacesWiped = new HashSet<>();
when(mMockContext.getContentResolver()).thenReturn(mMockContentResolver);
// Reset observer instance to get new mock context on every run
@@ -167,6 +175,16 @@ public class RescuePartyTest {
anyBoolean()));
doAnswer((Answer<Void>) invocationOnMock -> null)
.when(() -> DeviceConfig.resetToDefaults(anyInt(), anyString()));
doAnswer((Answer<Boolean>) invocationOnMock -> {
DeviceConfig.Properties properties = invocationOnMock.getArgument(0);
String namespace = properties.getNamespace();
// record a wipe
if (properties.getKeyset().isEmpty()) {
mNamespacesWiped.add(namespace);
}
return true;
}
).when(() -> DeviceConfig.setProperties(any(DeviceConfig.Properties.class)));
// Mock PackageWatchdog
doAnswer((Answer<PackageWatchdog>) invocationOnMock -> mMockPackageWatchdog)
@@ -450,6 +468,122 @@ public class RescuePartyTest {
assertEquals(observer.onBootLoop(5), PackageHealthObserverImpact.USER_IMPACT_HIGH);
}
@Test
public void testResetDeviceConfigForPackagesOnlyRuntimeMap() {
RescueParty.onSettingsProviderPublished(mMockContext);
verify(() -> Settings.Config.registerMonitorCallback(eq(mMockContentResolver),
mMonitorCallbackCaptor.capture()));
// Record DeviceConfig accesses
RescuePartyObserver observer = RescuePartyObserver.getInstance(mMockContext);
RemoteCallback monitorCallback = mMonitorCallbackCaptor.getValue();
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));
monitorCallback.sendResult(getConfigNamespaceUpdateBundle(NAMESPACE2));
monitorCallback.sendResult(getConfigNamespaceUpdateBundle(NAMESPACE3));
doReturn("").when(() -> DeviceConfig.getString(
eq(RescueParty.NAMESPACE_CONFIGURATION),
eq(RescueParty.NAMESPACE_TO_PACKAGE_MAPPING_FLAG),
eq("")));
RescueParty.resetDeviceConfigForPackages(Arrays.asList(new String[]{CALLING_PACKAGE1}));
ArraySet<String> expectedNamespacesWiped = new ArraySet<String>(
Arrays.asList(new String[]{NAMESPACE1, NAMESPACE2}));
assertEquals(mNamespacesWiped, expectedNamespacesWiped);
}
@Test
public void testResetDeviceConfigForPackagesOnlyPresetMap() {
RescueParty.onSettingsProviderPublished(mMockContext);
verify(() -> Settings.Config.registerMonitorCallback(eq(mMockContentResolver),
mMonitorCallbackCaptor.capture()));
String presetMapping = NAMESPACE1 + ":" + CALLING_PACKAGE1 + ","
+ NAMESPACE2 + ":" + CALLING_PACKAGE2 + ","
+ NAMESPACE3 + ":" + CALLING_PACKAGE1;
doReturn(presetMapping).when(() -> DeviceConfig.getString(
eq(RescueParty.NAMESPACE_CONFIGURATION),
eq(RescueParty.NAMESPACE_TO_PACKAGE_MAPPING_FLAG),
eq("")));
RescueParty.resetDeviceConfigForPackages(Arrays.asList(new String[]{CALLING_PACKAGE1}));
ArraySet<String> expectedNamespacesWiped = new ArraySet<String>(
Arrays.asList(new String[]{NAMESPACE1, NAMESPACE3}));
assertEquals(mNamespacesWiped, expectedNamespacesWiped);
}
@Test
public void testResetDeviceConfigForPackagesBothMaps() {
RescueParty.onSettingsProviderPublished(mMockContext);
verify(() -> Settings.Config.registerMonitorCallback(eq(mMockContentResolver),
mMonitorCallbackCaptor.capture()));
// Record DeviceConfig accesses
RescuePartyObserver observer = RescuePartyObserver.getInstance(mMockContext);
RemoteCallback monitorCallback = mMonitorCallbackCaptor.getValue();
monitorCallback.sendResult(getConfigAccessBundle(CALLING_PACKAGE1, NAMESPACE1));
monitorCallback.sendResult(getConfigAccessBundle(CALLING_PACKAGE1, NAMESPACE2));
monitorCallback.sendResult(getConfigAccessBundle(CALLING_PACKAGE2, NAMESPACE2));
monitorCallback.sendResult(getConfigAccessBundle(CALLING_PACKAGE2, NAMESPACE3));
monitorCallback.sendResult(getConfigAccessBundle(CALLING_PACKAGE3, NAMESPACE4));
// Fake DeviceConfig value changes
monitorCallback.sendResult(getConfigNamespaceUpdateBundle(NAMESPACE1));
monitorCallback.sendResult(getConfigNamespaceUpdateBundle(NAMESPACE2));
monitorCallback.sendResult(getConfigNamespaceUpdateBundle(NAMESPACE3));
monitorCallback.sendResult(getConfigNamespaceUpdateBundle(NAMESPACE4));
String presetMapping = NAMESPACE1 + ":" + CALLING_PACKAGE1 + ","
+ NAMESPACE2 + ":" + CALLING_PACKAGE2 + ","
+ NAMESPACE4 + ":" + CALLING_PACKAGE3;
doReturn(presetMapping).when(() -> DeviceConfig.getString(
eq(RescueParty.NAMESPACE_CONFIGURATION),
eq(RescueParty.NAMESPACE_TO_PACKAGE_MAPPING_FLAG),
eq("")));
RescueParty.resetDeviceConfigForPackages(
Arrays.asList(new String[]{CALLING_PACKAGE1, CALLING_PACKAGE2}));
ArraySet<String> expectedNamespacesWiped = new ArraySet<String>(
Arrays.asList(new String[]{NAMESPACE1, NAMESPACE2, NAMESPACE3}));
assertEquals(mNamespacesWiped, expectedNamespacesWiped);
}
@Test
public void testResetDeviceConfigNoExceptionWhenFlagMalformed() {
RescueParty.onSettingsProviderPublished(mMockContext);
verify(() -> Settings.Config.registerMonitorCallback(eq(mMockContentResolver),
mMonitorCallbackCaptor.capture()));
// Record DeviceConfig accesses
RescuePartyObserver observer = RescuePartyObserver.getInstance(mMockContext);
RemoteCallback monitorCallback = mMonitorCallbackCaptor.getValue();
monitorCallback.sendResult(getConfigAccessBundle(CALLING_PACKAGE1, NAMESPACE1));
monitorCallback.sendResult(getConfigAccessBundle(CALLING_PACKAGE2, NAMESPACE3));
monitorCallback.sendResult(getConfigAccessBundle(CALLING_PACKAGE3, NAMESPACE4));
// Fake DeviceConfig value changes
monitorCallback.sendResult(getConfigNamespaceUpdateBundle(NAMESPACE1));
monitorCallback.sendResult(getConfigNamespaceUpdateBundle(NAMESPACE2));
monitorCallback.sendResult(getConfigNamespaceUpdateBundle(NAMESPACE3));
monitorCallback.sendResult(getConfigNamespaceUpdateBundle(NAMESPACE4));
String invalidPresetMapping = NAMESPACE2 + ":" + CALLING_PACKAGE2 + ","
+ NAMESPACE1 + "." + CALLING_PACKAGE2;
doReturn(invalidPresetMapping).when(() -> DeviceConfig.getString(
eq(RescueParty.NAMESPACE_CONFIGURATION),
eq(RescueParty.NAMESPACE_TO_PACKAGE_MAPPING_FLAG),
eq("")));
RescueParty.resetDeviceConfigForPackages(
Arrays.asList(new String[]{CALLING_PACKAGE1, CALLING_PACKAGE2}));
ArraySet<String> expectedNamespacesWiped = new ArraySet<String>(
Arrays.asList(new String[]{NAMESPACE1, NAMESPACE3}));
assertEquals(mNamespacesWiped, expectedNamespacesWiped);
}
private void verifySettingsResets(int resetMode, String[] resetNamespaces,
HashMap<String, Integer> configResetVerifiedTimesMap) {
verify(() -> Settings.Global.resetToDefaultsAsUser(mMockContentResolver, null,