treble_sepolicy_tests.py 15 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415
  1. from optparse import OptionParser
  2. from optparse import Option, OptionValueError
  3. import os
  4. import mini_parser
  5. import policy
  6. from policy import MatchPathPrefix
  7. import re
  8. import sys
  9. DEBUG=False
  10. '''
  11. Use file_contexts and policy to verify Treble requirements
  12. are not violated.
  13. '''
  14. ###
  15. # Differentiate between domains that are part of the core Android platform and
  16. # domains introduced by vendors
  17. coreAppdomain = {
  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. }
  31. coredomainWhitelist = {
  32. 'adbd',
  33. 'kernel',
  34. 'postinstall',
  35. 'postinstall_dexopt',
  36. 'recovery',
  37. 'system_server',
  38. 'vendor_init',
  39. }
  40. coredomainWhitelist |= coreAppdomain
  41. class scontext:
  42. def __init__(self):
  43. self.fromSystem = False
  44. self.fromVendor = False
  45. self.coredomain = False
  46. self.appdomain = False
  47. self.attributes = set()
  48. self.entrypoints = []
  49. self.entrypointpaths = []
  50. def PrintScontexts():
  51. for d in sorted(alldomains.keys()):
  52. sctx = alldomains[d]
  53. print d
  54. print "\tcoredomain="+str(sctx.coredomain)
  55. print "\tappdomain="+str(sctx.appdomain)
  56. print "\tfromSystem="+str(sctx.fromSystem)
  57. print "\tfromVendor="+str(sctx.fromVendor)
  58. print "\tattributes="+str(sctx.attributes)
  59. print "\tentrypoints="+str(sctx.entrypoints)
  60. print "\tentrypointpaths="
  61. if sctx.entrypointpaths is not None:
  62. for path in sctx.entrypointpaths:
  63. print "\t\t"+str(path)
  64. alldomains = {}
  65. coredomains = set()
  66. appdomains = set()
  67. vendordomains = set()
  68. pol = None
  69. # compat vars
  70. alltypes = set()
  71. oldalltypes = set()
  72. compatMapping = None
  73. pubtypes = set()
  74. # Distinguish between PRODUCT_FULL_TREBLE and PRODUCT_FULL_TREBLE_OVERRIDE
  75. FakeTreble = False
  76. def GetAllDomains(pol):
  77. global alldomains
  78. for result in pol.QueryTypeAttribute("domain", True):
  79. alldomains[result] = scontext()
  80. def GetAppDomains():
  81. global appdomains
  82. global alldomains
  83. for d in alldomains:
  84. # The application of the "appdomain" attribute is trusted because core
  85. # selinux policy contains neverallow rules that enforce that only zygote
  86. # and runas spawned processes may transition to processes that have
  87. # the appdomain attribute.
  88. if "appdomain" in alldomains[d].attributes:
  89. alldomains[d].appdomain = True
  90. appdomains.add(d)
  91. def GetCoreDomains():
  92. global alldomains
  93. global coredomains
  94. for d in alldomains:
  95. # TestCoredomainViolations will verify if coredomain was incorrectly
  96. # applied.
  97. if "coredomain" in alldomains[d].attributes:
  98. alldomains[d].coredomain = True
  99. coredomains.add(d)
  100. # check whether domains are executed off of /system or /vendor
  101. if d in coredomainWhitelist:
  102. continue
  103. # TODO, add checks to prevent app domains from being incorrectly
  104. # labeled as coredomain. Apps don't have entrypoints as they're always
  105. # dynamically transitioned to by zygote.
  106. if d in appdomains:
  107. continue
  108. if not alldomains[d].entrypointpaths:
  109. continue
  110. for path in alldomains[d].entrypointpaths:
  111. # Processes with entrypoint on /system
  112. if ((MatchPathPrefix(path, "/system") and not
  113. MatchPathPrefix(path, "/system/vendor")) or
  114. MatchPathPrefix(path, "/init") or
  115. MatchPathPrefix(path, "/charger")):
  116. alldomains[d].fromSystem = True
  117. # Processes with entrypoint on /vendor or /system/vendor
  118. if (MatchPathPrefix(path, "/vendor") or
  119. MatchPathPrefix(path, "/system/vendor")):
  120. alldomains[d].fromVendor = True
  121. ###
  122. # Add the entrypoint type and path(s) to each domain.
  123. #
  124. def GetDomainEntrypoints(pol):
  125. global alldomains
  126. for x in pol.QueryExpandedTERule(tclass=set(["file"]), perms=set(["entrypoint"])):
  127. if not x.sctx in alldomains:
  128. continue
  129. alldomains[x.sctx].entrypoints.append(str(x.tctx))
  130. # postinstall_file represents a special case specific to A/B OTAs.
  131. # Update_engine mounts a partition and relabels it postinstall_file.
  132. # There is no file_contexts entry associated with postinstall_file
  133. # so skip the lookup.
  134. if x.tctx == "postinstall_file":
  135. continue
  136. entrypointpath = pol.QueryFc(x.tctx)
  137. if not entrypointpath:
  138. continue
  139. alldomains[x.sctx].entrypointpaths.extend(entrypointpath)
  140. ###
  141. # Get attributes associated with each domain
  142. #
  143. def GetAttributes(pol):
  144. global alldomains
  145. for domain in alldomains:
  146. for result in pol.QueryTypeAttribute(domain, False):
  147. alldomains[domain].attributes.add(result)
  148. def GetAllTypes(pol, oldpol):
  149. global alltypes
  150. global oldalltypes
  151. alltypes = pol.GetAllTypes(False)
  152. oldalltypes = oldpol.GetAllTypes(False)
  153. def setup(pol):
  154. GetAllDomains(pol)
  155. GetAttributes(pol)
  156. GetDomainEntrypoints(pol)
  157. GetAppDomains()
  158. GetCoreDomains()
  159. # setup for the policy compatibility tests
  160. def compatSetup(pol, oldpol, mapping, types):
  161. global compatMapping
  162. global pubtypes
  163. GetAllTypes(pol, oldpol)
  164. compatMapping = mapping
  165. pubtypes = types
  166. def DomainsWithAttribute(attr):
  167. global alldomains
  168. domains = []
  169. for domain in alldomains:
  170. if attr in alldomains[domain].attributes:
  171. domains.append(domain)
  172. return domains
  173. #############################################################
  174. # Tests
  175. #############################################################
  176. def TestCoredomainViolations():
  177. global alldomains
  178. # verify that all domains launched from /system have the coredomain
  179. # attribute
  180. ret = ""
  181. violators = []
  182. for d in alldomains:
  183. domain = alldomains[d]
  184. if domain.fromSystem and "coredomain" not in domain.attributes:
  185. violators.append(d);
  186. if len(violators) > 0:
  187. ret += "The following domain(s) must be associated with the "
  188. ret += "\"coredomain\" attribute because they are executed off of "
  189. ret += "/system:\n"
  190. ret += " ".join(str(x) for x in sorted(violators)) + "\n"
  191. # verify that all domains launched form /vendor do not have the coredomain
  192. # attribute
  193. violators = []
  194. for d in alldomains:
  195. domain = alldomains[d]
  196. if domain.fromVendor and "coredomain" in domain.attributes:
  197. violators.append(d)
  198. if len(violators) > 0:
  199. ret += "The following domains must not be associated with the "
  200. ret += "\"coredomain\" attribute because they are executed off of "
  201. ret += "/vendor or /system/vendor:\n"
  202. ret += " ".join(str(x) for x in sorted(violators)) + "\n"
  203. return ret
  204. ###
  205. # Make sure that any new public type introduced in the new policy that was not
  206. # present in the old policy has been recorded in the mapping file.
  207. def TestNoUnmappedNewTypes():
  208. global alltypes
  209. global oldalltypes
  210. global compatMapping
  211. global pubtypes
  212. newt = alltypes - oldalltypes
  213. ret = ""
  214. violators = []
  215. for n in newt:
  216. if n in pubtypes and compatMapping.rTypeattributesets.get(n) is None:
  217. violators.append(n)
  218. if len(violators) > 0:
  219. ret += "SELinux: The following public types were found added to the "
  220. ret += "policy without an entry into the compatibility mapping file(s) "
  221. ret += "found in private/compat/V.v/V.v[.ignore].cil, where V.v is the "
  222. ret += "latest API level.\n"
  223. ret += " ".join(str(x) for x in sorted(violators)) + "\n\n"
  224. ret += "See examples of how to fix this:\n"
  225. ret += "https://android-review.git.corp.google.com/c/platform/system/sepolicy/+/781036\n"
  226. ret += "https://android-review.git.corp.google.com/c/platform/system/sepolicy/+/852612\n"
  227. return ret
  228. ###
  229. # Make sure that any public type removed in the current policy has its
  230. # declaration added to the mapping file for use in non-platform policy
  231. def TestNoUnmappedRmTypes():
  232. global alltypes
  233. global oldalltypes
  234. global compatMapping
  235. rmt = oldalltypes - alltypes
  236. ret = ""
  237. violators = []
  238. for o in rmt:
  239. if o in compatMapping.pubtypes and not o in compatMapping.types:
  240. violators.append(o)
  241. if len(violators) > 0:
  242. ret += "SELinux: The following formerly public types were removed from "
  243. ret += "policy without a declaration in the compatibility mapping "
  244. ret += "found in private/compat/V.v/V.v[.ignore].cil, where V.v is the "
  245. ret += "latest API level.\n"
  246. ret += " ".join(str(x) for x in sorted(violators)) + "\n\n"
  247. ret += "See examples of how to fix this:\n"
  248. ret += "https://android-review.git.corp.google.com/c/platform/system/sepolicy/+/822743\n"
  249. return ret
  250. def TestTrebleCompatMapping():
  251. ret = TestNoUnmappedNewTypes()
  252. ret += TestNoUnmappedRmTypes()
  253. return ret
  254. def TestViolatorAttribute(attribute):
  255. global FakeTreble
  256. ret = ""
  257. if FakeTreble:
  258. return ret
  259. violators = DomainsWithAttribute(attribute)
  260. if len(violators) > 0:
  261. ret += "SELinux: The following domains violate the Treble ban "
  262. ret += "against use of the " + attribute + " attribute: "
  263. ret += " ".join(str(x) for x in sorted(violators)) + "\n"
  264. return ret
  265. def TestViolatorAttributes():
  266. ret = TestViolatorAttribute("binder_in_vendor_violators")
  267. ret += TestViolatorAttribute("socket_between_core_and_vendor_violators")
  268. ret += TestViolatorAttribute("vendor_executes_system_violators")
  269. return ret
  270. # TODO move this to sepolicy_tests
  271. def TestCoreDataTypeViolations():
  272. global pol
  273. return pol.AssertPathTypesDoNotHaveAttr(["/data/vendor/", "/data/vendor_ce/",
  274. "/data/vendor_de/"], [], "core_data_file_type")
  275. ###
  276. # extend OptionParser to allow the same option flag to be used multiple times.
  277. # This is used to allow multiple file_contexts files and tests to be
  278. # specified.
  279. #
  280. class MultipleOption(Option):
  281. ACTIONS = Option.ACTIONS + ("extend",)
  282. STORE_ACTIONS = Option.STORE_ACTIONS + ("extend",)
  283. TYPED_ACTIONS = Option.TYPED_ACTIONS + ("extend",)
  284. ALWAYS_TYPED_ACTIONS = Option.ALWAYS_TYPED_ACTIONS + ("extend",)
  285. def take_action(self, action, dest, opt, value, values, parser):
  286. if action == "extend":
  287. values.ensure_value(dest, []).append(value)
  288. else:
  289. Option.take_action(self, action, dest, opt, value, values, parser)
  290. Tests = {"CoredomainViolations": TestCoredomainViolations,
  291. "CoreDatatypeViolations": TestCoreDataTypeViolations,
  292. "TrebleCompatMapping": TestTrebleCompatMapping,
  293. "ViolatorAttributes": TestViolatorAttributes}
  294. if __name__ == '__main__':
  295. usage = "treble_sepolicy_tests -l $(ANDROID_HOST_OUT)/lib64/libsepolwrap.so "
  296. usage += "-f nonplat_file_contexts -f plat_file_contexts "
  297. usage += "-p curr_policy -b base_policy -o old_policy "
  298. usage +="-m mapping file [--test test] [--help]"
  299. parser = OptionParser(option_class=MultipleOption, usage=usage)
  300. parser.add_option("-b", "--basepolicy", dest="basepolicy", metavar="FILE")
  301. parser.add_option("-u", "--base-pub-policy", dest="base_pub_policy",
  302. metavar="FILE")
  303. parser.add_option("-f", "--file_contexts", dest="file_contexts",
  304. metavar="FILE", action="extend", type="string")
  305. parser.add_option("-l", "--library-path", dest="libpath", metavar="FILE")
  306. parser.add_option("-m", "--mapping", dest="mapping", metavar="FILE")
  307. parser.add_option("-o", "--oldpolicy", dest="oldpolicy", metavar="FILE")
  308. parser.add_option("-p", "--policy", dest="policy", metavar="FILE")
  309. parser.add_option("-t", "--test", dest="tests", action="extend",
  310. help="Test options include "+str(Tests))
  311. parser.add_option("--fake-treble", action="store_true", dest="faketreble",
  312. default=False)
  313. (options, args) = parser.parse_args()
  314. if not options.libpath:
  315. sys.exit("Must specify path to libsepolwrap library\n" + parser.usage)
  316. if not os.path.exists(options.libpath):
  317. sys.exit("Error: library-path " + options.libpath + " does not exist\n"
  318. + parser.usage)
  319. if not options.policy:
  320. sys.exit("Must specify current monolithic policy file\n" + parser.usage)
  321. if not os.path.exists(options.policy):
  322. sys.exit("Error: policy file " + options.policy + " does not exist\n"
  323. + parser.usage)
  324. if not options.file_contexts:
  325. sys.exit("Error: Must specify file_contexts file(s)\n" + parser.usage)
  326. for f in options.file_contexts:
  327. if not os.path.exists(f):
  328. sys.exit("Error: File_contexts file " + f + " does not exist\n" +
  329. parser.usage)
  330. # Mapping files and public platform policy are only necessary for the
  331. # TrebleCompatMapping test.
  332. if options.tests is None or options.tests is "TrebleCompatMapping":
  333. if not options.basepolicy:
  334. sys.exit("Must specify the current platform-only policy file\n"
  335. + parser.usage)
  336. if not options.mapping:
  337. sys.exit("Must specify a compatibility mapping file\n"
  338. + parser.usage)
  339. if not options.oldpolicy:
  340. sys.exit("Must specify the previous monolithic policy file\n"
  341. + parser.usage)
  342. if not options.base_pub_policy:
  343. sys.exit("Must specify the current platform-only public policy "
  344. + ".cil file\n" + parser.usage)
  345. basepol = policy.Policy(options.basepolicy, None, options.libpath)
  346. oldpol = policy.Policy(options.oldpolicy, None, options.libpath)
  347. mapping = mini_parser.MiniCilParser(options.mapping)
  348. pubpol = mini_parser.MiniCilParser(options.base_pub_policy)
  349. compatSetup(basepol, oldpol, mapping, pubpol.types)
  350. if options.faketreble:
  351. FakeTreble = True
  352. pol = policy.Policy(options.policy, options.file_contexts, options.libpath)
  353. setup(pol)
  354. if DEBUG:
  355. PrintScontexts()
  356. results = ""
  357. # If an individual test is not specified, run all tests.
  358. if options.tests is None:
  359. for t in Tests.values():
  360. results += t()
  361. else:
  362. for tn in options.tests:
  363. t = Tests.get(tn)
  364. if t:
  365. results += t()
  366. else:
  367. err = "Error: unknown test: " + tn + "\n"
  368. err += "Available tests:\n"
  369. for tn in Tests.keys():
  370. err += tn + "\n"
  371. sys.exit(err)
  372. if len(results) > 0:
  373. sys.exit(results)