diff options
12 files changed, 1265 insertions, 479 deletions
diff --git a/services/core/java/com/android/server/integrity/engine/RuleEvaluationEngine.java b/services/core/java/com/android/server/integrity/engine/RuleEvaluationEngine.java index e90612e54105..f0bb192e3b61 100644 --- a/services/core/java/com/android/server/integrity/engine/RuleEvaluationEngine.java +++ b/services/core/java/com/android/server/integrity/engine/RuleEvaluationEngine.java @@ -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) { diff --git a/services/core/java/com/android/server/integrity/engine/RuleEvaluator.java b/services/core/java/com/android/server/integrity/engine/RuleEvaluator.java index 641650557f4c..7deae466ac3f 100644 --- a/services/core/java/com/android/server/integrity/engine/RuleEvaluator.java +++ b/services/core/java/com/android/server/integrity/engine/RuleEvaluator.java @@ -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; /** @@ -40,67 +46,40 @@ final class RuleEvaluator { * <p>Rules must be in disjunctive normal form (DNF). A rule should contain AND'ed formulas * only. All rules are OR'ed together by default. * - * @param rules The list of rules to evaluate. + * @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}. + * against. + * @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); - } - 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()); + boolean denied = false; + Rule denyRule = null; + for (Rule rule : matchedRules) { + switch (rule.getEffect()) { + case DENY: + if (!denied) { + denied = true; + denyRule = rule; + } + 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; - } - } 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; + Slog.e(TAG, "Matched an unknown effect rule: " + rule); + return IntegrityCheckResult.allow(); } } - - 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; } } diff --git a/services/core/java/com/android/server/integrity/model/AppInstallMetadata.java b/services/core/java/com/android/server/integrity/model/AppInstallMetadata.java index 660bd2e0d62e..dfc373bb97dc 100644 --- a/services/core/java/com/android/server/integrity/model/AppInstallMetadata.java +++ b/services/core/java/com/android/server/integrity/model/AppInstallMetadata.java @@ -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); diff --git a/services/core/java/com/android/server/integrity/model/AtomicFormula.java b/services/core/java/com/android/server/integrity/model/AtomicFormula.java index b9b46e3e1aae..a75752841cbc 100644 --- a/services/core/java/com/android/server/integrity/model/AtomicFormula.java +++ b/services/core/java/com/android/server/integrity/model/AtomicFormula.java @@ -17,220 +17,404 @@ 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.Nullable; +import android.annotation.IntDef; +import android.annotation.NonNull; +import android.annotation.SystemApi; +import android.os.Parcel; +import android.os.Parcelable; import android.util.Slog; +import com.android.internal.annotations.VisibleForTesting; + +import java.lang.annotation.Retention; +import java.lang.annotation.RetentionPolicy; import java.util.Objects; /** * Represents a simple formula consisting of an app install metadata field and a value. * * <p>Instances of this class are immutable. + * + * @hide */ -public final class AtomicFormula extends Formula { +@SystemApi +@VisibleForTesting +public abstract class AtomicFormula implements Formula { private static final String TAG = "AtomicFormula"; - public enum Key { - PACKAGE_NAME, - APP_CERTIFICATE, - INSTALLER_NAME, - INSTALLER_CERTIFICATE, - VERSION_CODE, - PRE_INSTALLED - } + @IntDef( + value = { + PACKAGE_NAME, + APP_CERTIFICATE, + INSTALLER_NAME, + INSTALLER_CERTIFICATE, + VERSION_CODE, + PRE_INSTALLED, + }) + @Retention(RetentionPolicy.SOURCE) + public @interface Key {} - public enum Operator { - EQ, - LT, - LE, - GT, - GE - } + @IntDef(value = {EQ, LT, LE, GT, GE}) + @Retention(RetentionPolicy.SOURCE) + public @interface Operator {} - private final Key mKey; - private final Operator mOperator; - - // The value of a key can take either 1 of 3 forms: String, Integer, or Boolean. - // It cannot have multiple values. - @Nullable - private final String mStringValue; - @Nullable - private final Integer mIntValue; - @Nullable - private final Boolean mBoolValue; - - public AtomicFormula(Key key, Operator operator, String stringValue) { - validateOperator(key, operator); - checkArgument( - key == Key.PACKAGE_NAME || key == Key.APP_CERTIFICATE || key == Key.INSTALLER_NAME - || key == Key.INSTALLER_CERTIFICATE, - String.format("Key %s cannot have string value", key)); - this.mKey = checkNotNull(key); - this.mOperator = checkNotNull(operator); - this.mStringValue = checkNotNull(stringValue); - this.mIntValue = null; - this.mBoolValue = null; - } + public static final int PACKAGE_NAME = 0; + public static final int APP_CERTIFICATE = 1; + public static final int INSTALLER_NAME = 2; + public static final int INSTALLER_CERTIFICATE = 3; + public static final int VERSION_CODE = 4; + public static final int PRE_INSTALLED = 5; - public AtomicFormula(Key key, Operator operator, Integer intValue) { - validateOperator(key, operator); - checkArgument(key == Key.VERSION_CODE, - String.format("Key %s cannot have integer value", key)); - this.mKey = checkNotNull(key); - this.mOperator = checkNotNull(operator); - this.mStringValue = null; - this.mIntValue = checkNotNull(intValue); - this.mBoolValue = null; - } + public static final int EQ = 0; + public static final int LT = 1; + public static final int LE = 2; + public static final int GT = 3; + public static final int GE = 4; - public AtomicFormula(Key key, Operator operator, Boolean boolValue) { - validateOperator(key, operator); - checkArgument(key == Key.PRE_INSTALLED, - String.format("Key %s cannot have boolean value", key)); - this.mKey = checkNotNull(key); - this.mOperator = checkNotNull(operator); - this.mStringValue = null; - this.mIntValue = null; - this.mBoolValue = checkNotNull(boolValue); - } + private final @Key int mKey; - public Key getKey() { - return mKey; + public AtomicFormula(@Key int key) { + mKey = key; } - public Operator getOperator() { - return mOperator; - } + /** An {@link AtomicFormula} with an key and int value. */ + public static final class IntAtomicFormula extends AtomicFormula implements Parcelable { + private final int mValue; + private final @Operator int mOperator; - public String getStringValue() { - return mStringValue; - } + /** + * Constructs a new {@link IntAtomicFormula}. + * + * <p>This formula will hold if and only if the corresponding information of an install + * specified by {@code key} is of the correct relationship to {@code value} as specified by + * {@code operator}. + * + * @throws IllegalArgumentException if {@code key} is not {@link #VERSION_CODE} + */ + public IntAtomicFormula(@Key int key, @Operator int operator, int value) { + super(key); + checkArgument( + key == VERSION_CODE, + String.format("Key %s cannot be used with IntAtomicFormula", keyToString(key))); + mOperator = operator; + mValue = value; + } - public Integer getIntValue() { - return mIntValue; - } + IntAtomicFormula(Parcel in) { + super(in.readInt()); + mValue = in.readInt(); + mOperator = in.readInt(); + } - public Boolean getBoolValue() { - return mBoolValue; - } + @NonNull + public static final Creator<IntAtomicFormula> CREATOR = + new Creator<IntAtomicFormula>() { + @Override + public IntAtomicFormula createFromParcel(Parcel in) { + return new IntAtomicFormula(in); + } + + @Override + public IntAtomicFormula[] newArray(int size) { + return new IntAtomicFormula[size]; + } + }; - /** - * Get string representation of the value of the key in the formula. - * - * @return string representation of the value of the key. - */ - public String getValue() { - if (mStringValue != null) { - return mStringValue; + @Override + public boolean isSatisfied(@NonNull AppInstallMetadata appInstallMetadata) { + int metadataValue = getMetadataValueByKey(appInstallMetadata); + switch (mOperator) { + case EQ: + return metadataValue == mValue; + case LE: + return metadataValue <= mValue; + case LT: + return metadataValue < mValue; + case GE: + return metadataValue >= mValue; + case GT: + return metadataValue > mValue; + default: + Slog.i(TAG, String.format("Unexpected operator %d", mOperator)); + return false; + } } - if (mIntValue != null) { - return mIntValue.toString(); + + @Override + public String toString() { + return String.format( + "(%s %s %s)", keyToString(getKey()), operatorToString(mOperator), mValue); } - return mBoolValue.toString(); - } - /** - * Check if the formula is true when substituting its {@link Key} with the string value. - * - * @param value String value to substitute the key with. - * @return {@code true} if the formula is true, and {@code false} otherwise. - */ - public boolean isMatch(String value) { - switch (mOperator) { - case EQ: - return mStringValue.equals(value); + @Override + public boolean equals(Object o) { + if (this == o) { + return true; + } + if (o == null || getClass() != o.getClass()) { + return false; + } + IntAtomicFormula that = (IntAtomicFormula) o; + return getKey() == that.getKey() && mValue == that.mValue; } - Slog.i(TAG, String.format("Found operator %s for value %s", mOperator, mStringValue)); - return false; - } - /** - * Check if the formula is true when substituting its {@link Key} with the integer value. - * - * @param value Integer value to substitute the key with. - * @return {@code true} if the formula is true, and {@code false} otherwise. - */ - public boolean isMatch(int value) { - switch (mOperator) { - case EQ: - return mIntValue == value; - case LE: - return mIntValue <= value; - case LT: - return mIntValue < value; - case GE: - return mIntValue >= value; - case GT: - return mIntValue > value; + @Override + public int hashCode() { + return Objects.hash(getKey(), mOperator, mValue); } - Slog.i(TAG, String.format("Found operator %s for value %s", mOperator, mIntValue)); - return false; - } - /** - * Check if the formula is true when substituting its {@link Key} with the boolean value. - * - * @param value Boolean value to substitute the key with. - * @return {@code true} if the formula is true, and {@code false} otherwise. - */ - public boolean isMatch(boolean value) { - switch (mOperator) { - case EQ: - return mBoolValue == value; + @Override + public int describeContents() { + return 0; + } + + @Override + public void writeToParcel(@NonNull Parcel dest, int flags) { + dest.writeInt(getKey()); + dest.writeInt(mValue); + dest.writeInt(mOperator); + } + + private int getMetadataValueByKey(AppInstallMetadata appInstallMetadata) { + switch (getKey()) { + case VERSION_CODE: + return appInstallMetadata.getVersionCode(); + default: + throw new IllegalStateException( + "Unexpected key in IntAtomicFormula" + getKey()); + } } - Slog.i(TAG, String.format("Found operator %s for value %s", mOperator, mBoolValue)); - return false; } - @Override - public String toString() { - return String.format("%s %s %s", mKey, mOperator, getValue()); + /** An {@link AtomicFormula} with a key and string value. */ + public static final class StringAtomicFormula extends AtomicFormula implements Parcelable { + private final String mValue; + + /** + * Constructs a new {@link StringAtomicFormula}. + * + * <p>This formula will hold if and only if the corresponding information of an install + * specified by {@code key} equals {@code value}. + * + * @throws IllegalArgumentException if {@code key} is not one of {@link #PACKAGE_NAME}, + * {@link #APP_CERTIFICATE}, {@link #INSTALLER_NAME} and {@link #INSTALLER_CERTIFICATE} + */ + public StringAtomicFormula(@Key int key, @NonNull String value) { + super(key); + checkArgument( + key == PACKAGE_NAME + || key == APP_CERTIFICATE + || key == INSTALLER_CERTIFICATE + || key == INSTALLER_NAME, + String.format( + "Key %s cannot be used with StringAtomicFormula", keyToString(key))); + mValue = value; + } + + StringAtomicFormula(Parcel in) { + super(in.readInt()); + mValue = in.readStringNoHelper(); + } + + @NonNull + public static final Creator<StringAtomicFormula> CREATOR = + new Creator<StringAtomicFormula>() { + @Override + public StringAtomicFormula createFromParcel(Parcel in) { + return new StringAtomicFormula(in); + } + + @Override + public StringAtomicFormula[] newArray(int size) { + return new StringAtomicFormula[size]; + } + }; + + @Override + public boolean isSatisfied(@NonNull AppInstallMetadata appInstallMetadata) { + String metadataValue = getMetadataValueByKey(appInstallMetadata); + return metadataValue.equals(mValue); + } + + @Override + public String toString() { + return String.format("(%s %s %s)", keyToString(getKey()), operatorToString(EQ), mValue); + } + + @Override + public boolean equals(Object o) { + if (this == o) { + return true; + } + if (o == null || getClass() != o.getClass()) { + return false; + } + StringAtomicFormula that = (StringAtomicFormula) o; + return getKey() == that.getKey() && Objects.equals(mValue, that.mValue); + } + + @Override + public int hashCode() { + return Objects.hash(getKey(), mValue); + } + + @Override + public int describeContents() { + return 0; + } + + @Override + public void writeToParcel(@NonNull Parcel dest, int flags) { + dest.writeInt(getKey()); + dest.writeStringNoHelper(mValue); + } + + private String getMetadataValueByKey(AppInstallMetadata appInstallMetadata) { + switch (getKey()) { + case PACKAGE_NAME: + return appInstallMetadata.getPackageName(); + case APP_CERTIFICATE: + return appInstallMetadata.getAppCertificate(); + case INSTALLER_CERTIFICATE: + return appInstallMetadata.getInstallerCertificate(); + case INSTALLER_NAME: + return appInstallMetadata.getInstallerName(); + default: + throw new IllegalStateException( + "Unexpected key in StringAtomicFormula: " + getKey()); + } + } } - @Override - public boolean equals(Object o) { - if (this == o) { - return true; + /** An {@link AtomicFormula} with a key and boolean value. */ + public static final class BooleanAtomicFormula extends AtomicFormula implements Parcelable { + private final boolean mValue; + + /** + * Constructs a new {@link BooleanAtomicFormula}. + * + * <p>This formula will hold if and only if the corresponding information of an install + * specified by {@code key} equals {@code value}. + * + * @throws IllegalArgumentException if {@code key} is not {@link #PRE_INSTALLED} + */ + public BooleanAtomicFormula(@Key int key, boolean value) { + super(key); + checkArgument( + key == PRE_INSTALLED, + String.format( + "Key %s cannot be used with BooleanAtomicFormula", keyToString(key))); + mValue = value; + } + + BooleanAtomicFormula(Parcel in) { + super(in.readInt()); + mValue = in.readByte() != 0; + } + + @NonNull + public static final Creator<BooleanAtomicFormula> CREATOR = + new Creator<BooleanAtomicFormula>() { + @Override + public BooleanAtomicFormula createFromParcel(Parcel in) { + return new BooleanAtomicFormula(in); + } + + @Override + public BooleanAtomicFormula[] newArray(int size) { + return new BooleanAtomicFormula[size]; + } + }; + + @Override + public boolean isSatisfied(@NonNull AppInstallMetadata appInstallMetadata) { + boolean metadataValue = getMetadataValueByKey(appInstallMetadata); + return metadataValue == mValue; } - if (o == null || getClass() != o.getClass()) { - return false; + + @Override + public String toString() { + return String.format("(%s %s %s)", keyToString(getKey()), operatorToString(EQ), mValue); + } + + @Override + public boolean equals(Object o) { + if (this == o) { + return true; + } + if (o == null || getClass() != o.getClass()) { + return false; + } + BooleanAtomicFormula that = (BooleanAtomicFormula) o; + return getKey() == that.getKey() && mValue == that.mValue; + } + + @Override + public int hashCode() { + return Objects.hash(getKey(), mValue); + } + + @Override + public int describeContents() { + return 0; + } + + @Override + public void writeToParcel(@NonNull Parcel dest, int flags) { + dest.writeInt(getKey()); + dest.writeByte((byte) (mValue ? 1 : 0)); + } + + private boolean getMetadataValueByKey(AppInstallMetadata appInstallMetadata) { + switch (getKey()) { + case PRE_INSTALLED: + return appInstallMetadata.isPreInstalled(); + default: + throw new IllegalStateException( + "Unexpected key in BooleanAtomicFormula: " + getKey()); + } } - AtomicFormula that = (AtomicFormula) o; - return mKey == that.mKey - && mOperator == that.mOperator - && Objects.equals(mStringValue, that.mStringValue) - && Objects.equals(mIntValue, that.mIntValue) - && Objects.equals(mBoolValue, that.mBoolValue); } - @Override - public int hashCode() { - return Objects.hash(mKey, mOperator, mStringValue, mIntValue, mBoolValue); + public int getKey() { + return mKey; } - private void validateOperator(Key key, Operator operator) { - boolean validOperator; + String keyToString(int key) { switch (key) { case PACKAGE_NAME: + return "PACKAGE_NAME"; case APP_CERTIFICATE: + return "APP_CERTIFICATE"; + case VERSION_CODE: + return "VERSION_CODE"; case INSTALLER_NAME: + return "INSTALLER_NAME"; case INSTALLER_CERTIFICATE: + return "INSTALLER_CERTIFICATE"; case PRE_INSTALLED: - validOperator = (operator == Operator.EQ); - break; - case VERSION_CODE: - validOperator = true; - break; + return "PRE_INSTALLED"; default: - Slog.i(TAG, String.format("Found operator %s for key %s", operator, key)); - validOperator = false; + throw new IllegalArgumentException("Unknown key " + key); } - if (!validOperator) { - throw new IllegalArgumentException( - String.format("Invalid operator %s used for key %s", operator, key)); + } + + String operatorToString(int op) { + switch (op) { + case EQ: + return "EQ"; + case LT: + return "LT"; + case LE: + return "LE"; + case GT: + return "GT"; + case GE: + return "GE"; + default: + throw new IllegalArgumentException("Unknown operator " + op); } } } diff --git a/services/core/java/com/android/server/integrity/model/Formula.java b/services/core/java/com/android/server/integrity/model/Formula.java index 9db445378dce..852ece536f31 100644 --- a/services/core/java/com/android/server/integrity/model/Formula.java +++ b/services/core/java/com/android/server/integrity/model/Formula.java @@ -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 */ -public abstract class Formula { +@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 + */ + 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); + } + } } diff --git a/services/core/java/com/android/server/integrity/model/IntegrityCheckResult.java b/services/core/java/com/android/server/integrity/model/IntegrityCheckResult.java index 7aeb0c1b188e..ef0751d329d0 100644 --- a/services/core/java/com/android/server/integrity/model/IntegrityCheckResult.java +++ b/services/core/java/com/android/server/integrity/model/IntegrityCheckResult.java @@ -16,6 +16,8 @@ package com.android.server.integrity.model; +import android.annotation.Nullable; + /** * A class encapsulating the result from the evaluation engine after evaluating rules against app * install metadata. @@ -31,9 +33,9 @@ public final class IntegrityCheckResult { } private final Effect mEffect; - private final Rule mRule; + @Nullable private final Rule mRule; - private IntegrityCheckResult(Effect effect, Rule rule) { + private IntegrityCheckResult(Effect effect, @Nullable Rule rule) { this.mEffect = effect; this.mRule = rule; } @@ -49,10 +51,19 @@ public final class IntegrityCheckResult { /** * Create an ALLOW evaluation outcome. * - * @return An evaluation outcome with ALLOW effect and empty rule. + * @return An evaluation outcome with ALLOW effect and no rule. */ public static IntegrityCheckResult allow() { - return new IntegrityCheckResult(Effect.ALLOW, Rule.EMPTY); + return new IntegrityCheckResult(Effect.ALLOW, null); + } + + /** + * Create an ALLOW evaluation outcome. + * + * @return An evaluation outcome with ALLOW effect and rule causing that effect. + */ + public static IntegrityCheckResult allow(Rule rule) { + return new IntegrityCheckResult(Effect.ALLOW, rule); } /** diff --git a/services/core/java/com/android/server/integrity/model/OpenFormula.java b/services/core/java/com/android/server/integrity/model/OpenFormula.java index 21da629fba2e..f29706adeebb 100644 --- a/services/core/java/com/android/server/integrity/model/OpenFormula.java +++ b/services/core/java/com/android/server/integrity/model/OpenFormula.java @@ -17,8 +17,19 @@ 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.IntDef; +import android.annotation.NonNull; +import android.annotation.SystemApi; +import android.os.Parcel; +import android.os.Parcelable; +import android.util.Slog; + +import com.android.internal.annotations.VisibleForTesting; + +import java.lang.annotation.Retention; +import java.lang.annotation.RetentionPolicy; +import java.util.ArrayList; import java.util.Collections; import java.util.List; import java.util.Objects; @@ -27,40 +38,108 @@ import java.util.Objects; * Represents a complex formula consisting of other simple and complex formulas. * * <p>Instances of this class are immutable. + * + * @hide */ -public final class OpenFormula extends Formula { +@SystemApi +@VisibleForTesting +public final class OpenFormula implements Formula, Parcelable { + private static final String TAG = "OpenFormula"; - public enum Connector { - AND, - OR, - NOT - } + @IntDef( + value = { + AND, OR, NOT, + }) + @Retention(RetentionPolicy.SOURCE) + public @interface Connector {} + + /** Boolean AND operator. */ + public static final int AND = 0; - private final Connector mConnector; + /** Boolean OR operator. */ + public static final int OR = 1; + + /** Boolean NOT operator. */ + public static final int NOT = 2; + + private final @Connector int mConnector; private final List<Formula> mFormulas; - public OpenFormula(Connector connector, List<Formula> formulas) { + @NonNull + public static final Creator<OpenFormula> CREATOR = + new Creator<OpenFormula>() { + @Override + public OpenFormula createFromParcel(Parcel in) { + return new OpenFormula(in); + } + + @Override + public OpenFormula[] newArray(int size) { + return new OpenFormula[size]; + } + }; + + /** + * Create a new formula from operator and operands. + * + * @throws IllegalArgumentException if the number of operands is not matching the requirements + * for that operator (at least 2 for {@link #AND} and {@link #OR}, 1 for {@link #NOT}). + */ + public OpenFormula(@Connector int connector, @NonNull List<Formula> formulas) { validateFormulas(connector, formulas); - this.mConnector = checkNotNull(connector); - this.mFormulas = Collections.unmodifiableList(checkNotNull(formulas)); + this.mConnector = connector; + this.mFormulas = Collections.unmodifiableList(formulas); } - public Connector getConnector() { + OpenFormula(Parcel in) { + mConnector = in.readInt(); + int length = in.readInt(); + checkArgument(length >= 0, "Must have non-negative length. Got " + length); + mFormulas = new ArrayList<>(length); + for (int i = 0; i < length; i++) { + mFormulas.add(Formula.readFromParcel(in)); + } + } + + public @Connector int getConnector() { return mConnector; } + @NonNull public List<Formula> getFormulas() { return mFormulas; } @Override + public boolean isSatisfied(@NonNull AppInstallMetadata appInstallMetadata) { + switch (mConnector) { + case NOT: + return !mFormulas.get(0).isSatisfied(appInstallMetadata); + case AND: + return mFormulas.stream() + .allMatch(formula -> formula.isSatisfied(appInstallMetadata)); + case OR: + return mFormulas.stream() + .anyMatch(formula -> formula.isSatisfied(appInstallMetadata)); + default: + Slog.i(TAG, "Unknown connector " + mConnector); + return false; + } + } + + @Override public String toString() { StringBuilder sb = new StringBuilder(); - for (int i = 0; i < mFormulas.size(); i++) { - if (i > 0) { - sb.append(String.format(" %s ", mConnector)); + if (mFormulas.size() == 1) { + sb.append(String.format("%s ", connectorToString(mConnector))); + sb.append(mFormulas.get(0).toString()); + } else { + for (int i = 0; i < mFormulas.size(); i++) { + if (i > 0) { + sb.append(String.format(" %s ", connectorToString(mConnector))); + } + sb.append(mFormulas.get(i).toString()); } - sb.append(mFormulas.get(i).toString()); } return sb.toString(); } @@ -74,8 +153,7 @@ public final class OpenFormula extends Formula { return false; } OpenFormula that = (OpenFormula) o; - return mConnector == that.mConnector - && mFormulas.equals(that.mFormulas); + return mConnector == that.mConnector && mFormulas.equals(that.mFormulas); } @Override @@ -83,17 +161,50 @@ public final class OpenFormula extends Formula { return Objects.hash(mConnector, mFormulas); } - private void validateFormulas(Connector connector, List<Formula> formulas) { + @Override + public int describeContents() { + return 0; + } + + @Override + public void writeToParcel(@NonNull Parcel dest, int flags) { + dest.writeInt(mConnector); + dest.writeInt(mFormulas.size()); + for (Formula formula : mFormulas) { + Formula.writeToParcel(formula, dest, flags); + } + } + + private void validateFormulas(@Connector int connector, List<Formula> formulas) { switch (connector) { case AND: case OR: - checkArgument(formulas.size() >= 2, - String.format("Connector %s must have at least 2 formulas", connector)); + checkArgument( + formulas.size() >= 2, + String.format( + "Connector %s must have at least 2 formulas", + connectorToString(connector))); break; case NOT: - checkArgument(formulas.size() == 1, - String.format("Connector %s must have 1 formula only", connector)); + checkArgument( + formulas.size() == 1, + String.format( + "Connector %s must have 1 formula only", + connectorToString(connector))); break; } } + + private String connectorToString(int connector) { + switch (connector) { + case AND: + return "AND"; + case OR: + return "OR"; + case NOT: + return "NOT"; + default: + throw new IllegalArgumentException("Unknown connector " + connector); + } + } } diff --git a/services/core/java/com/android/server/integrity/model/Rule.java b/services/core/java/com/android/server/integrity/model/Rule.java index 63b9b911ff4f..14dcb26d1025 100644 --- a/services/core/java/com/android/server/integrity/model/Rule.java +++ b/services/core/java/com/android/server/integrity/model/Rule.java @@ -18,55 +18,96 @@ package com.android.server.integrity.model; import static com.android.internal.util.Preconditions.checkNotNull; +import android.annotation.IntDef; +import android.annotation.NonNull; +import android.annotation.SystemApi; +import android.os.Parcel; +import android.os.Parcelable; + +import com.android.internal.annotations.VisibleForTesting; + +import java.lang.annotation.Retention; +import java.lang.annotation.RetentionPolicy; import java.util.Objects; /** * Represent rules to be used in the rule evaluation engine to match against app installs. * * <p>Instances of this class are immutable. + * + * @hide */ -public final class Rule { +@SystemApi +@VisibleForTesting +public final class Rule implements Parcelable { - public enum Effect { - DENY - } + @IntDef( + value = { + DENY, + FORCE_ALLOW, + }) + @Retention(RetentionPolicy.SOURCE) + public @interface Effect {} + + /** If this rule matches the install, the install should be denied. */ + public static final int DENY = 0; - // Holds an empty rule instance. - public static final Rule EMPTY = new Rule(); + /** + * If this rule matches the install, the install will be allowed regardless of other matched + * rules. + */ + public static final int FORCE_ALLOW = 1; private final Formula mFormula; - private final Effect mEffect; + private final @Effect int mEffect; - private Rule() { - this.mFormula = null; - this.mEffect = null; + public Rule(@NonNull Formula formula, @Effect int effect) { + this.mFormula = checkNotNull(formula); + this.mEffect = effect; } - public Rule(Formula formula, Effect effect) { - this.mFormula = checkNotNull(formula); - this.mEffect = checkNotNull(effect); + Rule(Parcel in) { + mFormula = Formula.readFromParcel(in); + mEffect = in.readInt(); } - /** - * Indicates whether the rule is empty or not. - * - * @return {@code true} if the rule is empty, and {@code false} otherwise. - */ - public boolean isEmpty() { - return mFormula == null && mEffect == null; + @NonNull + public static final Creator<Rule> CREATOR = + new Creator<Rule>() { + @Override + public Rule createFromParcel(Parcel in) { + return new Rule(in); + } + + @Override + public Rule[] newArray(int size) { + return new Rule[size]; + } + }; + + @Override + public int describeContents() { + return 0; + } + + @Override + public void writeToParcel(@NonNull Parcel dest, int flags) { + Formula.writeToParcel(mFormula, dest, flags); + dest.writeInt(mEffect); } + @NonNull public Formula getFormula() { return mFormula; } - public Effect getEffect() { + public @Effect int getEffect() { return mEffect; } @Override public String toString() { - return String.format("Rule: %s, %s", mFormula, mEffect); + return String.format("Rule: %s, %s", mFormula, effectToString(mEffect)); } @Override @@ -78,12 +119,22 @@ public final class Rule { return false; } Rule that = (Rule) o; - return Objects.equals(mFormula, that.mFormula) - && Objects.equals(mEffect, that.mEffect); + return Objects.equals(mFormula, that.mFormula) && mEffect == that.mEffect; } @Override public int hashCode() { return Objects.hash(mFormula, mEffect); } + + private String effectToString(int effect) { + switch (effect) { + case DENY: + return "DENY"; + case FORCE_ALLOW: + return "FORCE_ALLOW"; + default: + throw new IllegalArgumentException("Unknown effect " + effect); + } + } } diff --git a/services/tests/servicestests/src/com/android/server/integrity/engine/RuleEvaluatorTest.java b/services/tests/servicestests/src/com/android/server/integrity/engine/RuleEvaluatorTest.java index baf1ed00c7b5..e52aca36bb05 100644 --- a/services/tests/servicestests/src/com/android/server/integrity/engine/RuleEvaluatorTest.java +++ b/services/tests/servicestests/src/com/android/server/integrity/engine/RuleEvaluatorTest.java @@ -16,11 +16,15 @@ package com.android.server.integrity.engine; +import static com.android.server.integrity.model.IntegrityCheckResult.Effect.ALLOW; +import static com.android.server.integrity.model.IntegrityCheckResult.Effect.DENY; + import static org.junit.Assert.assertEquals; -import static org.junit.Assert.assertNotEquals; import com.android.server.integrity.model.AppInstallMetadata; import com.android.server.integrity.model.AtomicFormula; +import com.android.server.integrity.model.AtomicFormula.StringAtomicFormula; +import com.android.server.integrity.model.IntegrityCheckResult; import com.android.server.integrity.model.OpenFormula; import com.android.server.integrity.model.Rule; @@ -43,141 +47,176 @@ public class RuleEvaluatorTest { new AppInstallMetadata.Builder() .setPackageName(PACKAGE_NAME_1) .setAppCertificate(APP_CERTIFICATE) + .setVersionCode(2) .build(); @Test - public void testMatchRules_emptyRules() { + public void testEvaluateRules_noRules_allow() { List<Rule> rules = new ArrayList<>(); - Rule matchedRule = RuleEvaluator.evaluateRules(rules, APP_INSTALL_METADATA); + IntegrityCheckResult result = RuleEvaluator.evaluateRules(rules, APP_INSTALL_METADATA); - assertEquals(Rule.EMPTY, matchedRule); + assertEquals(ALLOW, result.getEffect()); } @Test - public void testMatchRules_emptyMatch() { - Rule rule1 = new Rule( - new AtomicFormula(AtomicFormula.Key.PACKAGE_NAME, AtomicFormula.Operator.EQ, - PACKAGE_NAME_2), Rule.Effect.DENY); + public void testEvaluateRules_noMatchedRules_allow() { + Rule rule1 = + new Rule( + new StringAtomicFormula(AtomicFormula.PACKAGE_NAME, PACKAGE_NAME_2), + Rule.DENY); - Rule matchedRule = RuleEvaluator.evaluateRules(Collections.singletonList(rule1), - APP_INSTALL_METADATA); + IntegrityCheckResult result = + RuleEvaluator.evaluateRules(Collections.singletonList(rule1), APP_INSTALL_METADATA); - assertEquals(Rule.EMPTY, matchedRule); + assertEquals(ALLOW, result.getEffect()); } - @Test - public void testMatchRules_oneMatch() { - Rule rule1 = new Rule( - new AtomicFormula(AtomicFormula.Key.PACKAGE_NAME, AtomicFormula.Operator.EQ, - PACKAGE_NAME_1), Rule.Effect.DENY); - Rule rule2 = new Rule( - new AtomicFormula(AtomicFormula.Key.PACKAGE_NAME, AtomicFormula.Operator.EQ, - PACKAGE_NAME_2), Rule.Effect.DENY); - - Rule matchedRule = RuleEvaluator.evaluateRules(Arrays.asList(rule1, rule2), - APP_INSTALL_METADATA); - - assertEquals(rule1, matchedRule); + public void testEvaluateRules_oneMatch_deny() { + Rule rule1 = + new Rule( + new StringAtomicFormula(AtomicFormula.PACKAGE_NAME, PACKAGE_NAME_1), + Rule.DENY); + Rule rule2 = + new Rule( + new StringAtomicFormula(AtomicFormula.PACKAGE_NAME, PACKAGE_NAME_2), + Rule.DENY); + + IntegrityCheckResult result = + RuleEvaluator.evaluateRules(Arrays.asList(rule1, rule2), APP_INSTALL_METADATA); + + assertEquals(DENY, result.getEffect()); + assertEquals(rule1, result.getRule()); } @Test - public void testMatchRules_multipleMatches() { - Rule rule1 = new Rule( - new AtomicFormula(AtomicFormula.Key.PACKAGE_NAME, AtomicFormula.Operator.EQ, - PACKAGE_NAME_1), Rule.Effect.DENY); - OpenFormula openFormula2 = new OpenFormula(OpenFormula.Connector.AND, Arrays.asList( - new AtomicFormula(AtomicFormula.Key.PACKAGE_NAME, AtomicFormula.Operator.EQ, - PACKAGE_NAME_1), - new AtomicFormula(AtomicFormula.Key.APP_CERTIFICATE, - AtomicFormula.Operator.EQ, - APP_CERTIFICATE))); - Rule rule2 = new Rule( - openFormula2, Rule.Effect.DENY); - - Rule matchedRule = RuleEvaluator.evaluateRules(Arrays.asList(rule1, rule2), - APP_INSTALL_METADATA); - - assertNotEquals(Rule.EMPTY, matchedRule); + public void testEvaluateRules_multipleMatches_deny() { + Rule rule1 = + new Rule( + new StringAtomicFormula(AtomicFormula.PACKAGE_NAME, PACKAGE_NAME_1), + Rule.DENY); + OpenFormula openFormula2 = + new OpenFormula( + OpenFormula.AND, + Arrays.asList( + new StringAtomicFormula(AtomicFormula.PACKAGE_NAME, PACKAGE_NAME_1), + new StringAtomicFormula( + AtomicFormula.APP_CERTIFICATE, APP_CERTIFICATE))); + Rule rule2 = new Rule(openFormula2, Rule.DENY); + + IntegrityCheckResult result = + RuleEvaluator.evaluateRules(Arrays.asList(rule1, rule2), APP_INSTALL_METADATA); + + assertEquals(DENY, result.getEffect()); + assertEquals(rule1, result.getRule()); } @Test - public void testMatchRules_ruleWithNot() { - OpenFormula openFormula = new OpenFormula(OpenFormula.Connector.NOT, - Collections.singletonList( - new AtomicFormula(AtomicFormula.Key.PACKAGE_NAME, AtomicFormula.Operator.EQ, - PACKAGE_NAME_2))); - Rule rule = new Rule(openFormula, Rule.Effect.DENY); - - Rule matchedRule = RuleEvaluator.evaluateRules(Collections.singletonList(rule), - APP_INSTALL_METADATA); - - assertEquals(rule, matchedRule); + public void testEvaluateRules_ruleWithNot_deny() { + OpenFormula openFormula = + new OpenFormula( + OpenFormula.NOT, + Collections.singletonList( + new StringAtomicFormula( + AtomicFormula.PACKAGE_NAME, PACKAGE_NAME_2))); + Rule rule = new Rule(openFormula, Rule.DENY); + + IntegrityCheckResult result = + RuleEvaluator.evaluateRules(Collections.singletonList(rule), APP_INSTALL_METADATA); + + assertEquals(DENY, result.getEffect()); + assertEquals(rule, result.getRule()); } @Test - public void testMatchRules_ruleWithIntegerOperators() { - Rule rule1 = new Rule( - new AtomicFormula(AtomicFormula.Key.VERSION_CODE, AtomicFormula.Operator.GT, - 1), Rule.Effect.DENY); - - Rule matchedRule = RuleEvaluator.evaluateRules(Collections.singletonList(rule1), - APP_INSTALL_METADATA); + public void testEvaluateRules_ruleWithIntegerOperators_deny() { + Rule rule = + new Rule( + new AtomicFormula.IntAtomicFormula( + AtomicFormula.VERSION_CODE, AtomicFormula.GT, 1), + Rule.DENY); + + IntegrityCheckResult result = + RuleEvaluator.evaluateRules(Collections.singletonList(rule), APP_INSTALL_METADATA); + + assertEquals(DENY, result.getEffect()); + assertEquals(rule, result.getRule()); + } - assertEquals(rule1, matchedRule); + @Test + public void testEvaluateRules_validForm_deny() { + OpenFormula openFormula = + new OpenFormula( + OpenFormula.AND, + Arrays.asList( + new StringAtomicFormula(AtomicFormula.PACKAGE_NAME, PACKAGE_NAME_1), + new StringAtomicFormula( + AtomicFormula.APP_CERTIFICATE, APP_CERTIFICATE))); + Rule rule = new Rule(openFormula, Rule.DENY); + + IntegrityCheckResult result = + RuleEvaluator.evaluateRules(Collections.singletonList(rule), APP_INSTALL_METADATA); + + assertEquals(DENY, result.getEffect()); + assertEquals(rule, result.getRule()); } @Test - public void testMatchRules_validForm() { - OpenFormula openFormula = new OpenFormula(OpenFormula.Connector.AND, Arrays.asList( - new AtomicFormula(AtomicFormula.Key.PACKAGE_NAME, AtomicFormula.Operator.EQ, - PACKAGE_NAME_1), - new AtomicFormula(AtomicFormula.Key.APP_CERTIFICATE, - AtomicFormula.Operator.EQ, - APP_CERTIFICATE))); - Rule rule = new Rule( - openFormula, Rule.Effect.DENY); - - Rule matchedRule = RuleEvaluator.evaluateRules(Collections.singletonList(rule), - APP_INSTALL_METADATA); - - assertEquals(rule, matchedRule); + public void testEvaluateRules_ruleNotInDNF_ignoreAndAllow() { + OpenFormula openFormula = + new OpenFormula( + OpenFormula.OR, + Arrays.asList( + new StringAtomicFormula(AtomicFormula.PACKAGE_NAME, PACKAGE_NAME_1), + new StringAtomicFormula( + AtomicFormula.APP_CERTIFICATE, APP_CERTIFICATE))); + Rule rule = new Rule(openFormula, Rule.DENY); + + IntegrityCheckResult result = + RuleEvaluator.evaluateRules(Collections.singletonList(rule), APP_INSTALL_METADATA); + + assertEquals(ALLOW, result.getEffect()); } @Test - public void testMatchRules_ruleNotInDNF() { - OpenFormula openFormula = new OpenFormula(OpenFormula.Connector.OR, Arrays.asList( - new AtomicFormula(AtomicFormula.Key.PACKAGE_NAME, AtomicFormula.Operator.EQ, - PACKAGE_NAME_1), - new AtomicFormula(AtomicFormula.Key.APP_CERTIFICATE, - AtomicFormula.Operator.EQ, - APP_CERTIFICATE))); - Rule rule = new Rule( - openFormula, Rule.Effect.DENY); - - Rule matchedRule = RuleEvaluator.evaluateRules(Collections.singletonList(rule), - APP_INSTALL_METADATA); - - assertEquals(Rule.EMPTY, matchedRule); + public void testEvaluateRules_openFormulaWithNot_allow() { + OpenFormula openSubFormula = + new OpenFormula( + OpenFormula.AND, + Arrays.asList( + new StringAtomicFormula(AtomicFormula.PACKAGE_NAME, PACKAGE_NAME_2), + new StringAtomicFormula( + AtomicFormula.APP_CERTIFICATE, APP_CERTIFICATE))); + OpenFormula openFormula = + new OpenFormula(OpenFormula.NOT, Collections.singletonList(openSubFormula)); + Rule rule = new Rule(openFormula, Rule.DENY); + + IntegrityCheckResult result = + RuleEvaluator.evaluateRules(Collections.singletonList(rule), APP_INSTALL_METADATA); + + assertEquals(ALLOW, result.getEffect()); } @Test - public void testMatchRules_openFormulaWithNot() { - OpenFormula openSubFormula = new OpenFormula(OpenFormula.Connector.AND, Arrays.asList( - new AtomicFormula(AtomicFormula.Key.PACKAGE_NAME, AtomicFormula.Operator.EQ, - PACKAGE_NAME_2), - new AtomicFormula(AtomicFormula.Key.APP_CERTIFICATE, - AtomicFormula.Operator.EQ, - APP_CERTIFICATE))); - OpenFormula openFormula = new OpenFormula(OpenFormula.Connector.NOT, - Collections.singletonList(openSubFormula)); - Rule rule = new Rule( - openFormula, Rule.Effect.DENY); - - Rule matchedRule = RuleEvaluator.evaluateRules(Collections.singletonList(rule), - APP_INSTALL_METADATA); - - assertEquals(Rule.EMPTY, matchedRule); + public void testEvaluateRules_forceAllow() { + Rule rule1 = + new Rule( + new StringAtomicFormula(AtomicFormula.PACKAGE_NAME, PACKAGE_NAME_1), + Rule.FORCE_ALLOW); + OpenFormula openFormula2 = + new OpenFormula( + OpenFormula.AND, + Arrays.asList( + new StringAtomicFormula(AtomicFormula.PACKAGE_NAME, PACKAGE_NAME_1), + new StringAtomicFormula( + AtomicFormula.APP_CERTIFICATE, APP_CERTIFICATE))); + Rule rule2 = new Rule(openFormula2, Rule.DENY); + + IntegrityCheckResult result = + RuleEvaluator.evaluateRules(Arrays.asList(rule1, rule2), APP_INSTALL_METADATA); + + assertEquals(ALLOW, result.getEffect()); + assertEquals(rule1, result.getRule()); } } diff --git a/services/tests/servicestests/src/com/android/server/integrity/model/AtomicFormulaTest.java b/services/tests/servicestests/src/com/android/server/integrity/model/AtomicFormulaTest.java index 1cb2fb3517d3..c8c5ecaed43a 100644 --- a/services/tests/servicestests/src/com/android/server/integrity/model/AtomicFormulaTest.java +++ b/services/tests/servicestests/src/com/android/server/integrity/model/AtomicFormulaTest.java @@ -19,6 +19,14 @@ package com.android.server.integrity.model; import static com.android.server.testutils.TestUtils.assertExpectException; import static org.junit.Assert.assertEquals; +import static org.junit.Assert.assertFalse; +import static org.junit.Assert.assertTrue; + +import android.os.Parcel; + +import com.android.server.integrity.model.AtomicFormula.BooleanAtomicFormula; +import com.android.server.integrity.model.AtomicFormula.IntAtomicFormula; +import com.android.server.integrity.model.AtomicFormula.StringAtomicFormula; import org.junit.Test; import org.junit.runner.RunWith; @@ -29,32 +37,26 @@ public class AtomicFormulaTest { @Test public void testValidAtomicFormula_stringValue() { - AtomicFormula atomicFormula = new AtomicFormula(AtomicFormula.Key.PACKAGE_NAME, - AtomicFormula.Operator.EQ, "com.test.app"); + StringAtomicFormula stringAtomicFormula = + new StringAtomicFormula(AtomicFormula.PACKAGE_NAME, "com.test.app"); - assertEquals(AtomicFormula.Key.PACKAGE_NAME, atomicFormula.getKey()); - assertEquals(AtomicFormula.Operator.EQ, atomicFormula.getOperator()); - assertEquals("com.test.app", atomicFormula.getStringValue()); + assertEquals(AtomicFormula.PACKAGE_NAME, stringAtomicFormula.getKey()); } @Test public void testValidAtomicFormula_intValue() { - AtomicFormula atomicFormula = new AtomicFormula(AtomicFormula.Key.VERSION_CODE, - AtomicFormula.Operator.LE, 1); + IntAtomicFormula intAtomicFormula = + new IntAtomicFormula(AtomicFormula.VERSION_CODE, AtomicFormula.LE, 1); - assertEquals(AtomicFormula.Key.VERSION_CODE, atomicFormula.getKey()); - assertEquals(AtomicFormula.Operator.LE, atomicFormula.getOperator()); - assertEquals(1, atomicFormula.getIntValue().intValue()); + assertEquals(AtomicFormula.VERSION_CODE, intAtomicFormula.getKey()); } @Test public void testValidAtomicFormula_boolValue() { - AtomicFormula atomicFormula = new AtomicFormula(AtomicFormula.Key.PRE_INSTALLED, - AtomicFormula.Operator.EQ, true); + BooleanAtomicFormula atomicFormula = + new BooleanAtomicFormula(AtomicFormula.PRE_INSTALLED, true); - assertEquals(AtomicFormula.Key.PRE_INSTALLED, atomicFormula.getKey()); - assertEquals(AtomicFormula.Operator.EQ, atomicFormula.getOperator()); - assertEquals(true, atomicFormula.getBoolValue()); + assertEquals(AtomicFormula.PRE_INSTALLED, atomicFormula.getKey()); } @Test @@ -62,9 +64,9 @@ public class AtomicFormulaTest { assertExpectException( IllegalArgumentException.class, /* expectedExceptionMessageRegex */ - String.format("Key %s cannot have string value", AtomicFormula.Key.VERSION_CODE), - () -> new AtomicFormula(AtomicFormula.Key.VERSION_CODE, AtomicFormula.Operator.EQ, - "test-value")); + String.format( + "Key VERSION_CODE cannot be used with StringAtomicFormula"), + () -> new StringAtomicFormula(AtomicFormula.VERSION_CODE, "test-value")); } @Test @@ -72,9 +74,9 @@ public class AtomicFormulaTest { assertExpectException( IllegalArgumentException.class, /* expectedExceptionMessageRegex */ - String.format("Key %s cannot have integer value", AtomicFormula.Key.PACKAGE_NAME), - () -> new AtomicFormula(AtomicFormula.Key.PACKAGE_NAME, AtomicFormula.Operator.EQ, - 1)); + String.format( + "Key PACKAGE_NAME cannot be used with IntAtomicFormula"), + () -> new IntAtomicFormula(AtomicFormula.PACKAGE_NAME, AtomicFormula.EQ, 1)); } @Test @@ -82,19 +84,193 @@ public class AtomicFormulaTest { assertExpectException( IllegalArgumentException.class, /* expectedExceptionMessageRegex */ - String.format("Key %s cannot have boolean value", AtomicFormula.Key.PACKAGE_NAME), - () -> new AtomicFormula(AtomicFormula.Key.PACKAGE_NAME, AtomicFormula.Operator.EQ, - true)); + String.format( + "Key PACKAGE_NAME cannot be used with BooleanAtomicFormula"), + () -> new BooleanAtomicFormula(AtomicFormula.PACKAGE_NAME, true)); } @Test - public void testValidateOperator_invalidKeyOperatorPair() { - assertExpectException( - IllegalArgumentException.class, - /* expectedExceptionMessageRegex */ - String.format("Invalid operator %s used for key %s", - AtomicFormula.Operator.LE, AtomicFormula.Key.PACKAGE_NAME), - () -> new AtomicFormula(AtomicFormula.Key.PACKAGE_NAME, AtomicFormula.Operator.LE, - "test-value")); + public void testIsSatisfiable_string_true() { + StringAtomicFormula stringAtomicFormula = + new StringAtomicFormula(AtomicFormula.PACKAGE_NAME, "com.test.app"); + AppInstallMetadata appInstallMetadata = + getAppInstallMetadataBuilder().setPackageName("com.test.app").build(); + + assertTrue(stringAtomicFormula.isSatisfied(appInstallMetadata)); + } + + @Test + public void testIsSatisfiable_string_false() { + StringAtomicFormula stringAtomicFormula = + new StringAtomicFormula(AtomicFormula.PACKAGE_NAME, "com.test.app"); + AppInstallMetadata appInstallMetadata = + getAppInstallMetadataBuilder().setPackageName("com.foo.bar").build(); + + assertFalse(stringAtomicFormula.isSatisfied(appInstallMetadata)); + } + + @Test + public void testIsSatisfiable_int_eq_true() { + IntAtomicFormula intAtomicFormula = + new IntAtomicFormula(AtomicFormula.VERSION_CODE, AtomicFormula.EQ, 0); + AppInstallMetadata appInstallMetadata = + getAppInstallMetadataBuilder().setVersionCode(0).build(); + + assertTrue(intAtomicFormula.isSatisfied(appInstallMetadata)); + } + + @Test + public void testIsSatisfiable_int_eq_false() { + IntAtomicFormula intAtomicFormula = + new IntAtomicFormula(AtomicFormula.VERSION_CODE, AtomicFormula.EQ, 0); + AppInstallMetadata appInstallMetadata = + getAppInstallMetadataBuilder().setVersionCode(1).build(); + + assertFalse(intAtomicFormula.isSatisfied(appInstallMetadata)); + } + + @Test + public void testIsSatisfiable_int_gt_true() { + IntAtomicFormula intAtomicFormula = + new IntAtomicFormula(AtomicFormula.VERSION_CODE, AtomicFormula.GT, 0); + AppInstallMetadata appInstallMetadata = + getAppInstallMetadataBuilder().setVersionCode(1).build(); + + assertTrue(intAtomicFormula.isSatisfied(appInstallMetadata)); + } + + @Test + public void testIsSatisfiable_int_gt_false() { + IntAtomicFormula intAtomicFormula = + new IntAtomicFormula(AtomicFormula.VERSION_CODE, AtomicFormula.GT, 1); + AppInstallMetadata appInstallMetadata = + getAppInstallMetadataBuilder().setVersionCode(0).build(); + + assertFalse(intAtomicFormula.isSatisfied(appInstallMetadata)); + } + + @Test + public void testIsSatisfiable_int_ge_true() { + IntAtomicFormula intAtomicFormula = + new IntAtomicFormula(AtomicFormula.VERSION_CODE, AtomicFormula.GE, 0); + AppInstallMetadata appInstallMetadata = + getAppInstallMetadataBuilder().setVersionCode(1).build(); + + assertTrue(intAtomicFormula.isSatisfied(appInstallMetadata)); + } + + @Test + public void testIsSatisfiable_int_ge_false() { + IntAtomicFormula intAtomicFormula = + new IntAtomicFormula(AtomicFormula.VERSION_CODE, AtomicFormula.GE, 1); + AppInstallMetadata appInstallMetadata = + getAppInstallMetadataBuilder().setVersionCode(0).build(); + + assertFalse(intAtomicFormula.isSatisfied(appInstallMetadata)); + } + + @Test + public void testIsSatisfiable_int_lt_true() { + IntAtomicFormula intAtomicFormula = + new IntAtomicFormula(AtomicFormula.VERSION_CODE, AtomicFormula.LT, 1); + AppInstallMetadata appInstallMetadata = + getAppInstallMetadataBuilder().setVersionCode(0).build(); + + assertTrue(intAtomicFormula.isSatisfied(appInstallMetadata)); + } + + @Test + public void testIsSatisfiable_int_lt_false() { + IntAtomicFormula intAtomicFormula = + new IntAtomicFormula(AtomicFormula.VERSION_CODE, AtomicFormula.LT, 1); + AppInstallMetadata appInstallMetadata = + getAppInstallMetadataBuilder().setVersionCode(2).build(); + + assertFalse(intAtomicFormula.isSatisfied(appInstallMetadata)); + } + + @Test + public void testIsSatisfiable_int_le_true() { + IntAtomicFormula intAtomicFormula = + new IntAtomicFormula(AtomicFormula.VERSION_CODE, AtomicFormula.LE, 1); + AppInstallMetadata appInstallMetadata = + getAppInstallMetadataBuilder().setVersionCode(0).build(); + + assertTrue(intAtomicFormula.isSatisfied(appInstallMetadata)); + } + + @Test + public void testIsSatisfiable_int_le_false() { + IntAtomicFormula intAtomicFormula = + new IntAtomicFormula(AtomicFormula.VERSION_CODE, AtomicFormula.LE, 1); + AppInstallMetadata appInstallMetadata = + getAppInstallMetadataBuilder().setVersionCode(2).build(); + + assertFalse(intAtomicFormula.isSatisfied(appInstallMetadata)); + } + + @Test + public void testIsSatisfiable_bool_true() { + BooleanAtomicFormula boolFormula = + new BooleanAtomicFormula(AtomicFormula.PRE_INSTALLED, true); + AppInstallMetadata appInstallMetadata = + getAppInstallMetadataBuilder().setIsPreInstalled(true).build(); + + assertTrue(boolFormula.isSatisfied(appInstallMetadata)); + } + + @Test + public void testIsSatisfiable_bool_false() { + BooleanAtomicFormula boolFormula = + new BooleanAtomicFormula(AtomicFormula.PRE_INSTALLED, true); + AppInstallMetadata appInstallMetadata = + getAppInstallMetadataBuilder().setIsPreInstalled(false).build(); + + assertFalse(boolFormula.isSatisfied(appInstallMetadata)); + } + + @Test + public void testParcelUnparcel_string() { + StringAtomicFormula formula = new StringAtomicFormula(AtomicFormula.PACKAGE_NAME, "abc"); + Parcel p = Parcel.obtain(); + formula.writeToParcel(p, 0); + p.setDataPosition(0); + StringAtomicFormula newFormula = StringAtomicFormula.CREATOR.createFromParcel(p); + + assertEquals(formula, newFormula); + } + + @Test + public void testParcelUnparcel_int() { + IntAtomicFormula formula = + new IntAtomicFormula(AtomicFormula.VERSION_CODE, AtomicFormula.LT, 1); + Parcel p = Parcel.obtain(); + formula.writeToParcel(p, 0); + p.setDataPosition(0); + IntAtomicFormula newFormula = IntAtomicFormula.CREATOR.createFromParcel(p); + + assertEquals(formula, newFormula); + } + + @Test + public void testParcelUnparcel_bool() { + BooleanAtomicFormula formula = new BooleanAtomicFormula(AtomicFormula.PRE_INSTALLED, true); + Parcel p = Parcel.obtain(); + formula.writeToParcel(p, 0); + p.setDataPosition(0); + BooleanAtomicFormula newFormula = BooleanAtomicFormula.CREATOR.createFromParcel(p); + + assertEquals(formula, newFormula); + } + + /** 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); } } diff --git a/services/tests/servicestests/src/com/android/server/integrity/model/OpenFormulaTest.java b/services/tests/servicestests/src/com/android/server/integrity/model/OpenFormulaTest.java index 2133a7d3550b..ecabb5276790 100644 --- a/services/tests/servicestests/src/com/android/server/integrity/model/OpenFormulaTest.java +++ b/services/tests/servicestests/src/com/android/server/integrity/model/OpenFormulaTest.java @@ -19,6 +19,10 @@ package com.android.server.integrity.model; import static com.android.server.testutils.TestUtils.assertExpectException; import static org.junit.Assert.assertEquals; +import static org.junit.Assert.assertFalse; +import static org.junit.Assert.assertTrue; + +import android.os.Parcel; import org.junit.Test; import org.junit.runner.RunWith; @@ -30,17 +34,17 @@ import java.util.Collections; @RunWith(JUnit4.class) public class OpenFormulaTest { - private static final AtomicFormula ATOMIC_FORMULA_1 = new AtomicFormula( - AtomicFormula.Key.PACKAGE_NAME, AtomicFormula.Operator.EQ, "test1"); - private static final AtomicFormula ATOMIC_FORMULA_2 = new AtomicFormula( - AtomicFormula.Key.VERSION_CODE, AtomicFormula.Operator.EQ, 1); + private static final AtomicFormula ATOMIC_FORMULA_1 = + new AtomicFormula.StringAtomicFormula(AtomicFormula.PACKAGE_NAME, "test1"); + private static final AtomicFormula ATOMIC_FORMULA_2 = + new AtomicFormula.IntAtomicFormula(AtomicFormula.VERSION_CODE, AtomicFormula.EQ, 1); @Test public void testValidOpenFormula() { - OpenFormula openFormula = new OpenFormula(OpenFormula.Connector.AND, - Arrays.asList(ATOMIC_FORMULA_1, ATOMIC_FORMULA_2)); + OpenFormula openFormula = + new OpenFormula(OpenFormula.AND, Arrays.asList(ATOMIC_FORMULA_1, ATOMIC_FORMULA_2)); - assertEquals(OpenFormula.Connector.AND, openFormula.getConnector()); + assertEquals(OpenFormula.AND, openFormula.getConnector()); assertEquals(Arrays.asList(ATOMIC_FORMULA_1, ATOMIC_FORMULA_2), openFormula.getFormulas()); } @@ -49,10 +53,10 @@ public class OpenFormulaTest { assertExpectException( IllegalArgumentException.class, /* expectedExceptionMessageRegex */ - String.format("Connector %s must have at least 2 formulas", - OpenFormula.Connector.AND), - () -> new OpenFormula(OpenFormula.Connector.AND, - Collections.singletonList(ATOMIC_FORMULA_1))); + String.format("Connector AND must have at least 2 formulas"), + () -> + new OpenFormula( + OpenFormula.AND, Collections.singletonList(ATOMIC_FORMULA_1))); } @Test @@ -60,8 +64,159 @@ public class OpenFormulaTest { assertExpectException( IllegalArgumentException.class, /* expectedExceptionMessageRegex */ - String.format("Connector %s must have 1 formula only", OpenFormula.Connector.NOT), - () -> new OpenFormula(OpenFormula.Connector.NOT, - Arrays.asList(ATOMIC_FORMULA_1, ATOMIC_FORMULA_2))); + String.format("Connector NOT must have 1 formula only"), + () -> + new OpenFormula( + OpenFormula.NOT, + Arrays.asList(ATOMIC_FORMULA_1, ATOMIC_FORMULA_2))); + } + + @Test + public void testIsSatisfiable_notFalse_true() { + OpenFormula openFormula = new OpenFormula(OpenFormula.NOT, Arrays.asList(ATOMIC_FORMULA_1)); + AppInstallMetadata appInstallMetadata = + getAppInstallMetadataBuilder().setPackageName("test2").build(); + // validate assumptions about the metadata + assertFalse(ATOMIC_FORMULA_1.isSatisfied(appInstallMetadata)); + + assertTrue(openFormula.isSatisfied(appInstallMetadata)); + } + + @Test + public void testIsSatisfiable_notTrue_false() { + OpenFormula openFormula = new OpenFormula(OpenFormula.NOT, Arrays.asList(ATOMIC_FORMULA_1)); + AppInstallMetadata appInstallMetadata = + getAppInstallMetadataBuilder().setPackageName("test1").build(); + // validate assumptions about the metadata + assertTrue(ATOMIC_FORMULA_1.isSatisfied(appInstallMetadata)); + + assertFalse(openFormula.isSatisfied(appInstallMetadata)); + } + + @Test + public void testIsSatisfiable_trueAndTrue_true() { + OpenFormula openFormula = + new OpenFormula(OpenFormula.AND, Arrays.asList(ATOMIC_FORMULA_1, ATOMIC_FORMULA_2)); + AppInstallMetadata appInstallMetadata = + getAppInstallMetadataBuilder().setPackageName("test1").setVersionCode(1).build(); + // validate assumptions about the metadata + assertTrue(ATOMIC_FORMULA_1.isSatisfied(appInstallMetadata)); + assertTrue(ATOMIC_FORMULA_2.isSatisfied(appInstallMetadata)); + + assertTrue(openFormula.isSatisfied(appInstallMetadata)); + } + + @Test + public void testIsSatisfiable_trueAndFalse_false() { + OpenFormula openFormula = + new OpenFormula(OpenFormula.AND, Arrays.asList(ATOMIC_FORMULA_1, ATOMIC_FORMULA_2)); + AppInstallMetadata appInstallMetadata = + getAppInstallMetadataBuilder().setPackageName("test1").setVersionCode(2).build(); + // validate assumptions about the metadata + assertTrue(ATOMIC_FORMULA_1.isSatisfied(appInstallMetadata)); + assertFalse(ATOMIC_FORMULA_2.isSatisfied(appInstallMetadata)); + + assertFalse(openFormula.isSatisfied(appInstallMetadata)); + } + + @Test + public void testIsSatisfiable_falseAndTrue_false() { + OpenFormula openFormula = + new OpenFormula(OpenFormula.AND, Arrays.asList(ATOMIC_FORMULA_1, ATOMIC_FORMULA_2)); + AppInstallMetadata appInstallMetadata = + getAppInstallMetadataBuilder().setPackageName("test2").setVersionCode(1).build(); + // validate assumptions about the metadata + assertFalse(ATOMIC_FORMULA_1.isSatisfied(appInstallMetadata)); + assertTrue(ATOMIC_FORMULA_2.isSatisfied(appInstallMetadata)); + + assertFalse(openFormula.isSatisfied(appInstallMetadata)); + } + + @Test + public void testIsSatisfiable_falseAndFalse_false() { + OpenFormula openFormula = + new OpenFormula(OpenFormula.AND, Arrays.asList(ATOMIC_FORMULA_1, ATOMIC_FORMULA_2)); + AppInstallMetadata appInstallMetadata = + getAppInstallMetadataBuilder().setPackageName("test2").setVersionCode(2).build(); + // validate assumptions about the metadata + assertFalse(ATOMIC_FORMULA_1.isSatisfied(appInstallMetadata)); + assertFalse(ATOMIC_FORMULA_2.isSatisfied(appInstallMetadata)); + + assertFalse(openFormula.isSatisfied(appInstallMetadata)); + } + + @Test + public void testIsSatisfiable_trueOrTrue_true() { + OpenFormula openFormula = + new OpenFormula(OpenFormula.OR, Arrays.asList(ATOMIC_FORMULA_1, ATOMIC_FORMULA_2)); + AppInstallMetadata appInstallMetadata = + getAppInstallMetadataBuilder().setPackageName("test1").setVersionCode(1).build(); + // validate assumptions about the metadata + assertTrue(ATOMIC_FORMULA_1.isSatisfied(appInstallMetadata)); + assertTrue(ATOMIC_FORMULA_2.isSatisfied(appInstallMetadata)); + + assertTrue(openFormula.isSatisfied(appInstallMetadata)); + } + + @Test + public void testIsSatisfiable_trueOrFalse_true() { + OpenFormula openFormula = + new OpenFormula(OpenFormula.OR, Arrays.asList(ATOMIC_FORMULA_1, ATOMIC_FORMULA_2)); + AppInstallMetadata appInstallMetadata = + getAppInstallMetadataBuilder().setPackageName("test1").setVersionCode(2).build(); + // validate assumptions about the metadata + assertTrue(ATOMIC_FORMULA_1.isSatisfied(appInstallMetadata)); + assertFalse(ATOMIC_FORMULA_2.isSatisfied(appInstallMetadata)); + + assertTrue(openFormula.isSatisfied(appInstallMetadata)); + } + + @Test + public void testIsSatisfiable_falseOrTrue_true() { + OpenFormula openFormula = + new OpenFormula(OpenFormula.OR, Arrays.asList(ATOMIC_FORMULA_1, ATOMIC_FORMULA_2)); + AppInstallMetadata appInstallMetadata = + getAppInstallMetadataBuilder().setPackageName("test2").setVersionCode(1).build(); + // validate assumptions about the metadata + assertFalse(ATOMIC_FORMULA_1.isSatisfied(appInstallMetadata)); + assertTrue(ATOMIC_FORMULA_2.isSatisfied(appInstallMetadata)); + + assertTrue(openFormula.isSatisfied(appInstallMetadata)); + } + + @Test + public void testIsSatisfiable_falseOrFalse_false() { + OpenFormula openFormula = + new OpenFormula(OpenFormula.OR, Arrays.asList(ATOMIC_FORMULA_1, ATOMIC_FORMULA_2)); + AppInstallMetadata appInstallMetadata = + getAppInstallMetadataBuilder().setPackageName("test2").setVersionCode(2).build(); + // validate assumptions about the metadata + assertFalse(ATOMIC_FORMULA_1.isSatisfied(appInstallMetadata)); + assertFalse(ATOMIC_FORMULA_2.isSatisfied(appInstallMetadata)); + + assertFalse(openFormula.isSatisfied(appInstallMetadata)); + } + + @Test + public void testParcelUnparcel() { + OpenFormula formula = + new OpenFormula(OpenFormula.AND, Arrays.asList(ATOMIC_FORMULA_2, ATOMIC_FORMULA_1)); + Parcel p = Parcel.obtain(); + formula.writeToParcel(p, 0); + p.setDataPosition(0); + OpenFormula newFormula = OpenFormula.CREATOR.createFromParcel(p); + + assertEquals(formula, newFormula); + } + + /** 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); } } diff --git a/services/tests/servicestests/src/com/android/server/integrity/model/RuleTest.java b/services/tests/servicestests/src/com/android/server/integrity/model/RuleTest.java index 048ee707d8fe..e0c36fdfc546 100644 --- a/services/tests/servicestests/src/com/android/server/integrity/model/RuleTest.java +++ b/services/tests/servicestests/src/com/android/server/integrity/model/RuleTest.java @@ -20,7 +20,8 @@ import static com.android.server.testutils.TestUtils.assertExpectException; import static org.junit.Assert.assertEquals; import static org.junit.Assert.assertNotEquals; -import static org.junit.Assert.assertNull; + +import android.os.Parcel; import org.junit.Test; import org.junit.runner.RunWith; @@ -31,23 +32,13 @@ import java.util.Arrays; @RunWith(JUnit4.class) public class RuleTest { - private static final Rule.Effect DENY_EFFECT = Rule.Effect.DENY; + private static final @Rule.Effect int DENY_EFFECT = Rule.DENY; private static final String PACKAGE_NAME = "com.test.app"; private static final String APP_CERTIFICATE = "test_cert"; private static final Formula PACKAGE_NAME_ATOMIC_FORMULA = - new AtomicFormula(AtomicFormula.Key.PACKAGE_NAME, AtomicFormula.Operator.EQ, - PACKAGE_NAME); + new AtomicFormula.StringAtomicFormula(AtomicFormula.PACKAGE_NAME, PACKAGE_NAME); private static final Formula APP_CERTIFICATE_ATOMIC_FORMULA = - new AtomicFormula(AtomicFormula.Key.APP_CERTIFICATE, AtomicFormula.Operator.EQ, - APP_CERTIFICATE); - - @Test - public void testEmptyRule() { - Rule emptyRule = Rule.EMPTY; - - assertNull(emptyRule.getFormula()); - assertNull(emptyRule.getEffect()); - } + new AtomicFormula.StringAtomicFormula(AtomicFormula.APP_CERTIFICATE, APP_CERTIFICATE); @Test public void testValidRule() { @@ -58,14 +49,6 @@ public class RuleTest { } @Test - public void testInvalidRule_invalidEffect() { - assertExpectException( - NullPointerException.class, - /* expectedExceptionMessageRegex */ null, - () -> new Rule(PACKAGE_NAME_ATOMIC_FORMULA, null)); - } - - @Test public void testInvalidRule_invalidFormula() { assertExpectException( NullPointerException.class, @@ -75,14 +58,17 @@ public class RuleTest { @Test public void testToString() { - OpenFormula openFormula = new OpenFormula(OpenFormula.Connector.AND, - Arrays.asList(PACKAGE_NAME_ATOMIC_FORMULA, APP_CERTIFICATE_ATOMIC_FORMULA)); - Rule rule = new Rule(openFormula, Rule.Effect.DENY); - - String toString = rule.toString(); - - assertEquals(String.format("Rule: PACKAGE_NAME EQ %s AND APP_CERTIFICATE EQ %s, DENY", - PACKAGE_NAME, APP_CERTIFICATE), toString); + OpenFormula openFormula = + new OpenFormula( + OpenFormula.AND, + Arrays.asList(PACKAGE_NAME_ATOMIC_FORMULA, APP_CERTIFICATE_ATOMIC_FORMULA)); + Rule rule = new Rule(openFormula, Rule.DENY); + + assertEquals( + String.format( + "Rule: (PACKAGE_NAME EQ %s) AND (APP_CERTIFICATE EQ %s), DENY", + PACKAGE_NAME, APP_CERTIFICATE), + rule.toString()); } @Test @@ -100,4 +86,24 @@ public class RuleTest { assertNotEquals(rule1, rule2); } + + @Test + public void testParcelUnparcel() { + Rule rule = + new Rule( + new OpenFormula( + OpenFormula.AND, + Arrays.asList( + APP_CERTIFICATE_ATOMIC_FORMULA, + new OpenFormula( + OpenFormula.NOT, + Arrays.asList(PACKAGE_NAME_ATOMIC_FORMULA)))), + Rule.DENY); + Parcel p = Parcel.obtain(); + rule.writeToParcel(p, 0); + p.setDataPosition(0); + Rule newRule = Rule.CREATOR.createFromParcel(p); + + assertEquals(newRule, rule); + } } |