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

Commit 097f65d6 authored by Song Pan's avatar Song Pan
Browse files

Prepare rule / formula classes to be ready for external API.

Since changes to external API requires extra review, we want to make the CL as
small as possible. So we make the changes in the internal directory first and
move them in one go. The class content is copied from http://ag/9652137.

Test: unit test.
Bug: 143689885
Change-Id: I00d4a782e2247b974c90a1cfe7ea151212e7b90d
parent 90fa6de9
Loading
Loading
Loading
Loading
+2 −16
Original line number Diff line number Diff line
@@ -16,8 +16,6 @@

package com.android.server.integrity.engine;

import android.util.Slog;

import com.android.server.integrity.model.AppInstallMetadata;
import com.android.server.integrity.model.IntegrityCheckResult;
import com.android.server.integrity.model.Rule;
@@ -53,23 +51,11 @@ public final class RuleEvaluationEngine {
     *
     * @param appInstallMetadata Metadata of the app to be installed, and to evaluate the rules
     *                           against.
     * @return A rule matching the metadata. If there are multiple matching rules, returns any. If
     * no rules are matching, returns {@link Rule#EMPTY}.
     * @return result of the integrity check
     */
    public IntegrityCheckResult evaluate(AppInstallMetadata appInstallMetadata) {
        List<Rule> rules = loadRules(appInstallMetadata);
        Rule matchedRule = RuleEvaluator.evaluateRules(rules, appInstallMetadata);
        if (matchedRule == Rule.EMPTY) {
            return IntegrityCheckResult.allow();
        } else {
            switch (matchedRule.getEffect()) {
                case DENY:
                    return IntegrityCheckResult.deny(matchedRule);
                default:
                    Slog.e(TAG, "Matched a non-DENY rule: " + matchedRule);
                    return IntegrityCheckResult.allow();
            }
        }
        return RuleEvaluator.evaluateRules(rules, appInstallMetadata);
    }

    private List<Rule> loadRules(AppInstallMetadata appInstallMetadata) {
+33 −54
Original line number Diff line number Diff line
@@ -16,14 +16,20 @@

package com.android.server.integrity.engine;

import static com.android.server.integrity.model.Rule.DENY;
import static com.android.server.integrity.model.Rule.FORCE_ALLOW;

import android.annotation.NonNull;
import android.util.Slog;

import com.android.server.integrity.model.AppInstallMetadata;
import com.android.server.integrity.model.AtomicFormula;
import com.android.server.integrity.model.Formula;
import com.android.server.integrity.model.IntegrityCheckResult;
import com.android.server.integrity.model.OpenFormula;
import com.android.server.integrity.model.Rule;

import java.util.ArrayList;
import java.util.List;

/**
@@ -43,64 +49,37 @@ final class RuleEvaluator {
     * @param rules The list of rules to evaluate.
     * @param appInstallMetadata Metadata of the app to be installed, and to evaluate the rules
     *     against.
     * @return A rule matching the metadata. If there are multiple matching rules, returns any. If
     * no rules are matching, returns {@link Rule#EMPTY}.
     * @return result of the integrity check
     */
    static Rule evaluateRules(List<Rule> rules, AppInstallMetadata appInstallMetadata) {
    @NonNull
    static IntegrityCheckResult evaluateRules(
            List<Rule> rules, AppInstallMetadata appInstallMetadata) {
        List<Rule> matchedRules = new ArrayList<>();
        for (Rule rule : rules) {
            if (isConjunctionOfFormulas(rule.getFormula()) && isMatch(rule, appInstallMetadata)) {
                return rule;
            }
            if (isConjunctionOfFormulas(rule.getFormula())
                    && rule.getFormula().isSatisfied(appInstallMetadata)) {
                matchedRules.add(rule);
            }
        return Rule.EMPTY;
        }

    /**
     * Match a rule against app install metadata.
     */
    private static boolean isMatch(Rule rule, AppInstallMetadata appInstallMetadata) {
        return isMatch(rule.getFormula(), appInstallMetadata);
        boolean denied = false;
        Rule denyRule = null;
        for (Rule rule : matchedRules) {
            switch (rule.getEffect()) {
                case DENY:
                    if (!denied) {
                        denied = true;
                        denyRule = rule;
                    }

    private static boolean isMatch(Formula formula, AppInstallMetadata appInstallMetadata) {
        if (formula instanceof AtomicFormula) {
            AtomicFormula atomicFormula = (AtomicFormula) formula;
            switch (atomicFormula.getKey()) {
                case PACKAGE_NAME:
                    return atomicFormula.isMatch(appInstallMetadata.getPackageName());
                case APP_CERTIFICATE:
                    return atomicFormula.isMatch(appInstallMetadata.getAppCertificate());
                case INSTALLER_NAME:
                    return atomicFormula.isMatch(appInstallMetadata.getInstallerName());
                case INSTALLER_CERTIFICATE:
                    return atomicFormula.isMatch(appInstallMetadata.getInstallerCertificate());
                case VERSION_CODE:
                    return atomicFormula.isMatch(appInstallMetadata.getVersionCode());
                case PRE_INSTALLED:
                    return atomicFormula.isMatch(appInstallMetadata.isPreInstalled());
                    break;
                case FORCE_ALLOW:
                    return IntegrityCheckResult.allow(rule);
                default:
                    Slog.i(TAG, String.format("Returned no match for unknown key %s",
                            atomicFormula.getKey()));
                    return false;
                    Slog.e(TAG, "Matched an unknown effect rule: " + rule);
                    return IntegrityCheckResult.allow();
            }
        } else if (formula instanceof OpenFormula) {
            OpenFormula openFormula = (OpenFormula) formula;
            // A rule is in disjunctive normal form, so there are no OR connectors.
            switch (openFormula.getConnector()) {
                case NOT:
                    // NOT connector has only 1 formula attached.
                    return !isMatch(openFormula.getFormulas().get(0), appInstallMetadata);
                case AND:
                    return openFormula.getFormulas().stream().allMatch(
                            subFormula -> isMatch(subFormula, appInstallMetadata));
                default:
                    Slog.i(TAG, String.format("Returned no match for unknown connector %s",
                            openFormula.getConnector()));
                    return false;
        }
        }

        return false;
        return denied ? IntegrityCheckResult.deny(denyRule) : IntegrityCheckResult.allow();
    }

    private static boolean isConjunctionOfFormulas(Formula formula) {
@@ -111,7 +90,7 @@ final class RuleEvaluator {
            return true;
        }
        OpenFormula openFormula = (OpenFormula) formula;
        return openFormula.getConnector() == OpenFormula.Connector.AND
        return openFormula.getConnector() == OpenFormula.AND
                && openFormula.getFormulas().stream().allMatch(RuleEvaluator::isAtomicFormula);
    }

@@ -120,7 +99,7 @@ final class RuleEvaluator {
            return true;
        }
        OpenFormula openFormula = (OpenFormula) formula;
        return openFormula.getConnector() == OpenFormula.Connector.NOT
        return openFormula.getConnector() == OpenFormula.NOT
                && openFormula.getFormulas().get(0) instanceof AtomicFormula;
    }
}
+26 −13
Original line number Diff line number Diff line
@@ -19,7 +19,11 @@ package com.android.server.integrity.model;
import static com.android.internal.util.Preconditions.checkArgument;
import static com.android.internal.util.Preconditions.checkNotNull;

import android.annotation.NonNull;
import android.annotation.Nullable;
import android.annotation.SystemApi;

import com.android.internal.annotations.VisibleForTesting;

/**
 * The app install metadata.
@@ -28,7 +32,11 @@ import android.annotation.Nullable;
 * to the rule evaluation engine to evaluate the metadata against the rules.
 *
 * <p>Instances of this class are immutable.
 *
 * @hide
 */
@SystemApi
@VisibleForTesting
public final class AppInstallMetadata {
    private final String mPackageName;
    // Raw string encoding for the SHA-256 hash of the certificate of the app.
@@ -48,10 +56,12 @@ public final class AppInstallMetadata {
        this.mIsPreInstalled = builder.mIsPreInstalled;
    }

    @NonNull
    public String getPackageName() {
        return mPackageName;
    }

    @NonNull
    public String getAppCertificate() {
        return mAppCertificate;
    }
@@ -66,23 +76,17 @@ public final class AppInstallMetadata {
        return mInstallerCertificate;
    }

    /**
     * @see AppInstallMetadata.Builder#setVersionCode(int)
     */
    /** @see AppInstallMetadata.Builder#setVersionCode(int) */
    public int getVersionCode() {
        return mVersionCode;
    }

    /**
     * @see AppInstallMetadata.Builder#setIsPreInstalled(boolean)
     */
    /** @see AppInstallMetadata.Builder#setIsPreInstalled(boolean) */
    public boolean isPreInstalled() {
        return mIsPreInstalled;
    }

    /**
     * Builder class for constructing {@link AppInstallMetadata} objects.
     */
    /** Builder class for constructing {@link AppInstallMetadata} objects. */
    public static final class Builder {
        private String mPackageName;
        private String mAppCertificate;
@@ -96,7 +100,8 @@ public final class AppInstallMetadata {
         *
         * @see AppInstallMetadata#getPackageName()
         */
        public Builder setPackageName(String packageName) {
        @NonNull
        public Builder setPackageName(@NonNull String packageName) {
            this.mPackageName = checkNotNull(packageName);
            return this;
        }
@@ -109,7 +114,8 @@ public final class AppInstallMetadata {
         *
         * @see AppInstallMetadata#getAppCertificate()
         */
        public Builder setAppCertificate(String appCertificate) {
        @NonNull
        public Builder setAppCertificate(@NonNull String appCertificate) {
            this.mAppCertificate = checkNotNull(appCertificate);
            return this;
        }
@@ -119,7 +125,8 @@ public final class AppInstallMetadata {
         *
         * @see AppInstallMetadata#getInstallerName()
         */
        public Builder setInstallerName(String installerName) {
        @NonNull
        public Builder setInstallerName(@NonNull String installerName) {
            this.mInstallerName = checkNotNull(installerName);
            return this;
        }
@@ -132,7 +139,8 @@ public final class AppInstallMetadata {
         *
         * @see AppInstallMetadata#getInstallerCertificate()
         */
        public Builder setInstallerCertificate(String installerCertificate) {
        @NonNull
        public Builder setInstallerCertificate(@NonNull String installerCertificate) {
            this.mInstallerCertificate = checkNotNull(installerCertificate);
            return this;
        }
@@ -142,6 +150,7 @@ public final class AppInstallMetadata {
         *
         * @see AppInstallMetadata#getVersionCode()
         */
        @NonNull
        public Builder setVersionCode(int versionCode) {
            this.mVersionCode = versionCode;
            return this;
@@ -152,6 +161,7 @@ public final class AppInstallMetadata {
         *
         * @see AppInstallMetadata#isPreInstalled()
         */
        @NonNull
        public Builder setIsPreInstalled(boolean isPreInstalled) {
            this.mIsPreInstalled = isPreInstalled;
            return this;
@@ -159,7 +169,10 @@ public final class AppInstallMetadata {

        /**
         * Build {@link AppInstallMetadata}.
         *
         * @throws IllegalArgumentException if package name or app certificate is null
         */
        @NonNull
        public AppInstallMetadata build() {
            checkArgument(mPackageName != null);
            checkArgument(mAppCertificate != null);
+348 −164

File changed.

Preview size limit exceeded, changes collapsed.

+76 −1
Original line number Diff line number Diff line
@@ -16,9 +16,84 @@

package com.android.server.integrity.model;

import android.annotation.NonNull;
import android.annotation.SystemApi;
import android.os.Parcel;
import android.os.Parcelable;

import com.android.internal.annotations.VisibleForTesting;
import com.android.server.integrity.model.AtomicFormula.BooleanAtomicFormula;
import com.android.server.integrity.model.AtomicFormula.IntAtomicFormula;
import com.android.server.integrity.model.AtomicFormula.StringAtomicFormula;

/**
 * Represents a rule logic/content.
 *
 * @hide
 */
@SystemApi
@VisibleForTesting
public interface Formula {

    int OPEN_FORMULA_TAG = 0;
    int STRING_ATOMIC_FORMULA_TAG = 1;
    int INT_ATOMIC_FORMULA_TAG = 2;
    int BOOLEAN_ATOMIC_FORMULA_TAG = 3;

    /**
     * Returns if this formula can be satisfied by substituting the corresponding information of
     * {@code appInstallMetadata} into the formula.
     */
    boolean isSatisfied(@NonNull AppInstallMetadata appInstallMetadata);

    /**
     * Write a {@link Formula} to {@link android.os.Parcel}.
     *
     * <p>This helper method is needed because non-final class/interface are not allowed to be
     * {@link Parcelable}.
     *
     * @throws IllegalArgumentException if {@link Formula} is not a recognized subclass
     */
public abstract class Formula {
    static void writeToParcel(@NonNull Formula formula, @NonNull Parcel dest, int flags) {
        if (formula instanceof OpenFormula) {
            dest.writeInt(OPEN_FORMULA_TAG);
            ((OpenFormula) formula).writeToParcel(dest, flags);
        } else if (formula instanceof StringAtomicFormula) {
            dest.writeInt(STRING_ATOMIC_FORMULA_TAG);
            ((StringAtomicFormula) formula).writeToParcel(dest, flags);
        } else if (formula instanceof IntAtomicFormula) {
            dest.writeInt(INT_ATOMIC_FORMULA_TAG);
            ((IntAtomicFormula) formula).writeToParcel(dest, flags);
        } else if (formula instanceof BooleanAtomicFormula) {
            dest.writeInt(BOOLEAN_ATOMIC_FORMULA_TAG);
            ((BooleanAtomicFormula) formula).writeToParcel(dest, flags);
        } else {
            throw new IllegalArgumentException("Unrecognized class " + formula.getClass());
        }
    }

    /**
     * Read a {@link Formula} from a {@link android.os.Parcel}.
     *
     * <p>We need this (hacky) helper method because non-final class/interface cannot be {@link
     * Parcelable} (api lint error).
     *
     * @throws IllegalArgumentException if the parcel cannot be parsed
     */
    @NonNull
    static Formula readFromParcel(@NonNull Parcel in) {
        int tag = in.readInt();
        switch (tag) {
            case OPEN_FORMULA_TAG:
                return OpenFormula.CREATOR.createFromParcel(in);
            case STRING_ATOMIC_FORMULA_TAG:
                return StringAtomicFormula.CREATOR.createFromParcel(in);
            case INT_ATOMIC_FORMULA_TAG:
                return IntAtomicFormula.CREATOR.createFromParcel(in);
            case BOOLEAN_ATOMIC_FORMULA_TAG:
                return BooleanAtomicFormula.CREATOR.createFromParcel(in);
            default:
                throw new IllegalArgumentException("Unknown formula tag " + tag);
        }
    }
}
Loading