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

Commit 0c8f2e01 authored by Victor Hsieh's avatar Victor Hsieh
Browse files

Force verifying updated priv apps on boot if enabled

If ro.apk_verity.mode is non-zero (default 0), package manager
will force verifying updated priv apps in /data, and make sure the
certificate matches the original one in /system, which we trust.

Test: logcat shows force collecting cert only if enabled
Bug: 30972906

Change-Id: I33f98930aaaaf76dbe2c0bc664e841f7892eb00e
parent 3db5c8c0
Loading
Loading
Loading
Loading
+13 −7
Original line number Diff line number Diff line
@@ -8193,12 +8193,12 @@ Slog.e("TODD",
    }
    private void collectCertificatesLI(PackageSetting ps, PackageParser.Package pkg,
            final @ParseFlags int parseFlags) throws PackageManagerException {
            final @ParseFlags int parseFlags, boolean forceCollect) throws PackageManagerException {
        // When upgrading from pre-N MR1, verify the package time stamp using the package
        // directory and not the APK file.
        final long lastModifiedTime = mIsPreNMR1Upgrade
                ? new File(pkg.codePath).lastModified() : getLastModifiedTime(pkg);
        if (ps != null
        if (ps != null && !forceCollect
                && ps.codePathString.equals(pkg.codePath)
                && ps.timeStamp == lastModifiedTime
                && !isCompatSignatureUpdateNeeded(pkg)
@@ -8221,7 +8221,8 @@ Slog.e("TODD",
            Slog.w(TAG, "PackageSetting for " + ps.name
                    + " is missing signatures.  Collecting certs again to recover them.");
        } else {
            Slog.i(TAG, toString() + " changed; collecting certs");
            Slog.i(TAG, pkg.codePath + " changed; collecting certs" +
                    (forceCollect ? " (forced)" : ""));
        }
        try {
@@ -8530,8 +8531,11 @@ Slog.e("TODD",
                    + " better than this " + pkg.getLongVersionCode());
        }
        // verify certificates against what was last scanned
        collectCertificatesLI(pkgSetting, pkg, parseFlags);
        // Verify certificates against what was last scanned. If it is an updated priv app, we will
        // force the verification. Full apk verification will happen unless apk verity is set up for
        // the file. In that case, only small part of the apk is verified upfront.
        collectCertificatesLI(pkgSetting, pkg, parseFlags,
                PackageManagerServiceUtils.isApkVerificationForced(disabledPkgSetting));
        boolean shouldHideSystemApp = false;
        // A new application appeared on /system, but, we already have a copy of
@@ -9835,6 +9839,7 @@ Slog.e("TODD",
        final @ScanFlags int scanFlags = request.scanFlags;
        final PackageSetting oldPkgSetting = request.oldPkgSetting;
        final PackageSetting originalPkgSetting = request.originalPkgSetting;
        final PackageSetting disabledPkgSetting = request.disabledPkgSetting;
        final UserHandle user = request.user;
        final String realPkgName = request.realPkgName;
        final PackageSetting pkgSetting = result.pkgSetting;
@@ -9917,7 +9922,7 @@ Slog.e("TODD",
            try {
                final boolean compareCompat = isCompatSignatureUpdateNeeded(pkg);
                final boolean compareRecover = isRecoverSignatureUpdateNeeded(pkg);
                final boolean compatMatch = verifySignatures(signatureCheckPs,
                final boolean compatMatch = verifySignatures(signatureCheckPs, disabledPkgSetting,
                        pkg.mSigningDetails, compareCompat, compareRecover);
                // The new KeySets will be re-added later in the scanning process.
                if (compatMatch) {
@@ -16687,8 +16692,9 @@ Slog.e("TODD",
                    try {
                        final boolean compareCompat = isCompatSignatureUpdateNeeded(pkg);
                        final boolean compareRecover = isRecoverSignatureUpdateNeeded(pkg);
                        // We don't care about disabledPkgSetting on install for now.
                        final boolean compatMatch = verifySignatures(
                                signatureCheckPs, pkg.mSigningDetails, compareCompat,
                                signatureCheckPs, null, pkg.mSigningDetails, compareCompat,
                                compareRecover);
                        // The new KeySets will be re-added later in the scanning process.
                        if (compatMatch) {
+43 −2
Original line number Diff line number Diff line
@@ -33,10 +33,12 @@ import com.android.server.pm.dex.DexManager;
import com.android.server.pm.dex.PackageDexUsage;

import android.annotation.NonNull;
import android.annotation.Nullable;
import android.app.AppGlobals;
import android.content.Intent;
import android.content.pm.PackageManager;
import android.content.pm.PackageParser;
import android.content.pm.PackageParser.PackageParserException;
import android.content.pm.ResolveInfo;
import android.content.pm.Signature;
import android.os.Build;
@@ -45,6 +47,7 @@ import android.os.Environment;
import android.os.FileUtils;
import android.os.Process;
import android.os.RemoteException;
import android.os.SystemProperties;
import android.os.UserHandle;
import android.service.pm.PackageServiceDumpProto;
import android.system.ErrnoException;
@@ -546,14 +549,47 @@ public class PackageManagerServiceUtils {
        return false;
    }

    /**
     * Make sure the updated priv app is signed with the same key as the original APK file on the
     * /system partition.
     *
     * <p>The rationale is that {@code disabledPkg} is a PackageSetting backed by xml files in /data
     * and is not tamperproof.
     */
    private static boolean matchSignatureInSystem(PackageSetting pkgSetting,
            PackageSetting disabledPkgSetting) {
        try {
            PackageParser.collectCertificates(disabledPkgSetting.pkg,
                    PackageParser.PARSE_IS_SYSTEM_DIR);
            if (compareSignatures(pkgSetting.signatures.mSignatures,
                        disabledPkgSetting.signatures.mSignatures)
                    != PackageManager.SIGNATURE_MATCH) {
                logCriticalInfo(Log.ERROR, "Updated system app mismatches cert on /system: " +
                        pkgSetting.name);
                return false;
            }
        } catch (PackageParserException e) {
            logCriticalInfo(Log.ERROR, "Failed to collect cert for " + pkgSetting.name + ": " +
                    e.getMessage());
            return false;
        }
        return true;
    }

    /** Returns true to force apk verification if the updated package (in /data) is a priv app. */
    static boolean isApkVerificationForced(@Nullable PackageSetting disabledPs) {
        return disabledPs != null && disabledPs.isPrivileged() &&
                SystemProperties.getInt("ro.apk_verity.mode", 0) != 0;
    }

    /**
     * Verifies that signatures match.
     * @returns {@code true} if the compat signatures were matched; otherwise, {@code false}.
     * @throws PackageManagerException if the signatures did not match.
     */
    public static boolean verifySignatures(PackageSetting pkgSetting,
            PackageParser.SigningDetails parsedSignatures, boolean compareCompat,
            boolean compareRecover)
            PackageSetting disabledPkgSetting, PackageParser.SigningDetails parsedSignatures,
            boolean compareCompat, boolean compareRecover)
            throws PackageManagerException {
        final String packageName = pkgSetting.name;
        boolean compatMatch = false;
@@ -572,6 +608,11 @@ public class PackageManagerServiceUtils {
                        packageName, pkgSetting.signatures.mSignatures,
                        parsedSignatures.signatures);
            }

            if (!match && isApkVerificationForced(disabledPkgSetting)) {
                match = matchSignatureInSystem(pkgSetting, disabledPkgSetting);
            }

            if (!match) {
                throw new PackageManagerException(INSTALL_FAILED_UPDATE_INCOMPATIBLE,
                        "Package " + packageName +