| # Copyright 2018 The Chromium OS Authors. All rights reserved. |
| # Use of this source code is governed by a BSD-style license that can be |
| # found in the LICENSE file. |
| |
| import logging |
| import os |
| import stat |
| import xattr |
| |
| from autotest_lib.client.bin import test, utils |
| from autotest_lib.client.common_lib import error |
| |
| def _get_process_context(pid=None): |
| """Returns the SELinux context for a process.""" |
| if pid is None: |
| pid = 'self' |
| with open('/proc/{}/attr/current'.format(pid)) as f: |
| return f.read().rstrip('\0') |
| |
| def _get_file_label(path, nofollow=False): |
| """Returns the SELinux label for a file.""" |
| return xattr.getxattr(path, 'security.selinux', nofollow).rstrip('\0') |
| |
| def _assert_true(actual, msg='Unexpected false condition'): |
| """Raises an error if the condition isn't true.""" |
| if not actual: |
| raise error.TestFail(msg) |
| |
| def _assert_false(actual, msg='Unexpected true condition'): |
| """Raises an error if the condition isn't true.""" |
| if actual: |
| raise error.TestFail(msg) |
| |
| def _assert_prefix(expected_prefix, actual, msg='value'): |
| """Raises an error if two values aren't equal.""" |
| if not actual.startswith(expected_prefix): |
| raise error.TestFail('Unexpected {}: Expected prefix: {}, Actual: {}' |
| .format(msg, expected_prefix, actual)) |
| |
| def _assert_eq(expected, actual, msg='value'): |
| """Raises an error if two values aren't equal.""" |
| if expected != actual: |
| raise error.TestFail('Unexpected {}: Expected: {}, Actual: {}' |
| .format(msg, expected, actual)) |
| |
| def _assert_in(expected_set, actual, msg='value'): |
| """Raises an error if a value is not contained in a set.""" |
| if actual not in expected_set: |
| raise error.TestFail('Unexpected {}: Expected one of: {}, Actual: {}' |
| .format(msg, expected_set, actual)) |
| |
| def _check_file_labels_recursively( |
| top_dir, expected, nofollow=True, relevant=None, check_top_dir=True, |
| prefix_match=False): |
| """Check if all files in |top_dir| have the expected label.""" |
| if check_top_dir: |
| label = _get_file_label(top_dir, nofollow) |
| if prefix_match: |
| _assert_prefix(expected, label, 'label for %s' % top_dir) |
| else: |
| _assert_eq(expected, label, 'label for %s' % top_dir) |
| for root, dirs, files in os.walk(top_dir): |
| for name in dirs + files: |
| path = os.path.join(root, name) |
| if relevant is not None and not relevant(path): |
| continue |
| label = _get_file_label(path, nofollow) |
| if prefix_match: |
| _assert_prefix(expected, label, 'label for %s' % path) |
| else: |
| _assert_eq(expected, label, 'label for %s' % path) |
| |
| def _log_writable_files(tree_to_check): |
| """Logs all writable files in |tree_to_check|.""" |
| for root, _, files in os.walk(tree_to_check): |
| for name in files: |
| file_path = os.path.join(root, name) |
| if os.lstat(file_path).st_mode & ( |
| stat.S_IWUSR | stat.S_IWGRP | stat.S_IWOTH): |
| logging.info('%s is writable', file_path) |
| |
| def _log_files(tree_to_check): |
| """Logs all files and directories in |tree_to_check|.""" |
| for root, dirs, files in os.walk(tree_to_check): |
| for name in files: |
| file_path = os.path.join(root, name) |
| file_label = _get_file_label(file_path, nofollow=True) |
| logging.info('%s has %s', file_path, file_label) |
| for name in dirs: |
| dir_path = os.path.join(root, name) |
| dir_label = _get_file_label(dir_path, nofollow=True) |
| logging.info('%s (directory) has %s', dir_path, dir_label) |
| |
| def _get_drm_render_sys_devices(): |
| """Returns a list of DRM render nodes under /sys/devices.""" |
| trees = [] |
| drm_class_tree = '/sys/class/drm' |
| drm_render_prefix = 'renderD' |
| # Iterate over all devices in the class |
| for entry in os.listdir(drm_class_tree): |
| # Pick only render nodes |
| if not entry.startswith(drm_render_prefix): |
| continue |
| |
| entry_tree = os.path.join(drm_class_tree, entry) |
| device_tree = os.path.join(entry_tree, 'device') |
| device_link = os.readlink(device_tree) |
| # The symlink seems to be relative, so we need to resolve it by |
| # concatenating it to the base directory and getting realpath of |
| # that. |
| # |
| # Note that even if the symlink is absolute, os.path.join() |
| # would handle it correctly, as it detects absolute components |
| # being joined. |
| device_tree = os.path.join(entry_tree, device_link) |
| real_device_tree = os.path.realpath(device_tree) |
| trees.append(real_device_tree) |
| return trees |
| |
| CHROME_SSH_CONTEXT = 'u:r:cros_ssh_session:s0' |
| |
| class security_SELinux(test.test): |
| """Tests that various SELinux context and labels are correct. Note that |
| tests of the browser and SELinux should go into autotest-chrome and tests |
| of ARC++ and SELinux should go into cheets_SELinuxTest. |
| """ |
| |
| version = 1 |
| |
| def _check_selinux_enforcing(self): |
| """Test that SELinux is enforcing.""" |
| r = utils.run('getenforce', |
| stdout_tee=utils.TEE_TO_LOGS, |
| stderr_tee=utils.TEE_TO_LOGS, |
| ignore_status=True) |
| if r.exit_status != 0 or len(r.stderr) > 0: |
| raise error.TestFail(r.stderr) |
| _assert_eq('Enforcing', r.stdout.strip()) |
| |
| def _check_test_context(self): |
| """Test that this test is running under the cros_ssh_session context.""" |
| _assert_eq(CHROME_SSH_CONTEXT, |
| _get_process_context(), |
| 'context for current process') |
| |
| def _check_init_label(self): |
| """Test that /sbin/init is labeled chromeos_init_exec.""" |
| _assert_eq('u:object_r:chromeos_init_exec:s0', |
| _get_file_label('/sbin/init'), |
| 'label for /sbin/init') |
| |
| def _check_cras_label(self): |
| """Test that /run/cras directory is labeled properly.""" |
| _check_file_labels_recursively('/run/cras', 'u:object_r:cras_socket:s0') |
| |
| def _check_sys_devices_system_cpu_labels(self): |
| """Test that files in /sys/devices/system/cpu are labeled properly.""" |
| sys_tree_to_check = '/sys/devices/system/cpu' |
| |
| def is_writable_file(path): |
| """Checks if path is writable.""" |
| st = os.lstat(path) |
| if not stat.S_ISREG(st.st_mode): |
| return False # not a file |
| return st.st_mode & (stat.S_IWUSR | stat.S_IWGRP | stat.S_IWOTH) |
| |
| try: |
| _check_file_labels_recursively( |
| sys_tree_to_check, |
| 'u:object_r:sysfs_devices_system_cpu:s0', |
| relevant=lambda f: not is_writable_file(f)) |
| # Writable files must have 'sysfs' label (b/34814534) |
| _check_file_labels_recursively(sys_tree_to_check, |
| 'u:object_r:sysfs:s0', |
| relevant=is_writable_file, |
| check_top_dir=False) |
| except: |
| _log_writable_files(sys_tree_to_check) |
| raise |
| |
| def _check_sys_fs_cgroup_labels(self): |
| """Test that files in /sys/fs/cgroup are labeled properly.""" |
| sys_tree_to_check = '/sys/fs/cgroup' |
| # First, check the label for the root of the tree. |sys_tree_to_check| |
| # is actually a tmpfs mounted on the directory. |
| file_label = _get_file_label(sys_tree_to_check) |
| _assert_eq('u:object_r:tmpfs:s0', |
| file_label, 'label for %s' % sys_tree_to_check) |
| # Then, check each file and directory in the tree. |
| try: |
| _check_file_labels_recursively(sys_tree_to_check, |
| 'u:object_r:cgroup:s0', |
| check_top_dir=False) |
| except: |
| _log_files(sys_tree_to_check) |
| raise |
| |
| def _check_sys_fs_pstore_labels(self): |
| """Test that files in /sys/fs/pstore are labeled properly.""" |
| sys_tree_to_check = '/sys/fs/pstore' |
| # First, check the label for the root of the tree. |
| file_label = _get_file_label(sys_tree_to_check) |
| _assert_eq('u:object_r:pstorefs:s0', |
| file_label, 'label for %s' % sys_tree_to_check) |
| |
| def _check_sys_fs_selinux_labels(self): |
| """Test that files in /sys/fs/selinux are labeled properly.""" |
| sys_tree_to_check = '/sys/fs/selinux' |
| |
| def is_null_file(path): |
| """SELinux filesystem has an analog of /dev/null that we want to |
| ignore. |
| """ |
| return path == os.path.join(sys_tree_to_check, 'null') |
| |
| try: |
| _check_file_labels_recursively( |
| sys_tree_to_check, |
| 'u:object_r:selinuxfs:s0', |
| relevant=lambda f: not is_null_file(f)) |
| _check_file_labels_recursively(sys_tree_to_check, |
| 'u:object_r:null_device:s0', |
| relevant=is_null_file, |
| check_top_dir=False) |
| except: |
| _log_files(sys_tree_to_check) |
| raise |
| |
| def _check_sys_kernel_config_labels(self): |
| """Test that files in /sys/kernel/config are labeled properly.""" |
| sys_tree_to_check = '/sys/kernel/config' |
| if not os.path.exists(sys_tree_to_check): |
| return |
| # First, check the label for the root of the tree. |
| file_label = _get_file_label(sys_tree_to_check) |
| _assert_eq('u:object_r:configfs:s0', |
| file_label, 'label for %s' % sys_tree_to_check) |
| |
| def _check_sys_kernel_debug_labels(self): |
| """Test /sys/kernel/debug labels.""" |
| def _check_labels(relative_path, expected_label): |
| absolute_path = os.path.join('/', relative_path) |
| live_label = _get_file_label(absolute_path) |
| _assert_eq(expected_label, live_label, |
| "context for Host's %s" % absolute_path) |
| |
| # Check debugfs |
| _check_labels('sys/kernel/debug', |
| 'u:object_r:debugfs:s0') |
| # Check some debugfs/tracing files |
| for debugfs_file in ['tracing', 'tracing/tracing_on']: |
| _check_labels('sys/kernel/debug/%s' % debugfs_file, |
| 'u:object_r:debugfs_tracing:s0') |
| |
| # Check debugfs/tracing/trace_marker |
| _check_labels('sys/kernel/debug/tracing/trace_marker', |
| 'u:object_r:debugfs_trace_marker:s0') |
| |
| def _check_wayland_sock_label(self): |
| """Test that the Wayland socket is labeled properly.""" |
| label = _get_file_label('/run/chrome/wayland-0') |
| _assert_eq('u:object_r:wayland_socket:s0', label, |
| 'label for Wayland socket') |
| |
| def run_once(self): |
| """All the tests in this unit are run from here.""" |
| # Check if SELinux is enforced on the DUT. |
| self._check_selinux_enforcing() |
| # Check process contexts. |
| self._check_test_context() |
| # Check files. |
| self._check_init_label() |
| # Check container-side files from the init mount namespace. |
| self._check_cras_label() |
| self._check_sys_devices_system_cpu_labels() |
| self._check_sys_fs_cgroup_labels() |
| self._check_sys_fs_pstore_labels() |
| self._check_sys_fs_selinux_labels() |
| self._check_sys_kernel_config_labels() |
| self._check_sys_kernel_debug_labels() |
| self._check_wayland_sock_label() |