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

Commit 9dfa6749 authored by Narayan Kamath's avatar Narayan Kamath
Browse files

StagingManager: Implement signature verification for APEXes.

We compare signatures on the staged APEX with signatures of the
existing APEX package. They must be an exact match.

Test: atest apex_e2e_tests
Test: adb install --apex --staged
Change-Id: I07189409e4e2601a5808b345afff147564b460fa
parent 027ff086
Loading
Loading
Loading
Loading
+23 −15
Original line number Diff line number Diff line
@@ -57,6 +57,7 @@ import android.content.pm.IPackageInstallerSession;
import android.content.pm.PackageInfo;
import android.content.pm.PackageInstaller;
import android.content.pm.PackageInstaller.SessionInfo;
import android.content.pm.PackageInstaller.SessionInfo.StagedSessionErrorCode;
import android.content.pm.PackageInstaller.SessionParams;
import android.content.pm.PackageManager;
import android.content.pm.PackageParser;
@@ -982,9 +983,9 @@ public class PackageInstallerSession extends IPackageInstallerSession.Stub {
        mSealed = true;

        // Read transfers from the original owner stay open, but as the session's data
        // cannot be modified anymore, there is no leak of information.
        // For staged sessions, the validation is performed by StagingManager.
        if (!params.isMultiPackage && !params.isStaged) {
        // cannot be modified anymore, there is no leak of information. For staged sessions,
        // further validation may be performed by the staging manager.
        if (!params.isMultiPackage) {
            final PackageInfo pkgInfo = mPm.getPackageInfo(
                    params.appPackageName, PackageManager.GET_SIGNATURES
                            | PackageManager.MATCH_STATIC_SHARED_LIBRARIES /*flags*/, userId);
@@ -1328,18 +1329,15 @@ public class PackageInstallerSession extends IPackageInstallerSession.Stub {
        mSigningDetails = apk.signingDetails;
        mResolvedBaseFile = addedFile;

        assertApkConsistentLocked(String.valueOf(addedFile), apk);

        if (mSigningDetails == PackageParser.SigningDetails.UNKNOWN) {
            try {
                // STOPSHIP: For APEX we should also implement proper APK Signature verification.
                mSigningDetails = ApkSignatureVerifier.plsCertsNoVerifyOnlyCerts(
                    pkgInfo.applicationInfo.sourceDir,
                    PackageParser.SigningDetails.SignatureSchemeVersion.JAR);
            } catch (PackageParserException e) {
                throw new PackageManagerException(INSTALL_FAILED_INVALID_APK,
                    "Couldn't obtain signatures from base APK");
            }
        // STOPSHIP: Ensure that we remove the non-staged version of APEX installs in production
        // because we currently do not verify that signatures are consistent with the previously
        // installed version in that case.
        //
        // When that happens, this hack can be reverted and we can rely on APEXd to map between
        // APEX files and their package names instead of parsing it out of the AndroidManifest
        // such as here.
        if (params.appPackageName == null) {
            params.appPackageName = mPackageName;
        }
    }

@@ -2012,6 +2010,16 @@ public class PackageInstallerSession extends IPackageInstallerSession.Stub {
        }
    }

    /** {@hide} */
    void setStagedSessionFailed(@StagedSessionErrorCode int errorCode) {
        synchronized (mLock) {
            mStagedSessionReady = false;
            mStagedSessionApplied = false;
            mStagedSessionFailed = true;
            mStagedSessionErrorCode = errorCode;
        }
    }

    private void destroyInternal() {
        synchronized (mLock) {
            mSealed = true;
+72 −3
Original line number Diff line number Diff line
@@ -17,10 +17,23 @@
package com.android.server.pm;

import android.annotation.NonNull;
import android.apex.ApexInfo;
import android.apex.IApexService;
import android.content.pm.PackageInstaller;
import android.content.pm.PackageInstaller.SessionInfo;
import android.content.pm.PackageManager;
import android.content.pm.PackageParser.PackageParserException;
import android.content.pm.PackageParser.SigningDetails;
import android.content.pm.PackageParser.SigningDetails.SignatureSchemeVersion;
import android.content.pm.ParceledListSlice;
import android.content.pm.Signature;
import android.os.Handler;
import android.os.RemoteException;
import android.os.ServiceManager;
import android.text.TextUtils;
import android.util.Slog;
import android.util.SparseArray;
import android.util.apk.ApkSignatureVerifier;

import com.android.internal.annotations.GuardedBy;
import com.android.internal.os.BackgroundThread;
@@ -71,14 +84,70 @@ public class StagingManager {
        return new ParceledListSlice<>(result);
    }

    private static boolean validateApexSignatureLocked(String apexPath, String packageName) {
        final SigningDetails signingDetails;
        try {
            signingDetails = ApkSignatureVerifier.verify(apexPath, SignatureSchemeVersion.JAR);
        } catch (PackageParserException e) {
            Slog.e(TAG, "Unable to parse APEX package: " + apexPath, e);
            return false;
        }

        final IApexService apex = IApexService.Stub.asInterface(
                ServiceManager.getService("apexservice"));
        final ApexInfo apexInfo;
        try {
            apexInfo = apex.getActivePackage(packageName);
        } catch (RemoteException re) {
            Slog.e(TAG, "Unable to contact APEXD", re);
            return false;
        }

        if (apexInfo == null || TextUtils.isEmpty(apexInfo.packageName)) {
            // TODO: What is the right thing to do here ? This implies there's no active package
            // with the given name. This should never be the case in production (where we only
            // accept updates to existing APEXes) but may be required for testing.
            return true;
        }

        final SigningDetails existingSigningDetails;
        try {
            existingSigningDetails = ApkSignatureVerifier.verify(
                apexInfo.packagePath, SignatureSchemeVersion.JAR);
        } catch (PackageParserException e) {
            Slog.e(TAG, "Unable to parse APEX package: " + apexInfo.packagePath, e);
            return false;
        }

        // Now that we have both sets of signatures, demand that they're an exact match.
        if (Signature.areExactMatch(existingSigningDetails.signatures, signingDetails.signatures)) {
            return true;
        }

        return false;
    }

    void commitSession(@NonNull PackageInstallerSession sessionInfo) {
        updateStoredSession(sessionInfo);

        mBgHandler.post(() -> {
            // TODO(b/118865310): Dispatch the session to apexd/PackageManager for verification. For
            //                    now we directly mark it as ready.
            sessionInfo.setStagedSessionReady();
            mPm.sendSessionUpdatedBroadcast(sessionInfo.generateInfo(), sessionInfo.userId);

            SessionInfo session = sessionInfo.generateInfo(false);
            // For APEXes, we validate the signature here before we write the package to the
            // staging directory. For APKs, the signature verification will be done by the package
            // manager at the point at which it applies the staged install.
            //
            // TODO: Decide whether we want to fail fast by detecting signature mismatches right
            // away.
            if ((sessionInfo.params.installFlags & PackageManager.INSTALL_APEX) != 0) {
                if (!validateApexSignatureLocked(session.resolvedBaseCodePath,
                        session.appPackageName)) {
                    sessionInfo.setStagedSessionFailed(SessionInfo.VERIFICATION_FAILED);
                }
            }

            mPm.sendSessionUpdatedBroadcast(sessionInfo.generateInfo(false), sessionInfo.userId);
        });
    }