Skip to content
Snippets Groups Projects
Commit 370a52fe authored by Jeff Vander Stoep's avatar Jeff Vander Stoep
Browse files

Improve data separation test coverage

Two areas need better coverage:
1. Tests are not verifying that files in /data/vendor do not have the
core_data_file_type attribute.
2. No error is thrown if a type lives in both /data/vendor
/data/<not vendor>.

Bug: 72998741
Test: build all selinux policies on master (assert build time tests)
Test: build and boot Marlin and Taimen, verify no selinux denials and
    everything works as expected.

Change-Id: I133a068123139a599b9b81ddcc254616894621eb
(cherry picked from commit 55d5e284)
parent 4eb10d80
No related branches found
No related tags found
No related merge requests found
...@@ -33,9 +33,10 @@ python_defaults { ...@@ -33,9 +33,10 @@ python_defaults {
python_binary_host { python_binary_host {
name: "treble_sepolicy_tests", name: "treble_sepolicy_tests",
srcs: [ srcs: [
"treble_sepolicy_tests.py", "FcSort.py",
"mini_parser.py", "mini_parser.py",
"policy.py", "policy.py",
"treble_sepolicy_tests.py",
], ],
required: ["libsepolwrap"], required: ["libsepolwrap"],
defaults: ["py2_only"], defaults: ["py2_only"],
...@@ -44,8 +45,9 @@ python_binary_host { ...@@ -44,8 +45,9 @@ python_binary_host {
python_binary_host { python_binary_host {
name: "sepolicy_tests", name: "sepolicy_tests",
srcs: [ srcs: [
"sepolicy_tests.py", "FcSort.py",
"policy.py", "policy.py",
"sepolicy_tests.py",
], ],
required: ["libsepolwrap"], required: ["libsepolwrap"],
defaults: ["py2_only"], defaults: ["py2_only"],
......
#!/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:])
...@@ -3,6 +3,7 @@ import re ...@@ -3,6 +3,7 @@ import re
import os import os
import sys import sys
import platform import platform
import FcSort
### ###
# Check whether the regex will match a file path starting with the provided # Check whether the regex will match a file path starting with the provided
...@@ -45,10 +46,26 @@ class Policy: ...@@ -45,10 +46,26 @@ class Policy:
__ExpandedRules = set() __ExpandedRules = set()
__Rules = set() __Rules = set()
__FcDict = None __FcDict = None
__FcSorted = None
__libsepolwrap = None __libsepolwrap = None
__policydbP = None __policydbP = None
__BUFSIZE = 2048 __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 # Check that path prefixes that match MatchPrefix, and do not Match
# DoNotMatchPrefix have the attribute Attr. # DoNotMatchPrefix have the attribute Attr.
# For example assert that all types in /sys, and not in /sys/kernel/debugfs # For example assert that all types in /sys, and not in /sys/kernel/debugfs
...@@ -198,18 +215,51 @@ class Policy: ...@@ -198,18 +215,51 @@ class Policy:
self.__libsepolwrap.destroy_type_iter(TypeIterP) self.__libsepolwrap.destroy_type_iter(TypeIterP)
return AllTypes 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): def __GetTypesByFilePathPrefix(self, MatchPrefixes, DoNotMatchPrefixes):
Types = set() Types = set()
for Type in self.__FcDict:
for pathregex in self.__FcDict[Type]: MatchPrefixesWithIndex = []
if not MatchPathPrefixes(pathregex, MatchPrefixes): for MatchPrefix in MatchPrefixes:
continue MatchPrefixesWithIndex.append(self.__FcSortedIndex(MatchPrefix))
if MatchPathPrefixes(pathregex, DoNotMatchPrefixes):
for MatchPrefixWithIndex in MatchPrefixesWithIndex:
PathTypes = self.__MatchPathPrefixTypes(*MatchPrefixWithIndex)
for PathType in PathTypes:
if MatchPathPrefixes(PathType[0], DoNotMatchPrefixes):
continue continue
Types.add(Type) Types.add(PathType[1])
return Types return Types
def __GetTERules(self, policydbP, avtabIterP, Rules): def __GetTERules(self, policydbP, avtabIterP, Rules):
if Rules is None: if Rules is None:
Rules = set() Rules = set()
...@@ -313,6 +363,7 @@ class Policy: ...@@ -313,6 +363,7 @@ class Policy:
self.__FcDict[t] = [rec[0]] self.__FcDict[t] = [rec[0]]
except: except:
pass pass
self.__FcSorted = FcSort.FcSort(FcPaths)
# load policy # load policy
def __InitPolicy(self, PolicyPath): def __InitPolicy(self, PolicyPath):
......
...@@ -24,8 +24,8 @@ def TestVendorTypeViolations(pol): ...@@ -24,8 +24,8 @@ def TestVendorTypeViolations(pol):
return pol.AssertPathTypesHaveAttr(["/vendor/"], [], "vendor_file_type") return pol.AssertPathTypesHaveAttr(["/vendor/"], [], "vendor_file_type")
def TestCoreDataTypeViolations(pol): def TestCoreDataTypeViolations(pol):
return pol.AssertPathTypesHaveAttr(["/data/"], ["/data/vendor/", return pol.AssertPathTypesHaveAttr(["/data/"], ["/data/vendor",
"/data/vendor_ce/", "/data/vendor_de/"], "core_data_file_type") "/data/vendor_ce", "/data/vendor_de"], "core_data_file_type")
### ###
# extend OptionParser to allow the same option flag to be used multiple times. # extend OptionParser to allow the same option flag to be used multiple times.
......
...@@ -71,6 +71,7 @@ alldomains = {} ...@@ -71,6 +71,7 @@ alldomains = {}
coredomains = set() coredomains = set()
appdomains = set() appdomains = set()
vendordomains = set() vendordomains = set()
pol = None
# compat vars # compat vars
alltypes = set() alltypes = set()
...@@ -287,6 +288,12 @@ def TestViolatorAttributes(): ...@@ -287,6 +288,12 @@ def TestViolatorAttributes():
ret += TestViolatorAttribute("vendor_executes_system_violators") ret += TestViolatorAttribute("vendor_executes_system_violators")
return ret 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. # 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 # This is used to allow multiple file_contexts files and tests to be
...@@ -305,6 +312,7 @@ class MultipleOption(Option): ...@@ -305,6 +312,7 @@ class MultipleOption(Option):
Option.take_action(self, action, dest, opt, value, values, parser) Option.take_action(self, action, dest, opt, value, values, parser)
Tests = {"CoredomainViolations": TestCoredomainViolations, Tests = {"CoredomainViolations": TestCoredomainViolations,
"CoreDatatypeViolations": TestCoreDataTypeViolations,
"TrebleCompatMapping": TestTrebleCompatMapping, "TrebleCompatMapping": TestTrebleCompatMapping,
"ViolatorAttributes": TestViolatorAttributes} "ViolatorAttributes": TestViolatorAttributes}
......
0% Loading or .
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment