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