Initial commit; kernel source import

This commit is contained in:
Nathan
2025-04-06 23:50:55 -05:00
commit 25c6d769f4
45093 changed files with 18199410 additions and 0 deletions

View File

@@ -0,0 +1,17 @@
config SECURITY_CHROMIUMOS
bool "Chromium OS Security Module"
depends on SECURITY
help
The purpose of the Chromium OS security module is to reduce attacking
surface by preventing access to general purpose access modes not
required by Chromium OS. Currently: the mount operation is
restricted by requiring a mount point path without symbolic links,
and loading modules is limited to only the root filesystem.
config SECURITY_CHROMIUMOS_READONLY_PROC_SELF_MEM
bool "Force /proc/<pid>/mem paths to be read-only"
default y
help
When enabled, attempts to open /proc/self/mem for write access
will always fail. Write access to this file allows bypassing
of memory map permissions (such as modifying read-only code).

View File

@@ -0,0 +1 @@
obj-$(CONFIG_SECURITY_CHROMIUMOS) += inode_mark.o lsm.o securityfs.o utils.o

View File

@@ -0,0 +1,348 @@
/*
* Linux Security Module for Chromium OS
*
* Copyright 2016 Google Inc. All Rights Reserved
*
* Authors:
* Mattias Nissler <mnissler@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.
*/
#include <linux/atomic.h>
#include <linux/compiler.h>
#include <linux/dcache.h>
#include <linux/fs.h>
#include <linux/fsnotify_backend.h>
#include <linux/hash.h>
#include <linux/mutex.h>
#include <linux/rculist.h>
#include <linux/slab.h>
#include <linux/spinlock.h>
#include "inode_mark.h"
/*
* This file implements facilities to pin inodes in core and attach some
* meta data to them. We use fsnotify inode marks as a vehicle to attach the
* meta data.
*/
struct chromiumos_inode_mark {
struct fsnotify_mark mark;
struct inode *inode;
enum chromiumos_inode_security_policy
policies[CHROMIUMOS_NUMBER_OF_POLICIES];
};
static inline struct chromiumos_inode_mark *
chromiumos_to_inode_mark(struct fsnotify_mark *mark)
{
return container_of(mark, struct chromiumos_inode_mark, mark);
}
/*
* Hashtable entry that contains tracking information specific to the file
* system identified by the corresponding super_block. This contains the
* fsnotify group that holds all the marks for inodes belonging to the
* super_block.
*/
struct chromiumos_super_block_mark {
atomic_t refcnt;
struct hlist_node node;
struct super_block *sb;
struct fsnotify_group *fsn_group;
};
#define CHROMIUMOS_SUPER_BLOCK_HASH_BITS 8
#define CHROMIUMOS_SUPER_BLOCK_HASH_SIZE (1 << CHROMIUMOS_SUPER_BLOCK_HASH_BITS)
static struct hlist_head chromiumos_super_block_hash_table
[CHROMIUMOS_SUPER_BLOCK_HASH_SIZE] __read_mostly;
static DEFINE_MUTEX(chromiumos_super_block_hash_lock);
static struct hlist_head *chromiumos_super_block_hlist(struct super_block *sb)
{
return &chromiumos_super_block_hash_table[hash_ptr(
sb, CHROMIUMOS_SUPER_BLOCK_HASH_BITS)];
}
static void chromiumos_super_block_put(struct chromiumos_super_block_mark *sbm)
{
if (atomic_dec_and_test(&sbm->refcnt)) {
mutex_lock(&chromiumos_super_block_hash_lock);
hlist_del_rcu(&sbm->node);
mutex_unlock(&chromiumos_super_block_hash_lock);
synchronize_rcu();
fsnotify_destroy_group(sbm->fsn_group);
kfree(sbm);
}
}
static struct chromiumos_super_block_mark *
chromiumos_super_block_lookup(struct super_block *sb)
{
struct hlist_head *hlist = chromiumos_super_block_hlist(sb);
struct chromiumos_super_block_mark *sbm;
struct chromiumos_super_block_mark *matching_sbm = NULL;
rcu_read_lock();
hlist_for_each_entry_rcu(sbm, hlist, node) {
if (sbm->sb == sb && atomic_inc_not_zero(&sbm->refcnt)) {
matching_sbm = sbm;
break;
}
}
rcu_read_unlock();
return matching_sbm;
}
static int chromiumos_handle_fsnotify_event(struct fsnotify_group *group,
struct fsnotify_mark *inode_mark,
struct fsnotify_mark *vfsmount_mark,
struct fsnotify_event *event)
{
/*
* This should never get called because a zero mask is set on the inode
* marks. All cases of marks going away (inode deletion, unmount,
* explicit removal) are handled in chromiumos_freeing_mark.
*/
WARN_ON_ONCE(1);
return 0;
}
static void chromiumos_freeing_mark(struct fsnotify_mark *mark,
struct fsnotify_group *group)
{
struct chromiumos_inode_mark *inode_mark =
chromiumos_to_inode_mark(mark);
iput(inode_mark->inode);
inode_mark->inode = NULL;
chromiumos_super_block_put(group->private);
}
static const struct fsnotify_ops chromiumos_fsn_ops = {
.handle_event = chromiumos_handle_fsnotify_event,
.freeing_mark = chromiumos_freeing_mark,
};
static struct chromiumos_super_block_mark *
chromiumos_super_block_create(struct super_block *sb)
{
struct hlist_head *hlist = chromiumos_super_block_hlist(sb);
struct chromiumos_super_block_mark *sbm = NULL;
WARN_ON(!mutex_is_locked(&chromiumos_super_block_hash_lock));
/* No match found, create a new entry. */
sbm = kzalloc(sizeof(*sbm), GFP_KERNEL);
if (!sbm)
return ERR_PTR(-ENOMEM);
atomic_set(&sbm->refcnt, 1);
sbm->sb = sb;
sbm->fsn_group = fsnotify_alloc_group(&chromiumos_fsn_ops);
if (IS_ERR(sbm->fsn_group)) {
int ret = PTR_ERR(sbm->fsn_group);
kfree(sbm);
return ERR_PTR(ret);
}
sbm->fsn_group->private = sbm;
hlist_add_head_rcu(&sbm->node, hlist);
return sbm;
}
static struct chromiumos_super_block_mark *
chromiumos_super_block_get(struct super_block *sb)
{
struct chromiumos_super_block_mark *sbm;
mutex_lock(&chromiumos_super_block_hash_lock);
sbm = chromiumos_super_block_lookup(sb);
if (!sbm)
sbm = chromiumos_super_block_create(sb);
mutex_unlock(&chromiumos_super_block_hash_lock);
return sbm;
}
static void chromiumos_free_mark(struct fsnotify_mark *mark)
{
iput(chromiumos_to_inode_mark(mark)->inode);
kfree(mark);
}
/*
* This will only ever get called if the metadata does not already exist for
* an inode, so no need to worry about freeing an existing mark.
*/
static int
chromiumos_inode_mark_create(
struct chromiumos_super_block_mark *sbm,
struct inode *inode,
enum chromiumos_inode_security_policy_type type,
enum chromiumos_inode_security_policy policy)
{
struct chromiumos_inode_mark *inode_mark;
int ret;
size_t i;
WARN_ON(!mutex_is_locked(&sbm->fsn_group->mark_mutex));
inode_mark = kzalloc(sizeof(*inode_mark), GFP_KERNEL);
if (!inode_mark)
return -ENOMEM;
/*
* Initialize chromiumos_free_mark() to be the routine that will be
* called when the mark is freed.
*/
fsnotify_init_mark(&inode_mark->mark, chromiumos_free_mark);
inode_mark->inode = igrab(inode);
if (!inode_mark->inode) {
ret = -ENOENT;
goto out;
}
/* Initialize all policies to inherit. */
for (i = 0; i < CHROMIUMOS_NUMBER_OF_POLICIES; i++)
inode_mark->policies[i] = CHROMIUMOS_INODE_POLICY_INHERIT;
inode_mark->policies[type] = policy;
ret = fsnotify_add_mark_locked(&inode_mark->mark, sbm->fsn_group,
inode_mark->inode, NULL, false);
if (ret)
goto out;
/* Take an sbm reference so the created mark is accounted for. */
atomic_inc(&sbm->refcnt);
out:
fsnotify_put_mark(&inode_mark->mark);
return ret;
}
int chromiumos_update_inode_security_policy(
struct inode *inode,
enum chromiumos_inode_security_policy_type type,
enum chromiumos_inode_security_policy policy)
{
struct chromiumos_super_block_mark *sbm;
struct fsnotify_mark *mark;
bool free_mark = false;
int ret;
size_t i;
sbm = chromiumos_super_block_get(inode->i_sb);
if (IS_ERR(sbm))
return PTR_ERR(sbm);
mutex_lock(&sbm->fsn_group->mark_mutex);
mark = fsnotify_find_inode_mark(sbm->fsn_group, inode);
if (mark) {
ACCESS_ONCE(chromiumos_to_inode_mark(mark)->policies[type]) =
policy;
/*
* Frees mark if all policies are
* CHROMIUM_INODE_POLICY_INHERIT.
*/
free_mark = true;
for (i = 0; i < CHROMIUMOS_NUMBER_OF_POLICIES; i++) {
if (chromiumos_to_inode_mark(mark)->policies[i]
!= CHROMIUMOS_INODE_POLICY_INHERIT) {
free_mark = false;
break;
}
}
if (free_mark)
fsnotify_destroy_mark_locked(mark, sbm->fsn_group);
ret = 0;
} else {
ret = chromiumos_inode_mark_create(sbm, inode, type, policy);
}
mutex_unlock(&sbm->fsn_group->mark_mutex);
chromiumos_super_block_put(sbm);
/* This must happen after dropping the mark mutex. */
if (mark)
fsnotify_put_mark(mark);
return ret;
}
/* Flushes all inode security policies. */
int chromiumos_flush_inode_security_policies(struct super_block *sb)
{
struct chromiumos_super_block_mark *sbm;
sbm = chromiumos_super_block_lookup(sb);
if (sbm) {
fsnotify_clear_marks_by_group(sbm->fsn_group);
chromiumos_super_block_put(sbm);
}
return 0;
}
enum chromiumos_inode_security_policy chromiumos_get_inode_security_policy(
struct dentry *dentry,
enum chromiumos_inode_security_policy_type type)
{
struct chromiumos_super_block_mark *sbm;
/*
* Initializes policy to CHROMIUM_INODE_POLICY_INHERIT, which is
* the value that will be returned if neither |dentry| nor any
* directory in its path has been asigned an inode security policy
* value for the given type.
*/
enum chromiumos_inode_security_policy policy =
CHROMIUMOS_INODE_POLICY_INHERIT;
if (!dentry || !dentry->d_inode ||
type >= CHROMIUMOS_NUMBER_OF_POLICIES)
return policy;
sbm = chromiumos_super_block_lookup(dentry->d_inode->i_sb);
if (!sbm)
return policy;
/* Walk the dentry path and look for a traversal policy. */
rcu_read_lock();
while (1) {
struct fsnotify_mark *mark = fsnotify_find_inode_mark(
sbm->fsn_group, dentry->d_inode);
if (mark) {
struct chromiumos_inode_mark *inode_mark =
chromiumos_to_inode_mark(mark);
policy = ACCESS_ONCE(inode_mark->policies[type]);
fsnotify_put_mark(mark);
if (policy != CHROMIUMOS_INODE_POLICY_INHERIT)
break;
}
if (IS_ROOT(dentry))
break;
dentry = dentry->d_parent;
}
rcu_read_unlock();
chromiumos_super_block_put(sbm);
return policy;
}

View File

@@ -0,0 +1,47 @@
/*
* Linux Security Module for Chromium OS
*
* Copyright 2016 Google Inc. All Rights Reserved
*
* Authors:
* Mattias Nissler <mnissler@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.
*/
/* FS feature availability policy for inode. */
enum chromiumos_inode_security_policy {
CHROMIUMOS_INODE_POLICY_INHERIT, /* Inherit policy from parent dir */
CHROMIUMOS_INODE_POLICY_ALLOW,
CHROMIUMOS_INODE_POLICY_BLOCK,
};
/*
* Inode security policy types available for use. To add an additional
* security policy, simply add a new member here, add the corresponding policy
* files in securityfs.c, and associate the files being added with the new enum
* member.
*/
enum chromiumos_inode_security_policy_type {
CHROMIUMOS_SYMLINK_TRAVERSAL = 0,
CHROMIUMOS_FIFO_ACCESS,
CHROMIUMOS_NUMBER_OF_POLICIES, /* Do not add entries after this line. */
};
extern int chromiumos_update_inode_security_policy(
struct inode *inode,
enum chromiumos_inode_security_policy_type type,
enum chromiumos_inode_security_policy policy);
int chromiumos_flush_inode_security_policies(struct super_block *sb);
extern enum chromiumos_inode_security_policy
chromiumos_get_inode_security_policy(
struct dentry *dentry,
enum chromiumos_inode_security_policy_type type);

