diff --git a/tests/Android.bp b/tests/Android.bp index 144b9951564063c13686be3b8025c7cb6c34d2dd..93a41b9532d9f6b3a73aac72c022704aaf051b9b 100644 --- a/tests/Android.bp +++ b/tests/Android.bp @@ -33,9 +33,10 @@ python_defaults { python_binary_host { name: "treble_sepolicy_tests", srcs: [ - "treble_sepolicy_tests.py", + "FcSort.py", "mini_parser.py", "policy.py", + "treble_sepolicy_tests.py", ], required: ["libsepolwrap"], defaults: ["py2_only"], @@ -44,8 +45,9 @@ python_binary_host { python_binary_host { name: "sepolicy_tests", srcs: [ - "sepolicy_tests.py", + "FcSort.py", "policy.py", + "sepolicy_tests.py", ], required: ["libsepolwrap"], defaults: ["py2_only"], diff --git a/tests/FcSort.py b/tests/FcSort.py new file mode 100755 index 0000000000000000000000000000000000000000..7cf1998be5fdf6509cfbb3ad1bf8d4a295f7a4fe --- /dev/null +++ b/tests/FcSort.py @@ -0,0 +1,125 @@ +#!/usr/bin/env python +import sys +import os + +class FileContextsNode: + path = None + fileType = None + context = None + Type = None + meta = None + stemLen = None + strLen = None + Type = None + def __init__(self, path, fileType, context, meta, stemLen, strLen): + self.path = path + self.fileType = fileType + self.context = context + self.meta = meta + self.stemLen = stemLen + self.strlen = strLen + self.Type = context.split(":")[2] + +metaChars = frozenset(['.', '^', '$', '?', '*', '+', '|', '[', '(', '{']) +escapedMetaChars = frozenset(['\.', '\^', '\$', '\?', '\*', '\+', '\|', '\[', '\(', '\{']) + +def getStemLen(path): + global metaChars + stemLen = 0 + i = 0 + while i < len(path): + if path[i] == "\\": + i += 1 + elif path[i] in metaChars: + break + stemLen += 1 + i += 1 + return stemLen + + +def getIsMeta(path): + global metaChars + global escapedMetaChars + metaCharsCount = 0 + escapedMetaCharsCount = 0 + for c in metaChars: + if c in path: + metaCharsCount += 1 + for c in escapedMetaChars: + if c in path: + escapedMetaCharsCount += 1 + return metaCharsCount > escapedMetaCharsCount + +def CreateNode(line): + global metaChars + if (len(line) == 0) or (line[0] == '#'): + return None + + split = line.split() + path = split[0].strip() + context = split[-1].strip() + fileType = None + if len(split) == 3: + fileType = split[1].strip() + meta = getIsMeta(path) + stemLen = getStemLen(path) + strLen = len(path.replace("\\", "")) + + return FileContextsNode(path, fileType, context, meta, stemLen, strLen) + +def ReadFileContexts(files): + fc = [] + for f in files: + fd = open(f) + for line in fd: + node = CreateNode(line.strip()) + if node != None: + fc.append(node) + return fc + +# Comparator function for list.sort() based off of fc_sort.c +# Compares two FileContextNodes a and b and returns 1 if a is more +# specific or -1 if b is more specific. +def compare(a, b): + # The regex without metachars is more specific + if a.meta and not b.meta: + return -1 + if b.meta and not a.meta: + return 1 + + # The regex with longer stemlen (regex before any meta characters) is more specific. + if a.stemLen < b.stemLen: + return -1 + if b.stemLen < a.stemLen: + return 1 + + # The regex with longer string length is more specific + if a.strLen < b.strLen: + return -1 + if b.strLen < a.strLen: + return 1 + + # A regex with a fileType defined (e.g. file, dir) is more specific. + if a.fileType is None and b.fileType is not None: + return -1 + if b.fileType is None and a.fileType is not None: + return 1 + + # Regexes are equally specific. + return 0 + +def FcSort(files): + for f in files: + if not os.path.exists(f): + sys.exit("Error: File_contexts file " + f + " does not exist\n") + + Fc = ReadFileContexts(files) + Fc.sort(cmp=compare) + + return Fc + +if __name__ == '__main__': + if len(sys.argv) < 2: + sys.exit("Usage: fc_sort.py <file_contexts 1> <file_contexts 2> <file_contexts 3>") + + FcSorted = FcSort(sys.argv[1:]) diff --git a/tests/policy.py b/tests/policy.py index 2c4b0a6786615a59bc47ff1ccc5e040e1098f4bb..b51ebf2370548f8a933408968b25b953d10b334f 100644 --- a/tests/policy.py +++ b/tests/policy.py @@ -3,6 +3,7 @@ import re import os import sys import platform +import FcSort ### # Check whether the regex will match a file path starting with the provided @@ -45,10 +46,26 @@ class Policy: __ExpandedRules = set() __Rules = set() __FcDict = None + __FcSorted = None __libsepolwrap = None __policydbP = None __BUFSIZE = 2048 + def AssertPathTypesDoNotHaveAttr(self, MatchPrefix, DoNotMatchPrefix, Attr): + # Query policy for the types associated with Attr + TypesPol = self.QueryTypeAttribute(Attr, True) + # Search file_contexts to find types associated with input paths. + TypesFc = self.__GetTypesByFilePathPrefix(MatchPrefix, DoNotMatchPrefix) + violators = TypesFc.intersection(TypesPol) + ret = "" + if len(violators) > 0: + ret += "The following types on " + ret += " ".join(str(x) for x in sorted(MatchPrefix)) + ret += " must not be associated with the " + ret += "\"" + Attr + "\" attribute: " + ret += " ".join(str(x) for x in sorted(violators)) + "\n" + return ret + # 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 @@ -198,18 +215,51 @@ class Policy: self.__libsepolwrap.destroy_type_iter(TypeIterP) return AllTypes + def __ExactMatchPathPrefix(self, pathregex, prefix): + pattern = re.compile('^' + pathregex + "$") + if pattern.match(prefix): + return True + return False + + # Return a tuple (prefix, i) where i is the index of the most specific + # match of prefix in the sorted file_contexts. This is useful for limiting a + # file_contexts search to matches that are more specific and omitting less + # specific matches. For example, finding all matches to prefix /data/vendor + # should not include /data(/.*)? if /data/vendor(/.*)? is also specified. + def __FcSortedIndex(self, prefix): + index = 0 + for i in range(0, len(self.__FcSorted)): + if self.__ExactMatchPathPrefix(self.__FcSorted[i].path, prefix): + index = i + return prefix, index + + # Return a tuple of (path, Type) for all matching paths. Use the sorted + # file_contexts and index returned from __FcSortedIndex() to limit results + # to results that are more specific than the prefix. + def __MatchPathPrefixTypes(self, prefix, index): + PathType = [] + for i in range(index, len(self.__FcSorted)): + if MatchPathPrefix(self.__FcSorted[i].path, prefix): + PathType.append((self.__FcSorted[i].path, self.__FcSorted[i].Type)) + return PathType + + # Return types that match MatchPrefixes but do not match + # DoNotMatchPrefixes 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): + + MatchPrefixesWithIndex = [] + for MatchPrefix in MatchPrefixes: + MatchPrefixesWithIndex.append(self.__FcSortedIndex(MatchPrefix)) + + for MatchPrefixWithIndex in MatchPrefixesWithIndex: + PathTypes = self.__MatchPathPrefixTypes(*MatchPrefixWithIndex) + for PathType in PathTypes: + if MatchPathPrefixes(PathType[0], DoNotMatchPrefixes): continue - Types.add(Type) + Types.add(PathType[1]) return Types - def __GetTERules(self, policydbP, avtabIterP, Rules): if Rules is None: Rules = set() @@ -313,6 +363,7 @@ class Policy: self.__FcDict[t] = [rec[0]] except: pass + self.__FcSorted = FcSort.FcSort(FcPaths) # load policy def __InitPolicy(self, PolicyPath): diff --git a/tests/sepolicy_tests.py b/tests/sepolicy_tests.py index ca95f8a1892d6efba6191ae6ffd028f120a22410..2cf4ae8148d9547ebe18c6ea72d0760cebd0c542 100644 --- a/tests/sepolicy_tests.py +++ b/tests/sepolicy_tests.py @@ -24,8 +24,8 @@ def TestVendorTypeViolations(pol): return pol.AssertPathTypesHaveAttr(["/vendor/"], [], "vendor_file_type") def TestCoreDataTypeViolations(pol): - return pol.AssertPathTypesHaveAttr(["/data/"], ["/data/vendor/", - "/data/vendor_ce/", "/data/vendor_de/"], "core_data_file_type") + return pol.AssertPathTypesHaveAttr(["/data/"], ["/data/vendor", + "/data/vendor_ce", "/data/vendor_de"], "core_data_file_type") ### # extend OptionParser to allow the same option flag to be used multiple times. diff --git a/tests/treble_sepolicy_tests.py b/tests/treble_sepolicy_tests.py index 2f9e9948b9c3ffbeaa2060b59a32fd0fca664ee5..cfa8ef9c88a0939dc8869e4ca95b95af978b3373 100644 --- a/tests/treble_sepolicy_tests.py +++ b/tests/treble_sepolicy_tests.py @@ -71,6 +71,7 @@ alldomains = {} coredomains = set() appdomains = set() vendordomains = set() +pol = None # compat vars alltypes = set() @@ -287,6 +288,12 @@ def TestViolatorAttributes(): ret += TestViolatorAttribute("vendor_executes_system_violators") return ret +# TODO move this to sepolicy_tests +def TestCoreDataTypeViolations(): + global pol + return pol.AssertPathTypesDoNotHaveAttr(["/data/vendor/", "/data/vendor_ce/", + "/data/vendor_de/"], [], "core_data_file_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 @@ -305,6 +312,7 @@ class MultipleOption(Option): Option.take_action(self, action, dest, opt, value, values, parser) Tests = {"CoredomainViolations": TestCoredomainViolations, + "CoreDatatypeViolations": TestCoreDataTypeViolations, "TrebleCompatMapping": TestTrebleCompatMapping, "ViolatorAttributes": TestViolatorAttributes}