From 7f7c3b8229b245bc4928cb4a52c0db0e52609f21 Mon Sep 17 00:00:00 2001
From: Dan Cashman <dcashman@google.com>
Date: Tue, 25 Jul 2017 13:09:33 -0700
Subject: [PATCH] Add 26.0 api compatibility check infrastructure.

Add support to the treble_sepolicy_tests suite that explicitly look at
the old and current policy versions, as well as the compatibility file,
to determine if any new types have been added without a compatibility
entry.  This first test catches the most common and likely changes that
could change the type label of an object for which vendor policy may have
needed access.  It also should prove the basis for additional compatibility
checks between old and new policies.

Bug: 36899958
Test: Policy builds and tests pass.
Change-Id: I609c913e6354eb10a04cc1a029ddd9fa0e592a4c
---
 Android.mk                     | 107 ++++++++++++++++++++++++++++++---
 tests/Android.bp               |   8 ++-
 tests/mini_parser.py           | 100 ++++++++++++++++++++++++++++++
 tests/policy.py                |  22 +++++++
 tests/sepol_wrap.cpp           |  81 ++++++++++++++++---------
 tests/treble_sepolicy_tests.py |  86 +++++++++++++++++++++++---
 6 files changed, 355 insertions(+), 49 deletions(-)
 create mode 100644 tests/mini_parser.py

diff --git a/Android.mk b/Android.mk
index 8ec1ebf2b..0503b5544 100644
--- a/Android.mk
+++ b/Android.mk
@@ -1155,30 +1155,121 @@ LOCAL_MODULE_TAGS := tests
 
 include $(BUILD_SYSTEM)/base_rules.mk
 
-# 26.0_compat - the current plat_sepolicy.cil built
-# with the compatibility file targeting the 26.0
-# SELinux release.
+# 26.0_plat - the platform policy shipped as part of the 26.0 release.  This is
+# built to enable us to determine the diff between the current policy and the
+# 26.0 policy, which will be used in tests to make sure that compatibility has
+# been maintained by our mapping files.
+26.0_PLAT_PUBLIC_POLICY := $(LOCAL_PATH)/prebuilts/api/26.0/public
+26.0_PLAT_PRIVATE_POLICY := $(LOCAL_PATH)/prebuilts/api/26.0/private
+26.0_plat_policy.conf := $(intermediates)/26.0_plat_policy.conf
+$(26.0_plat_policy.conf): PRIVATE_MLS_SENS := $(MLS_SENS)
+$(26.0_plat_policy.conf): PRIVATE_MLS_CATS := $(MLS_CATS)
+$(26.0_plat_policy.conf): PRIVATE_TGT_ARCH := $(my_target_arch)
+$(26.0_plat_policy.conf): PRIVATE_TGT_WITH_ASAN := $(with_asan)
+$(26.0_plat_policy.conf): PRIVATE_ADDITIONAL_M4DEFS := $(LOCAL_ADDITIONAL_M4DEFS)
+$(26.0_plat_policy.conf): PRIVATE_FULL_TREBLE := true
+$(26.0_plat_policy.conf): $(call build_policy, $(sepolicy_build_files), \
+$(26.0_PLAT_PUBLIC_POLICY) $(26.0_PLAT_PRIVATE_POLICY))
+	$(transform-policy-to-conf)
+	$(hide) sed '/dontaudit/d' $@ > $@.dontaudit
+
+built_26.0_plat_sepolicy := $(intermediates)/built_26.0_plat_sepolicy
+$(built_26.0_plat_sepolicy): PRIVATE_ADDITIONAL_CIL_FILES := \
+  $(call build_policy, technical_debt.cil , $(26.0_PLAT_PRIVATE_POLICY))
+$(built_26.0_plat_sepolicy): $(26.0_plat_policy.conf) $(HOST_OUT_EXECUTABLES)/checkpolicy \
+  $(HOST_OUT_EXECUTABLES)/secilc \
+  $(call build_policy, technical_debt.cil, $(26.0_PLAT_PRIVATE_POLICY))
+	@mkdir -p $(dir $@)
+	$(hide) $(CHECKPOLICY_ASAN_OPTIONS) $(HOST_OUT_EXECUTABLES)/checkpolicy -M -C -c \
+		$(POLICYVERS) -o $@ $<
+	$(hide) cat $(PRIVATE_ADDITIONAL_CIL_FILES) >> $@
+	$(hide) $(HOST_OUT_EXECUTABLES)/secilc -M true -G -c $(POLICYVERS) $@ -o $@ -f /dev/null
+
+26.0_plat_policy.conf :=
+
+
+# 26.0_compat - the current plat_sepolicy.cil built with the compatibility file
+# targeting the 26.0 SELinux release.  This ensures that our policy will build
+# when used on a device that has non-platform policy targetting the 26.0 release.
 26.0_compat := $(intermediates)/26.0_compat
