diff options
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 |