818
security/chromiumos/lsm.c Normal file
View File

@@ -0,0 +1,818 @@
/*
* 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/fs.h>
#include <linux/fs_struct.h>
#include <linux/hashtable.h>
#include <linux/module.h>
#include <linux/mount.h>
#include <linux/path.h>
#include <linux/root_dev.h>
#include <linux/sched.h> /* current and other task related stuff */
#include <linux/security.h>
#include "inode_mark.h"
#include "process_management.h"
#include "utils.h"
#define NUM_BITS 8 // 128 buckets in hash table
static DEFINE_HASHTABLE(process_setuid_policy_hashtable, NUM_BITS);
/*
* Bool signifying whether to disable fixups for process management related
* routines in the kernel (setuid, setgid, kill). Default value is false. Can
* be overridden by 'disable_process_management_policies' flag. Static vars get
* initialized to 0/false since in BSS.
**/
static bool disable_process_management_policies;
/* Disable process management policies if flag passed */
static int set_disable_process_management_policies(char *str)
{
disable_process_management_policies = true;
return 1;
}
__setup("disable_process_management_policies=",
set_disable_process_management_policies);
/*
* Hash table entry to store process management policy signifying that 'parent'
* user can use 'child' user for process management (for now that just means
* 'parent' can set*uid() to 'child'). Will be adding exceptions for set*gid()
* and kill() in the future.
*/
struct entry {
struct hlist_node next;
struct hlist_node dlist; /* for deletion cleanup */
uint64_t parent_kuid;
uint64_t child_kuid;
};
static DEFINE_HASHTABLE(sb_nosymfollow_hashtable, NUM_BITS);
struct sb_entry {
struct hlist_node next;
struct hlist_node dlist; /* for deletion cleanup */
uintptr_t sb;
};
static int chromiumos_security_sb_mount(const char *dev_name, struct path *path,
const char *type, unsigned long flags,
void *data)
{
int error = current->total_link_count ? -ELOOP : 0;
if (error) {
char *cmdline;
cmdline = printable_cmdline(current);
pr_notice("Mount path with symlinks prohibited - "
"pid=%d cmdline=%s\n",
task_pid_nr(current), cmdline);
kfree(cmdline);
}
return error;
}
static void report_load_module(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);
kfree(alloced);
alloced = pathname;
}
}
}
cmdline = printable_cmdline(current);
pr_notice("init_module %s module=%s pid=%d cmdline=%s\n",
operation, pathname, task_pid_nr(current), cmdline);
kfree(cmdline);
kfree(alloced);
}
static int module_locking = 1;
static struct dentry *locked_root;
static DEFINE_SPINLOCK(locked_root_spinlock);
static DEFINE_SPINLOCK(process_setuid_policy_hashtable_spinlock);
static DEFINE_SPINLOCK(sb_nosymfollow_hashtable_spinlock);
#ifdef CONFIG_SYSCTL
static int zero;
static int one = 1;
static struct ctl_path chromiumos_sysctl_path[] = {
{ .procname = "kernel", },
{ .procname = "chromiumos", },
{ }
};
static struct ctl_table chromiumos_sysctl_table[] = {
{
.procname = "module_locking",
.data = &module_locking,
.maxlen = sizeof(int),
.mode = 0644,
.proc_handler = proc_dointvec_minmax,
.extra1 = &zero,
.extra2 = &one,
},
{ }
};
/* Check if the root device is read-only (e.g. dm-verity is enabled).
* This must be called after early kernel init, since then the rootdev
* is available.
*/
static bool rootdev_readonly(void)
{
bool rc;
struct block_device *bdev;
const fmode_t mode = FMODE_WRITE;
bdev = blkdev_get_by_dev(ROOT_DEV, mode, NULL);
if (IS_ERR(bdev)) {
/* In this weird case, assume it is read-only. */
pr_info("dev(%u,%u): FMODE_WRITE disallowed?!\n",
MAJOR(ROOT_DEV), MINOR(ROOT_DEV));
return true;
}
rc = bdev_read_only(bdev);
blkdev_put(bdev, mode);
pr_info("dev(%u,%u): %s\n", MAJOR(ROOT_DEV), MINOR(ROOT_DEV),
rc ? "read-only" : "writable");
return rc;
}
static void check_locking_enforcement(void)
{
/* If module locking is not being enforced, allow sysctl to change
* modes for testing.
*/
if (!rootdev_readonly()) {
if (!register_sysctl_paths(chromiumos_sysctl_path,
chromiumos_sysctl_table))
pr_notice("sysctl registration failed!\n");
else
pr_info("module locking can be disabled.\n");
} else
pr_info("module locking engaged.\n");
}
#else
static void check_locking_enforcement(void) { }
#endif
/* Check for entry in hash table. */
static bool chromiumos_check_sb_nosymfollow_hashtable(struct super_block *sb)
{
struct sb_entry *entry;
uintptr_t sb_pointer = (uintptr_t)sb;
bool found = false;
rcu_read_lock();
hash_for_each_possible_rcu(sb_nosymfollow_hashtable,
entry, next, sb_pointer) {
if (entry->sb == sb_pointer) {
found = true;
break;
}
}
rcu_read_unlock();
/*
* Its possible that a policy gets added in between the time we check
* above and when we return false here. Such a race condition should
* not affect this check however, since it would only be relevant if
* userspace tried to traverse a symlink on a filesystem before that
* filesystem was done being mounted (or potentially while it was being
* remounted with new mount flags).
*/
return found;
}
/* Add entry to hash table. */
static int chromiumos_add_sb_nosymfollow_hashtable(struct super_block *sb)
{
struct sb_entry *new;
uintptr_t sb_pointer = (uintptr_t)sb;
/* Return if entry already exists */
if (chromiumos_check_sb_nosymfollow_hashtable(sb))
return 0;
new = kzalloc(sizeof(struct sb_entry), GFP_KERNEL);
if (!new)
return -ENOMEM;
new->sb = sb_pointer;
spin_lock(&sb_nosymfollow_hashtable_spinlock);
hash_add_rcu(sb_nosymfollow_hashtable, &new->next, sb_pointer);
spin_unlock(&sb_nosymfollow_hashtable_spinlock);
return 0;
}
/* Flush all entries from hash table. */
void chromiumos_flush_sb_nosymfollow_hashtable(void)
{
struct sb_entry *entry;
struct hlist_node *hlist_node;
unsigned int bkt_loop_cursor;
HLIST_HEAD(free_list);
/*
* Could probably use hash_for_each_rcu here instead, but this should
* be fine as well.
*/
spin_lock(&sb_nosymfollow_hashtable_spinlock);
hash_for_each_safe(sb_nosymfollow_hashtable, bkt_loop_cursor,
hlist_node, entry, next) {
hash_del_rcu(&entry->next);
hlist_add_head(&entry->dlist, &free_list);
}
spin_unlock(&sb_nosymfollow_hashtable_spinlock);
synchronize_rcu();
hlist_for_each_entry_safe(entry, hlist_node, &free_list, dlist)
kfree(entry);
}
/* Remove entry from hash table. */
static void chromiumos_remove_sb_nosymfollow_hashtable(struct super_block *sb)
{
struct sb_entry *entry;
struct hlist_node *hlist_node;
uintptr_t sb_pointer = (uintptr_t)sb;
bool free_entry = false;
/*
* Could probably use hash_for_each_rcu here instead, but this should
* be fine as well.
*/
spin_lock(&sb_nosymfollow_hashtable_spinlock);
hash_for_each_possible_safe(sb_nosymfollow_hashtable, entry,
hlist_node, next, sb_pointer) {
if (entry->sb == sb_pointer) {
hash_del_rcu(&entry->next);
free_entry = true;
break;
}
}
spin_unlock(&sb_nosymfollow_hashtable_spinlock);
if (free_entry) {
synchronize_rcu();
kfree(entry);
}
}
int chromiumos_security_sb_umount(struct vfsmount *mnt, int flags)
{
/* If mnt->mnt_sb is in nosymfollow hashtable, remove it. */
chromiumos_remove_sb_nosymfollow_hashtable(mnt->mnt_sb);
return 0;
}
static int chromiumos_security_load_module(struct file *file)
{
struct dentry *module_root;
if (!file) {
if (!module_locking) {
report_load_module(NULL, "old-api-locking-ignored");
return 0;
}
report_load_module(NULL, "old-api-denied");
return -EPERM;
}
module_root = file->f_path.mnt->mnt_root;
/* First loaded module defines the root for all others. */
spin_lock(&locked_root_spinlock);
if (!locked_root) {
locked_root = dget(module_root);
/*
* Unlock now since it's only locked_root we care about.
* In the worst case, we will (correctly) report locking
* failures before we have announced that locking is
* enabled. This would be purely cosmetic.
*/
spin_unlock(&locked_root_spinlock);
report_load_module(&file->f_path, "locked");
check_locking_enforcement();
} else {
spin_unlock(&locked_root_spinlock);
}
if (module_root != locked_root) {
if (unlikely(!module_locking)) {
report_load_module(&file->f_path, "locking-ignored");
return 0;
}
report_load_module(&file->f_path, "denied");
return -EPERM;
}
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 nameidata *nd)
{
static char accessed_path[PATH_MAX];
enum chromiumos_inode_security_policy policy;
/* Deny if symlinks have been disabled on this superblock. */
if (chromiumos_check_sb_nosymfollow_hashtable(dentry->d_sb)) {
WARN(1,
"Blocked symlink traversal for path %x:%x:%s (symlinks were disabled on this FS through the 'nosymfollow' mount option)\n",
MAJOR(dentry->d_sb->s_dev),
MINOR(dentry->d_sb->s_dev),
dentry_path(dentry, accessed_path, PATH_MAX));
return -EACCES;
}
policy = chromiumos_get_inode_security_policy(
dentry,
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;
}
int chromiumos_security_file_open(
struct file *file,
const struct cred *cred)
{
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,
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;
}
bool chromiumos_check_setuid_policy_hashtable_key(kuid_t parent)
{
struct entry *entry;
rcu_read_lock();
hash_for_each_possible_rcu(process_setuid_policy_hashtable,
entry, next, __kuid_val(parent)) {
if (entry->parent_kuid == __kuid_val(parent)) {
rcu_read_unlock();
return true;
}
}
rcu_read_unlock();
/*
* Using RCU, its possible that a policy gets added in between the time
* we check above and when we return false here. This is fine, since
* policy updates only happen during system startup, well before
* sandboxed system services start running and the policies need to be
* queried.
*/
return false;
}
bool chromiumos_check_setuid_policy_hashtable_key_value(kuid_t parent,
kuid_t child)
{
struct entry *entry;
rcu_read_lock();
hash_for_each_possible_rcu(process_setuid_policy_hashtable,
entry, next, __kuid_val(parent)) {
if (entry->parent_kuid == __kuid_val(parent) &&
entry->child_kuid == __kuid_val(child)) {
rcu_read_unlock();
return true;
}
}
rcu_read_unlock();
/*
* Using RCU, its possible that a policy gets added in between the time
* we check above and when we return false here. This is fine, since
* policy updates only happen during system startup, well before
* sandboxed system services start running and the policies need to be
* queried.
*/
return false;
}
bool setuid_syscall(int num)
{
#ifdef CONFIG_X86_64
if (!(num == __NR_setreuid ||
num == __NR_setuid ||
num == __NR_setresuid ||
num == __NR_setfsuid))
return false;
#elif defined CONFIG_ARM64
if (!(num == __NR_compat_setuid ||
num == __NR_compat_setreuid ||
num == __NR_compat_setfsuid ||
num == __NR_compat_setresuid ||
num == __NR_compat_setreuid32 ||
num == __NR_compat_setresuid32 ||
num == __NR_compat_setuid32 ||
num == __NR_compat_setfsuid32))
return false;
#else /* CONFIG_ARM */
if (!(num == __NR_setreuid32 ||
num == __NR_setuid32 ||
num == __NR_setresuid32 ||
num == __NR_setfsuid32))
return false;
#endif
return true;
}
int chromiumos_security_capable(const struct cred *cred,
struct user_namespace *ns,
int cap)
{
/* The current->mm check will fail if this is a kernel thread. */
if (!disable_process_management_policies &&
cap == CAP_SETUID &&
current->mm &&
chromiumos_check_setuid_policy_hashtable_key(cred->uid)) {
// syscall_get_nr can theoretically return 0 or -1, but that
// would signify that the syscall is being aborted due to a
// signal, so we don't need to check for this case here.
if (!(setuid_syscall(syscall_get_nr(current,
current_pt_regs())))) {
// Deny if we're not in a set*uid() syscall to avoid
// giving powers gated by CAP_SETUID that are related
// to functionality other than calling set*uid() (e.g.
// allowing user to set up userns uid mappings).
WARN(1,
"Operation requires CAP_SETUID, which is not available to UID %u for operations besides approved set*uid transitions\n",
__kuid_val(cred->uid));
return -1;
}
}
return 0;
}
/*
* This hook inspects the string pointed to by the first parameter, looking for
* the "nosymfollow" mount option. The second parameter points to an empty
* page-sized buffer that is used for holding LSM-specific mount options that
* are grabbed (after this function executes, in security_sb_copy_data) from
* the mount string in the first parameter. Since the chromiumos LSM is stacked
* ahead of SELinux for ChromeOS, the page-sized buffer is empty when this
* function is called. If the "nosymfollow" mount option is encountered in this
* function, we write "nosymflw" to the empty page-sized buffer which lets us
* transmit information which will be visible in chromiumos_sb_kern_mount
* signifying that symlinks should be disabled for the sb. We store this token
* at a spot in the buffer that is at a greater offset than the bytes needed to
* record the rest of the LSM-specific mount options (e.g. those for SELinux).
* The "nosymfollow" option will be stripped from the mount string if it is
* encountered.
*/
int chromiumos_sb_copy_data(char *orig, char *copy)
{
char *orig_copy;
char *orig_copy_cur;
char *option;
size_t offset = 0;
bool found = false;
if (!orig || *orig == 0)
return 0;
orig_copy = alloc_secdata();
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, ",");
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)
strcpy(copy + offset + 1, "nosymflw");
free_secdata(orig_copy);
return 0;
}
/*
* Emit a warning when no entry found in whitelist. These will show up in
* kernel warning reports collected by the crash reporter, so we have some
* insight regarding failures that need addressing.
*/
void chromiumos_setuid_policy_warning(kuid_t parent, kuid_t child)
{
WARN(1,
"UID %u is restricted to using certain whitelisted UIDs for process management, and %u is not in the whitelist.\n",
__kuid_val(parent),
__kuid_val(child));
}
bool chromiumos_uid_transition_allowed(kuid_t parent, kuid_t child)
{
if (chromiumos_check_setuid_policy_hashtable_key_value(parent, child))
return true;
chromiumos_setuid_policy_warning(parent, child);
return false;
}
/*
* Check whether there is either an exception for user under old cred struct to
* use user under new cred struct, or the UID transition is allowed (by Linux
* set*uid rules) even without CAP_SETUID.
*/
int chromiumos_security_task_fix_setuid(struct cred *new,
const struct cred *old, int flags)
{
/*
* Do nothing if feature is turned off by kernel compile flag or there
* are no setuid restrictions for this UID.
*/
if (disable_process_management_policies ||
!chromiumos_check_setuid_policy_hashtable_key(old->uid))
return 0;
switch (flags) {
case LSM_SETID_RE:
/*
* Users for which setuid restrictions exist can only set the
* real UID to the real UID or the effective UID, unless an
* explicit whitelist policy allows the transition.
*/
if (!uid_eq(old->uid, new->uid) &&
!uid_eq(old->euid, new->uid)) {
if (!chromiumos_uid_transition_allowed(old->uid,
new->uid))
return -EPERM;
}
/*
* Users for which setuid restrictions exist can only set the
* effective UID to the real UID, the effective UID, or the
* saved set-UID, unless an explicit whitelist policy allows
* the transition.
*/
if (!uid_eq(old->uid, new->euid) &&
!uid_eq(old->euid, new->euid) &&
!uid_eq(old->suid, new->euid)) {
if (!chromiumos_uid_transition_allowed(old->euid,
new->euid))
return -EPERM;
}
break;
case LSM_SETID_ID:
/*
* Users for which setuid restrictions exist cannot change the
* real UID or saved set-UID unless an explicit whitelist
* policy allows the transition.
*/
if (!uid_eq(old->uid, new->uid)) {
if (!chromiumos_uid_transition_allowed(old->uid,
new->uid))
return -EPERM;
}
if (!uid_eq(old->suid, new->suid)) {
if (!chromiumos_uid_transition_allowed(old->suid,
new->suid))
return -EPERM;
}
break;
case LSM_SETID_RES:
/*
* Users for which setuid restrictions exist cannot change the
* real UID, effective UID, or saved set-UID to anything but
* one of: the current real UID, the current effective UID or
* the current saved set-user-ID unless an explicit whitelist
* policy allows the transition.
*/
if (!uid_eq(new->uid, old->uid) &&
!uid_eq(new->uid, old->euid) &&
!uid_eq(new->uid, old->suid)) {
if (!chromiumos_uid_transition_allowed(old->uid,
new->uid))
return -EPERM;
}
if (!uid_eq(new->euid, old->uid) &&
!uid_eq(new->euid, old->euid) &&
!uid_eq(new->euid, old->suid)) {
if (!chromiumos_uid_transition_allowed(old->euid,
new->euid))
return -EPERM;
}
if (!uid_eq(new->suid, old->uid) &&
!uid_eq(new->suid, old->euid) &&
!uid_eq(new->suid, old->suid)) {
if (!chromiumos_uid_transition_allowed(old->suid,
new->suid))
return -EPERM;
}
break;
case LSM_SETID_FS:
/*
* Users for which setuid restrictions exist cannot change the
* filesystem UID to anything but one of: the current real UID,
* the current effective UID or the current saved set-UID
* unless an explicit whitelist policy allows the transition.
*/
if (!uid_eq(new->fsuid, old->uid) &&
!uid_eq(new->fsuid, old->euid) &&
!uid_eq(new->fsuid, old->suid) &&
!uid_eq(new->fsuid, old->fsuid)) {
if (!chromiumos_uid_transition_allowed(old->fsuid,
new->fsuid))
return -EPERM;
}
break;
}
return 0;
}
/* Add process management policy to hash table */
int chromiumos_add_process_management_entry(kuid_t parent, kuid_t child)
{
struct entry *new;
/* Return if entry already exists */
if (chromiumos_check_setuid_policy_hashtable_key_value(parent,
child))
return 0;
new = kzalloc(sizeof(struct entry), GFP_KERNEL);
if (!new)
return -ENOMEM;
new->parent_kuid = __kuid_val(parent);
new->child_kuid = __kuid_val(child);
spin_lock(&process_setuid_policy_hashtable_spinlock);
hash_add_rcu(process_setuid_policy_hashtable,
&new->next,
__kuid_val(parent));
spin_unlock(&process_setuid_policy_hashtable_spinlock);
return 0;
}
void chromiumos_flush_process_management_entries(void)
{
struct entry *entry;
struct hlist_node *hlist_node;
unsigned int bkt_loop_cursor;
HLIST_HEAD(free_list);
/*
* Could probably use hash_for_each_rcu here instead, but this should
* be fine as well.
*/
hash_for_each_safe(process_setuid_policy_hashtable, bkt_loop_cursor,
hlist_node, entry, next) {
spin_lock(&process_setuid_policy_hashtable_spinlock);
hash_del_rcu(&entry->next);
spin_unlock(&process_setuid_policy_hashtable_spinlock);
hlist_add_head(&entry->dlist, &free_list);
}
synchronize_rcu();
hlist_for_each_entry_safe(entry, hlist_node, &free_list, dlist)
kfree(entry);
}
static struct security_operations chromiumos_security_ops = {
.name = "chromiumos",
.sb_mount = chromiumos_security_sb_mount,
.kernel_module_from_file = chromiumos_security_load_module,
.inode_follow_link = chromiumos_security_inode_follow_link,
.file_open = chromiumos_security_file_open,
.sb_umount = chromiumos_security_sb_umount,
};
/* Unfortunately the kernel doesn't implement memmem function. */
static void *search_buffer(void *haystack, size_t haystacklen,
const void *needle, size_t needlelen)
{
if (!needlelen)
return (void *)haystack;
while (haystacklen >= needlelen) {
haystacklen--;
if (!memcmp(haystack, needle, needlelen))
return (void *)haystack;
haystack++;
}
return NULL;
}
int chromiumos_sb_kern_mount(struct super_block *sb, int flags, void *data)
{
int ret;
char search_str[10] = "\0nosymflw";
if (!data)
return 0;
if (search_buffer(data, PAGE_SIZE, search_str, 10)) {
ret = chromiumos_add_sb_nosymfollow_hashtable(sb);
if (ret)
return ret;
}
return 0;
}
static int __init chromiumos_security_init(void)
{
int error;
error = register_security(&chromiumos_security_ops);
if (error)
panic("Could not register Chromium OS security module");
return error;
}
security_initcall(chromiumos_security_init);
/* Should not be mutable after boot, so not listed in sysfs (perm == 0). */
module_param(module_locking, int, 0);
MODULE_PARM_DESC(module_locking, "Module loading restrictions (default: true)");

