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

Commit 6e3677c0 authored by Song Pan's avatar Song Pan
Browse files

Implement methods in AppIntegrityManagerServiceImpl.

1. Pushing rules.
2. Getting version/rule provider.
3. Handle integrity verification request

Bug:143689885

Test: atest AppIntegrityManagerServiceImplTest
Change-Id: I0f2d554efbd3ba115eab10238908c9c2a3f2139c
parent d56dcd2f
Loading
Loading
Loading
Loading
+10 −0
Original line number Diff line number Diff line
@@ -50,6 +50,8 @@ import android.content.ContentCaptureOptions;
import android.content.Context;
import android.content.IRestrictionsManager;
import android.content.RestrictionsManager;
import android.content.integrity.AppIntegrityManager;
import android.content.integrity.IAppIntegrityManager;
import android.content.om.IOverlayManager;
import android.content.om.OverlayManager;
import android.content.pm.CrossProfileApps;
@@ -1246,6 +1248,14 @@ public final class SystemServiceRegistry {
                                IIncrementalManagerNative.Stub.asInterface(b));
                    }});
        //CHECKSTYLE:ON IndentationCheck
        registerService(Context.APP_INTEGRITY_SERVICE, AppIntegrityManager.class,
                new CachedServiceFetcher<AppIntegrityManager>() {
                    @Override
                    public AppIntegrityManager createService(ContextImpl ctx)
                            throws ServiceNotFoundException {
                        IBinder b = ServiceManager.getServiceOrThrow(Context.APP_INTEGRITY_SERVICE);
                        return new AppIntegrityManager(IAppIntegrityManager.Stub.asInterface(b));
                    }});

        sInitializing = true;
        try {
+13 −0
Original line number Diff line number Diff line
@@ -86,6 +86,19 @@ public final class AppInstallMetadata {
        return mIsPreInstalled;
    }

    @Override
    public String toString() {
        return String.format(
                "AppInstallMetadata { PackageName = %s, AppCert = %s, InstallerName = %s,"
                    + " InstallerCert = %s, VersionCode = %d, PreInstalled = %b }",
                mPackageName,
                mAppCertificate,
                mInstallerName == null ? "null" : mInstallerName,
                mInstallerCertificate == null ? "null" : mInstallerCertificate,
                mVersionCode,
                mIsPreInstalled);
    }

    /** Builder class for constructing {@link AppInstallMetadata} objects. */
    public static final class Builder {
        private String mPackageName;
+2 −2
Original line number Diff line number Diff line
@@ -37,7 +37,7 @@ public class AppIntegrityManagerService extends SystemService {

    @Override
    public void onStart() {
        mService = new AppIntegrityManagerServiceImpl(mContext);
        // TODO: define and publish a binder service.
        mService = AppIntegrityManagerServiceImpl.create(mContext);
        publishBinderService(Context.APP_INTEGRITY_SERVICE, mService);
    }
}
+431 −14
Original line number Diff line number Diff line
@@ -17,38 +17,98 @@
package com.android.server.integrity;

import static android.content.Intent.ACTION_PACKAGE_NEEDS_INTEGRITY_VERIFICATION;
import static android.content.Intent.EXTRA_ORIGINATING_UID;
import static android.content.Intent.EXTRA_PACKAGE_NAME;
import static android.content.Intent.EXTRA_VERSION_CODE;
import static android.content.integrity.AppIntegrityManager.EXTRA_STATUS;
import static android.content.integrity.AppIntegrityManager.STATUS_FAILURE;
import static android.content.integrity.AppIntegrityManager.STATUS_SUCCESS;
import static android.content.pm.PackageManager.EXTRA_VERIFICATION_ID;

import android.annotation.Nullable;
import android.content.BroadcastReceiver;
import android.content.Context;
import android.content.Intent;
import android.content.IntentFilter;
import android.content.IntentSender;
import android.content.integrity.AppInstallMetadata;
import android.content.integrity.IAppIntegrityManager;
import android.content.integrity.Rule;
import android.content.pm.PackageInfo;
import android.content.pm.PackageManager;
import android.content.pm.PackageManagerInternal;
import android.content.pm.ParceledListSlice;
import android.content.pm.Signature;
import android.net.Uri;
import android.os.Binder;
import android.os.Handler;
import android.os.HandlerThread;
import android.os.RemoteException;
import android.util.Slog;

import com.android.internal.R;
import com.android.internal.annotations.VisibleForTesting;
import com.android.server.LocalServices;
import com.android.server.integrity.engine.RuleEvaluationEngine;
import com.android.server.integrity.model.IntegrityCheckResult;
import com.android.server.integrity.model.RuleMetadata;

import java.io.ByteArrayInputStream;
import java.io.File;
import java.io.InputStream;
import java.nio.charset.StandardCharsets;
import java.security.MessageDigest;
import java.security.NoSuchAlgorithmException;
import java.security.cert.CertificateEncodingException;
import java.security.cert.CertificateException;
import java.security.cert.CertificateFactory;
import java.security.cert.X509Certificate;

/** Implementation of {@link AppIntegrityManagerService}. */
class AppIntegrityManagerServiceImpl {
public class AppIntegrityManagerServiceImpl extends IAppIntegrityManager.Stub {
    private static final String TAG = "AppIntegrityManagerServiceImpl";

    private static final String PACKAGE_MIME_TYPE = "application/vnd.android.package-archive";
    private static final char[] HEX_CHARS = "0123456789ABCDEF".toCharArray();
    private static final String PACKAGE_INSTALLER = "com.google.android.packageinstaller";
    private static final String BASE_APK_FILE = "base.apk";

    private static final String ADB_INSTALLER = "adb";
    private static final String UNKNOWN_INSTALLER = "";
    private static final String INSTALLER_CERT_NOT_APPLICABLE = "";

    // Access to files inside mRulesDir is protected by mRulesLock;
    private final Context mContext;
    private final Handler mHandler;
    private final PackageManagerInternal mPackageManagerInternal;
    private final RuleEvaluationEngine mEvaluationEngine;
    private final IntegrityFileManager mIntegrityFileManager;

    AppIntegrityManagerServiceImpl(Context context) {
        mContext = context;

    /** Create an instance of {@link AppIntegrityManagerServiceImpl}. */
    public static AppIntegrityManagerServiceImpl create(Context context) {
        HandlerThread handlerThread = new HandlerThread("AppIntegrityManagerServiceHandler");
        handlerThread.start();
        mHandler = handlerThread.getThreadHandler();

        mPackageManagerInternal = LocalServices.getService(PackageManagerInternal.class);
        return new AppIntegrityManagerServiceImpl(
                context,
                LocalServices.getService(PackageManagerInternal.class),
                RuleEvaluationEngine.getRuleEvaluationEngine(),
                IntegrityFileManager.getInstance(),
                handlerThread.getThreadHandler());
    }

    @VisibleForTesting
    AppIntegrityManagerServiceImpl(
            Context context,
            PackageManagerInternal packageManagerInternal,
            RuleEvaluationEngine evaluationEngine,
            IntegrityFileManager integrityFileManager,
            Handler handler) {
        mContext = context;
        mPackageManagerInternal = packageManagerInternal;
        mEvaluationEngine = evaluationEngine;
        mIntegrityFileManager = integrityFileManager;
        mHandler = handler;

        IntentFilter integrityVerificationFilter = new IntentFilter();
        integrityVerificationFilter.addAction(ACTION_PACKAGE_NEEDS_INTEGRITY_VERIFICATION);
@@ -74,14 +134,371 @@ class AppIntegrityManagerServiceImpl {
                mHandler);
    }

    // protected broadcasts cannot be sent in the test.
    @VisibleForTesting
    void handleIntegrityVerification(Intent intent) {
    @Override
    public void updateRuleSet(
            String version, ParceledListSlice<Rule> rules, IntentSender statusReceiver)
            throws RemoteException {
        String ruleProvider = getCallerPackageNameOrThrow();

        mHandler.post(
                () -> {
                    boolean success = true;
                    try {
                        mIntegrityFileManager.writeRules(version, ruleProvider, rules.getList());
                    } catch (Exception e) {
                        Slog.e(TAG, "Error writing rules.", e);
                        success = false;
                    }

                    Intent intent = new Intent();
                    intent.putExtra(EXTRA_STATUS, success ? STATUS_SUCCESS : STATUS_FAILURE);
                    try {
                        statusReceiver.sendIntent(
                                mContext,
                                /* code= */ 0,
                                intent,
                                /* onFinished= */ null,
                                /* handler= */ null);
                    } catch (IntentSender.SendIntentException e) {
                        Slog.e(TAG, "Error sending status feedback.", e);
                    }
                });
    }

    @Override
    public String getCurrentRuleSetVersion() throws RemoteException {
        getCallerPackageNameOrThrow();

        RuleMetadata ruleMetadata = mIntegrityFileManager.readMetadata();
        return (ruleMetadata != null && ruleMetadata.getVersion() != null)
                ? ruleMetadata.getVersion()
                : "";
    }

    @Override
    public String getCurrentRuleSetProvider() throws RemoteException {
        getCallerPackageNameOrThrow();

        RuleMetadata ruleMetadata = mIntegrityFileManager.readMetadata();
        return (ruleMetadata != null && ruleMetadata.getRuleProvider() != null)
                ? ruleMetadata.getRuleProvider()
                : "";
    }

    private void handleIntegrityVerification(Intent intent) {
        int verificationId = intent.getIntExtra(EXTRA_VERIFICATION_ID, -1);
        // TODO: implement this method.
        try {
            Slog.i(TAG, "Received integrity verification intent " + intent.toString());
            Slog.i(TAG, "Extras " + intent.getExtras());

            AppInstallMetadata.Builder builder = new AppInstallMetadata.Builder();

            String packageName = intent.getStringExtra(EXTRA_PACKAGE_NAME);
            String installerPackageName = getInstallerPackageName(intent);
            String appCert = getAppCertificateFingerprint(intent.getData());

            builder.setPackageName(getPackageNameNormalized(packageName));
            builder.setAppCertificate(appCert == null ? "" : appCert);
            builder.setVersionCode(intent.getIntExtra(EXTRA_VERSION_CODE, -1));
            builder.setInstallerName(getPackageNameNormalized(installerPackageName));
            builder.setInstallerCertificate(
                    getInstallerCertificateFingerprint(installerPackageName));
            builder.setIsPreInstalled(isSystemApp(packageName));

            AppInstallMetadata appInstallMetadata = builder.build();

            Slog.i(TAG, "To be verified: " + appInstallMetadata);
            IntegrityCheckResult result = mEvaluationEngine.evaluate(appInstallMetadata);
            Slog.i(
                    TAG,
                    "Integrity check result: "
                            + result.getEffect()
                            + " due to "
                            + result.getRule());
            mPackageManagerInternal.setIntegrityVerificationResult(
                    verificationId,
                    result.getEffect() == IntegrityCheckResult.Effect.ALLOW
                            ? PackageManagerInternal.INTEGRITY_VERIFICATION_ALLOW
                            : PackageManagerInternal.INTEGRITY_VERIFICATION_REJECT);
        } catch (IllegalArgumentException e) {
            // This exception indicates something is wrong with the input passed by package manager.
            // e.g., someone trying to trick the system. We block installs in this case.
            Slog.e(TAG, "Invalid input to integrity verification", e);

            mPackageManagerInternal.setIntegrityVerificationResult(
                    verificationId, PackageManagerInternal.INTEGRITY_VERIFICATION_REJECT);
        } catch (Exception e) {
            // Other exceptions indicate an error within the integrity component implementation and
            // we allow them.
            Slog.e(TAG, "Error handling integrity verification", e);

            mPackageManagerInternal.setIntegrityVerificationResult(
                    verificationId, PackageManagerInternal.INTEGRITY_VERIFICATION_ALLOW);
        }
    }

    /**
     * Verify the UID and return the installer package name.
     *
     * @return the package name of the installer, or null if it cannot be determined or it is
     *     installed via adb.
     */
    @Nullable
    private String getInstallerPackageName(Intent intent) {
        String installer =
                intent.getStringExtra(PackageManager.EXTRA_VERIFICATION_INSTALLER_PACKAGE);
        if (installer == null) {
            return ADB_INSTALLER;
        }
        int installerUid = intent.getIntExtra(PackageManager.EXTRA_VERIFICATION_INSTALLER_UID, -1);
        if (installerUid < 0) {
            Slog.e(
                    TAG,
                    "Installer cannot be determined: installer: "
                            + installer
                            + " installer UID: "
                            + installerUid);
            return UNKNOWN_INSTALLER;
        }

        try {
            int actualInstallerUid =
                    mContext.getPackageManager().getPackageUid(installer, /* flags= */ 0);
            if (actualInstallerUid != installerUid) {
                // Installer package name can be faked but the installerUid cannot.
                Slog.e(
                        TAG,
                        "Installer "
                                + installer
                                + " has UID "
                                + actualInstallerUid
                                + " which doesn't match alleged installer UID "
                                + installerUid);
                return UNKNOWN_INSTALLER;
            }
        } catch (PackageManager.NameNotFoundException e) {
            Slog.e(TAG, "Installer package " + installer + " not found.");
            return UNKNOWN_INSTALLER;
        }

        // At this time we can trust "installer".

        // A common way for apps to install packages is to send an intent to PackageInstaller. In
        // that case, the installer will always show up as PackageInstaller which is not what we
        // want.
        if (installer.equals(PACKAGE_INSTALLER)) {
            int originatingUid = intent.getIntExtra(EXTRA_ORIGINATING_UID, -1);
            if (originatingUid < 0) {
                Slog.e(TAG, "Installer is package installer but originating UID not found.");
                return UNKNOWN_INSTALLER;
            }
            String[] installerPackages =
                    mContext.getPackageManager().getPackagesForUid(originatingUid);
            if (installerPackages == null || installerPackages.length == 0) {
                Slog.e(TAG, "No package found associated with originating UID " + originatingUid);
                return UNKNOWN_INSTALLER;
            }
            // In the case of multiple package sharing a UID, we just return the first one.
            return installerPackages[0];
        }

        return installer;
    }

    /** We will use the SHA256 digest of a package name if it is more than 32 bytes long. */
    private String getPackageNameNormalized(String packageName) {
        if (packageName.length() <= 32) {
            return packageName;
        }

        try {
            MessageDigest messageDigest = MessageDigest.getInstance("SHA-256");
            byte[] hashBytes = messageDigest.digest(packageName.getBytes(StandardCharsets.UTF_8));
            return toHexString(hashBytes);
        } catch (NoSuchAlgorithmException e) {
            throw new RuntimeException("SHA-256 algorithm not found", e);
        }
    }

    private String getAppCertificateFingerprint(Uri dataUri) {
        PackageInfo packageInfo = getPackageArchiveInfo(dataUri);
        return getFingerprint(getSignature(packageInfo));
    }

    private String getInstallerCertificateFingerprint(String installer) {
        if (installer.equals(ADB_INSTALLER) || installer.equals(UNKNOWN_INSTALLER)) {
            return INSTALLER_CERT_NOT_APPLICABLE;
        }
        try {
            PackageInfo installerInfo =
                    mContext.getPackageManager()
                            .getPackageInfo(installer, PackageManager.GET_SIGNATURES);
            return getFingerprint(getSignature(installerInfo));
        } catch (PackageManager.NameNotFoundException e) {
            Slog.i(TAG, "Installer package " + installer + " not found.");
            return "";
        }
    }

    private static Signature getSignature(PackageInfo packageInfo) {
        if (packageInfo.signatures == null || packageInfo.signatures.length < 1) {
            throw new IllegalArgumentException("Package signature not found in " + packageInfo);
        }
        // Only the first element is guaranteed to be present.
        return packageInfo.signatures[0];
    }

    private static String getFingerprint(Signature cert) {
        InputStream input = new ByteArrayInputStream(cert.toByteArray());

        CertificateFactory factory;
        try {
            factory = CertificateFactory.getInstance("X509");
        } catch (CertificateException e) {
            throw new RuntimeException("Error getting CertificateFactory", e);
        }
        X509Certificate certificate = null;
        try {
            if (factory != null) {
                certificate = (X509Certificate) factory.generateCertificate(input);
            }
        } catch (CertificateException e) {
            throw new RuntimeException("Error getting X509Certificate", e);
        }

        if (certificate == null) {
            throw new RuntimeException("X509 Certificate not found");
        }

        try {
            MessageDigest digest = MessageDigest.getInstance("SHA-256");
            byte[] publicKey = digest.digest(certificate.getEncoded());
            return toHexString(publicKey);
        } catch (NoSuchAlgorithmException | CertificateEncodingException e) {
            throw new IllegalArgumentException("Error error computing fingerprint", e);
        }
    }

    private static String toHexString(byte[] bytes) {
        // each byte is represented by two hex chars
        StringBuffer hexString = new StringBuffer(bytes.length * 2);
        for (int i = 0; i < bytes.length; i++) {
            hexString.append(String.format("%02X", bytes[i]));
        }
        return new String(hexString);
    }

    private PackageInfo getPackageArchiveInfo(Uri dataUri) {
        File installationPath = getInstallationPath(dataUri);
        if (installationPath == null) {
            throw new IllegalArgumentException("Installation path is null, package not found");
        }
        PackageInfo packageInfo;
        try {
            // The installation path will be a directory for a multi-apk install on L+
            if (installationPath.isDirectory()) {
                packageInfo = getMultiApkInfo(installationPath);
            } else {
                packageInfo =
                        mContext.getPackageManager()
                                .getPackageArchiveInfo(
                                        installationPath.getPath(), PackageManager.GET_SIGNATURES);
            }
            return packageInfo;
        } catch (Exception e) {
            throw new IllegalArgumentException("Exception reading " + dataUri, e);
        }
    }

    private PackageInfo getMultiApkInfo(File multiApkDirectory) {
        // The base apk will normally be called base.apk
        File baseFile = new File(multiApkDirectory, BASE_APK_FILE);
        PackageInfo basePackageInfo =
                mContext.getPackageManager()
                        .getPackageArchiveInfo(
                                baseFile.getAbsolutePath(), PackageManager.GET_SIGNATURES);

        if (basePackageInfo == null) {
            for (File apkFile : multiApkDirectory.listFiles()) {
                if (apkFile.isDirectory()) {
                    continue;
                }

                // If we didn't find a base.apk, then try to parse each apk until we find the one
                // that succeeds.
                basePackageInfo =
                        mContext.getPackageManager()
                                .getPackageArchiveInfo(
                                        apkFile.getAbsolutePath(),
                                        PackageManager.GET_SIGNING_CERTIFICATES);
                if (basePackageInfo != null) {
                    Slog.i(TAG, "Found package info from " + apkFile);
                    break;
                }
            }
        }

        if (basePackageInfo == null) {
            throw new IllegalArgumentException(
                    "Base package info cannot be found from installation directory");
        }

        return basePackageInfo;
    }

    private File getInstallationPath(Uri dataUri) {
        if (dataUri == null) {
            throw new IllegalArgumentException("Null data uri");
        }

        String scheme = dataUri.getScheme();
        if (!"file".equalsIgnoreCase(scheme)) {
            throw new IllegalArgumentException("Unsupported scheme for " + dataUri);
        }

        File installationPath = new File(dataUri.getPath());
        if (!installationPath.exists()) {
            throw new IllegalArgumentException("Cannot find file for " + dataUri);
        }
        if (!installationPath.canRead()) {
            throw new IllegalArgumentException("Cannot read file for " + dataUri);
        }
        return installationPath;
    }

    private String getCallerPackageNameOrThrow() {
        final String[] allowedRuleProviders =
                mContext.getResources()
                        .getStringArray(R.array.config_integrityRuleProviderPackages);
        for (String packageName : allowedRuleProviders) {
            try {
                // At least in tests, getPackageUid gives "NameNotFound" but getPackagesFromUid
                // give the correct package name.
                int uid = mContext.getPackageManager().getPackageUid(packageName, 0);
                if (uid == Binder.getCallingUid()) {
                    // Caller is allowed in the config.
                    if (isSystemApp(packageName)) {
                        return packageName;
                    }
                }
            } catch (PackageManager.NameNotFoundException e) {
                // Ignore the exception. We don't expect the app to be necessarily installed.
                Slog.i(TAG, "Rule provider package " + packageName + " not installed.");
            }
        }
        throw new SecurityException(
                "Only system packages specified in config_integrityRuleProviderPackages are"
                        + " allowed to call this method.");
    }

    private boolean isSystemApp(String packageName) {
        try {
            PackageInfo existingPackageInfo =
                    mContext.getPackageManager().getPackageInfo(packageName, /* flags= */ 0);
            return existingPackageInfo.applicationInfo != null
                    && existingPackageInfo.applicationInfo.isSystemApp();
        } catch (PackageManager.NameNotFoundException e) {
            return false;
        }
    }
}
Loading