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

Commit 6b6d653c authored by JW Wang's avatar JW Wang
Browse files

Move signature checking out of ApexManager

Now the check is in PackageSessionVerifier for both staged and
non-staged APEX. This ensures a consistent behavior between
staged and non-staged APEX when it comes to signature checking.

* Remove the dependency on ApexPackageInfo from ApexManager
  which helps us migrate from ApexPackageInfo.

* Performance slightly improved (measured on Pixel4a) for
  APEX signature checking:

  shim APEX: 13-16ms -> 10-13ms
  com.android.art: 340-355ms -> 160-180ms
  com.android.cellbroadcast: 165-180ms -> 79-85ms

  This is because the SigningDetails of the install session is
  reused without re-calculation.

Bug: 225756739
Test: atest CtsStagedInstallHostTestCases \
            StagedInstallInternalTest \
	    GtsStagedInstallHostTestCases \
	    com.android.server.pm.ApexManagerTest

Change-Id: I6e3adf0f3f80764dcdb9cc26ef281f25566e854e
parent 791bcea5
Loading
Loading
Loading
Loading
+2 −2
Original line number Diff line number Diff line
@@ -499,9 +499,9 @@ public abstract class PackageManagerInternal {

    /**
     * Prunes the cache of the APKs in the given APEXes.
     * @param apexPackages The list of APEX packages that may contain APK-in-APEX.
     * @param apexPackageNames The list of APEX package names that may contain APK-in-APEX.
     */
    public abstract void pruneCachedApksInApex(@NonNull List<PackageInfo> apexPackages);
    public abstract void pruneCachedApksInApex(@NonNull List<String> apexPackageNames);

    /**
     * @return The SetupWizard package name.
+3 −32
Original line number Diff line number Diff line
@@ -49,11 +49,8 @@ import com.android.internal.annotations.VisibleForTesting;
import com.android.internal.util.IndentingPrintWriter;
import com.android.internal.util.Preconditions;
import com.android.modules.utils.build.UnboundedSdkLevel;
import com.android.server.pm.parsing.PackageParser2;
import com.android.server.pm.parsing.pkg.AndroidPackage;
import com.android.server.pm.parsing.pkg.ParsedPackage;
import com.android.server.pm.pkg.component.ParsedApexSystemService;
import com.android.server.pm.pkg.parsing.PackageInfoWithoutStateUtils;
import com.android.server.utils.TimingsTraceAndSlog;

import com.google.android.collect.Lists;
@@ -360,8 +357,7 @@ public abstract class ApexManager {
     *
     * @return {@code ApeInfo} about the newly installed APEX package.
     */
    abstract ApexInfo installPackage(File apexFile, PackageParser2 packageParser,
            ApexPackageInfo apexPackageInfo) throws PackageManagerException;
    abstract ApexInfo installPackage(File apexFile) throws PackageManagerException;

    /**
     * Get a list of apex system services implemented in an apex.
@@ -896,37 +892,13 @@ public abstract class ApexManager {
        }

        @Override
        ApexInfo installPackage(File apexFile, PackageParser2 packageParser,
                ApexPackageInfo apexPackageInfo)
        ApexInfo installPackage(File apexFile)
                throws PackageManagerException {
            try {
                final int flags = PackageManager.GET_META_DATA
                        | PackageManager.GET_SIGNING_CERTIFICATES
                        | PackageManager.GET_SIGNATURES;
                final ParsedPackage parsedPackage = packageParser.parsePackage(
                        apexFile, flags, /* useCaches= */ false);
                final PackageInfo newApexPkg = PackageInfoWithoutStateUtils.generate(parsedPackage,
                        /* apexInfo= */ null, flags);
                if (newApexPkg == null) {
                    throw new PackageManagerException(PackageManager.INSTALL_FAILED_INVALID_APK,
                            "Failed to generate package info for " + apexFile.getAbsolutePath());
                }
                final PackageInfo existingApexPkg = apexPackageInfo.getPackageInfo(
                        newApexPkg.packageName, MATCH_ACTIVE_PACKAGE);
                if (existingApexPkg == null) {
                    Slog.w(TAG, "Attempting to install new APEX package " + newApexPkg.packageName);
                    throw new PackageManagerException(PackageManager.INSTALL_FAILED_PACKAGE_CHANGED,
                            "It is forbidden to install new APEX packages");
                }
                checkApexSignature(existingApexPkg, newApexPkg);
                return waitForApexService().installAndActivatePackage(apexFile.getAbsolutePath());
            } catch (RemoteException e) {
                throw new PackageManagerException(PackageManager.INSTALL_FAILED_INTERNAL_ERROR,
                        "apexservice not available");
            } catch (PackageManagerException e) {
                // Catching it in order not to fall back to Exception which rethrows the
                // PackageManagerException with a common error code.
                throw e;
            } catch (Exception e) {
                // TODO(b/187864524): is INSTALL_FAILED_INTERNAL_ERROR is the right error code here?
                throw new PackageManagerException(PackageManager.INSTALL_FAILED_INTERNAL_ERROR,
@@ -1159,8 +1131,7 @@ public abstract class ApexManager {
        }

        @Override
        ApexInfo installPackage(File apexFile, PackageParser2 packageParser,
                ApexPackageInfo apexPackageInfo) {
        ApexInfo installPackage(File apexFile) {
            throw new UnsupportedOperationException("APEX updates are not supported");
        }

+1 −2
Original line number Diff line number Diff line
@@ -872,8 +872,7 @@ final class InstallPackageHelper {
                                + " got: " + apexes.length);
            }
            try (PackageParser2 packageParser = mPm.mInjector.getScanningPackageParser()) {
                ApexInfo apexInfo = mApexManager.installPackage(
                        apexes[0], packageParser, mPm.mApexPackageInfo);
                ApexInfo apexInfo = mApexManager.installPackage(apexes[0]);
                if (ApexPackageInfo.ENABLE_FEATURE_SCAN_APEX) {
                    ParsedPackage parsedPackage = packageParser.parsePackage(
                            new File(apexInfo.modulePath), 0, /* useCaches= */ false);
+3 −3
Original line number Diff line number Diff line
@@ -6172,7 +6172,7 @@ public class PackageManagerService implements PackageSender, TestUtilityService
        }

        @Override
        public void pruneCachedApksInApex(@NonNull List<PackageInfo> apexPackages) {
        public void pruneCachedApksInApex(@NonNull List<String> apexPackageNames) {
            if (mCacheDir == null) {
                return;
            }
@@ -6180,9 +6180,9 @@ public class PackageManagerService implements PackageSender, TestUtilityService
            final PackageCacher cacher = new PackageCacher(mCacheDir);
            synchronized (mLock) {
                final Computer snapshot = snapshot();
                for (int i = 0, size = apexPackages.size(); i < size; i++) {
                for (int i = 0, size = apexPackageNames.size(); i < size; i++) {
                    final List<String> apkNames =
                            mApexManager.getApksInApex(apexPackages.get(i).packageName);
                            mApexManager.getApksInApex(apexPackageNames.get(i));
                    for (int j = 0, apksInApex = apkNames.size(); j < apksInApex; j++) {
                        final AndroidPackage pkg = snapshot.getPackage(apkNames.get(j));
                        cacher.cleanCachedResult(new File(pkg.getPath()));
+50 −76
Original line number Diff line number Diff line
@@ -46,7 +46,6 @@ import com.android.server.LocalServices;
import com.android.server.SystemConfig;
import com.android.server.pm.parsing.PackageParser2;
import com.android.server.pm.parsing.pkg.ParsedPackage;
import com.android.server.pm.pkg.parsing.PackageInfoWithoutStateUtils;
import com.android.server.rollback.RollbackManagerInternal;

import java.io.File;
@@ -101,10 +100,12 @@ final class PackageSessionVerifier {
                    for (PackageInstallerSession child : session.getChildSessions()) {
                        checkApexUpdateAllowed(child);
                        checkRebootlessApex(child);
                        checkApexSignature(child);
                    }
                } else {
                    checkApexUpdateAllowed(session);
                    checkRebootlessApex(session);
                    checkApexSignature(session);
                }
                verifyAPK(session, callback);
            } catch (PackageManagerException e) {
@@ -115,6 +116,47 @@ final class PackageSessionVerifier {
        });
    }

    private SigningDetails getSigningDetails(PackageInfo apexPkg) throws PackageManagerException {
        final String apexPath = apexPkg.applicationInfo.sourceDir;
        final int minSignatureScheme =
                ApkSignatureVerifier.getMinimumSignatureSchemeVersionForTargetSdk(
                        apexPkg.applicationInfo.targetSdkVersion);
        final ParseTypeImpl input = ParseTypeImpl.forDefaultParsing();
        final ParseResult<SigningDetails> result = ApkSignatureVerifier.verify(
                input, apexPath, minSignatureScheme);
        if (result.isError()) {
            throw new PackageManagerException(PackageManager.INSTALL_FAILED_VERIFICATION_FAILURE,
                    "Failed to verify APEX package " + apexPath + " : "
                            + result.getException(), result.getException());
        }
        return result.getResult();
    }

    private void checkApexSignature(PackageInstallerSession session)
            throws PackageManagerException {
        if (!session.isApexSession()) {
            return;
        }
        final String packageName = session.getPackageName();
        final PackageInfo existingApexPkg = mPm.snapshotComputer().getPackageInfo(
                session.getPackageName(), PackageManager.MATCH_APEX, UserHandle.USER_SYSTEM);
        if (existingApexPkg == null) {
            throw new PackageManagerException(PackageManager.INSTALL_FAILED_PACKAGE_CHANGED,
                    "Attempting to install new APEX package " + packageName);
        }
        final SigningDetails existingSigningDetails = getSigningDetails(existingApexPkg);
        final SigningDetails newSigningDetails = session.getSigningDetails();
        if (newSigningDetails.checkCapability(existingSigningDetails,
                SigningDetails.CertCapabilities.INSTALLED_DATA)
                || existingSigningDetails.checkCapability(newSigningDetails,
                SigningDetails.CertCapabilities.ROLLBACK)) {
            return;
        }
        throw new PackageManagerException(PackageManager.INSTALL_FAILED_VERIFICATION_FAILURE,
                "APK container signature of APEX package " + packageName
                        + " is not compatible with the one currently installed on device");
    }

    /**
     * Runs verifications particular to APK. This includes APEX sessions since an APEX can also
     * be treated as APK.
@@ -283,13 +325,10 @@ final class PackageSessionVerifier {
        // APEX checks. For single-package sessions, check if they contain an APEX. For
        // multi-package sessions, find all the child sessions that contain an APEX.
        if (hasApex) {
            final List<PackageInfo> apexPackages = submitSessionToApexService(session, rollbackId);
            for (int i = 0, size = apexPackages.size(); i < size; i++) {
                validateApexSignature(apexPackages.get(i));
            }
            final List<String> apexPackageNames = submitSessionToApexService(session, rollbackId);
            final PackageManagerInternal packageManagerInternal =
                    LocalServices.getService(PackageManagerInternal.class);
            packageManagerInternal.pruneCachedApksInApex(apexPackages);
            packageManagerInternal.pruneCachedApksInApex(apexPackageNames);
        }
    }

@@ -333,62 +372,7 @@ final class PackageSessionVerifier {
        }
    }

    /**
     * Validates the signature used to sign the container of the new apex package
     *
     * @param newApexPkg The new apex package that is being installed
     */
    private void validateApexSignature(PackageInfo newApexPkg) throws PackageManagerException {
        // Get signing details of the new package
        final String apexPath = newApexPkg.applicationInfo.sourceDir;
        final String packageName = newApexPkg.packageName;
        int minSignatureScheme = ApkSignatureVerifier.getMinimumSignatureSchemeVersionForTargetSdk(
                newApexPkg.applicationInfo.targetSdkVersion);

        final ParseTypeImpl input = ParseTypeImpl.forDefaultParsing();
        final ParseResult<SigningDetails> newResult = ApkSignatureVerifier.verify(
                input.reset(), apexPath, minSignatureScheme);
        if (newResult.isError()) {
            throw new PackageManagerException(PackageManager.INSTALL_FAILED_VERIFICATION_FAILURE,
                    "Failed to parse APEX package " + apexPath + " : "
                            + newResult.getException(), newResult.getException());
        }
        final SigningDetails newSigningDetails = newResult.getResult();

        // Get signing details of the existing package
        final PackageInfo existingApexPkg = mPm.snapshotComputer().getPackageInfo(
                packageName, PackageManager.MATCH_APEX, UserHandle.USER_SYSTEM);
        if (existingApexPkg == null) {
            // This should never happen, because submitSessionToApexService ensures that no new
            // apexes were installed.
            throw new IllegalStateException("Unknown apex package " + packageName);
        }

        final ParseResult<SigningDetails> existingResult = ApkSignatureVerifier.verify(
                input.reset(), existingApexPkg.applicationInfo.sourceDir,
                SigningDetails.SignatureSchemeVersion.JAR);
        if (existingResult.isError()) {
            throw new PackageManagerException(PackageManager.INSTALL_FAILED_VERIFICATION_FAILURE,
                    "Failed to parse APEX package " + existingApexPkg.applicationInfo.sourceDir
                            + " : " + existingResult.getException(), existingResult.getException());
        }
        final SigningDetails existingSigningDetails = existingResult.getResult();

        // Verify signing details for upgrade
        if (newSigningDetails.checkCapability(existingSigningDetails,
                SigningDetails.CertCapabilities.INSTALLED_DATA)
                || existingSigningDetails.checkCapability(newSigningDetails,
                SigningDetails.CertCapabilities.ROLLBACK)) {
            return;
        }

        throw new PackageManagerException(PackageManager.INSTALL_FAILED_VERIFICATION_FAILURE,
                "APK-container signature of APEX package " + packageName + " with version "
                        + newApexPkg.versionCodeMajor + " and path " + apexPath + " is not"
                        + " compatible with the one currently installed on device");
    }

    private List<PackageInfo> submitSessionToApexService(StagingManager.StagedSession session,
    private List<String> submitSessionToApexService(StagingManager.StagedSession session,
            int rollbackId) throws PackageManagerException {
        final IntArray childSessionIds = new IntArray();
        if (session.isMultiPackage()) {
@@ -413,32 +397,22 @@ final class PackageSessionVerifier {
        // submitStagedSession will throw a PackageManagerException if apexd verification fails,
        // which will be propagated to populate stagedSessionErrorMessage of this session.
        final ApexInfoList apexInfoList = mApexManager.submitStagedSession(apexSessionParams);
        final List<PackageInfo> result = new ArrayList<>();
        final List<String> apexPackageNames = new ArrayList<>();
        for (ApexInfo apexInfo : apexInfoList.apexInfos) {
            final PackageInfo packageInfo;
            final int flags = PackageManager.GET_META_DATA;
            final ParsedPackage parsedPackage;
            try (PackageParser2 packageParser = mPackageParserSupplier.get()) {
                File apexFile = new File(apexInfo.modulePath);
                final ParsedPackage parsedPackage = packageParser.parsePackage(
                        apexFile, flags, false);
                packageInfo = PackageInfoWithoutStateUtils.generate(parsedPackage, apexInfo, flags);
                if (packageInfo == null) {
                    throw new PackageManagerException(
                            PackageManager.INSTALL_FAILED_VERIFICATION_FAILURE,
                            "Unable to generate package info: " + apexInfo.modulePath);
                }
                parsedPackage = packageParser.parsePackage(apexFile, 0, false);
            } catch (PackageManagerException e) {
                throw new PackageManagerException(
                        PackageManager.INSTALL_FAILED_VERIFICATION_FAILURE,
                        "Failed to parse APEX package " + apexInfo.modulePath + " : " + e, e);
            }
            result.add(packageInfo);
            apexPackageNames.add(packageInfo.packageName);
            apexPackageNames.add(parsedPackage.getPackageName());
        }
        Slog.d(TAG, "Session " + session.sessionId() + " has following APEX packages: "
                + apexPackageNames);
        return result;
        return apexPackageNames;
    }

    private int retrieveRollbackIdForCommitSession(int sessionId) throws PackageManagerException {
Loading