View File

@@ -0,0 +1,38 @@
/*
* Linux Security Module for Chromium OS
*
* Copyright 2018 Google LLC. All Rights Reserved
*
* Author:
* Micah Morton <mortonm@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.
*/
#ifndef _SECURITY_PROCESS_MANAGEMENT_H
#define _SECURITY_PROCESS_MANAGEMENT_H
#include <linux/types.h>
/* Function type. */
enum chromiumos_process_management_file_write_type {
CHROMIUMOS_PROCESS_MANAGEMENT_ADD, /* Add whitelist policy. */
CHROMIUMOS_PROCESS_MANAGEMENT_FLUSH, /* Flush whitelist policies. */
};
/*
* Add entry to chromiumos process management policies to allow user 'parent'
* to use user 'child' for process management.
*/
int chromiumos_add_process_management_entry(kuid_t parent, kuid_t child);
void chromiumos_flush_process_management_entries(void);
#endif /* _SECURITY_PROCESS_MANAGEMENT_H */

View File

@@ -0,0 +1,397 @@
/*
* Linux Security Module for Chromium OS
*
* Copyright 2016 Google Inc. All Rights Reserved
*
* Authors:
* Mattias Nissler <mnissler@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.
*/
#include <linux/capability.h>
#include <linux/dcache.h>
#include <linux/fs.h>
#include <linux/namei.h>
#include <linux/sched.h>
#include <linux/security.h>
#include <linux/string.h>
#include <linux/uaccess.h>
#include "inode_mark.h"
#include "process_management.h"
static struct dentry *chromiumos_dir;
static struct dentry *chromiumos_inode_policy_dir;
static struct dentry *chromiumos_process_management_policy_dir;
struct chromiumos_inode_policy_file_entry {
const char *name;
int (*handle_write)(struct chromiumos_inode_policy_file_entry *,
struct dentry *);
enum chromiumos_inode_security_policy_type type;
enum chromiumos_inode_security_policy policy;
struct dentry *dentry;
};
struct chromiumos_process_management_file_entry {
const char *name;
enum chromiumos_process_management_file_write_type type;
struct dentry *dentry;
};
static int chromiumos_inode_policy_file_write(
struct chromiumos_inode_policy_file_entry *file_entry,
struct dentry *dentry)
{
return chromiumos_update_inode_security_policy(dentry->d_inode,
file_entry->type, file_entry->policy);
}
/*
* Causes all marks to be removed from inodes thus removing all inode security
* policies.
*/
static int chromiumos_inode_policy_file_flush_write(
struct chromiumos_inode_policy_file_entry *file_entry,
struct dentry *dentry)
{
return chromiumos_flush_inode_security_policies(dentry->d_sb);
}
static struct chromiumos_inode_policy_file_entry
chromiumos_inode_policy_files[] = {
{.name = "block_symlink",
.handle_write = chromiumos_inode_policy_file_write,
.type = CHROMIUMOS_SYMLINK_TRAVERSAL,
.policy = CHROMIUMOS_INODE_POLICY_BLOCK},
{.name = "allow_symlink",
.handle_write = chromiumos_inode_policy_file_write,
.type = CHROMIUMOS_SYMLINK_TRAVERSAL,
.policy = CHROMIUMOS_INODE_POLICY_ALLOW},
{.name = "reset_symlink",
.handle_write = chromiumos_inode_policy_file_write,
.type = CHROMIUMOS_SYMLINK_TRAVERSAL,
.policy = CHROMIUMOS_INODE_POLICY_INHERIT},
{.name = "block_fifo",
.handle_write = chromiumos_inode_policy_file_write,
.type = CHROMIUMOS_FIFO_ACCESS,
.policy = CHROMIUMOS_INODE_POLICY_BLOCK},
{.name = "allow_fifo",
.handle_write = chromiumos_inode_policy_file_write,
.type = CHROMIUMOS_FIFO_ACCESS,
.policy = CHROMIUMOS_INODE_POLICY_ALLOW},
{.name = "reset_fifo",
.handle_write = chromiumos_inode_policy_file_write,
.type = CHROMIUMOS_FIFO_ACCESS,
.policy = CHROMIUMOS_INODE_POLICY_INHERIT},
{.name = "flush_policies",
.handle_write = &chromiumos_inode_policy_file_flush_write},
};
static struct chromiumos_process_management_file_entry
chromiumos_process_management_files[] = {
{.name = "add_whitelist_policy",
.type = CHROMIUMOS_PROCESS_MANAGEMENT_ADD},
{.name = "flush_whitelist_policies",
.type = CHROMIUMOS_PROCESS_MANAGEMENT_FLUSH},
};
static int chromiumos_resolve_path(const char __user *buf, size_t len,
struct path *path)
{
char *filename = NULL;
char *canonical_buf = NULL;
char *canonical;
int ret;
if (len + 1 > PATH_MAX)
return -EINVAL;
/*
* Copy the path to a kernel buffer. We can't use user_path_at()
* since it expects a zero-terminated path, which we generally don't
* have here.
*/
filename = kzalloc(len + 1, GFP_KERNEL);
if (!filename)
return -ENOMEM;
if (copy_from_user(filename, buf, len)) {
ret = -EFAULT;
goto out;
}
ret = kern_path(filename, 0, path);
if (ret)
goto out;
/*
* Make sure the path is canonical, i.e. it didn't contain symlinks. To
* check this we convert |path| back to an absolute path (within the
* global root) and compare the resulting path name with the passed-in
* |filename|. This is stricter than needed (i.e. consecutive slashes
* don't get ignored), but that's fine for our purposes.
*/
canonical_buf = kzalloc(len + 1, GFP_KERNEL);
if (!canonical_buf) {
ret = -ENOMEM;
goto out;
}
canonical = d_absolute_path(path, canonical_buf, len + 1);
if (IS_ERR(canonical)) {
ret = PTR_ERR(canonical);
/* Buffer too short implies |filename| wasn't canonical. */
if (ret == -ENAMETOOLONG)
ret = -EMLINK;
goto out;
}
ret = strcmp(filename, canonical) ? -EMLINK : 0;
out:
kfree(canonical_buf);
if (ret < 0)
path_put(path);
kfree(filename);
return ret;
}
static ssize_t chromiumos_inode_file_write(
struct file *file,
const char __user *buf,
size_t len,
loff_t *ppos)
{
struct chromiumos_inode_policy_file_entry *file_entry =
file->f_inode->i_private;
struct path path = {};
int ret;
if (!ns_capable(current_cred()->user_ns, CAP_SYS_ADMIN))
return -EPERM;
if (*ppos != 0)
return -EINVAL;
ret = chromiumos_resolve_path(buf, len, &path);
if (ret)
return ret;
ret = file_entry->handle_write(file_entry, path.dentry);
path_put(&path);
return ret < 0 ? ret : len;
}
/*
* In the case the input buffer contains one or more invalid UIDS, the kuid_t
* variables pointed to by 'parent' and 'child' will get updated but this
* function will return an error.
*/
static int chromiumos_parse_process_management_policy(const char __user *buf,
size_t len,
kuid_t *parent,
kuid_t *child)
{
char *kern_buf;
char *parent_buf;
char *child_buf;
const char separator[] = ":";
int ret;
size_t first_substring_length;
long parsed_parent;
long parsed_child;
/* Duplicate string from user memory and NULL-terminate */
kern_buf = memdup_user_nul(buf, len);
if (IS_ERR(kern_buf))
return PTR_ERR(kern_buf);
/*
* Format of |buf| string should be <UID>:<UID>.
* Find location of ":" in kern_buf (copied from |buf|).
*/
first_substring_length = strcspn(kern_buf, separator);
if (first_substring_length == 0 || first_substring_length == len) {
ret = -EINVAL;
goto free_kern;
}
parent_buf = kmemdup_nul(kern_buf, first_substring_length, GFP_KERNEL);
if (!parent_buf) {
ret = -ENOMEM;
goto free_kern;
}
ret = kstrtol(parent_buf, 0, &parsed_parent);
if (ret)
goto free_both;
child_buf = kern_buf + first_substring_length + 1;
ret = kstrtol(child_buf, 0, &parsed_child);
if (ret)
goto free_both;
*parent = make_kuid(current_user_ns(), parsed_parent);
if (!uid_valid(*parent)) {
ret = -EINVAL;
goto free_both;
}
*child = make_kuid(current_user_ns(), parsed_child);
if (!uid_valid(*child)) {
ret = -EINVAL;
goto free_both;
}
free_both:
kfree(parent_buf);
free_kern:
kfree(kern_buf);
return ret;
}
static ssize_t chromiumos_process_management_file_write(struct file *file,
const char __user *buf,
size_t len,
loff_t *ppos)
{
struct chromiumos_process_management_file_entry *file_entry =
file->f_inode->i_private;
kuid_t parent;
kuid_t child;
int ret;
if (!ns_capable(current_user_ns(), CAP_SYS_ADMIN))
return -EPERM;
if (*ppos != 0)
return -EINVAL;
if (file_entry->type == CHROMIUMOS_PROCESS_MANAGEMENT_FLUSH) {
chromiumos_flush_process_management_entries();
return len;
}
/* file_entry->type must equal CHROMIUMOS_PROCESS_MANAGEMENT_ADD */
ret = chromiumos_parse_process_management_policy(buf, len, &parent,
&child);
if (ret)
return ret;
ret = chromiumos_add_process_management_entry(parent, child);
if (ret)
return ret;
/* Return len on success so caller won't keep trying to write */
return len;
}
static const struct file_operations chromiumos_inode_policy_file_fops = {
.write = chromiumos_inode_file_write,
};
static const struct file_operations chromiumos_process_management_file_fops = {
.write = chromiumos_process_management_file_write,
};
static void chromiumos_shutdown_securityfs(void)
{
int i;
for (i = 0; i < ARRAY_SIZE(chromiumos_inode_policy_files); ++i) {
struct chromiumos_inode_policy_file_entry *entry =
&chromiumos_inode_policy_files[i];
securityfs_remove(entry->dentry);
entry->dentry = NULL;
}
for (i = 0; i < ARRAY_SIZE(chromiumos_process_management_files); ++i) {
struct chromiumos_process_management_file_entry *entry =
&chromiumos_process_management_files[i];
securityfs_remove(entry->dentry);
entry->dentry = NULL;
}
securityfs_remove(chromiumos_inode_policy_dir);
chromiumos_inode_policy_dir = NULL;
securityfs_remove(chromiumos_process_management_policy_dir);
chromiumos_process_management_policy_dir = NULL;
securityfs_remove(chromiumos_dir);
chromiumos_dir = NULL;
}
static int chromiumos_init_securityfs(void)
{
int i;
int ret;
chromiumos_dir = securityfs_create_dir("chromiumos", NULL);
if (!chromiumos_dir) {
ret = PTR_ERR(chromiumos_dir);
goto error;
}
chromiumos_inode_policy_dir =
securityfs_create_dir(
"inode_security_policies",
chromiumos_dir);
if (!chromiumos_inode_policy_dir) {
ret = PTR_ERR(chromiumos_inode_policy_dir);
goto error;
}
for (i = 0; i < ARRAY_SIZE(chromiumos_inode_policy_files); ++i) {
struct chromiumos_inode_policy_file_entry *entry =
&chromiumos_inode_policy_files[i];
entry->dentry = securityfs_create_file(
entry->name, 0200, chromiumos_inode_policy_dir,
entry, &chromiumos_inode_policy_file_fops);
if (IS_ERR(entry->dentry)) {
ret = PTR_ERR(entry->dentry);
goto error;
}
}
chromiumos_process_management_policy_dir =
securityfs_create_dir(
"process_management_policies",
chromiumos_dir);
if (!chromiumos_process_management_policy_dir) {
ret = PTR_ERR(chromiumos_process_management_policy_dir);
goto error;
}
for (i = 0; i < ARRAY_SIZE(chromiumos_process_management_files); ++i) {
struct chromiumos_process_management_file_entry *entry =
&chromiumos_process_management_files[i];
entry->dentry = securityfs_create_file(
entry->name,
0200,
chromiumos_process_management_policy_dir,
entry, &chromiumos_process_management_file_fops);
if (IS_ERR(entry->dentry)) {
ret = PTR_ERR(entry->dentry);
goto error;
}
}
return 0;
error:
chromiumos_shutdown_securityfs();
return ret;
}
fs_initcall(chromiumos_init_securityfs);

