blob: 901f7020997d919f87bc3c98797974b0fc3656f9 [file] [log] [blame]
Jeff Vander Stoepbdfc0302017-05-25 09:53:47 -07001from optparse import OptionParser
2from optparse import Option, OptionValueError
3import os
4import policy
5import re
6import sys
7
8'''
9Use file_contexts and policy to verify Treble requirements
10are not violated.
11'''
12###
13# Differentiate between domains that are part of the core Android platform and
14# domains introduced by vendors
15coreAppdomain = {
16 'bluetooth',
17 'ephemeral_app',
18 'isolated_app',
19 'nfc',
20 'platform_app',
21 'priv_app',
22 'radio',
23 'shared_relro',
24 'shell',
25 'system_app',
26 'untrusted_app',
27 'untrusted_app_25',
28 'untrusted_v2_app',
29 }
30coredomainWhitelist = {
31 'adbd',
32 'kernel',
33 'postinstall',
34 'postinstall_dexopt',
35 'recovery',
36 'system_server',
37 }
38coredomainWhitelist |= coreAppdomain
39
40class scontext:
41 def __init__(self):
42 self.fromSystem = False
43 self.fromVendor = False
44 self.coredomain = False
45 self.appdomain = False
46 self.attributes = set()
47 self.entrypoints = []
48 self.entrypointpaths = []
49
50def PrintScontext(domain, sctx):
51 print domain
52 print "\tcoredomain="+str(sctx.coredomain)
53 print "\tappdomain="+str(sctx.appdomain)
54 print "\tfromSystem="+str(sctx.fromSystem)
55 print "\tfromVendor="+str(sctx.fromVendor)
56 print "\tattributes="+str(sctx.attributes)
57 print "\tentrypoints="+str(sctx.entrypoints)
58 print "\tentrypointpaths="
59 if sctx.entrypointpaths is not None:
60 for path in sctx.entrypointpaths:
61 print "\t\t"+str(path)
62
63alldomains = {}
64coredomains = set()
65appdomains = set()
66vendordomains = set()
67
68###
69# Check whether the regex will match a file path starting with the provided
70# prefix
71#
72# Compares regex entries in file_contexts with a path prefix. Regex entries
73# are often more specific than this file prefix. For example, the regex could
74# be /system/bin/foo\.sh and the prefix could be /system. This function
75# loops over the regex removing characters from the end until
76# 1) there is a match - return True or 2) run out of characters - return
77# False.
78#
79def MatchPathPrefix(pathregex, prefix):
80 for i in range(len(pathregex), 0, -1):
81 try:
82 pattern = re.compile('^' + pathregex[0:i] + "$")
83 except:
84 continue
85 if pattern.match(prefix):
86 return True
87 return False
88
89def GetAllDomains(pol):
90 global alldomains
91 for result in pol.QueryTypeAttribute("domain", True):
92 alldomains[result] = scontext()
93
94def GetAppDomains():
95 global appdomains
96 global alldomains
97 for d in alldomains:
98 # The application of the "appdomain" attribute is trusted because core
99 # selinux policy contains neverallow rules that enforce that only zygote
100 # and runas spawned processes may transition to processes that have
101 # the appdomain attribute.
102 if "appdomain" in alldomains[d].attributes:
103 alldomains[d].appdomain = True
104 appdomains.add(d)
105
106
107def GetCoreDomains():
108 global alldomains
109 global coredomains
110 for d in alldomains:
111 # TestCoredomainViolators will verify if coredomain was incorrectly
112 # applied.
113 if "coredomain" in alldomains[d].attributes:
114 alldomains[d].coredomain = True
115 coredomains.add(d)
116 # check whether domains are executed off of /system or /vendor
117 if d in coredomainWhitelist:
118 continue
119 # TODO, add checks to prevent app domains from being incorrectly
120 # labeled as coredomain. Apps don't have entrypoints as they're always
121 # dynamically transitioned to by zygote.
122 if d in appdomains:
123 continue
124 if not alldomains[d].entrypointpaths:
125 continue
126 for path in alldomains[d].entrypointpaths:
127 # Processes with entrypoint on /system
128 if ((MatchPathPrefix(path, "/system") and not
129 MatchPathPrefix(path, "/system/vendor")) or
130 MatchPathPrefix(path, "/init") or
131 MatchPathPrefix(path, "/charger")):
132 alldomains[d].fromSystem = True
133 # Processes with entrypoint on /vendor or /system/vendor
134 if (MatchPathPrefix(path, "/vendor") or
135 MatchPathPrefix(path, "/system/vendor")):
136 alldomains[d].fromVendor = True
137
138###
139# Add the entrypoint type and path(s) to each domain.
140#
141def GetDomainEntrypoints(pol):
142 global alldomains
143 for x in pol.QueryTERule(tclass="file", perms=["entrypoint"]):
144 if not x.sctx in alldomains:
145 continue
146 alldomains[x.sctx].entrypoints.append(str(x.tctx))
147 # postinstall_file represents a special case specific to A/B OTAs.
148 # Update_engine mounts a partition and relabels it postinstall_file.
149 # There is no file_contexts entry associated with postinstall_file
150 # so skip the lookup.
151 if x.tctx == "postinstall_file":
152 continue
153 alldomains[x.sctx].entrypointpaths = pol.QueryFc(x.tctx)
154###
155# Get attributes associated with each domain
156#
157def GetAttributes(pol):
158 global alldomains
159 for domain in alldomains:
160 for result in pol.QueryTypeAttribute(domain, False):
161 alldomains[domain].attributes.add(result)
162
163def setup(pol):
164 GetAllDomains(pol)
165 GetAttributes(pol)
166 GetDomainEntrypoints(pol)
167 GetAppDomains()
168 GetCoreDomains()
169
170#############################################################
171# Tests
172#############################################################
173def TestCoredomainViolations():
174 global alldomains
175 # verify that all domains launched from /system have the coredomain
176 # attribute
177 ret = ""
178 violators = []
179 for d in alldomains:
180 domain = alldomains[d]
181 if domain.fromSystem and "coredomain" not in domain.attributes:
182 violators.append(d);
183 if len(violators) > 0:
184 ret += "The following domain(s) must be associated with the "
185 ret += "\"coredomain\" attribute because they are executed off of "
186 ret += "/system:\n"
187 ret += " ".join(str(x) for x in sorted(violators)) + "\n"
188
189 # verify that all domains launched form /vendor do not have the coredomain
190 # attribute
191 violators = []
192 for d in alldomains:
193 domain = alldomains[d]
194 if domain.fromVendor and "coredomain" in domain.attributes:
195 violators.append(d)
196 if len(violators) > 0:
197 ret += "The following domains must not be associated with the "
198 ret += "\"coredomain\" attribute because they are executed off of "
199 ret += "/vendor or /system/vendor:\n"
200 ret += " ".join(str(x) for x in sorted(violators)) + "\n"
201
202 return ret
203
204###
205# extend OptionParser to allow the same option flag to be used multiple times.
206# This is used to allow multiple file_contexts files and tests to be
207# specified.
208#
209class MultipleOption(Option):
210 ACTIONS = Option.ACTIONS + ("extend",)
211 STORE_ACTIONS = Option.STORE_ACTIONS + ("extend",)
212 TYPED_ACTIONS = Option.TYPED_ACTIONS + ("extend",)
213 ALWAYS_TYPED_ACTIONS = Option.ALWAYS_TYPED_ACTIONS + ("extend",)
214
215 def take_action(self, action, dest, opt, value, values, parser):
216 if action == "extend":
217 values.ensure_value(dest, []).append(value)
218 else:
219 Option.take_action(self, action, dest, opt, value, values, parser)
220
221Tests = ["CoredomainViolators"]
222
223if __name__ == '__main__':
224 usage = "sepolicy-trebletests -f nonplat_file_contexts -f "
225 usage +="plat_file_contexts -p policy [--test test] [--help]"
226 parser = OptionParser(option_class=MultipleOption, usage=usage)
227 parser.add_option("-f", "--file_contexts", dest="file_contexts",
228 metavar="FILE", action="extend", type="string")
229 parser.add_option("-p", "--policy", dest="policy", metavar="FILE")
230 parser.add_option("-t", "--test", dest="test", action="extend",
231 help="Test options include "+str(Tests))
232
233 (options, args) = parser.parse_args()
234
235 if not options.policy:
236 sys.exit("Must specify monolithic policy file\n" + parser.usage)
237 if not os.path.exists(options.policy):
238 sys.exit("Error: policy file " + options.policy + " does not exist\n"
239 + parser.usage)
240
241 if not options.file_contexts:
242 sys.exit("Error: Must specify file_contexts file(s)\n" + parser.usage)
243 for f in options.file_contexts:
244 if not os.path.exists(f):
245 sys.exit("Error: File_contexts file " + f + " does not exist\n" +
246 parser.usage)
247
248 pol = policy.Policy(options.policy, options.file_contexts)
249 setup(pol)
250
251 results = ""
252 # If an individual test is not specified, run all tests.
253 if options.test is None or "CoredomainViolations" in options.tests:
254 results += TestCoredomainViolations()
255
256 if len(results) > 0:
257 sys.exit(results)