-26.0_mapping_cil := $(LOCAL_PATH)/prebuilts/api/26.0/26.0.cil
+26.0_mapping.cil := $(LOCAL_PATH)/prebuilts/api/26.0/26.0.cil
+26.0_mapping.ignore.cil := $(LOCAL_PATH)/prebuilts/api/26.0/26.0.ignore.cil
 26.0_nonplat := $(LOCAL_PATH)/prebuilts/api/26.0/nonplat_sepolicy.cil
 $(26.0_compat): PRIVATE_CIL_FILES := \
-$(built_plat_cil) $(26.0_mapping_cil) $(26.0_nonplat)
+$(built_plat_cil) $(26.0_mapping.cil) $(26.0_nonplat)
 $(26.0_compat): $(HOST_OUT_EXECUTABLES)/secilc \
-$(built_plat_cil) $(26.0_mapping_cil) $(26.0_nonplat)
+$(built_plat_cil) $(26.0_mapping.cil) $(26.0_nonplat)
 	$(hide) $(HOST_OUT_EXECUTABLES)/secilc -M true -G -N -c $(POLICYVERS) \
 		$(PRIVATE_CIL_FILES) -o $@ -f /dev/null
 
+# 26.0_mapping.combined.cil - a combination of the mapping file used when
+# combining the current platform policy with nonplatform policy based on the
+# 26.0 policy release and also a special ignored file that exists purely for
+# these tests.
+26.0_mapping.combined.cil := $(intermediates)/26.0_mapping.combined.cil
+$(26.0_mapping.combined.cil): $(26.0_mapping.cil) $(26.0_mapping.ignore.cil)
+	mkdir -p $(dir $@)
+	cat $^ > $@
+
+# plat_sepolicy - the current platform policy only, built into a policy binary.
+# TODO - this currently excludes partner extensions, but support should be added
+# to enable partners to add their own compatibility mapping
+BASE_PLAT_PUBLIC_POLICY := $(filter-out $(BOARD_PLAT_PUBLIC_SEPOLICY_DIR), $(PLAT_PUBLIC_POLICY))
+BASE_PLAT_PRIVATE_POLICY := $(filter-out $(BOARD_PLAT_PRIVATE_SEPOLICY_DIR), $(PLAT_PRIVATE_POLICY))
+base_plat_policy.conf := $(intermediates)/base_plat_policy.conf
+$(base_plat_policy.conf): PRIVATE_MLS_SENS := $(MLS_SENS)
+$(base_plat_policy.conf): PRIVATE_MLS_CATS := $(MLS_CATS)
+$(base_plat_policy.conf): PRIVATE_TGT_ARCH := $(my_target_arch)
+$(base_plat_policy.conf): PRIVATE_TGT_WITH_ASAN := $(with_asan)
+$(base_plat_policy.conf): PRIVATE_ADDITIONAL_M4DEFS := $(LOCAL_ADDITIONAL_M4DEFS)
+$(base_plat_policy.conf): PRIVATE_FULL_TREBLE := true
+$(base_plat_policy.conf): $(call build_policy, $(sepolicy_build_files), \
+$(BASE_PLAT_PUBLIC_POLICY) $(BASE_PLAT_PRIVATE_POLICY))
+	$(transform-policy-to-conf)
+	$(hide) sed '/dontaudit/d' $@ > $@.dontaudit
+
+built_plat_sepolicy := $(intermediates)/built_plat_sepolicy
+$(built_plat_sepolicy): PRIVATE_ADDITIONAL_CIL_FILES := \
+  $(call build_policy, $(sepolicy_build_cil_workaround_files), $(BASE_PLAT_PRIVATE_POLICY))
+$(built_plat_sepolicy): $(base_plat_policy.conf) $(HOST_OUT_EXECUTABLES)/checkpolicy \
+$(HOST_OUT_EXECUTABLES)/secilc \
+$(call build_policy, $(sepolicy_build_cil_workaround_files), $(BASE_PLAT_PRIVATE_POLICY))
+	@mkdir -p $(dir $@)
+	$(hide) $(CHECKPOLICY_ASAN_OPTIONS) $(HOST_OUT_EXECUTABLES)/checkpolicy -M -C -c \
+		$(POLICYVERS) -o $@ $<
+	$(hide) cat $(PRIVATE_ADDITIONAL_CIL_FILES) >> $@
+	$(hide) $(HOST_OUT_EXECUTABLES)/secilc -M true -G -c $(POLICYVERS) $@ -o $@ -f /dev/null
+
 treble_sepolicy_tests := $(intermediates)/treble_sepolicy_tests
 $(treble_sepolicy_tests): PRIVATE_PLAT_FC := $(built_plat_fc)
 $(treble_sepolicy_tests): PRIVATE_NONPLAT_FC := $(built_nonplat_fc)
 $(treble_sepolicy_tests): PRIVATE_SEPOLICY := $(built_sepolicy)
