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