| 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 |
| |