+$(treble_sepolicy_tests): PRIVATE_SEPOLICY_OLD := $(built_26.0_plat_sepolicy)
+$(treble_sepolicy_tests): PRIVATE_COMBINED_MAPPING := $(26.0_mapping.combined.cil)
+$(treble_sepolicy_tests): PRIVATE_PLAT_SEPOLICY := $(built_plat_sepolicy)
 $(treble_sepolicy_tests): $(HOST_OUT_EXECUTABLES)/treble_sepolicy_tests.py \
-$(built_plat_fc) $(built_nonplat_fc) $(built_sepolicy) $(26.0_compat)
+$(built_plat_fc) $(built_nonplat_fc) $(built_sepolicy) $(built_plat_sepolicy) \
+$(built_26.0_plat_sepolicy) $(26.0_compat) $(26.0_mapping.combined.cil)
 	@mkdir -p $(dir $@)
-	$(hide) python $(HOST_OUT_EXECUTABLES)/treble_sepolicy_tests.py -l $(HOST_OUT)/lib64 -f $(PRIVATE_PLAT_FC) -f $(PRIVATE_NONPLAT_FC) -p $(PRIVATE_SEPOLICY)
+	$(hide) python $(HOST_OUT_EXECUTABLES)/treble_sepolicy_tests.py -l \
+		$(HOST_OUT)/lib64 -f $(PRIVATE_PLAT_FC) -f $(PRIVATE_NONPLAT_FC) \
+		-b $(PRIVATE_PLAT_SEPOLICY) -m $(PRIVATE_COMBINED_MAPPING) \
+		-o $(PRIVATE_SEPOLICY_OLD) -p $(PRIVATE_SEPOLICY)
 	$(hide) touch $@
 
+26.0_PLAT_PUBLIC_POLICY :=
+26.0_PLAT_PRIVATE_POLICY :=
 26.0_compat :=
+26.0_mapping.cil :=
+26.0_mapping.combined.cil :=
+26.0_mapping.ignore.cil :=
+26.0_nonplat :=
+BASE_PLAT_PUBLIC_POLICY :=
+BASE_PLAT_PRIVATE_POLICY :=
+base_plat_policy.conf :=
+built_26.0_plat_sepolicy :=
+plat_sepolicy :=
+
 endif # ($(PRODUCT_FULL_TREBLE),true)
 #################################
 
diff --git a/tests/Android.bp b/tests/Android.bp
index e875497d5..19aca9ccd 100644
--- a/tests/Android.bp
+++ b/tests/Android.bp
@@ -6,6 +6,12 @@ cc_library_host_shared {
     export_include_dirs: ["include"],
 }
 
