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

Commit e8b12935 authored by Nikita Ioffe's avatar Nikita Ioffe Committed by Android (Google) Code Review
Browse files

Merge changes from topic "apex-installers-restriction" into sc-dev

* changes:
  Reject non-staged APEX install if there is staged install of same APEX
  Restrict what APEXes different installers can update
parents c2e4423c db989230
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);
+36 −5
Original line number Diff line number Diff line
@@ -2252,13 +2252,30 @@ 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;
                }
            }

            if (!params.isStaged) {
                // For non-staged APEX installs also check if there is a staged session that
                // contains the same APEX. If that's the case, we should fail this session.
                synchronized (mLock) {
                    int sessionId = mStagingManager.getSessionIdByPackageName(mPackageName);
                    if (sessionId != -1) {
                        onSessionValidationFailure(
                                PackageManager.INSTALL_FAILED_VERIFICATION_FAILURE,
                                "Staged session " + sessionId + " already contains "
                                        + mPackageName);
                        return;
                    }
                }
            }
        }

        if (params.isStaged) {
            mStagingManager.commitSession(mStagedSession);
@@ -2798,9 +2815,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);
    }

    /**
+20 −0
Original line number Diff line number Diff line
@@ -777,6 +777,26 @@ public class StagingManager {
        }
    }

    /**
     * Returns id of a committed and non-finalized stated session that contains same
     * {@code packageName}, or {@code -1} if no sessions have this {@code packageName} staged.
     */
    int getSessionIdByPackageName(@NonNull String packageName) {
        synchronized (mStagedSessions) {
            for (int i = 0; i < mStagedSessions.size(); i++) {
                StagedSession stagedSession = mStagedSessions.valueAt(i);
                if (!stagedSession.isCommitted() || stagedSession.isDestroyed()
                        || stagedSession.isInTerminalState()) {
                    continue;
                }
                if (stagedSession.getPackageName().equals(packageName)) {
                    return stagedSession.sessionId();
                }
            }
        }
        return -1;
    }

    @VisibleForTesting
    void createSession(@NonNull StagedSession sessionInfo) {
        synchronized (mStagedSessions) {
+60 −0
Original line number Diff line number Diff line
@@ -459,6 +459,66 @@ public class StagingManagerTest {
        assertThat(apkSession.getErrorMessage()).isEqualTo("Another apex session failed");
    }

    @Test
    public void getSessionIdByPackageName() throws Exception {
        FakeStagedSession session = new FakeStagedSession(239);
        session.setCommitted(true);
        session.setSessionReady();
        session.setPackageName("com.foo");

        mStagingManager.createSession(session);
        assertThat(mStagingManager.getSessionIdByPackageName("com.foo")).isEqualTo(239);
    }

    @Test
    public void getSessionIdByPackageName_appliedSession_ignores() throws Exception {
        FakeStagedSession session = new FakeStagedSession(37);
        session.setCommitted(true);
        session.setSessionApplied();
        session.setPackageName("com.foo");

        mStagingManager.createSession(session);
        assertThat(mStagingManager.getSessionIdByPackageName("com.foo")).isEqualTo(-1);
    }

    @Test
    public void getSessionIdByPackageName_failedSession_ignores() throws Exception {
        FakeStagedSession session = new FakeStagedSession(73);
        session.setCommitted(true);
        session.setSessionFailed(1, "whatevs");
        session.setPackageName("com.foo");

        mStagingManager.createSession(session);
        assertThat(mStagingManager.getSessionIdByPackageName("com.foo")).isEqualTo(-1);
    }

    @Test
    public void getSessionIdByPackageName_destroyedSession_ignores() throws Exception {
        FakeStagedSession session = new FakeStagedSession(23);
        session.setCommitted(true);
        session.setDestroyed(true);
        session.setPackageName("com.foo");

        mStagingManager.createSession(session);
        assertThat(mStagingManager.getSessionIdByPackageName("com.foo")).isEqualTo(-1);
    }

    @Test
    public void getSessionIdByPackageName_noSessions() throws Exception {
        assertThat(mStagingManager.getSessionIdByPackageName("com.foo")).isEqualTo(-1);
    }

    @Test
    public void getSessionIdByPackageName_noSessionHasThisPackage() throws Exception {
        FakeStagedSession session = new FakeStagedSession(37);
        session.setCommitted(true);
        session.setSessionApplied();
        session.setPackageName("com.foo");

        mStagingManager.createSession(session);
        assertThat(mStagingManager.getSessionIdByPackageName("com.bar")).isEqualTo(-1);
    }

    private StagingManager.StagedSession createSession(int sessionId, String packageName,
            long committedMillis) {
        PackageInstaller.SessionParams params = new PackageInstaller.SessionParams(
+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");
    }

    /**
Loading