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

Commit cc2578c3 authored by Nikita Ioffe's avatar Nikita Ioffe Committed by Automerger Merge Worker
Browse files

Restrict what APEXes different installers can update am: e501fd83 am: 85af284b

Original change: https://googleplex-android-review.googlesource.com/c/platform/frameworks/base/+/15331871

Change-Id: I62b1ad90cd47aee96437de8bf8d36e5ef9cc111d
parents cce04128 85af284b
Loading
Loading
Loading
Loading
+28 −4
Original line number Diff line number Diff line
@@ -241,7 +241,11 @@ public class SystemConfig {

    private final ArraySet<String> mRollbackWhitelistedPackages = new ArraySet<>();
    private final ArraySet<String> mWhitelistedStagedInstallers = new ArraySet<>();
    private final ArraySet<String> mAllowedVendorApexes = 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.
    private final ArrayMap<String, String> mAllowedVendorApexes = new ArrayMap<>();

    private String mModulesInstallerPackageName;

    /**
     * Map of system pre-defined, uniquely named actors; keys are namespace,
@@ -412,10 +416,14 @@ public class SystemConfig {
        return mWhitelistedStagedInstallers;
    }

    public Set<String> getAllowedVendorApexes() {
    public Map<String, String> getAllowedVendorApexes() {
        return mAllowedVendorApexes;
    }

    public String getModulesInstallerPackageName() {
        return mModulesInstallerPackageName;
    }

    public ArraySet<String> getAppDataIsolationWhitelistedApps() {
        return mAppDataIsolationWhitelistedApps;
    }
@@ -1210,12 +1218,21 @@ public class SystemConfig {
                    case "whitelisted-staged-installer": {
                        if (allowAppConfigs) {
                            String pkgname = parser.getAttributeValue(null, "package");
                            boolean isModulesInstaller = XmlUtils.readBooleanAttribute(
                                    parser, "isModulesInstaller", false);
                            if (pkgname == null) {
                                Slog.w(TAG, "<" + name + "> without package in " + permFile
                                        + " at " + parser.getPositionDescription());
                            } else {
                                mWhitelistedStagedInstallers.add(pkgname);
                            }
                            if (isModulesInstaller) {
                                if (mModulesInstallerPackageName != null) {
                                    throw new IllegalStateException(
                                            "Multiple modules installers");
                                }
                                mModulesInstallerPackageName = pkgname;
                            }
                        } else {
                            logNotAllowedInPartition(name, permFile, parser);
                        }
@@ -1224,11 +1241,18 @@ public class SystemConfig {
                    case "allowed-vendor-apex": {
                        if (allowVendorApex) {
                            String pkgName = parser.getAttributeValue(null, "package");
                            String installerPkgName = parser.getAttributeValue(
                                    null, "installerPackage");
                            if (pkgName == null) {
                                Slog.w(TAG, "<" + name + "> without package in " + permFile
                                        + " at " + parser.getPositionDescription());
                            } else {
                                mAllowedVendorApexes.add(pkgName);
                            }
                            if (installerPkgName == null) {
                                Slog.w(TAG, "<" + name + "> without installerPackage in " + permFile
                                        + " at " + parser.getPositionDescription());
                            }
                            if (pkgName != null && installerPkgName != null) {
                                mAllowedVendorApexes.put(pkgName, installerPkgName);
                            }
                        } else {
                            logNotAllowedInPartition(name, permFile, parser);
+21 −5
Original line number Diff line number Diff line
@@ -2252,9 +2252,11 @@ public class PackageInstallerSession extends IPackageInstallerSession.Stub {
                    (params.installFlags & PackageManager.INSTALL_DISABLE_ALLOWED_APEX_UPDATE_CHECK)
                        == 0;
            synchronized (mLock) {
                if (checkApexUpdateAllowed && !isApexUpdateAllowed(mPackageName)) {
                if (checkApexUpdateAllowed && !isApexUpdateAllowed(mPackageName,
                          mInstallSource.installerPackageName)) {
                    onSessionValidationFailure(PackageManager.INSTALL_FAILED_VERIFICATION_FAILURE,
                            "Update of APEX package " + mPackageName + " is not allowed");
                            "Update of APEX package " + mPackageName + " is not allowed for "
                                    + mInstallSource.installerPackageName);
                    return;
                }
            }
@@ -2798,9 +2800,23 @@ public class PackageInstallerSession extends IPackageInstallerSession.Stub {
        return sessionContains((s) -> !s.isApexSession());
    }

    private boolean isApexUpdateAllowed(String apexPackageName) {
        return mPm.getModuleInfo(apexPackageName, 0) != null
                || SystemConfig.getInstance().getAllowedVendorApexes().contains(apexPackageName);
    private boolean isApexUpdateAllowed(String apexPackageName, String installerPackageName) {
        if (mPm.getModuleInfo(apexPackageName, 0) != null) {
            final String modulesInstaller =
                    SystemConfig.getInstance().getModulesInstallerPackageName();
            if (modulesInstaller == null) {
                Slog.w(TAG, "No modules installer defined");
                return false;
            }
            return modulesInstaller.equals(installerPackageName);
        }
        final String vendorApexInstaller =
                SystemConfig.getInstance().getAllowedVendorApexes().get(apexPackageName);
        if (vendorApexInstaller == null) {
            Slog.w(TAG, apexPackageName + " is not allowed to be updated");
            return false;
        }
        return vendorApexInstaller.equals(installerPackageName);
    }

    /**
+45 −2
Original line number Diff line number Diff line
@@ -19,6 +19,7 @@ package com.android.server.systemconfig;
import static com.google.common.truth.Truth.assertThat;

import static org.junit.Assert.assertEquals;
import static org.testng.Assert.expectThrows;

import android.platform.test.annotations.Presubmit;
import android.util.ArrayMap;
@@ -200,6 +201,46 @@ public class SystemConfigTest {

        assertThat(mSysConfig.getWhitelistedStagedInstallers())
                .containsExactly("com.android.package1");
        assertThat(mSysConfig.getModulesInstallerPackageName()).isNull();
    }

    @Test
    public void readPermissions_parsesStagedInstallerWhitelist_modulesInstaller()
            throws IOException {
        final String contents =
                "<config>\n"
                + "    <whitelisted-staged-installer package=\"com.android.package1\" "
                + "         isModulesInstaller=\"true\" />\n"
                + "</config>";
        final File folder = createTempSubfolder("folder");
        createTempFile(folder, "staged-installer-whitelist.xml", contents);

        mSysConfig.readPermissions(folder, /* Grant all permission flags */ ~0);

        assertThat(mSysConfig.getWhitelistedStagedInstallers())
                .containsExactly("com.android.package1");
        assertThat(mSysConfig.getModulesInstallerPackageName())
                .isEqualTo("com.android.package1");
    }

    @Test
    public void readPermissions_parsesStagedInstallerWhitelist_multipleModulesInstallers()
            throws IOException {
        final String contents =
                "<config>\n"
                + "    <whitelisted-staged-installer package=\"com.android.package1\" "
                + "         isModulesInstaller=\"true\" />\n"
                + "    <whitelisted-staged-installer package=\"com.android.package2\" "
                + "         isModulesInstaller=\"true\" />\n"
                + "</config>";
        final File folder = createTempSubfolder("folder");
        createTempFile(folder, "staged-installer-whitelist.xml", contents);

        IllegalStateException e = expectThrows(
                IllegalStateException.class,
                () -> mSysConfig.readPermissions(folder, /* Grant all permission flags */ ~0));

        assertThat(e).hasMessageThat().contains("Multiple modules installers");
    }

    /**
@@ -230,14 +271,16 @@ public class SystemConfigTest {
            throws IOException {
        final String contents =
                "<config>\n"
                        + "    <allowed-vendor-apex package=\"com.android.apex1\" />\n"
                        + "    <allowed-vendor-apex package=\"com.android.apex1\" "
                        + "installerPackage=\"com.installer\" />\n"
                        + "</config>";
        final File folder = createTempSubfolder("folder");
        createTempFile(folder, "vendor-apex-allowlist.xml", contents);

        mSysConfig.readPermissions(folder, /* Grant all permission flags */ ~0);

        assertThat(mSysConfig.getAllowedVendorApexes()).containsExactly("com.android.apex1");
        assertThat(mSysConfig.getAllowedVendorApexes())
                .containsExactly("com.android.apex1", "com.installer");
    }

    /**
+91 −1
Original line number Diff line number Diff line
@@ -55,7 +55,7 @@ public class StagedInstallInternalTest {
    private static final TestApp TEST_APEX_WITH_APK_V2 = new TestApp("TestApexWithApkV2",
            APK_IN_APEX_TESTAPEX_NAME, 2, /*isApex*/true, APK_IN_APEX_TESTAPEX_NAME + "_v2.apex");
    private static final TestApp APEX_WRONG_SHA_V2 = new TestApp(
            "ApexWrongSha2", SHIM_APEX_PACKAGE_NAME, 2, /*isApex*/true,
            "ApexWrongSha2", SHIM_APEX_PACKAGE_NAME, 2, /* isApex= */ true,
            "com.android.apex.cts.shim.v2_wrong_sha.apex");

    private File mTestStateFile = new File(
@@ -236,6 +236,96 @@ public class StagedInstallInternalTest {
        assertThat(InstallUtils.getInstalledVersion(SHIM_APEX_PACKAGE_NAME)).isEqualTo(1);
    }

    @Test
    public void testApexInstallerNotInAllowListCanNotInstall_staged() throws Exception {
        assertThat(InstallUtils.getInstalledVersion(SHIM_APEX_PACKAGE_NAME)).isEqualTo(1);
        // We don't really care which APEX we are trying to install here, since the session creation
        // should fail immediately.
        InstallUtils.commitExpectingFailure(
                SecurityException.class,
                "Installer not allowed to commit staged install",
                Install.single(APEX_WRONG_SHA_V2).setBypassStangedInstallerCheck(false)
                        .setStaged());
    }

    @Test
    public void testApexInstallerNotInAllowListCanNotInstall_nonStaged() throws Exception {
        assertThat(InstallUtils.getInstalledVersion(SHIM_APEX_PACKAGE_NAME)).isEqualTo(1);
        // We don't really care which APEX we are trying to install here, since the session creation
        // should fail immediately.
        InstallUtils.commitExpectingFailure(
                SecurityException.class,
                "Installer not allowed to commit non-staged APEX install",
                Install.single(APEX_WRONG_SHA_V2).setBypassStangedInstallerCheck(false));
    }

    @Test
    public void testApexNotInAllowListCanNotInstall_staged() throws Exception {
        assertThat(InstallUtils.getInstalledVersion("test.apex.rebootless")).isEqualTo(1);
        TestApp apex = new TestApp("apex", "test.apex.rebootless", 2,
                /* isApex= */ true, "test.rebootless_apex_v2.apex");
        InstallUtils.commitExpectingFailure(
                AssertionError.class,
                "Update of APEX package test.apex.rebootless is not allowed "
                        + "for com.android.tests.stagedinstallinternal",
                Install.single(apex).setBypassAllowedApexUpdateCheck(false).setStaged());
    }

    @Test
    public void testApexNotInAllowListCanNotInstall_nonStaged() throws Exception {
        assertThat(InstallUtils.getInstalledVersion("test.apex.rebootless")).isEqualTo(1);
        TestApp apex = new TestApp("apex", "test.apex.rebootless", 2,
                /* isApex= */ true, "test.rebootless_apex_v2.apex");
        InstallUtils.commitExpectingFailure(
                AssertionError.class,
                "Update of APEX package test.apex.rebootless is not allowed "
                        + "for com.android.tests.stagedinstallinternal",
                Install.single(apex).setBypassAllowedApexUpdateCheck(false));
    }

    @Test
    public void testVendorApexWrongInstaller_staged() throws Exception {
        assertThat(InstallUtils.getInstalledVersion("test.apex.rebootless")).isEqualTo(1);
        TestApp apex = new TestApp("apex", "test.apex.rebootless", 2,
                /* isApex= */ true, "test.rebootless_apex_v2.apex");
        InstallUtils.commitExpectingFailure(
                AssertionError.class,
                "Update of APEX package test.apex.rebootless is not allowed "
                        + "for com.android.tests.stagedinstallinternal",
                Install.single(apex).setBypassAllowedApexUpdateCheck(false).setStaged());
    }

    @Test
    public void testVendorApexWrongInstaller_nonStaged() throws Exception {
        assertThat(InstallUtils.getInstalledVersion("test.apex.rebootless")).isEqualTo(1);
        TestApp apex = new TestApp("apex", "test.apex.rebootless", 2,
                /* isApex= */ true, "test.rebootless_apex_v2.apex");
        InstallUtils.commitExpectingFailure(
                AssertionError.class,
                "Update of APEX package test.apex.rebootless is not allowed "
                        + "for com.android.tests.stagedinstallinternal",
                Install.single(apex).setBypassAllowedApexUpdateCheck(false));
    }

    @Test
    public void testVendorApexCorrectInstaller_staged() throws Exception {
        assertThat(InstallUtils.getInstalledVersion("test.apex.rebootless")).isEqualTo(1);
        TestApp apex = new TestApp("apex", "test.apex.rebootless", 2,
                /* isApex= */ true, "test.rebootless_apex_v2.apex");
        int sessionId =
                Install.single(apex).setBypassAllowedApexUpdateCheck(false).setStaged().commit();
        InstallUtils.getPackageInstaller().abandonSession(sessionId);
    }

    @Test
    public void testVendorApexCorrectInstaller_nonStaged() throws Exception {
        assertThat(InstallUtils.getInstalledVersion("test.apex.rebootless")).isEqualTo(1);
        TestApp apex = new TestApp("apex", "test.apex.rebootless", 2,
                /* isApex= */ true, "test.rebootless_apex_v2.apex");
        Install.single(apex).setBypassAllowedApexUpdateCheck(false).commit();
        assertThat(InstallUtils.getInstalledVersion("test.apex.rebootless")).isEqualTo(2);
    }

    @Test
    public void testRebootlessUpdates() throws Exception {
        InstallUtils.dropShellPermissionIdentity();
+78 −2
Original line number Diff line number Diff line
@@ -43,7 +43,9 @@ import org.junit.Rule;
import org.junit.Test;
import org.junit.runner.RunWith;

import java.io.BufferedWriter;
import java.io.File;
import java.io.FileWriter;
import java.util.List;
import java.util.stream.Collectors;

@@ -60,6 +62,9 @@ public class StagedInstallInternalTest extends BaseHostJUnit4Test {
    private static final String APK_A = "TestAppAv1.apk";
    private static final String APK_IN_APEX_TESTAPEX_NAME = "com.android.apex.apkrollback.test";

    private static final String TEST_VENDOR_APEX_ALLOW_LIST =
            "/vendor/etc/sysconfig/test-vendor-apex-allow-list.xml";

    private final InstallUtilsHost mHostUtils = new InstallUtilsHost(this);

    /**
@@ -87,7 +92,8 @@ public class StagedInstallInternalTest extends BaseHostJUnit4Test {
                "/data/apex/active/" + APK_IN_APEX_TESTAPEX_NAME + "*.apex",
                "/data/apex/active/" + SHIM_APEX_PACKAGE_NAME + "*.apex",
                "/system/apex/test.rebootless_apex_v1.apex",
                "/data/apex/active/test.apex.rebootless*.apex");
                "/data/apex/active/test.apex.rebootless*.apex",
                TEST_VENDOR_APEX_ALLOW_LIST);
    }

    @Before
@@ -134,7 +140,23 @@ public class StagedInstallInternalTest extends BaseHostJUnit4Test {
        }
        getDevice().remountSystemWritable();
        assertTrue(getDevice().pushFile(apex, "/system/apex/" + fileName));
        getDevice().reboot();
    }

    private void pushTestVendorApexAllowList(String installerPackageName) throws Exception {
        if (!getDevice().isAdbRoot()) {
            getDevice().enableAdbRoot();
        }
        getDevice().remountSystemWritable();
        File file = File.createTempFile("test-vendor-apex-allow-list", ".xml");
        try (BufferedWriter writer = new BufferedWriter(new FileWriter(file))) {
            final String fmt =
                    "<config>\n"
                            + "    <allowed-vendor-apex package=\"test.apex.rebootless\" "
                            + "       installerPackage=\"%s\" />\n"
                            + "</config>";
            writer.write(String.format(fmt, installerPackageName));
        }
        getDevice().pushFile(file, TEST_VENDOR_APEX_ALLOW_LIST);
    }

    /**
@@ -144,6 +166,8 @@ public class StagedInstallInternalTest extends BaseHostJUnit4Test {
    @LargeTest
    public void testDuplicateApkInApexShouldFail() throws Exception {
        pushTestApex(APK_IN_APEX_TESTAPEX_NAME + "_v1.apex");
        getDevice().reboot();

        runPhase("testDuplicateApkInApexShouldFail_Commit");
        getDevice().reboot();
        runPhase("testDuplicateApkInApexShouldFail_Verify");
@@ -388,9 +412,61 @@ public class StagedInstallInternalTest extends BaseHostJUnit4Test {
        runPhase("testApexIsNotActivatedIfNotInCheckpointMode_VerifyPostReboot");
    }

    @Test
    public void testApexInstallerNotInAllowListCanNotInstall() throws Exception {
        assumeTrue("Device does not support updating APEX",
                mHostUtils.isApexUpdateSupported());

        runPhase("testApexInstallerNotInAllowListCanNotInstall_staged");
        runPhase("testApexInstallerNotInAllowListCanNotInstall_nonStaged");
    }

    @Test
    @LargeTest
    public void testApexNotInAllowListCanNotInstall() throws Exception {
        assumeTrue("Device does not support updating APEX",
                mHostUtils.isApexUpdateSupported());

        pushTestApex("test.rebootless_apex_v1.apex");
        getDevice().reboot();

        runPhase("testApexNotInAllowListCanNotInstall_staged");
        runPhase("testApexNotInAllowListCanNotInstall_nonStaged");
    }

    @Test
    @LargeTest
    public void testVendorApexWrongInstaller() throws Exception {
        assumeTrue("Device does not support updating APEX",
                mHostUtils.isApexUpdateSupported());

        pushTestVendorApexAllowList("com.wrong.installer");
        pushTestApex("test.rebootless_apex_v1.apex");
        getDevice().reboot();

        runPhase("testVendorApexWrongInstaller_staged");
        runPhase("testVendorApexWrongInstaller_nonStaged");
    }

    @Test
    @LargeTest
    public void testVendorApexCorrectInstaller() throws Exception {
        assumeTrue("Device does not support updating APEX",
                mHostUtils.isApexUpdateSupported());

        pushTestVendorApexAllowList("com.android.tests.stagedinstallinternal");
        pushTestApex("test.rebootless_apex_v1.apex");
        getDevice().reboot();

        runPhase("testVendorApexCorrectInstaller_staged");
        runPhase("testVendorApexCorrectInstaller_nonStaged");
    }

    @Test
    public void testRebootlessUpdates() throws Exception {
        pushTestApex("test.rebootless_apex_v1.apex");
        getDevice().reboot();

        runPhase("testRebootlessUpdates");
    }