+cc_prebuilt_binary {
+    name: "mini_parser.py",
+    srcs: ["mini_parser.py"],
+    host_supported: true,
+}
+
 cc_prebuilt_binary {
     name: "policy.py",
     srcs: ["policy.py"],
@@ -17,7 +23,7 @@ cc_prebuilt_binary {
     name: "treble_sepolicy_tests.py",
     srcs: ["treble_sepolicy_tests.py"],
     host_supported: true,
-    required: ["policy.py"],
+    required: ["mini_parser.py", "policy.py"],
 }
 
 cc_prebuilt_binary {
diff --git a/tests/mini_parser.py b/tests/mini_parser.py
new file mode 100644
index 000000000..57b3d59f8
--- /dev/null
+++ b/tests/mini_parser.py
@@ -0,0 +1,100 @@
+from os.path import basename
+import re
+import sys
+
+# A very limited parser whose job is to process the compatibility mapping
+# files and retrieve type and attribute information until proper support is
+# built into libsepol
+
+# get the text in the next matching parens
+
+class MiniCilParser:
+    types = set() # types declared in mapping
+    pubtypes = set()
+    typeattributes = set() # attributes declared in mapping
+    typeattributesets = {} # sets defined in mapping
+    rTypeattributesets = {} # reverse mapping of above sets
+    apiLevel = None
+
+    def _getNextStmt(self, infile):
+        parens = 0
+        s = ""
+        c = infile.read(1)
+        # get to first statement
+        while c and c != "(":
+            c = infile.read(1)
+
+        parens += 1
+        c = infile.read(1)
+        while c and parens != 0:
+            s += c
+            c = infile.read(1)
+            if c == ';':
+                # comment, get rid of rest of the line
+                while c != '\n':
+                    c = infile.read(1)
+            elif c == '(':
+                parens += 1
+            elif c == ')':
+                parens -= 1
+        return s
+
+    def _parseType(self, stmt):
+        m = re.match(r"type\s+(.+)", stmt)
+        self.types.update(set(m.group(1)))
+        return
+
+    def _parseTypeattribute(self, stmt):
+        m = re.match(r"typeattribute\s+(.+)", stmt)
+        self.typeattributes.update(set(m.group(1)))
+        return
+
+    def _parseTypeattributeset(self, stmt):
+        m = re.match(r"typeattributeset\s+(.+?)\s+\((.+?)\)", stmt, flags = re.M |re.S)
+        ta = m.group(1)
+        # this isn't proper expression parsing, but will do for our
+        # current use
+        tas = m.group(2).split()
+
+        if self.typeattributesets.get(ta) is None:
+            self.typeattributesets[ta] = set()
+        self.typeattributesets[ta].update(set(tas))
+        for t in tas:
+            if self.rTypeattributesets.get(t) is None:
+                self.rTypeattributesets[t] = set()
+            self.rTypeattributesets[t].update(set(ta))
+
+        # check to see if this typeattributeset is a versioned public type
+        pub = re.match(r"(\w+)_\d+_\d+", ta)
+        if pub is not None:
+            self.pubtypes.update(set(pub.group(1)))
+        return
+
+    def _parseStmt(self, stmt):
+        if re.match(r"type\s+.+", stmt):
+            self._parseType(stmt)
+        elif re.match(r"typeattribute\s+.+", stmt):
+            self._parseTypeattribute(stmt)
+        elif re.match(r"typeattributeset\s+.+", stmt):
+            self._parseTypeattributeset(stmt)
+        else:
+            m = re.match(r"(\w+)\s+.+", stmt)
+            ret = "Warning: Unknown statement type (" + m.group(1) + ") in "
+            ret += "mapping file, perhaps consider adding support for it in "
+            ret += "system/sepolicy/tests/mini_parser.py!\n"
+            print ret
+        return
+
+    def __init__(self, policyFile):
+        with open(policyFile, 'r') as infile:
+            s = self._getNextStmt(infile)
+            while s:
+                self._parseStmt(s)
+                s = self._getNextStmt(infile)
+        fn = basename(policyFile)
+        m = re.match(r"(\d+\.\d+).+\.cil", fn)
+        self.apiLevel = m.group(1)
+
+if __name__ == '__main__':
+    f = sys.argv[1]
+    p = MiniCilParser(f)
diff --git a/tests/policy.py b/tests/policy.py
index b70b836d0..15a537ec3 100644
--- a/tests/policy.py
+++ b/tests/policy.py
@@ -123,6 +123,26 @@ class Policy:
                 continue
             yield Rule
 
+
+    def GetAllTypes(self):
+        TypeIterP = self.__libsepolwrap.init_type_iter(self.__policydbP, None, False)
+        if (TypeIterP == None):
+            sys.exit("Failed to initialize type iterator")
+        buf = create_string_buffer(self.__BUFSIZE)
+        AllTypes = set()
+        while True:
+            ret = self.__libsepolwrap.get_type(buf, self.__BUFSIZE,
+                    self.__policydbP, TypeIterP)
+            if ret == 0:
+                AllTypes.add(buf.value)
+                continue
+            if ret == 1:
+                break;
+            # We should never get here.
+            sys.exit("Failed to import policy")
+        self.__libsepolwrap.destroy_type_iter(TypeIterP)
+        return AllTypes
+
     def __GetTypesByFilePathPrefix(self, MatchPrefixes, DoNotMatchPrefixes):
         Types = set()
         for Type in self.__FcDict:
@@ -203,6 +223,8 @@ class Policy:
 
     # load file_contexts
     def __InitFC(self, FcPaths):
+        if FcPaths is None:
+            return
         fc = []
         for path in FcPaths:
             if not os.path.exists(path):
diff --git a/tests/sepol_wrap.cpp b/tests/sepol_wrap.cpp
index a12d4383d..cd5336795 100644
--- a/tests/sepol_wrap.cpp
+++ b/tests/sepol_wrap.cpp
@@ -17,8 +17,11 @@
 #include <android-base/strings.h>
 #include <sepol_wrap.h>
 
-
+#define TYPE_ITER_LOOKUP   0
+#define TYPE_ITER_ALLTYPES 1
+#define TYPE_ITER_ALLATTRS 2
 struct type_iter {
+    unsigned int alltypes;
     type_datum *d;
     ebitmap_node *n;
     unsigned int length;
@@ -36,23 +39,33 @@ void *init_type_iter(void *policydbp, const char *type, bool is_attr)
         return NULL;
     }
 
-    out->d = static_cast<type_datum *>(hashtab_search(db->p_types.table, type));
-    if (is_attr && out->d->flavor != TYPE_ATTRIB) {
-        std::cerr << "\"" << type << "\" MUST be an attribute in the policy" << std::endl;
-        free(out);
-        return NULL;
-    } else if (!is_attr && out->d->flavor !=TYPE_TYPE) {
-        std::cerr << "\"" << type << "\" MUST be a type in the policy" << std::endl;
-        free(out);
-        return NULL;
-    }
-
-    if (is_attr) {
-        out->bit = ebitmap_start(&db->attr_type_map[out->d->s.value - 1], &out->n);
-        out->length = ebitmap_length(&db->attr_type_map[out->d->s.value - 1]);
+    if (type == NULL) {
+        out->length = db->p_types.nprim;
+        out->bit = 0;
+        if (is_attr)
+            out->alltypes = TYPE_ITER_ALLATTRS;
+        else
+            out->alltypes = TYPE_ITER_ALLTYPES;
     } else {
-        out->bit = ebitmap_start(&db->type_attr_map[out->d->s.value - 1], &out->n);
-        out->length = ebitmap_length(&db->type_attr_map[out->d->s.value - 1]);
+        out->alltypes = TYPE_ITER_LOOKUP;
+        out->d = static_cast<type_datum *>(hashtab_search(db->p_types.table, type));
+        if (is_attr && out->d->flavor != TYPE_ATTRIB) {
+            std::cerr << "\"" << type << "\" MUST be an attribute in the policy" << std::endl;
+            free(out);
+            return NULL;
+        } else if (!is_attr && out->d->flavor !=TYPE_TYPE) {
+            std::cerr << "\"" << type << "\" MUST be a type in the policy" << std::endl;
+            free(out);
+            return NULL;
+        }
+
+        if (is_attr) {
+            out->bit = ebitmap_start(&db->attr_type_map[out->d->s.value - 1], &out->n);
+            out->length = ebitmap_length(&db->attr_type_map[out->d->s.value - 1]);
+        } else {
+            out->bit = ebitmap_start(&db->type_attr_map[out->d->s.value - 1], &out->n);
+            out->length = ebitmap_length(&db->type_attr_map[out->d->s.value - 1]);
+        }
     }
 
     return static_cast<void *>(out);
@@ -65,7 +78,7 @@ void destroy_type_iter(void *type_iterp)
 }
 
 /*
- * print allow rule into *out buffer.
+ * print type into *out buffer.
  *
  * Returns -1 on error.
  * Returns 0 on successfully reading an avtab entry.
@@ -77,20 +90,28 @@ int get_type(char *out, size_t max_size, void *policydbp, void *type_iterp)
     policydb_t *db = static_cast<policydb_t *>(policydbp);
     struct type_iter *i = static_cast<struct type_iter *>(type_iterp);
 
-    for (; i->bit < i->length; i->bit = ebitmap_next(&i->n, i->bit)) {
-        if (!ebitmap_node_get_bit(i->n, i->bit)) {
-            continue;
-        }
-        len = snprintf(out, max_size, "%s", db->p_type_val_to_name[i->bit]);
-        if (len >= max_size) {
-               std::cerr << "type name exceeds buffer size." << std::endl;
-               return -1;
+    if (!i->alltypes) {
+        for (; i->bit < i->length; i->bit = ebitmap_next(&i->n, i->bit)) {
+            if (!ebitmap_node_get_bit(i->n, i->bit)) {
+                continue;
+            }
+            break;
         }
-        i->bit = ebitmap_next(&i->n, i->bit);
-        return 0;
     }
-
-    return 1;
+    if (i->bit >= i->length)
+        return 1;
+    while ((i->alltypes == TYPE_ITER_ALLATTRS
+            && db->type_val_to_struct[i->bit]->flavor != TYPE_ATTRIB)
+            || (i->alltypes == TYPE_ITER_ALLTYPES
+            && db->type_val_to_struct[i->bit]->flavor != TYPE_TYPE))
+        i->bit++;
+    len = snprintf(out, max_size, "%s", db->p_type_val_to_name[i->bit]);
+    if (len >= max_size) {
+        std::cerr << "type name exceeds buffer size." << std::endl;
+        return -1;
+    }
+    i->alltypes ? i->bit++ : i->bit = ebitmap_next(&i->n, i->bit);
+    return 0;
 }
 
 void *load_policy(const char *policy_path)
diff --git a/tests/treble_sepolicy_tests.py b/tests/treble_sepolicy_tests.py
index b358a14e1..7584cab5a 100644
--- a/tests/treble_sepolicy_tests.py
+++ b/tests/treble_sepolicy_tests.py
@@ -1,6 +1,7 @@
 from optparse import OptionParser
 from optparse import Option, OptionValueError
 import os
+import mini_parser
 import policy
 from policy import MatchPathPrefix
 import re
@@ -70,6 +71,11 @@ coredomains = set()
 appdomains = set()
 vendordomains = set()
 
+# compat vars
+alltypes = set()
+oldalltypes = set()
+compatMapping = None
+
 def GetAllDomains(pol):
     global alldomains
     for result in pol.QueryTypeAttribute("domain", True):
@@ -87,7 +93,6 @@ def GetAppDomains():
             alldomains[d].appdomain = True
             appdomains.add(d)
 
-
 def GetCoreDomains():
     global alldomains
     global coredomains
@@ -147,6 +152,12 @@ def GetAttributes(pol):
         for result in pol.QueryTypeAttribute(domain, False):
             alldomains[domain].attributes.add(result)
 
+def GetAllTypes(pol, oldpol):
+    global alltypes
+    global oldalltypes
+    alltypes = pol.GetAllTypes()
+    oldalltypes = oldpol.GetAllTypes()
+
 def setup(pol):
     GetAllDomains(pol)
     GetAttributes(pol)
@@ -154,6 +165,13 @@ def setup(pol):
     GetAppDomains()
     GetCoreDomains()
 
+# setup for the policy compatibility tests
+def compatSetup(pol, oldpol, mapping):
+    global compatMapping
+
+    GetAllTypes(pol, oldpol)
+    compatMapping = mapping
+
 #############################################################
 # Tests
 #############################################################
@@ -189,6 +207,30 @@ def TestCoredomainViolations():
     return ret
 
 ###
+# Make sure that any new type introduced in the new policy that was not present
+# in the old policy has been recorded in the mapping file.
+def TestNoUnmappedNewTypes():
+    global alltypes
+    global oldalltypes
+    newt = alltypes - oldalltypes
+    ret = ""
+    violators = []
+
+    for n in newt:
+        if compatMapping.rTypeattributesets.get(n) is None:
+            violators.append(n)
+
+    if len(violators) > 0:
+        ret += "SELinux: The following types were found added to the policy "
+        ret += "without an entry into the compatibility mapping file(s) found "
+        ret += "in prebuilts/api/" + compatMapping.apiLevel + "/\n"
+        ret += " ".join(str(x) for x in sorted(violators)) + "\n"
+    return ret
+
+def TestTrebleCompatMapping():
+    ret = TestNoUnmappedNewTypes()
+    return ret
+###
 # extend OptionParser to allow the same option flag to be used multiple times.
 # This is used to allow multiple file_contexts files and tests to be
 # specified.
@@ -205,17 +247,23 @@ class MultipleOption(Option):
         else:
             Option.take_action(self, action, dest, opt, value, values, parser)
 
-Tests = ["CoredomainViolations"]
+Tests = {"CoredomainViolations": TestCoredomainViolations,
+         "TrebleCompatMapping": TestTrebleCompatMapping }
 
 if __name__ == '__main__':
     usage = "treble_sepolicy_tests.py -f nonplat_file_contexts -f "
-    usage +="plat_file_contexts -p policy [--test test] [--help]"
+    usage +="plat_file_contexts -p curr_policy -b base_policy -o old_policy "
+    usage +="-m mapping file [--test test] [--help]"
     parser = OptionParser(option_class=MultipleOption, usage=usage)
+    parser.add_option("-b", "--basepolicy", dest="basepolicy", metavar="FILE")
     parser.add_option("-f", "--file_contexts", dest="file_contexts",
             metavar="FILE", action="extend", type="string")
-    parser.add_option("-p", "--policy", dest="policy", metavar="FILE")
     parser.add_option("-l", "--library-path", dest="libpath", metavar="FILE")
+    parser.add_option("-m", "--mapping", dest="mapping", metavar="FILE")
+    parser.add_option("-o", "--oldpolicy", dest="oldpolicy", metavar="FILE")
+    parser.add_option("-p", "--policy", dest="policy", metavar="FILE")
     parser.add_option("-t", "--test", dest="tests", action="extend",
+
             help="Test options include "+str(Tests))
 
     (options, args) = parser.parse_args()
@@ -225,9 +273,14 @@ if __name__ == '__main__':
     if not os.path.exists(options.libpath):
         sys.exit("Error: library-path " + options.libpath + " does not exist\n"
                 + parser.usage)
-
+    if not options.basepolicy:
+        sys.exit("Must specify the current platform-only policy file\n" + parser.usage)
+    if not options.mapping:
+        sys.exit("Must specify a compatibility mapping file\n" + parser.usage)
+    if not options.oldpolicy:
+        sys.exit("Must specify the previous monolithic policy file\n" + parser.usage)
     if not options.policy:
-        sys.exit("Must specify monolithic policy file\n" + parser.usage)
+        sys.exit("Must specify current monolithic policy file\n" + parser.usage)
     if not os.path.exists(options.policy):
         sys.exit("Error: policy file " + options.policy + " does not exist\n"
                 + parser.usage)
@@ -241,17 +294,30 @@ if __name__ == '__main__':
 
     pol = policy.Policy(options.policy, options.file_contexts, options.libpath)
     setup(pol)
+    basepol = policy.Policy(options.basepolicy, None, options.libpath)
+    oldpol = policy.Policy(options.oldpolicy, None, options.libpath)
+    mapping = mini_parser.MiniCilParser(options.mapping)
+    compatSetup(basepol, oldpol, mapping)
 
     if DEBUG:
         PrintScontexts()
 
     results = ""
     # If an individual test is not specified, run all tests.
-    if ( options.tests is None
-        or ("CoredomainViolations" in options.tests and len(options.tests) == 1)):
-        results += TestCoredomainViolations()
+    if options.tests is None:
+        for t in Tests.values():
+            results += t()
     else:
-        sys.exit("Error: unknown test(s): " + str(options.tests))
+        for tn in options.tests:
+            t = Tests.get(tn)
+            if t:
+                results += t()
+            else:
+                err = "Error: unknown test: " + tn + "\n"
+                err += "Available tests:\n"
+                for tn in Tests.keys():
+                    err += tn + "\n"
+                sys.exit(err)
 
     if len(results) > 0:
         sys.exit(results)
-- 
GitLab