summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--services/core/java/com/android/server/SystemConfig.java15
-rw-r--r--services/core/java/com/android/server/rollback/RollbackPackageHealthObserver.java20
-rw-r--r--services/tests/servicestests/src/com/android/server/rollback/RollbackPackageHealthObserverTest.java157
-rw-r--r--services/tests/servicestests/src/com/android/server/systemconfig/SystemConfigTest.java50
4 files changed, 242 insertions, 0 deletions
diff --git a/services/core/java/com/android/server/SystemConfig.java b/services/core/java/com/android/server/SystemConfig.java
index ff757962cd32..4854c37933e3 100644
--- a/services/core/java/com/android/server/SystemConfig.java
+++ b/services/core/java/com/android/server/SystemConfig.java
@@ -321,6 +321,7 @@ public class SystemConfig {
private ArrayMap<String, Set<String>> mPackageToUserTypeBlacklist = new ArrayMap<>();
private final ArraySet<String> mRollbackWhitelistedPackages = new ArraySet<>();
+ private final ArraySet<String> mAutomaticRollbackDenylistedPackages = new ArraySet<>();
private final ArraySet<String> mWhitelistedStagedInstallers = new ArraySet<>();
// A map from package name of vendor APEXes that can be updated to an installer package name
// allowed to install updates for it.
@@ -461,6 +462,10 @@ public class SystemConfig {
return mRollbackWhitelistedPackages;
}
+ public Set<String> getAutomaticRollbackDenylistedPackages() {
+ return mAutomaticRollbackDenylistedPackages;
+ }
+
public Set<String> getWhitelistedStagedInstallers() {
return mWhitelistedStagedInstallers;
}
@@ -1356,6 +1361,16 @@ public class SystemConfig {
}
XmlUtils.skipCurrentTag(parser);
} break;
+ case "automatic-rollback-denylisted-app": {
+ String pkgname = parser.getAttributeValue(null, "package");
+ if (pkgname == null) {
+ Slog.w(TAG, "<" + name + "> without package in " + permFile
+ + " at " + parser.getPositionDescription());
+ } else {
+ mAutomaticRollbackDenylistedPackages.add(pkgname);
+ }
+ XmlUtils.skipCurrentTag(parser);
+ } break;
case "whitelisted-staged-installer": {
if (allowAppConfigs) {
String pkgname = parser.getAttributeValue(null, "package");
diff --git a/services/core/java/com/android/server/rollback/RollbackPackageHealthObserver.java b/services/core/java/com/android/server/rollback/RollbackPackageHealthObserver.java
index fd64c759e362..7beb1edfe51f 100644
--- a/services/core/java/com/android/server/rollback/RollbackPackageHealthObserver.java
+++ b/services/core/java/com/android/server/rollback/RollbackPackageHealthObserver.java
@@ -36,12 +36,14 @@ import android.util.ArraySet;
import android.util.Slog;
import android.util.SparseArray;
+import com.android.internal.annotations.VisibleForTesting;
import com.android.internal.util.FrameworkStatsLog;
import com.android.internal.util.Preconditions;
import com.android.server.PackageWatchdog;
import com.android.server.PackageWatchdog.FailureReasons;
import com.android.server.PackageWatchdog.PackageHealthObserver;
import com.android.server.PackageWatchdog.PackageHealthObserverImpact;
+import com.android.server.SystemConfig;
import com.android.server.pm.ApexManager;
import java.io.BufferedReader;
@@ -358,6 +360,13 @@ final class RollbackPackageHealthObserver implements PackageHealthObserver {
private void rollbackPackage(RollbackInfo rollback, VersionedPackage failedPackage,
@FailureReasons int rollbackReason) {
assertInWorkerThread();
+
+ if (isAutomaticRollbackDenied(SystemConfig.getInstance(), failedPackage)) {
+ Slog.d(TAG, "Automatic rollback not allowed for package "
+ + failedPackage.getPackageName());
+ return;
+ }
+
final RollbackManager rollbackManager = mContext.getSystemService(RollbackManager.class);
int reasonToLog = WatchdogRollbackLogger.mapFailureReasonToMetric(rollbackReason);
final String failedPackageToLog;
@@ -420,6 +429,17 @@ final class RollbackPackageHealthObserver implements PackageHealthObserver {
}
/**
+ * Returns true if this package is not eligible for automatic rollback.
+ */
+ @VisibleForTesting
+ @AnyThread
+ public static boolean isAutomaticRollbackDenied(SystemConfig systemConfig,
+ VersionedPackage versionedPackage) {
+ return systemConfig.getAutomaticRollbackDenylistedPackages()
+ .contains(versionedPackage.getPackageName());
+ }
+
+ /**
* Two-phase rollback:
* 1. roll back rebootless apexes first
* 2. roll back all remaining rollbacks if native crash doesn't stop after (1) is done
diff --git a/services/tests/servicestests/src/com/android/server/rollback/RollbackPackageHealthObserverTest.java b/services/tests/servicestests/src/com/android/server/rollback/RollbackPackageHealthObserverTest.java
new file mode 100644
index 000000000000..0be678af12dc
--- /dev/null
+++ b/services/tests/servicestests/src/com/android/server/rollback/RollbackPackageHealthObserverTest.java
@@ -0,0 +1,157 @@
+/*
+ * Copyright (C) 2023 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.server.rollback;
+
+import static com.google.common.truth.Truth.assertThat;
+
+import android.content.pm.VersionedPackage;
+import android.util.Log;
+import android.util.Xml;
+
+import androidx.test.runner.AndroidJUnit4;
+
+import com.android.server.SystemConfig;
+
+import org.junit.Before;
+import org.junit.Rule;
+import org.junit.Test;
+import org.junit.rules.TemporaryFolder;
+import org.junit.runner.RunWith;
+import org.xmlpull.v1.XmlPullParser;
+
+import java.io.BufferedWriter;
+import java.io.File;
+import java.io.FileWriter;
+import java.io.IOException;
+import java.util.Scanner;
+
+@RunWith(AndroidJUnit4.class)
+public class RollbackPackageHealthObserverTest {
+ private static final String LOG_TAG = "RollbackPackageHealthObserverTest";
+
+ private SystemConfig mSysConfig;
+
+ @Rule public TemporaryFolder mTemporaryFolder = new TemporaryFolder();
+
+ @Before
+ public void setup() {
+ mSysConfig = new SystemConfigTestClass();
+ }
+
+ /**
+ * Subclass of SystemConfig without running the constructor.
+ */
+ private class SystemConfigTestClass extends SystemConfig {
+ SystemConfigTestClass() {
+ super(false);
+ }
+ }
+
+ /**
+ * Test that isAutomaticRollbackDenied works correctly when packages that are not
+ * denied are sent.
+ */
+ @Test
+ public void isRollbackAllowedTest_false() throws IOException {
+ final String contents =
+ "<config>\n"
+ + " <automatic-rollback-denylisted-app package=\"com.android.vending\" />\n"
+ + "</config>";
+ final File folder = createTempSubfolder("folder");
+ createTempFile(folder, "automatic-rollback-denylisted-app.xml", contents);
+
+ readPermissions(folder, /* Grant all permission flags */ ~0);
+
+ assertThat(RollbackPackageHealthObserver.isAutomaticRollbackDenied(mSysConfig,
+ new VersionedPackage("com.test.package", 1))).isEqualTo(false);
+ }
+
+ /**
+ * Test that isAutomaticRollbackDenied works correctly when packages that are
+ * denied are sent.
+ */
+ @Test
+ public void isRollbackAllowedTest_true() throws IOException {
+ final String contents =
+ "<config>\n"
+ + " <automatic-rollback-denylisted-app package=\"com.android.vending\" />\n"
+ + "</config>";
+ final File folder = createTempSubfolder("folder");
+ createTempFile(folder, "automatic-rollback-denylisted-app.xml", contents);
+
+ readPermissions(folder, /* Grant all permission flags */ ~0);
+
+ assertThat(RollbackPackageHealthObserver.isAutomaticRollbackDenied(mSysConfig,
+ new VersionedPackage("com.android.vending", 1))).isEqualTo(true);
+ }
+
+ /**
+ * Test that isAutomaticRollbackDenied works correctly when no config is present
+ */
+ @Test
+ public void isRollbackAllowedTest_noConfig() throws IOException {
+ final File folder = createTempSubfolder("folder");
+
+ readPermissions(folder, /* Grant all permission flags */ ~0);
+
+ assertThat(RollbackPackageHealthObserver.isAutomaticRollbackDenied(mSysConfig,
+ new VersionedPackage("com.android.vending", 1))).isEqualTo(false);
+ }
+
+ /**
+ * Creates folderName/fileName in the mTemporaryFolder and fills it with the contents.
+ *
+ * @param folder pre-existing subdirectory of mTemporaryFolder to put the file
+ * @param fileName name of the file (e.g. filename.xml) to create
+ * @param contents contents to write to the file
+ * @return the newly created file
+ */
+ private File createTempFile(File folder, String fileName, String contents)
+ throws IOException {
+ File file = new File(folder, fileName);
+ BufferedWriter bw = new BufferedWriter(new FileWriter(file));
+ bw.write(contents);
+ bw.close();
+
+ // Print to logcat for test debugging.
+ Log.d(LOG_TAG, "Contents of file " + file.getAbsolutePath());
+ Scanner input = new Scanner(file);
+ while (input.hasNextLine()) {
+ Log.d(LOG_TAG, input.nextLine());
+ }
+
+ return file;
+ }
+
+ private void readPermissions(File libraryDir, int permissionFlag) {
+ final XmlPullParser parser = Xml.newPullParser();
+ mSysConfig.readPermissions(parser, libraryDir, permissionFlag);
+ }
+
+ /**
+ * Creates folderName/fileName in the mTemporaryFolder and fills it with the contents.
+ *
+ * @param folderName subdirectory of mTemporaryFolder to put the file, creating if needed
+ * @return the folder
+ */
+ private File createTempSubfolder(String folderName)
+ throws IOException {
+ File folder = new File(mTemporaryFolder.getRoot(), folderName);
+ folder.mkdirs();
+ return folder;
+ }
+}
diff --git a/services/tests/servicestests/src/com/android/server/systemconfig/SystemConfigTest.java b/services/tests/servicestests/src/com/android/server/systemconfig/SystemConfigTest.java
index d073f5bfebe4..aca96ad20385 100644
--- a/services/tests/servicestests/src/com/android/server/systemconfig/SystemConfigTest.java
+++ b/services/tests/servicestests/src/com/android/server/systemconfig/SystemConfigTest.java
@@ -595,6 +595,56 @@ public class SystemConfigTest {
}
/**
+ * Test that getRollbackDenylistedPackages works correctly for the tag:
+ * {@code automatic-rollback-denylisted-app}.
+ */
+ @Test
+ public void automaticRollbackDeny_vending() throws IOException {
+ final String contents =
+ "<config>\n"
+ + " <automatic-rollback-denylisted-app package=\"com.android.vending\" />\n"
+ + "</config>";
+ final File folder = createTempSubfolder("folder");
+ createTempFile(folder, "automatic-rollback-denylisted-app.xml", contents);
+
+ readPermissions(folder, /* Grant all permission flags */ ~0);
+
+ assertThat(mSysConfig.getAutomaticRollbackDenylistedPackages())
+ .containsExactly("com.android.vending");
+ }
+
+ /**
+ * Test that getRollbackDenylistedPackages works correctly for the tag:
+ * {@code automatic-rollback-denylisted-app} without any packages.
+ */
+ @Test
+ public void automaticRollbackDeny_empty() throws IOException {
+ final String contents =
+ "<config>\n"
+ + " <automatic-rollback-denylisted-app />\n"
+ + "</config>";
+ final File folder = createTempSubfolder("folder");
+ createTempFile(folder, "automatic-rollback-denylisted-app.xml", contents);
+
+ readPermissions(folder, /* Grant all permission flags */ ~0);
+
+ assertThat(mSysConfig.getAutomaticRollbackDenylistedPackages()).isEmpty();
+ }
+
+ /**
+ * Test that getRollbackDenylistedPackages works correctly for the tag:
+ * {@code automatic-rollback-denylisted-app} without the corresponding config.
+ */
+ @Test
+ public void automaticRollbackDeny_noConfig() throws IOException {
+ final File folder = createTempSubfolder("folder");
+
+ readPermissions(folder, /* Grant all permission flags */ ~0);
+
+ assertThat(mSysConfig.getAutomaticRollbackDenylistedPackages()).isEmpty();
+ }
+
+ /**
* Tests that readPermissions works correctly for the tag: {@code update-ownership}.
*/
@Test