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