blob: f05ebd919b50c1c8404a19f4bd5cae0229801439 [file] [log] [blame]
# 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()