154
security/chromiumos/utils.c Normal file
View File

@@ -0,0 +1,154 @@
/*
* Utilities for the Linux Security Module for Chromium OS
* (Since CONFIG_AUDIT is disabled for Chrome OS, we must repurpose
* a bunch of the audit string handling logic here instead.)
*
* Copyright 2012 Google Inc. All Rights Reserved
*
* Author:
* 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.
*/
#include <linux/module.h>
#include <linux/security.h>
#include "utils.h"
/* Disallow double-quote and control characters other than space. */
static int contains_unprintable(const char *source, size_t len)
{
const unsigned char *p;
for (p = source; p < (const unsigned char *)source + len; p++) {
if (*p == '"' || *p < 0x20 || *p > 0x7e)
return 1;
}
return 0;
}
static char *hex_printable(const char *source, size_t len)
{
size_t i;
char *dest, *ptr;
const char *hex = "0123456789ABCDEF";
/* Need to double the length of the string, plus a NULL. */
if (len > (INT_MAX - 1) / 2)
return NULL;
dest = kmalloc((len * 2) + 1, GFP_KERNEL);
if (!dest)
return NULL;
for (ptr = dest, i = 0; i < len; i++) {
*ptr++ = hex[(source[i] & 0xF0) >> 4];
*ptr++ = hex[source[i] & 0x0F];
}
*ptr = '\0';
return dest;
}
static char *quoted_printable(const char *source, size_t len)
{
char *dest;
/* Need to add 2 double quotes and a NULL. */
if (len > INT_MAX - 3)
return NULL;
dest = kmalloc(len + 3, GFP_KERNEL);
if (!dest)
return NULL;
dest[0] = '"';
strcpy(dest + 1, source);
dest[len + 1] = '"';
dest[len + 2] = '\0';
return dest;
}
/* Return a string that has been sanitized and is safe to log. It is either
* in double-quotes, or is a series of hex digits.
*/
char *printable(char *source)
{
size_t len;
if (!source)
return NULL;
len = strlen(source);
if (contains_unprintable(source, len))
return hex_printable(source, len);
else
return quoted_printable(source, len);
}
/* Repurposed from fs/proc/base.c, with NULL-replacement for saner printing.
* Allocates the buffer itself.
*/
char *printable_cmdline(struct task_struct *task)
{
char *buffer = NULL, *sanitized;
int res, i;
unsigned int len;
struct mm_struct *mm;
mm = get_task_mm(task);
if (!mm)
goto out;
if (!mm->arg_end)
goto out_mm; /* Shh! No looking before we're done */
buffer = kmalloc(PAGE_SIZE, GFP_KERNEL);
if (!buffer)
goto out_mm;
len = mm->arg_end - mm->arg_start;
if (len > PAGE_SIZE)
len = PAGE_SIZE;
res = access_process_vm(task, mm->arg_start, buffer, len, 0);
/* Space-fill NULLs. */
if (res > 1)
for (i = 0; i < res - 2; ++i)
if (buffer[i] == '\0')
buffer[i] = ' ';
/* If the NULL at the end of args has been overwritten, then
* assume application is using setproctitle(3).
*/
if (res > 0 && buffer[res-1] != '\0' && len < PAGE_SIZE) {
len = strnlen(buffer, res);
if (len < res) {
res = len;
} else {
len = mm->env_end - mm->env_start;
if (len > PAGE_SIZE - res)
len = PAGE_SIZE - res;
res += access_process_vm(task, mm->env_start,
buffer+res, len, 0);
res = strnlen(buffer, res);
}
}
/* Make sure result is printable. */
sanitized = printable(buffer);
kfree(buffer);
buffer = sanitized;
out_mm:
mmput(mm);
out:
return buffer;
}

View File

@@ -0,0 +1,30 @@
/*
* Utilities for the Linux Security Module for Chromium OS
* (Since CONFIG_AUDIT is disabled for Chrome OS, we must repurpose
* a bunch of the audit string handling logic here instead.)
*
* Copyright 2012 Google Inc. All Rights Reserved
*
* Author:
* 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.
*/
#ifndef _SECURITY_CHROMIUMOS_UTILS_H
#define _SECURITY_CHROMIUMOS_UTILS_H
#include <linux/sched.h>
#include <linux/mm.h>
char *printable(char *source);
char *printable_cmdline(struct task_struct *task);
#endif /* _SECURITY_CHROMIUMOS_UTILS_H */