Donate to e Foundation | Murena handsets with /e/OS | Own a part of Murena! Learn more

Commit 62d61616 authored by Shrinidhi Hegde's avatar Shrinidhi Hegde
Browse files

Prevent Android watchdog from rolling back Phonesky in response to crashes

Presently, the Android framework automatically rolls back Phonesky when it detects many crashes, either from Phonesky or another persistent service right after startup. We want to be able to request Phonesky rollbacks, but do not want the framework to automatically roll us back, only when GMS Core requests it should a rollback occur.

In this change I have introduced a new deny automatic roll back list which contains Phonesky. Rollback manager will check if the failed package be rolled back automatically before rolling back.

Test: Unit tests and manual test
Bug: 259985211

Change-Id: I72940590e650c89fd59d6fc70fa2f7c2cea24de1
parent 17fc1ea2
Loading
Loading
Loading
Loading
+15 −0
Original line number Diff line number Diff line
@@ -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");
+20 −0
Original line number Diff line number Diff line
@@ -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;
@@ -419,6 +428,17 @@ final class RollbackPackageHealthObserver implements PackageHealthObserver {
                Collections.singletonList(failedPackage), rollbackReceiver.getIntentSender());
    }

    /**
     * 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
+157 −0
Original line number Diff line number Diff line
/*
 * 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;
    }
}
+50 −0
Original line number Diff line number Diff line
@@ -594,6 +594,56 @@ public class SystemConfigTest {
        assertFooIsOnlySharedLibrary();
    }

    /**
     * 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}.
     */