1# Copyright (c) 2011 The Chromium OS Authors. All rights reserved. 2# Use of this source code is governed by a BSD-style license that can be 3# found in the LICENSE file. 4 5import glob 6import logging 7import os 8import re 9import shutil 10import tempfile 11 12from autotest_lib.client.bin import test, utils 13from autotest_lib.client.common_lib import error 14 15class security_Minijail0(test.test): 16 version = 1 17 18 19 def is_64bit(self): 20 return os.path.isdir('/lib64') 21 22 23 def get_test_option(self, handle, name): 24 setup = '' 25 for l in handle.readlines(): 26 m = re.match('^# %s: (.*)' % name, l.strip()) 27 if m: 28 setup = m.group(1) 29 return setup 30 31 32 def run_test(self, path, static): 33 # Tests are shell scripts with a magic comment line of the form '# args: 34 # <stuff>' in them. The <stuff> is substituted in here as minijail0 35 # arguments. They can also optionally contain a magic comment of the 36 # form '# setup: <stuff>', in which case <stuff> is executed as a shell 37 # command before running the test. 38 # Another optional magic comment of the form '# expected_ugid <uid> 39 # <gid>' is used when entering a new user namespace, where <uid> and 40 # <gid> are the expected uid and gid 'outside' the user namespace. If 41 # expected_ugid is set, a temporary directory is created, and a 42 # temporary file is passed to tests as first argument. Tests should 43 # 'touch' that file and its uid/gid will be checked outside the user 44 # namespace. 45 # 46 # If '%T' is present in either of the above magic comments, a temporary 47 # directory is created, and its name is substituted for '%T' in both of 48 # them. 49 # If '%S' is present in either of the above magic comments, it is 50 # replaced with src folder of these tests. 51 args = self.get_test_option(file(path), 'args') 52 setup = self.get_test_option(file(path), 'setup') 53 args64 = self.get_test_option(file(path), 'args64') 54 args32 = self.get_test_option(file(path), 'args32') 55 expugid = self.get_test_option(file(path), 'expected_ugid').split(" ") 56 57 td = None 58 if setup: 59 if '%T' in setup: 60 td = tempfile.mkdtemp() 61 setup = setup.replace('%T', td) 62 if '%S' in setup: 63 setup = setup.replace('%S', self.srcdir) 64 utils.system(setup) 65 66 if self.is_64bit() and args64: 67 args = args + ' ' + args64 68 69 if (not self.is_64bit()) and args32: 70 args = args + ' ' + args32 71 72 if '%T' in args: 73 td = td or tempfile.mkdtemp() 74 args = args.replace('%T', td) 75 if '%S' in args: 76 args = args.replace('%S', self.srcdir) 77 78 userns_td = None 79 userns_file = '' 80 if len(expugid) == 2: 81 expuid, expgid = expugid 82 userns_td = tempfile.mkdtemp() 83 os.chmod(userns_td, 0777) 84 userns_file = userns_td + '/userns' 85 86 if static: 87 ret = utils.system('/sbin/minijail0 %s %s/staticbashexec %s %s' 88 % (args, self.srcdir, path, userns_file), 89 ignore_status=True) 90 else: 91 ret = utils.system('/sbin/minijail0 %s /bin/bash %s %s' 92 % (args, path, userns_file), 93 ignore_status=True) 94 if ret == 0 and len(expugid) == 2: 95 stat = os.stat(userns_file) 96 if str(stat.st_uid) != expuid or str(stat.st_gid) != expgid: 97 ret = 1 98 99 if td: 100 # The test better not have polluted our mount namespace :). 101 shutil.rmtree(td) 102 if userns_td: 103 shutil.rmtree(userns_td) 104 return ret 105 106 107 def setup(self): 108 os.chdir(self.srcdir) 109 utils.make() 110 111 112 def run_once(self): 113 failed = [] 114 ran = 0 115 for p in glob.glob('%s/test-*' % self.srcdir): 116 name = os.path.basename(p) 117 logging.info('Running: %s', name) 118 if self.run_test(p, static=False): 119 failed.append(name) 120 ran += 1 121 if name != 'test-caps': 122 if self.run_test(p, static=True): 123 failed.append(name + ' static') 124 ran += 1 125 if ran == 0: 126 failed.append("No tests found to run from %s!" % (self.srcdir)) 127 if failed: 128 logging.error('Failed: %s', failed) 129 raise error.TestFail('Failed: %s' % failed) 130