diff --git a/Android.mk b/Android.mk index e96c8a614fe293ef97bc7a34bde8496a831e9015..0f7be612f27f81c0d6dbe1a34ca629216d6fc10c 100644 --- a/Android.mk +++ b/Android.mk @@ -191,7 +191,8 @@ LOCAL_REQUIRED_MODULES += \ plat_and_mapping_sepolicy.cil.sha256 \ secilc \ plat_sepolicy_vers.txt \ - treble_sepolicy_tests + treble_sepolicy_tests \ + sepolicy_tests # Include precompiled policy, unless told otherwise ifneq ($(PRODUCT_PRECOMPILED_SEPOLICY),false) @@ -1158,6 +1159,24 @@ $(all_nonplat_mac_perms_files) nonplat_mac_perms_keys.tmp := all_nonplat_mac_perms_files := +################################# +include $(CLEAR_VARS) +LOCAL_MODULE := sepolicy_tests +LOCAL_MODULE_CLASS := ETC +LOCAL_MODULE_TAGS := tests + +include $(BUILD_SYSTEM)/base_rules.mk + +sepolicy_tests := $(intermediates)/sepolicy_tests +$(sepolicy_tests): PRIVATE_PLAT_FC := $(built_plat_fc) +$(sepolicy_tests): PRIVATE_NONPLAT_FC := $(built_nonplat_fc) +$(sepolicy_tests): PRIVATE_SEPOLICY := $(built_sepolicy) +$(sepolicy_tests): $(HOST_OUT_EXECUTABLES)/sepolicy_tests.py \ +$(built_plat_fc) $(built_nonplat_fc) $(built_sepolicy) + @mkdir -p $(dir $@) + $(hide) python $(HOST_OUT_EXECUTABLES)/sepolicy_tests.py -l $(HOST_OUT)/lib64 -f $(PRIVATE_PLAT_FC) -f $(PRIVATE_NONPLAT_FC) -p $(PRIVATE_SEPOLICY) + $(hide) touch $@ + ################################## ifeq ($(PRODUCT_FULL_TREBLE),true) include $(CLEAR_VARS) diff --git a/tests/Android.bp b/tests/Android.bp index 2c70f363f7adc9c2fa5b8ccf3f8e91b9a326faf2..e875497d5d22054105b9debbba4a86733de07f8c 100644 --- a/tests/Android.bp +++ b/tests/Android.bp @@ -19,3 +19,10 @@ cc_prebuilt_binary { host_supported: true, required: ["policy.py"], } + +cc_prebuilt_binary { + name: "sepolicy_tests.py", + srcs: ["sepolicy_tests.py"], + host_supported: true, + required: ["policy.py"], +} diff --git a/tests/policy.py b/tests/policy.py index e307656eb5865a284fd87bd7b64019928b65f85e..b70b836d0c8df3cfc9e1a2c16bb3b67969ee79bf 100644 --- a/tests/policy.py +++ b/tests/policy.py @@ -3,6 +3,33 @@ import re import os import sys +### +# Check whether the regex will match a file path starting with the provided +# prefix +# +# Compares regex entries in file_contexts with a path prefix. Regex entries +# are often more specific than this file prefix. For example, the regex could +# be /system/bin/foo\.sh and the prefix could be /system. This function +# loops over the regex removing characters from the end until +# 1) there is a match - return True or 2) run out of characters - return +# False. +# +def MatchPathPrefix(pathregex, prefix): + for i in range(len(pathregex), 0, -1): + try: + pattern = re.compile('^' + pathregex[0:i] + "$") + except: + continue + if pattern.match(prefix): + return True + return False + +def MatchPathPrefixes(pathregex, Prefixes): + for Prefix in Prefixes: + if MatchPathPrefix(pathregex, Prefix): + return True + return False + class TERule: def __init__(self, rule): data = rule.split(',') @@ -20,6 +47,27 @@ class Policy: __policydbP = None __BUFSIZE = 2048 + # Check that path prefixes that match MatchPrefix, and do not Match + # DoNotMatchPrefix have the attribute Attr. + # For example assert that all types in /sys, and not in /sys/kernel/debugfs + # have the sysfs_type attribute. + def AssertPathTypesHaveAttr(self, MatchPrefix, DoNotMatchPrefix, Attr): + # Query policy for the types associated with Attr + TypesPol = self.QueryTypeAttribute(Attr, True) + # Search file_contexts to find paths/types that should be associated with + # Attr. + TypesFc = self.__GetTypesByFilePathPrefix(MatchPrefix, DoNotMatchPrefix) + violators = TypesFc.difference(TypesPol) + + ret = "" + if len(violators) > 0: + ret += "The following types on " + ret += " ".join(str(x) for x in sorted(MatchPrefix)) + ret += " must be associated with the " + ret += "\"" + Attr + "\" attribute: " + ret += " ".join(str(x) for x in sorted(violators)) + "\n" + return ret + # Return all file_contexts entries that map to the input Type. def QueryFc(self, Type): if Type in self.__FcDict: @@ -35,18 +83,19 @@ class Policy: if (TypeIterP == None): sys.exit("Failed to initialize type iterator") buf = create_string_buffer(self.__BUFSIZE) - + TypeAttr = set() while True: ret = self.__libsepolwrap.get_type(buf, self.__BUFSIZE, self.__policydbP, TypeIterP) if ret == 0: - yield buf.value + TypeAttr.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 TypeAttr # Return all TERules that match: # (any scontext) or (any tcontext) or (any tclass) or (any perms), @@ -74,6 +123,17 @@ class Policy: continue yield Rule + def __GetTypesByFilePathPrefix(self, MatchPrefixes, DoNotMatchPrefixes): + Types = set() + for Type in self.__FcDict: + for pathregex in self.__FcDict[Type]: + if not MatchPathPrefixes(pathregex, MatchPrefixes): + continue + if MatchPathPrefixes(pathregex, DoNotMatchPrefixes): + continue + Types.add(Type) + return Types + def __GetTERules(self, policydbP, avtabIterP): if self.__Rules is None: diff --git a/tests/sepolicy_tests.py b/tests/sepolicy_tests.py new file mode 100644 index 0000000000000000000000000000000000000000..3f93ff4be33d537161dbbfd29a7bef7f39bec2fe --- /dev/null +++ b/tests/sepolicy_tests.py @@ -0,0 +1,85 @@ +from optparse import OptionParser +from optparse import Option, OptionValueError +import os +import policy +import re +import sys + +############################################################# +# Tests +############################################################# +def TestDataTypeViolations(pol): + return pol.AssertPathTypesHaveAttr(["/data/"], [], "data_file_type") + +def TestSysfsTypeViolations(pol): + return pol.AssertPathTypesHaveAttr(["/sys/"], ["/sys/kernel/debug/", + "/sys/kernel/tracing"], "sysfs_type") + +def TestDebugfsTypeViolations(pol): + # TODO: this should apply to genfs_context entries as well + return pol.AssertPathTypesHaveAttr(["/sys/kernel/debug/", + "/sys/kernel/tracing"], [], "debugfs_type") +### +# 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. +# +class MultipleOption(Option): + ACTIONS = Option.ACTIONS + ("extend",) + STORE_ACTIONS = Option.STORE_ACTIONS + ("extend",) + TYPED_ACTIONS = Option.TYPED_ACTIONS + ("extend",) + ALWAYS_TYPED_ACTIONS = Option.ALWAYS_TYPED_ACTIONS + ("extend",) + + def take_action(self, action, dest, opt, value, values, parser): + if action == "extend": + values.ensure_value(dest, []).append(value) + else: + Option.take_action(self, action, dest, opt, value, values, parser) + +Tests = ["TestDataTypeViolators"] + +if __name__ == '__main__': + usage = "sepolicy_tests.py -f nonplat_file_contexts -f " + usage +="plat_file_contexts -p policy [--test test] [--help]" + parser = OptionParser(option_class=MultipleOption, usage=usage) + 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("-t", "--test", dest="test", action="extend", + help="Test options include "+str(Tests)) + + (options, args) = parser.parse_args() + + if not options.libpath: + sys.exit("Must specify path to host libraries\n" + parser.usage) + if not os.path.exists(options.libpath): + sys.exit("Error: library-path " + options.libpath + " does not exist\n" + + parser.usage) + + if not options.policy: + sys.exit("Must specify 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) + + if not options.file_contexts: + sys.exit("Error: Must specify file_contexts file(s)\n" + parser.usage) + for f in options.file_contexts: + if not os.path.exists(f): + sys.exit("Error: File_contexts file " + f + " does not exist\n" + + parser.usage) + + pol = policy.Policy(options.policy, options.file_contexts, options.libpath) + + results = "" + # If an individual test is not specified, run all tests. + if options.test is None or "TestDataTypeViolations" in options.tests: + results += TestDataTypeViolations(pol) + if options.test is None or "TestSysfsTypeViolations" in options.tests: + results += TestSysfsTypeViolations(pol) + if options.test is None or "TestDebugfsTypeViolations" in options.tests: + results += TestDebugfsTypeViolations(pol) + + if len(results) > 0: + sys.exit(results) diff --git a/tests/treble_sepolicy_tests.py b/tests/treble_sepolicy_tests.py index ddccaba4d22208625a4d7a29165e32b129f1386b..770ff97d695fb0f2fb1a9cf9fbd44b1a3b7057cf 100644 --- a/tests/treble_sepolicy_tests.py +++ b/tests/treble_sepolicy_tests.py @@ -2,6 +2,7 @@ from optparse import OptionParser from optparse import Option, OptionValueError import os import policy +from policy import MatchPathPrefix import re import sys @@ -69,27 +70,6 @@ coredomains = set() appdomains = set() vendordomains = set() -### -# Check whether the regex will match a file path starting with the provided -# prefix -# -# Compares regex entries in file_contexts with a path prefix. Regex entries -# are often more specific than this file prefix. For example, the regex could -# be /system/bin/foo\.sh and the prefix could be /system. This function -# loops over the regex removing characters from the end until -# 1) there is a match - return True or 2) run out of characters - return -# False. -# -def MatchPathPrefix(pathregex, prefix): - for i in range(len(pathregex), 0, -1): - try: - pattern = re.compile('^' + pathregex[0:i] + "$") - except: - continue - if pattern.match(prefix): - return True - return False - def GetAllDomains(pol): global alldomains for result in pol.QueryTypeAttribute("domain", True):