• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
1# Copyright 2018 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 logging
6import os
7import stat
8import xattr
9
10from autotest_lib.client.bin import test, utils
11from autotest_lib.client.common_lib import error
12
13def _get_process_context(pid=None):
14    """Returns the SELinux context for a process."""
15    if pid is None:
16        pid = 'self'
17    with open('/proc/{}/attr/current'.format(pid)) as f:
18        return f.read().rstrip('\0')
19
20def _get_file_label(path, nofollow=False):
21    """Returns the SELinux label for a file."""
22    return xattr.getxattr(path, 'security.selinux', nofollow).rstrip('\0')
23
24def _assert_true(actual, msg='Unexpected false condition'):
25    """Raises an error if the condition isn't true."""
26    if not actual:
27        raise error.TestFail(msg)
28
29def _assert_false(actual, msg='Unexpected true condition'):
30    """Raises an error if the condition isn't true."""
31    if actual:
32        raise error.TestFail(msg)
33
34def _assert_prefix(expected_prefix, actual, msg='value'):
35    """Raises an error if two values aren't equal."""
36    if not actual.startswith(expected_prefix):
37        raise error.TestFail('Unexpected {}: Expected prefix: {}, Actual: {}'
38                             .format(msg, expected_prefix, actual))
39
40def _assert_eq(expected, actual, msg='value'):
41    """Raises an error if two values aren't equal."""
42    if expected != actual:
43        raise error.TestFail('Unexpected {}: Expected: {}, Actual: {}'
44                             .format(msg, expected, actual))
45
46def _assert_in(expected_set, actual, msg='value'):
47    """Raises an error if a value is not contained in a set."""
48    if actual not in expected_set:
49        raise error.TestFail('Unexpected {}: Expected one of: {}, Actual: {}'
50                             .format(msg, expected_set, actual))
51
52def _check_file_labels_recursively(
53    top_dir, expected, nofollow=True, relevant=None, check_top_dir=True,
54    prefix_match=False):
55    """Check if all files in |top_dir| have the expected label."""
56    if check_top_dir:
57        label = _get_file_label(top_dir, nofollow)
58        if prefix_match:
59          _assert_prefix(expected, label, 'label for %s' % top_dir)
60        else:
61          _assert_eq(expected, label, 'label for %s' % top_dir)
62    for root, dirs, files in os.walk(top_dir):
63        for name in dirs + files:
64            path = os.path.join(root, name)
65            if relevant is not None and not relevant(path):
66              continue
67            label = _get_file_label(path, nofollow)
68            if prefix_match:
69              _assert_prefix(expected, label, 'label for %s' % path)
70            else:
71              _assert_eq(expected, label, 'label for %s' % path)
72
73def _log_writable_files(tree_to_check):
74    """Logs all writable files in |tree_to_check|."""
75    for root, _, files in os.walk(tree_to_check):
76        for name in files:
77            file_path = os.path.join(root, name)
78            if os.lstat(file_path).st_mode & (
79                stat.S_IWUSR | stat.S_IWGRP | stat.S_IWOTH):
80                logging.info('%s is writable', file_path)
81
82def _log_files(tree_to_check):
83    """Logs all files and directories in |tree_to_check|."""
84    for root, dirs, files in os.walk(tree_to_check):
85        for name in files:
86            file_path = os.path.join(root, name)
87            file_label = _get_file_label(file_path, nofollow=True)
88            logging.info('%s has %s', file_path, file_label)
89        for name in dirs:
90            dir_path = os.path.join(root, name)
91            dir_label = _get_file_label(dir_path, nofollow=True)
92            logging.info('%s (directory) has %s', dir_path, dir_label)
93
94def _get_drm_render_sys_devices():
95    """Returns a list of DRM render nodes under /sys/devices."""
96    trees = []
97    drm_class_tree = '/sys/class/drm'
98    drm_render_prefix = 'renderD'
99    # Iterate over all devices in the class
100    for entry in os.listdir(drm_class_tree):
101        # Pick only render nodes
102        if not entry.startswith(drm_render_prefix):
103            continue
104
105        entry_tree = os.path.join(drm_class_tree, entry)
106        device_tree = os.path.join(entry_tree, 'device')
107        device_link = os.readlink(device_tree)
108        # The symlink seems to be relative, so we need to resolve it by
109        # concatenating it to the base directory and getting realpath of
110        # that.
111        #
112        # Note that even if the symlink is absolute, os.path.join()
113        # would handle it correctly, as it detects absolute components
114        # being joined.
115        device_tree = os.path.join(entry_tree, device_link)
116        real_device_tree = os.path.realpath(device_tree)
117        trees.append(real_device_tree)
118    return trees
119
120CHROME_SSH_CONTEXT = 'u:r:cros_ssh_session:s0'
121
122class security_SELinux(test.test):
123    """Tests that various SELinux context and labels are correct. Note that
124    tests of the browser and SELinux should go into autotest-chrome and tests
125    of ARC++ and SELinux should go into cheets_SELinuxTest.
126    """
127
128    version = 1
129
130    def _check_selinux_enforcing(self):
131        """Test that SELinux is enforcing."""
132        r = utils.run('getenforce',
133                      stdout_tee=utils.TEE_TO_LOGS,
134                      stderr_tee=utils.TEE_TO_LOGS,
135                      ignore_status=True)
136        if r.exit_status !=  0 or len(r.stderr) > 0:
137            raise error.TestFail(r.stderr)
138        _assert_eq('Enforcing', r.stdout.strip())
139
140    def _check_test_context(self):
141        """Test that this test is running under the cros_ssh_session context."""
142        _assert_eq(CHROME_SSH_CONTEXT,
143                   _get_process_context(),
144                   'context for current process')
145
146    def _check_init_label(self):
147        """Test that /sbin/init is labeled chromeos_init_exec."""
148        _assert_eq('u:object_r:chromeos_init_exec:s0',
149                   _get_file_label('/sbin/init'),
150                   'label for /sbin/init')
151
152    def _check_cras_label(self):
153        """Test that /run/cras directory is labeled properly."""
154        _check_file_labels_recursively('/run/cras', 'u:object_r:cras_socket:s0')
155
156    def _check_sys_devices_system_cpu_labels(self):
157        """Test that files in /sys/devices/system/cpu are labeled properly."""
158        sys_tree_to_check = '/sys/devices/system/cpu'
159
160        def is_writable_file(path):
161            """Checks if path is writable."""
162            st = os.lstat(path)
163            if not stat.S_ISREG(st.st_mode):
164                return False  # not a file
165            return st.st_mode & (stat.S_IWUSR | stat.S_IWGRP | stat.S_IWOTH)
166
167        try:
168            _check_file_labels_recursively(
169                sys_tree_to_check,
170                'u:object_r:sysfs_devices_system_cpu:s0',
171                relevant=lambda f: not is_writable_file(f))
172            # Writable files must have 'sysfs' label (b/34814534)
173            _check_file_labels_recursively(sys_tree_to_check,
174                                           'u:object_r:sysfs:s0',
175                                           relevant=is_writable_file,
176                                           check_top_dir=False)
177        except:
178            _log_writable_files(sys_tree_to_check)
179            raise
180
181    def _check_sys_fs_cgroup_labels(self):
182        """Test that files in /sys/fs/cgroup are labeled properly."""
183        sys_tree_to_check = '/sys/fs/cgroup'
184        # First, check the label for the root of the tree. |sys_tree_to_check|
185        # is actually a tmpfs mounted on the directory.
186        file_label = _get_file_label(sys_tree_to_check)
187        _assert_eq('u:object_r:tmpfs:s0',
188                   file_label, 'label for %s' % sys_tree_to_check)
189        # Then, check each file and directory in the tree.
190        try:
191            _check_file_labels_recursively(sys_tree_to_check,
192                                           'u:object_r:cgroup:s0',
193                                           check_top_dir=False)
194        except:
195            _log_files(sys_tree_to_check)
196            raise
197
198    def _check_sys_fs_pstore_labels(self):
199        """Test that files in /sys/fs/pstore are labeled properly."""
200        sys_tree_to_check = '/sys/fs/pstore'
201        # First, check the label for the root of the tree.
202        file_label = _get_file_label(sys_tree_to_check)
203        _assert_eq('u:object_r:pstorefs:s0',
204                   file_label, 'label for %s' % sys_tree_to_check)
205
206    def _check_sys_fs_selinux_labels(self):
207        """Test that files in /sys/fs/selinux are labeled properly."""
208        sys_tree_to_check = '/sys/fs/selinux'
209
210        def is_null_file(path):
211            """SELinux filesystem has an analog of /dev/null that we want to
212            ignore.
213            """
214            return path == os.path.join(sys_tree_to_check, 'null')
215
216        try:
217            _check_file_labels_recursively(
218                sys_tree_to_check,
219                'u:object_r:selinuxfs:s0',
220                relevant=lambda f: not is_null_file(f))
221            _check_file_labels_recursively(sys_tree_to_check,
222                                           'u:object_r:null_device:s0',
223                                           relevant=is_null_file,
224                                           check_top_dir=False)
225        except:
226            _log_files(sys_tree_to_check)
227            raise
228
229    def _check_sys_kernel_config_labels(self):
230        """Test that files in /sys/kernel/config are labeled properly."""
231        sys_tree_to_check = '/sys/kernel/config'
232        if not os.path.exists(sys_tree_to_check):
233            return
234        # First, check the label for the root of the tree.
235        file_label = _get_file_label(sys_tree_to_check)
236        _assert_eq('u:object_r:configfs:s0',
237                   file_label, 'label for %s' % sys_tree_to_check)
238
239    def _check_sys_kernel_debug_labels(self):
240        """Test /sys/kernel/debug labels."""
241        def _check_labels(relative_path, expected_label):
242            absolute_path = os.path.join('/', relative_path)
243            live_label = _get_file_label(absolute_path)
244            _assert_eq(expected_label, live_label,
245                       "context for Host's %s" % absolute_path)
246
247        # Check debugfs
248        _check_labels('sys/kernel/debug',
249                      'u:object_r:debugfs:s0')
250        # Check some debugfs/tracing files
251        for debugfs_file in ['tracing', 'tracing/tracing_on']:
252            _check_labels('sys/kernel/debug/%s' % debugfs_file,
253                          'u:object_r:debugfs_tracing:s0')
254
255        # Check debugfs/tracing/trace_marker
256        _check_labels('sys/kernel/debug/tracing/trace_marker',
257                      'u:object_r:debugfs_trace_marker:s0')
258
259    def _check_wayland_sock_label(self):
260        """Test that the Wayland socket is labeled properly."""
261        label = _get_file_label('/run/chrome/wayland-0')
262        _assert_eq('u:object_r:wayland_socket:s0', label,
263                   'label for Wayland socket')
264
265    def run_once(self):
266        """All the tests in this unit are run from here."""
267        # Check if SELinux is enforced on the DUT.
268        self._check_selinux_enforcing()
269        # Check process contexts.
270        self._check_test_context()
271        # Check files.
272        self._check_init_label()
273        # Check container-side files from the init mount namespace.
274        self._check_cras_label()
275        self._check_sys_devices_system_cpu_labels()
276        self._check_sys_fs_cgroup_labels()
277        self._check_sys_fs_pstore_labels()
278        self._check_sys_fs_selinux_labels()
279        self._check_sys_kernel_config_labels()
280        self._check_sys_kernel_debug_labels()
281        self._check_wayland_sock_label()
282