blob: 9ade38ba2996431fa8fa810871edc1c414fd1814 [file] [log] [blame]
/*
* Linux Security Module for Chromium OS
*
* Copyright 2011 Google Inc. All Rights Reserved
*
* Authors:
* Stephan Uphoff <ups@google.com>
* Kees Cook <keescook@chromium.org>
*
* This software is licensed under the terms of the GNU General Public
* License version 2, as published by the Free Software Foundation, and
* may be copied, distributed, and modified under those terms.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*/
#define pr_fmt(fmt) "Chromium OS LSM: " fmt
#include <asm/syscall.h>
#include <linux/audit.h>
#include <linux/binfmts.h>
#include <linux/cred.h>
#include <linux/fs.h>
#include <linux/fs_parser.h>
#include <linux/fs_struct.h>
#include <linux/lsm_hooks.h>
#include <linux/module.h>
#include <linux/mount.h>
#include <linux/namei.h> /* for nameidata_get_total_link_count */
#include <linux/path.h>
#include <linux/ptrace.h>
#include <linux/sched/task_stack.h>
#include <linux/sched.h> /* current and other task related stuff */
#include <linux/security.h>
#include <linux/shmem_fs.h>
#include <uapi/linux/mount.h>
#include "inode_mark.h"
#include "utils.h"
static int allow_overlayfs;
static int __init allow_overlayfs_set(char *__unused)
{
allow_overlayfs = 1;
return 1;
}
__setup("chromiumos.allow_overlayfs", allow_overlayfs_set);
#if defined(CONFIG_SECURITY_CHROMIUMOS_NO_UNPRIVILEGED_UNSAFE_MOUNTS) || \
defined(CONFIG_SECURITY_CHROMIUMOS_NO_SYMLINK_MOUNT)
static void report(const char *origin, const struct path *path, char *operation)
{
char *alloced = NULL, *cmdline;
char *pathname; /* Pointer to either static string or "alloced". */
if (!path)
pathname = "<unknown>";
else {
/* We will allow 11 spaces for ' (deleted)' to be appended */
alloced = pathname = kmalloc(PATH_MAX+11, GFP_KERNEL);
if (!pathname)
pathname = "<no_memory>";
else {
pathname = d_path(path, pathname, PATH_MAX+11);
if (IS_ERR(pathname))
pathname = "<too_long>";
else {
pathname = printable(pathname, PATH_MAX+11);
kfree(alloced);
alloced = pathname;
}
}
}
cmdline = printable_cmdline(current);
pr_notice("%s %s obj=%s pid=%d cmdline=%s\n", origin,
operation, pathname, task_pid_nr(current), cmdline);
kfree(cmdline);
kfree(alloced);
}
#endif
static int chromiumos_security_sb_mount(const char *dev_name,
const struct path *path,
const char *type, unsigned long flags,
void *data)
{
if (!allow_overlayfs && type && !strcmp(type, "overlay")) {
report("sb_mount", path, "Overlayfs mounts prohibited");
pr_notice("sb_mount dev=%s type=%s flags=%#lx\n",
dev_name, type, flags);
return -EPERM;
}
#ifdef CONFIG_SECURITY_CHROMIUMOS_NO_SYMLINK_MOUNT
if (nameidata_get_total_link_count()) {
report("sb_mount", path, "Mount path with symlinks prohibited");
pr_notice("sb_mount dev=%s type=%s flags=%#lx\n",
dev_name, type, flags);
return -ELOOP;
}
#endif
#ifdef CONFIG_SECURITY_CHROMIUMOS_NO_UNPRIVILEGED_UNSAFE_MOUNTS
if ((!(flags & (MS_BIND | MS_MOVE | MS_SHARED | MS_PRIVATE | MS_SLAVE |
MS_UNBINDABLE)) ||
((flags & MS_REMOUNT) && (flags & MS_BIND))) &&
!capable(CAP_SYS_ADMIN)) {
int required_mnt_flags = MNT_NOEXEC | MNT_NOSUID | MNT_NODEV;
if (flags & MS_REMOUNT) {
/*
* If this is a remount, we only require that the
* requested flags are a superset of the original mount
* flags. In addition, using nosymfollow is not
* initially required, but remount is not allowed to
* remove it.
*/
required_mnt_flags |= MNT_NOSYMFOLLOW;
required_mnt_flags &= path->mnt->mnt_flags;
}
/*
* The three flags we are interested in disallowing in
* unprivileged user namespaces (MS_NOEXEC, MS_NOSUID, MS_NODEV)
* cannot be modified when doing a bind-mount. The kernel
* attempts to dispatch calls to do_mount() within
* fs/namespace.c in the following order:
*
* * If the MS_REMOUNT flag is present, it calls do_remount().
* When MS_BIND is also present, it only allows to modify the
* per-mount flags, which are copied into
* |required_mnt_flags|. Otherwise it bails in the absence of
* the CAP_SYS_ADMIN in the init ns.
* * If the MS_BIND flag is present, the only other flag checked
* is MS_REC.
* * If any of the mount propagation flags are present
* (MS_SHARED, MS_PRIVATE, MS_SLAVE, MS_UNBINDABLE),
* flags_to_propagation_type() filters out any additional
* flags.
* * If MS_MOVE flag is present, all other flags are ignored.
*/
if ((required_mnt_flags & MNT_NOEXEC) && !(flags & MS_NOEXEC)) {
report("sb_mount", path,
"Mounting a filesystem with 'exec' flag requires CAP_SYS_ADMIN in init ns");
pr_notice("sb_mount dev=%s type=%s flags=%#lx\n",
dev_name, type, flags);
return -EPERM;
}
if ((required_mnt_flags & MNT_NOSUID) && !(flags & MS_NOSUID)) {
report("sb_mount", path,
"Mounting a filesystem with 'suid' flag requires CAP_SYS_ADMIN in init ns");
pr_notice("sb_mount dev=%s type=%s flags=%#lx\n",
dev_name, type, flags);
return -EPERM;
}
if ((required_mnt_flags & MNT_NODEV) && !(flags & MS_NODEV) &&
strcmp(type, "devpts")) {
report("sb_mount", path,
"Mounting a filesystem with 'dev' flag requires CAP_SYS_ADMIN in init ns");
pr_notice("sb_mount dev=%s type=%s flags=%#lx\n",
dev_name, type, flags);
return -EPERM;
}
}
#endif
return 0;
}
/*
* NOTE: The WARN() calls will emit a warning in cases of blocked symlink
* traversal attempts. These will show up in kernel warning reports
* collected by the crash reporter, so we have some insight on spurious
* failures that need addressing.
*/
static int chromiumos_security_inode_follow_link(struct dentry *dentry,
struct inode *inode, bool rcu)
{
static char accessed_path[PATH_MAX];
enum chromiumos_inode_security_policy policy;
policy = chromiumos_get_inode_security_policy(
dentry, inode,
CHROMIUMOS_SYMLINK_TRAVERSAL);
WARN(policy == CHROMIUMOS_INODE_POLICY_BLOCK,
"Blocked symlink traversal for path %x:%x:%s (see https://goo.gl/8xICW6 for context and rationale)\n",
MAJOR(dentry->d_sb->s_dev), MINOR(dentry->d_sb->s_dev),
dentry_path(dentry, accessed_path, PATH_MAX));
return policy == CHROMIUMOS_INODE_POLICY_BLOCK ? -EACCES : 0;
}
static int chromiumos_security_file_open(struct file *file)
{
static char accessed_path[PATH_MAX];
enum chromiumos_inode_security_policy policy;
struct dentry *dentry = file->f_path.dentry;
/* Returns 0 if file is not a FIFO */
if (!S_ISFIFO(file->f_inode->i_mode))
return 0;
policy = chromiumos_get_inode_security_policy(
dentry, dentry->d_inode,
CHROMIUMOS_FIFO_ACCESS);
/*
* Emit a warning in cases of blocked fifo access attempts. These will
* show up in kernel warning reports collected by the crash reporter,
* so we have some insight on spurious failures that need addressing.
*/
WARN(policy == CHROMIUMOS_INODE_POLICY_BLOCK,
"Blocked fifo access for path %x:%x:%s\n (see https://goo.gl/8xICW6 for context and rationale)\n",
MAJOR(dentry->d_sb->s_dev), MINOR(dentry->d_sb->s_dev),
dentry_path(dentry, accessed_path, PATH_MAX));
return policy == CHROMIUMOS_INODE_POLICY_BLOCK ? -EACCES : 0;
}
static int chromiumos_sb_eat_lsm_opts(char *orig, void **mnt_opts)
{
char *orig_copy;
char *orig_copy_cur;
char *option;
size_t offset = 0;
bool found = false;
if (!orig || *orig == 0)
return 0;
orig_copy = (char *)get_zeroed_page(GFP_KERNEL);
if (!orig_copy)
return -ENOMEM;
strncpy(orig_copy, orig, PAGE_SIZE);
memset(orig, 0, strlen(orig));
orig_copy_cur = orig_copy;
while (orig_copy_cur) {
option = strsep(&orig_copy_cur, ",");
/*
* Remove the option so that filesystems won't see it.
* do_mount() has already forced the MS_NOSYMFOLLOW flag on
* if it found this option, so no other action is needed.
*/
if (strcmp(option, "nosymfollow") == 0) {
if (found) /* Found multiple times. */
return -EINVAL;
found = true;
} else {
if (offset > 0) {
orig[offset] = ',';
offset++;
}
strcpy(orig + offset, option);
offset += strlen(option);
}
}
if (found) {
pr_notice("nosymfollow option should be changed to MS_NOSYMFOLLOW flag.\n");
}
free_page((unsigned long)orig_copy);
return 0;
}
static int chromiumos_bprm_creds_for_exec(struct linux_binprm *bprm)
{
struct file *file = bprm->file;
if (shmem_file(file)) {
char *cmdline = printable_cmdline(current);
audit_log(
audit_context(),
GFP_ATOMIC,
AUDIT_AVC,
"ChromeOS LSM: memfd execution attempt, cmd=%s, pid=%d",
cmdline ? cmdline : "(null)",
task_pid_nr(current));
kfree(cmdline);
pr_notice_ratelimited("memfd execution blocked\n");
return -EACCES;
}
return 0;
}
static int chromiumos_bpf(int cmd, union bpf_attr *attr, unsigned int size)
{
pr_notice_ratelimited("bpf syscall blocked\n");
return -EACCES;
}
static struct security_hook_list chromiumos_security_hooks[] = {
LSM_HOOK_INIT(sb_mount, chromiumos_security_sb_mount),
LSM_HOOK_INIT(inode_follow_link, chromiumos_security_inode_follow_link),
LSM_HOOK_INIT(file_open, chromiumos_security_file_open),
LSM_HOOK_INIT(sb_eat_lsm_opts, chromiumos_sb_eat_lsm_opts),
LSM_HOOK_INIT(bprm_creds_for_exec, chromiumos_bprm_creds_for_exec),
LSM_HOOK_INIT(bpf, chromiumos_bpf),
};
static int __init chromiumos_security_init(void)
{
security_add_hooks(chromiumos_security_hooks,
ARRAY_SIZE(chromiumos_security_hooks), "chromiumos");
pr_info("enabled");
return 0;
}
DEFINE_LSM(chromiumos) = {
.name = "chromiumos",
.init = chromiumos_security_init
};