blob: 0f9b278663fd470172c08c19263091cc8e889d3a [file] [log] [blame]
From 4821360a1b11331f12986a29798c57072b17c831 Mon Sep 17 00:00:00 2001
From: Stephan Uphoff <ups@chromium.org>
Date: Mon, 24 Oct 2011 19:12:48 +0000
Subject: [PATCH] CHROMIUM: LSM: chromiumos security module
Initially the chromiumos security module only prevents symlinks
in mount paths. Future versions will be more restrictive and
will be configurable using a driver interface.
This is a squash of the following commits:
CHROMIUM: kernel: Initial chromiumos security module.
CHROMIUM: LSM: add module loading restrictions
CHROMIUM: LSM: spinlock the root directory lock
CHROMIUM: LSM: fix sysfs parameter exposure
CHROMIUM: LSM: switch to stacked Chromium OS LSM
CHROMIUM: LSM: handle pinned filesystem umount
CHROMIUM: LSM: enable firmware pinning
CHROMIUM: LSM: check vfsmount to avoid disengagement
CHROMIUM: LSM: Assume read-only root if device is NULL
CHROMIUM: Don't allow building LSM as a module
CHROMIUM: LSM: kernel flag to allow mounting to symlinked path
CHROMIUM: fix a ChromiumOS-specific memory leak in fs/namei.c
CHROMIUM: LSM: handle errors from getname_flags() in nameidata_set_temporary()
Additionally, some fixups were applied to make the code work on top of
kernel 4.4 and the registration was updated to use regular LSM hooks.
Additional interfaces were exposed in fs/namei.c and do_mount() in
fs/namespace.c was updated to set up a temporary current->nameidata
while resolving the mountpoint path until sb_mount security hooks are
called, in order to make it possible to still check total_link_count
from those hooks.
It is still not possible to build LSMs as loadable modules, since
security_hooks_heads (in security/security.c) is not exported. This
could be fixed with the following upstream change:
EXPORT_SYMBOL(security_hook_heads);
For now, we're happy to keep this as a built-in only.
BUG=chromium-os:21954
TEST=Use a mount path with a symlink and observe mount failure.
Change-Id: I47ade3b7be684ab9e0533ce5ffd1e81009eaebfe
Signed-off-by: Stephan Uphoff <ups@chromium.org>
Reviewed-on: https://gerrit.chromium.org/gerrit/10581
Reviewed-by: Kees Cook <keescook@chromium.org>
Reviewed-by: Mandeep Singh Baines <msb@chromium.org>
[rebase44(filbranden): Resolved trivial conflicts in Kconfig. Updated
Makefile to use a separate "chromiumos_lsm" name for the module.
Introduced nameidata_get_total_link_count() and other APIs to make it
possible to keep a valid nameidata and to access that field from this
module. Updated registration to use the LSM_HOOK_INIT API instead of
adding explicit hooks to global security/security.c]
Signed-off-by: Filipe Brandenburger <filbranden@chromium.org>
Reviewed-on: https://chromium-review.googlesource.com/334277
Reviewed-on: https://chromium-review.googlesource.com/334278
Reviewed-on: https://chromium-review.googlesource.com/334279
Conflicts:
security/Makefile
[rebase412(groeck):
Resolved conflicts; include file name changes; API changes]
Signed-off-by: Guenter Roeck <groeck@chromium.org>
[rebase414(groeck): Squashed two more commits:
CHROMIUM: fix a ChromiumOS-specific memory leak in fs/namei.c
CHROMIUM: LSM: handle errors from getname_flags() in
nameidata_set_temporary()]
Signed-off-by: Guenter Roeck <groeck@chromium.org>
[rebase419(groeck):
kAPI changes:
fsnotify_add_mark_locked parameter change
FSNOTIFY_OBJ_ALL_TYPES -> FSNOTIFY_OBJ_ALL_TYPES_MASK
chromiumos_security_file_open() parameter change
Squashed commits:
CHROMIUM: LSM: make modules FS pinning work with unshared mount namespace
CHROMIUM: LSM: fix buffer over-read in printable_cmdline
CHROMIUM: LSM: set MODULE_PARAM_PREFIX to "lsm."
CHROMIUM: LSM: Add support for symlink traversal policy.
CHROMIUM: LSM: Updates for v4.9
Block FIFO access on stateful partition.
CHROMIUM: LSM: add more details to blocked symlink mount
CHROMIUM: LSM: handle rcu-walk when following link
]
Signed-off-by: Guenter Roeck <groeck@chromium.org>
Conflicts:
fs/namei.c (API changes)
fs/Makefile (conetxt)
fs/Kconfig (context)
[rebase54(groeck): Conflicts, squashed:
CHROMIUM: LSM: Deny mounting filesystems as exec in unprivileged userns
CHROMIUM: stack chromiumos LSM before other LSMs
CHROMIUM: Add process mgmt security policies to chromiumos LSM
CHROMIUM: "nosymfollow" mount option for chromiumos LSM
CHROMIUM: add WARN print statement to chromiumos LSM
CHROMIUM: Fix index computation for buffer inside printable_cmdline
CHROMIUM: rip out setuid stuff from chromiumos LSM
Need to include uapi/linux/mount.h directly
rebase53(rrangel):
chromiumos_handle_fsnotify_event: file_name now uses struct qstr
chromiumos_inode_mark_create: we don't have an fsid, so pass NULL
security_initcall has been replaced by DEFINE_LSM
lsm load order is now defined in Kconfig
(groeck: Added default for SECURITY_CHROMIUMOS)
alloc_secdata has been deleted
sb_copy_data was moved to into sb_eat_lsm_opts
sb_kern_mount was moved into into sb_set_mnt_opts
added support for fs_context_parse_param
had to rework the way nosymfollow was passed from sb_copy_data to sb_kern_mount
]
Signed-off-by: Guenter Roeck <groeck@chromium.org>
[rebase510(groeck): fs/namei API changes; took code from cont rebase
Squashed:
CHROMIUM: LSM: Convert symlink checks to MNT_NOSYMFOLLOW
CHROMIUM: LSM: Restore inode_follow_link hook
]
Signed-off-by: Guenter Roeck <groeck@chromium.org>
Change-Id: I5efb53275562e341cc3ba199cc9c2d8d225541ee
---
fs/namei.c | 53 +++++
fs/namespace.c | 10 +-
include/linux/namei.h | 4 +
security/Kconfig | 6 +
security/Makefile | 1 +
security/chromiumos/Kconfig | 29 +++
security/chromiumos/Makefile | 3 +
security/chromiumos/inode_mark.c | 353 +++++++++++++++++++++++++++++++
security/chromiumos/inode_mark.h | 47 ++++
security/chromiumos/lsm.c | 275 ++++++++++++++++++++++++
security/chromiumos/securityfs.c | 241 +++++++++++++++++++++
security/chromiumos/utils.c | 157 ++++++++++++++
security/chromiumos/utils.h | 30 +++
13 files changed, 1208 insertions(+), 1 deletion(-)
create mode 100644 security/chromiumos/Kconfig
create mode 100644 security/chromiumos/Makefile
create mode 100644 security/chromiumos/inode_mark.c
create mode 100644 security/chromiumos/inode_mark.h
create mode 100644 security/chromiumos/lsm.c
create mode 100644 security/chromiumos/securityfs.c
create mode 100644 security/chromiumos/utils.c
create mode 100644 security/chromiumos/utils.h
diff --git a/fs/namei.c b/fs/namei.c
--- a/fs/namei.c
+++ b/fs/namei.c
@@ -1025,6 +1025,59 @@ int sysctl_protected_hardlinks __read_mostly = 0;
int sysctl_protected_fifos __read_mostly;
int sysctl_protected_regular __read_mostly;
+/**
+ * nameidata_set_temporary - Used by Chromium OS LSM to check
+ * whether a mount point includes traversing symlinks.
+ */
+int nameidata_set_temporary(const char __user *dir_name)
+{
+ struct nameidata *tmp;
+ struct filename *name;
+
+ tmp = kmalloc(sizeof(*tmp), GFP_KERNEL);
+ if (unlikely(!tmp))
+ return -ENOMEM;
+ name = getname_flags(dir_name, LOOKUP_FOLLOW, NULL);
+ if (IS_ERR(name)) {
+ kfree(tmp);
+ return PTR_ERR(name);
+ }
+ set_nameidata(tmp, AT_FDCWD, name);
+ return 0;
+}
+
+/**
+ * nameidata_restore_temporary - Used by Chromium OS LSM to check
+ * whether a mount point includes traversing symlinks.
+ */
+void nameidata_restore_temporary(void)
+{
+ struct nameidata *tmp = current->nameidata;
+
+ restore_nameidata();
+ putname(tmp->name);
+ kfree(tmp);
+}
+
+/**
+ * nameidata_get_total_link_count - Used by security/chromiumos/lsm.c to check
+ * whether a mount point includes traversing symlinks.
+ */
+int nameidata_get_total_link_count(void)
+{
+ struct nameidata *tmp = current->nameidata;
+
+ if (unlikely(!tmp)) {
+ WARN(1, "Unexpectedly got here with current->nameidata == NULL");
+ /* Pretend we did traverse symlinks, that is the safe/sane
+ * result here from a security point of view...
+ */
+ return MAXSYMLINKS;
+ }
+ return tmp->total_link_count;
+}
+EXPORT_SYMBOL(nameidata_get_total_link_count);
+
/**
* may_follow_link - Check symlink following for unsafe situations
* @nd: nameidata pathwalk data
diff --git a/fs/namespace.c b/fs/namespace.c
--- a/fs/namespace.c
+++ b/fs/namespace.c
@@ -3325,10 +3325,18 @@ long do_mount(const char *dev_name, const char __user *dir_name,
struct path path;
int ret;
- ret = user_path_at(AT_FDCWD, dir_name, LOOKUP_FOLLOW, &path);
+ ret = nameidata_set_temporary(dir_name);
if (ret)
return ret;
+
+ ret = user_path_at(AT_FDCWD, dir_name, LOOKUP_FOLLOW, &path);
+ if (ret) {
+ nameidata_restore_temporary();
+ return ret;
+ }
+
ret = path_mount(dev_name, &path, type_page, flags, data_page);
+ nameidata_restore_temporary();
path_put(&path);
return ret;
}
diff --git a/include/linux/namei.h b/include/linux/namei.h
--- a/include/linux/namei.h
+++ b/include/linux/namei.h
@@ -79,6 +79,10 @@ extern void unlock_rename(struct dentry *, struct dentry *);
extern int __must_check nd_jump_link(struct path *path);
+extern int nameidata_set_temporary(const char __user *dir_name);
+extern void nameidata_restore_temporary(void);
+extern int nameidata_get_total_link_count(void);
+
static inline void nd_terminate_link(void *name, size_t len, size_t maxlen)
{
((char *) name)[min(len, maxlen)] = '\0';
diff --git a/security/Kconfig b/security/Kconfig
--- a/security/Kconfig
+++ b/security/Kconfig
@@ -239,11 +239,13 @@ source "security/yama/Kconfig"
source "security/safesetid/Kconfig"
source "security/lockdown/Kconfig"
source "security/landlock/Kconfig"
+source "security/chromiumos/Kconfig"
source "security/integrity/Kconfig"
choice
prompt "First legacy 'major LSM' to be initialized"
+ default DEFAULT_SECURITY_CHROMIUMOS if SECURITY_CHROMIUMOS
default DEFAULT_SECURITY_SELINUX if SECURITY_SELINUX
default DEFAULT_SECURITY_SMACK if SECURITY_SMACK
default DEFAULT_SECURITY_TOMOYO if SECURITY_TOMOYO
@@ -259,6 +261,9 @@ choice
Selects the legacy "major security module" that will be
initialized first. Overridden by non-default CONFIG_LSM.
+ config DEFAULT_SECURITY_CHROMIUMOS
+ bool "Chromium OS" if SECURITY_CHROMIUMOS=y
+
config DEFAULT_SECURITY_SELINUX
bool "SELinux" if SECURITY_SELINUX=y
@@ -282,6 +287,7 @@ config LSM
default "landlock,lockdown,yama,loadpin,safesetid,integrity,apparmor,selinux,smack,tomoyo,bpf" if DEFAULT_SECURITY_APPARMOR
default "landlock,lockdown,yama,loadpin,safesetid,integrity,tomoyo,bpf" if DEFAULT_SECURITY_TOMOYO
default "landlock,lockdown,yama,loadpin,safesetid,integrity,bpf" if DEFAULT_SECURITY_DAC
+ default "landlock,lockdown,yama,loadpin,safesetid,integrity,chromiumos,selinux" if DEFAULT_SECURITY_CHROMIUMOS
default "landlock,lockdown,yama,loadpin,safesetid,integrity,selinux,smack,tomoyo,apparmor,bpf"
help
A comma-separated list of LSMs, in initialization order.
diff --git a/security/Makefile b/security/Makefile
--- a/security/Makefile
+++ b/security/Makefile
@@ -12,6 +12,7 @@ obj-$(CONFIG_MMU) += min_addr.o
# Object file lists
obj-$(CONFIG_SECURITY) += security.o
obj-$(CONFIG_SECURITYFS) += inode.o
+obj-$(CONFIG_SECURITY_CHROMIUMOS) += chromiumos/
obj-$(CONFIG_SECURITY_SELINUX) += selinux/
obj-$(CONFIG_SECURITY_SMACK) += smack/
obj-$(CONFIG_SECURITY) += lsm_audit.o
diff --git a/security/chromiumos/Kconfig b/security/chromiumos/Kconfig
new file mode 100644
--- /dev/null
+++ b/security/chromiumos/Kconfig
@@ -0,0 +1,29 @@
+config SECURITY_CHROMIUMOS
+ bool "Chromium OS Security Module"
+ depends on SECURITY
+ depends on X86_64 || ARM64
+ 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. This
+ LSM is stacked ahead of any primary "full" LSM.
+
+config SECURITY_CHROMIUMOS_NO_SYMLINK_MOUNT
+ bool "Chromium OS Security: prohibit mount to symlinked target"
+ depends on SECURITY_CHROMIUMOS
+ default y
+ help
+ When enabled mount() syscall will return ELOOP whenever target path
+ contains any symlinks.
+
+config SECURITY_CHROMIUMOS_NO_UNPRIVILEGED_UNSAFE_MOUNTS
+ bool "Chromium OS Security: prohibit unsafe mounts in unprivileged user namespaces"
+ depends on SECURITY_CHROMIUMOS
+ default y
+ help
+ When enabled, mount() syscall will return EPERM whenever a new mount
+ is attempted that would cause the filesystem to have the exec, suid,
+ or dev flags if the caller does not have the CAP_SYS_ADMIN capability
+ in the init namespace.
diff --git a/security/chromiumos/Makefile b/security/chromiumos/Makefile
new file mode 100644
--- /dev/null
+++ b/security/chromiumos/Makefile
@@ -0,0 +1,3 @@
+obj-$(CONFIG_SECURITY_CHROMIUMOS) := chromiumos_lsm.o
+
+chromiumos_lsm-y := inode_mark.o lsm.o securityfs.o utils.o
diff --git a/security/chromiumos/inode_mark.c b/security/chromiumos/inode_mark.c
new file mode 100644
--- /dev/null
+++ b/security/chromiumos/inode_mark.c
@@ -0,0 +1,353 @@
+/*
+ * 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,
+ u32 mask, const void *data,
+ int data_type, struct inode *dir,
+ const struct qstr *file_name,
+ u32 cookie,
+ struct fsnotify_iter_info *iter_info)
+{
+ /*
+ * 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 void chromiumos_free_mark(struct fsnotify_mark *mark)
+{
+ iput(chromiumos_to_inode_mark(mark)->inode);
+ kfree(mark);
+}
+
+static const struct fsnotify_ops chromiumos_fsn_ops = {
+ .handle_event = chromiumos_handle_fsnotify_event,
+ .freeing_mark = chromiumos_freeing_mark,
+ .free_mark = chromiumos_free_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;
+}
+
+/*
+ * 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;
+
+ fsnotify_init_mark(&inode_mark->mark, sbm->fsn_group);
+ 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, &inode->i_fsnotify_marks,
+ type, false, NULL);
+ 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_mark(&inode->i_fsnotify_marks, sbm->fsn_group);
+ if (mark) {
+ WRITE_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_detach_mark(mark);
+ 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 (free_mark)
+ fsnotify_free_mark(mark);
+ 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,
+ FSNOTIFY_OBJ_ALL_TYPES_MASK);
+ chromiumos_super_block_put(sbm);
+ }
+
+ return 0;
+}
+
+enum chromiumos_inode_security_policy chromiumos_get_inode_security_policy(
+ struct dentry *dentry, struct inode *inode,
+ 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 || !inode || type >= CHROMIUMOS_NUMBER_OF_POLICIES)
+ return policy;
+
+ sbm = chromiumos_super_block_lookup(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_mark(
+ &inode->i_fsnotify_marks, sbm->fsn_group);
+ if (mark) {
+ struct chromiumos_inode_mark *inode_mark =
+ chromiumos_to_inode_mark(mark);
+ policy = READ_ONCE(inode_mark->policies[type]);
+ fsnotify_put_mark(mark);
+
+ if (policy != CHROMIUMOS_INODE_POLICY_INHERIT)
+ break;
+ }
+
+ if (IS_ROOT(dentry))
+ break;
+ dentry = READ_ONCE(dentry->d_parent);
+ if (!dentry)
+ break;
+ inode = d_inode_rcu(dentry);
+ if (!inode)
+ break;
+ }
+ rcu_read_unlock();
+
+ chromiumos_super_block_put(sbm);
+
+ return policy;
+}
diff --git a/security/chromiumos/inode_mark.h b/security/chromiumos/inode_mark.h
new file mode 100644
--- /dev/null
+++ b/security/chromiumos/inode_mark.h
@@ -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, struct inode *inode,
+ enum chromiumos_inode_security_policy_type type);
diff --git a/security/chromiumos/lsm.c b/security/chromiumos/lsm.c
new file mode 100644
--- /dev/null
+++ b/security/chromiumos/lsm.c
@@ -0,0 +1,275 @@
+/*
+ * 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/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 <uapi/linux/mount.h>
+
+#include "inode_mark.h"
+#include "utils.h"
+
+#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)
+{
+#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;
+}
+
+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.");
+ }
+
+ free_page((unsigned long)orig_copy);
+ return 0;
+}
+
+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),
+};
+
+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
+};
diff --git a/security/chromiumos/securityfs.c b/security/chromiumos/securityfs.c
new file mode 100644
--- /dev/null
+++ b/security/chromiumos/securityfs.c
@@ -0,0 +1,241 @@
+/*
+ * 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/cred.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"
+
+static struct dentry *chromiumos_dir;
+static struct dentry *chromiumos_inode_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;
+};
+
+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 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 (!capable(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;
+}
+
+static const struct file_operations chromiumos_inode_policy_file_fops = {
+ .write = chromiumos_inode_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;
+ }
+
+ securityfs_remove(chromiumos_inode_policy_dir);
+ chromiumos_inode_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;
+ }
+ }
+
+ return 0;
+
+error:
+ chromiumos_shutdown_securityfs();
+ return ret;
+}
+fs_initcall(chromiumos_init_securityfs);
diff --git a/security/chromiumos/utils.c b/security/chromiumos/utils.c
new file mode 100644
--- /dev/null
+++ b/security/chromiumos/utils.c
@@ -0,0 +1,157 @@
+/*
+ * 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/sched/mm.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] = '"';
+ strncpy(dest + 1, source, len);
+ 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 max_len)
+{
+ size_t len;
+
+ if (!source)
+ return NULL;
+
+ len = strnlen(source, max_len);
+ 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);
+ }
+ }
+
+ /* Make sure the buffer is always NULL-terminated. */
+ buffer[PAGE_SIZE-1] = 0;
+
+ /* Make sure result is printable. */
+ sanitized = printable(buffer, res);
+ kfree(buffer);
+ buffer = sanitized;
+
+out_mm:
+ mmput(mm);
+out:
+ return buffer;
+}
diff --git a/security/chromiumos/utils.h b/security/chromiumos/utils.h
new file mode 100644
--- /dev/null
+++ b/security/chromiumos/utils.h
@@ -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, size_t max_len);
+char *printable_cmdline(struct task_struct *task);
+
+#endif /* _SECURITY_CHROMIUMOS_UTILS_H */
--
2.33.0.464.g1972c5931b-goog