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

Commit e24b80aa authored by Song Pan's avatar Song Pan
Browse files

Implement allowed installers in manfiest rules.

Bug:145465546
Test: atest RuleEvaluationEngineTest
Change-Id: Ia6a13cb3a4da15c79fd91b56204c1fb446a76635
parent 8413638d
Loading
Loading
Loading
Loading
+10 −18
Original line number Diff line number Diff line
@@ -225,11 +225,13 @@ public class AppIntegrityManagerServiceImpl extends IAppIntegrityManager.Stub {
            builder.setIsPreInstalled(isSystemApp(packageName));

            AppInstallMetadata appInstallMetadata = builder.build();
            Map<String, String> allowedInstallers = getAllowedInstallers(packageInfo);

            Slog.i(TAG, "To be verified: " + appInstallMetadata);
            Slog.i(
                    TAG,
                    "To be verified: " + appInstallMetadata + " installers " + allowedInstallers);
            IntegrityCheckResult result =
                    mEvaluationEngine.evaluate(
                            appInstallMetadata, getAllowedInstallers(packageInfo));
                    mEvaluationEngine.evaluate(appInstallMetadata, allowedInstallers);
            Slog.i(
                    TAG,
                    "Integrity check result: "
@@ -371,7 +373,7 @@ public class AppIntegrityManagerServiceImpl extends IAppIntegrityManager.Stub {
                    String[] packageAndCert =
                            packageCertPair.split(INSTALLER_PACKAGE_CERT_DELIMITER);
                    if (packageAndCert.length == 2) {
                        String packageName = packageAndCert[0];
                        String packageName = getPackageNameNormalized(packageAndCert[0]);
                        String cert = packageAndCert[1];
                        packageCertMap.put(packageName, cert);
                    }
@@ -379,21 +381,9 @@ public class AppIntegrityManagerServiceImpl extends IAppIntegrityManager.Stub {
            }
        }

        Slog.i("DEBUG", "allowed installers map " + packageCertMap);
        return packageCertMap;
    }

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

    private static Signature getSignature(@NonNull PackageInfo packageInfo) {
        if (packageInfo.signatures == null || packageInfo.signatures.length < 1) {
            throw new IllegalArgumentException("Package signature not found in " + packageInfo);
@@ -463,7 +453,8 @@ public class AppIntegrityManagerServiceImpl extends IAppIntegrityManager.Stub {
        PackageInfo basePackageInfo =
                mContext.getPackageManager()
                        .getPackageArchiveInfo(
                                baseFile.getAbsolutePath(), PackageManager.GET_SIGNATURES);
                                baseFile.getAbsolutePath(),
                                PackageManager.GET_SIGNATURES | PackageManager.GET_META_DATA);

        if (basePackageInfo == null) {
            for (File apkFile : multiApkDirectory.listFiles()) {
@@ -477,7 +468,8 @@ public class AppIntegrityManagerServiceImpl extends IAppIntegrityManager.Stub {
                        mContext.getPackageManager()
                                .getPackageArchiveInfo(
                                        apkFile.getAbsolutePath(),
                                        PackageManager.GET_SIGNING_CERTIFICATES);
                                        PackageManager.GET_SIGNING_CERTIFICATES
                                                | PackageManager.GET_META_DATA);
                if (basePackageInfo != null) {
                    Slog.i(TAG, "Found package info from " + apkFile);
                    break;
+4 −4
Original line number Diff line number Diff line
@@ -24,14 +24,14 @@ import android.util.Slog;

import com.android.internal.annotations.VisibleForTesting;
import com.android.server.integrity.model.RuleMetadata;
import com.android.server.integrity.parser.RuleBinaryParser;
import com.android.server.integrity.parser.RuleMetadataParser;
import com.android.server.integrity.parser.RuleParseException;
import com.android.server.integrity.parser.RuleParser;
import com.android.server.integrity.serializer.RuleBinarySerializer;
import com.android.server.integrity.parser.RuleXmlParser;
import com.android.server.integrity.serializer.RuleMetadataSerializer;
import com.android.server.integrity.serializer.RuleSerializeException;
import com.android.server.integrity.serializer.RuleSerializer;
import com.android.server.integrity.serializer.RuleXmlSerializer;

import java.io.File;
import java.io.FileInputStream;
@@ -75,8 +75,8 @@ public class IntegrityFileManager {

    private IntegrityFileManager() {
        this(
                new RuleBinaryParser(),
                new RuleBinarySerializer(),
                new RuleXmlParser(),
                new RuleXmlSerializer(),
                Environment.getDataSystemDirectory());
    }

+47 −1
Original line number Diff line number Diff line
@@ -17,15 +17,21 @@
package com.android.server.integrity.engine;

import android.content.integrity.AppInstallMetadata;
import android.content.integrity.AtomicFormula;
import android.content.integrity.CompoundFormula;
import android.content.integrity.Formula;
import android.content.integrity.Rule;
import android.util.Slog;

import com.android.internal.annotations.VisibleForTesting;
import com.android.server.integrity.IntegrityFileManager;
import com.android.server.integrity.model.IntegrityCheckResult;

import java.util.ArrayList;
import java.util.Arrays;
import java.util.List;
import java.util.Map;
import java.util.Optional;

/**
 * The engine used to evaluate rules against app installs.
@@ -42,7 +48,8 @@ public class RuleEvaluationEngine {

    private final IntegrityFileManager mIntegrityFileManager;

    private RuleEvaluationEngine(IntegrityFileManager integrityFileManager) {
    @VisibleForTesting
    RuleEvaluationEngine(IntegrityFileManager integrityFileManager) {
        mIntegrityFileManager = integrityFileManager;
    }

@@ -64,6 +71,7 @@ public class RuleEvaluationEngine {
    public IntegrityCheckResult evaluate(
            AppInstallMetadata appInstallMetadata, Map<String, String> allowedInstallers) {
        List<Rule> rules = loadRules(appInstallMetadata);
        allowedInstallersRule(allowedInstallers).ifPresent(rules::add);
        return RuleEvaluator.evaluateRules(rules, appInstallMetadata);
    }

@@ -75,4 +83,42 @@ public class RuleEvaluationEngine {
            return new ArrayList<>();
        }
    }

    private static Optional<Rule> allowedInstallersRule(Map<String, String> allowedInstallers) {
        if (allowedInstallers.isEmpty()) {
            return Optional.empty();
        }

        List<Formula> formulas = new ArrayList<>(allowedInstallers.size());
        allowedInstallers.forEach(
                (installer, cert) -> {
                    formulas.add(allowedInstallerFormula(installer, cert));
                });

        // We need this special case since OR-formulas require at least two operands.
        Formula allInstallersFormula =
                formulas.size() == 1
                        ? formulas.get(0)
                        : new CompoundFormula(CompoundFormula.OR, formulas);

        return Optional.of(
                new Rule(
                        new CompoundFormula(
                                CompoundFormula.NOT, Arrays.asList(allInstallersFormula)),
                        Rule.DENY));
    }

    private static Formula allowedInstallerFormula(String installer, String cert) {
        return new CompoundFormula(
                CompoundFormula.AND,
                Arrays.asList(
                        new AtomicFormula.StringAtomicFormula(
                                AtomicFormula.INSTALLER_NAME,
                                installer,
                                /* isHashedValue= */ false),
                        new AtomicFormula.StringAtomicFormula(
                                AtomicFormula.INSTALLER_CERTIFICATE,
                                cert,
                                /* isHashedValue= */ false)));
    }
}
+1 −26
Original line number Diff line number Diff line
@@ -21,9 +21,6 @@ import static android.content.integrity.Rule.FORCE_ALLOW;

import android.annotation.NonNull;
import android.content.integrity.AppInstallMetadata;
import android.content.integrity.AtomicFormula;
import android.content.integrity.CompoundFormula;
import android.content.integrity.Formula;
import android.content.integrity.Rule;
import android.util.Slog;

@@ -56,8 +53,7 @@ final class RuleEvaluator {
            List<Rule> rules, AppInstallMetadata appInstallMetadata) {
        List<Rule> matchedRules = new ArrayList<>();
        for (Rule rule : rules) {
            if (isConjunctionOfFormulas(rule.getFormula())
                    && rule.getFormula().isSatisfied(appInstallMetadata)) {
            if (rule.getFormula().isSatisfied(appInstallMetadata)) {
                matchedRules.add(rule);
            }
        }
@@ -81,25 +77,4 @@ final class RuleEvaluator {
        }
        return denied ? IntegrityCheckResult.deny(denyRule) : IntegrityCheckResult.allow();
    }

    private static boolean isConjunctionOfFormulas(Formula formula) {
        if (formula == null) {
            return false;
        }
        if (isAtomicFormula(formula)) {
            return true;
        }
        CompoundFormula compoundFormula = (CompoundFormula) formula;
        return compoundFormula.getConnector() == CompoundFormula.AND
                && compoundFormula.getFormulas().stream().allMatch(RuleEvaluator::isAtomicFormula);
    }

    private static boolean isAtomicFormula(Formula formula) {
        if (formula instanceof AtomicFormula) {
            return true;
        }
        CompoundFormula compoundFormula = (CompoundFormula) formula;
        return compoundFormula.getConnector() == CompoundFormula.NOT
                && compoundFormula.getFormulas().get(0) instanceof AtomicFormula;
    }
}
+197 −0
Original line number Diff line number Diff line
/*
 * Copyright (C) 2019 The Android Open Source Project
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *      http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */

package com.android.server.integrity.engine;

import static org.junit.Assert.assertEquals;
import static org.mockito.ArgumentMatchers.any;
import static org.mockito.Mockito.when;

import android.content.integrity.AppInstallMetadata;
import android.content.integrity.Rule;

import com.android.server.integrity.IntegrityFileManager;
import com.android.server.integrity.model.IntegrityCheckResult;

import org.junit.Before;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.junit.runners.JUnit4;
import org.mockito.Mock;
import org.mockito.MockitoAnnotations;

import java.util.ArrayList;
import java.util.Collections;
import java.util.HashMap;
import java.util.List;
import java.util.Map;

@RunWith(JUnit4.class)
public class RuleEvaluationEngineTest {

    private static final String INSTALLER_1 = "installer1";
    private static final String INSTALLER_1_CERT = "installer1_cert";
    private static final String INSTALLER_2 = "installer2";
    private static final String INSTALLER_2_CERT = "installer2_cert";

    private static final String RANDOM_INSTALLER = "random";
    private static final String RANDOM_INSTALLER_CERT = "random_cert";

    @Mock private IntegrityFileManager mIntegrityFileManager;

    private RuleEvaluationEngine mEngine;

    @Before
    public void setUp() throws Exception {
        MockitoAnnotations.initMocks(this);

        mEngine = new RuleEvaluationEngine(mIntegrityFileManager);

        when(mIntegrityFileManager.readRules(any())).thenReturn(new ArrayList<>());
    }

    @Test
    public void testAllowedInstallers_empty() {
        Map<String, String> allowedInstallers = Collections.emptyMap();

        assertEquals(
                IntegrityCheckResult.Effect.ALLOW,
                mEngine.evaluate(
                                getAppInstallMetadataBuilder()
                                        .setInstallerName(INSTALLER_1)
                                        .setInstallerCertificate(INSTALLER_1_CERT)
                                        .build(),
                                allowedInstallers)
                        .getEffect());
        assertEquals(
                IntegrityCheckResult.Effect.ALLOW,
                mEngine.evaluate(
                                getAppInstallMetadataBuilder()
                                        .setInstallerName(INSTALLER_2)
                                        .setInstallerCertificate(INSTALLER_2_CERT)
                                        .build(),
                                allowedInstallers)
                        .getEffect());
        assertEquals(
                IntegrityCheckResult.Effect.ALLOW,
                mEngine.evaluate(
                                getAppInstallMetadataBuilder()
                                        .setInstallerName(RANDOM_INSTALLER)
                                        .setInstallerCertificate(RANDOM_INSTALLER_CERT)
                                        .build(),
                                allowedInstallers)
                        .getEffect());
    }

    @Test
    public void testAllowedInstallers_oneElement() {
        Map<String, String> allowedInstallers =
                Collections.singletonMap(INSTALLER_1, INSTALLER_1_CERT);

        assertEquals(
                IntegrityCheckResult.Effect.ALLOW,
                mEngine.evaluate(
                                getAppInstallMetadataBuilder()
                                        .setInstallerName(INSTALLER_1)
                                        .setInstallerCertificate(INSTALLER_1_CERT)
                                        .build(),
                                allowedInstallers)
                        .getEffect());
        assertEquals(
                IntegrityCheckResult.Effect.DENY,
                mEngine.evaluate(
                                getAppInstallMetadataBuilder()
                                        .setInstallerName(RANDOM_INSTALLER)
                                        .setInstallerCertificate(INSTALLER_1_CERT)
                                        .build(),
                                allowedInstallers)
                        .getEffect());
        assertEquals(
                IntegrityCheckResult.Effect.DENY,
                mEngine.evaluate(
                                getAppInstallMetadataBuilder()
                                        .setInstallerName(INSTALLER_1)
                                        .setInstallerCertificate(RANDOM_INSTALLER_CERT)
                                        .build(),
                                allowedInstallers)
                        .getEffect());
        assertEquals(
                IntegrityCheckResult.Effect.DENY,
                mEngine.evaluate(
                                getAppInstallMetadataBuilder()
                                        .setInstallerName(RANDOM_INSTALLER)
                                        .setInstallerCertificate(RANDOM_INSTALLER_CERT)
                                        .build(),
                                allowedInstallers)
                        .getEffect());
    }

    @Test
    public void testAllowedInstallers_multipleElement() {
        List<Rule> rules = new ArrayList<>();
        Map<String, String> allowedInstallers = new HashMap<>(2);
        allowedInstallers.put(INSTALLER_1, INSTALLER_1_CERT);
        allowedInstallers.put(INSTALLER_2, INSTALLER_2_CERT);

        assertEquals(
                IntegrityCheckResult.Effect.ALLOW,
                mEngine.evaluate(
                                getAppInstallMetadataBuilder()
                                        .setInstallerName(INSTALLER_1)
                                        .setInstallerCertificate(INSTALLER_1_CERT)
                                        .build(),
                                allowedInstallers)
                        .getEffect());
        assertEquals(
                IntegrityCheckResult.Effect.ALLOW,
                mEngine.evaluate(
                                getAppInstallMetadataBuilder()
                                        .setInstallerName(INSTALLER_2)
                                        .setInstallerCertificate(INSTALLER_2_CERT)
                                        .build(),
                                allowedInstallers)
                        .getEffect());
        assertEquals(
                IntegrityCheckResult.Effect.DENY,
                mEngine.evaluate(
                                getAppInstallMetadataBuilder()
                                        .setInstallerName(INSTALLER_1)
                                        .setInstallerCertificate(INSTALLER_2_CERT)
                                        .build(),
                                allowedInstallers)
                        .getEffect());
        assertEquals(
                IntegrityCheckResult.Effect.DENY,
                mEngine.evaluate(
                                getAppInstallMetadataBuilder()
                                        .setInstallerName(INSTALLER_2)
                                        .setInstallerCertificate(INSTALLER_1_CERT)
                                        .build(),
                                allowedInstallers)
                        .getEffect());
    }

    /** Returns a builder with all fields filled with some dummy data. */
    private AppInstallMetadata.Builder getAppInstallMetadataBuilder() {
        return new AppInstallMetadata.Builder()
                .setPackageName("abc")
                .setAppCertificate("abc")
                .setInstallerCertificate("abc")
                .setInstallerName("abc")
                .setVersionCode(-1)
                .setIsPreInstalled(true);
    }
}