|
|
Message-Id: <1458784008-16277-7-git-send-email-mic@digikod.net>
Date: Thu, 24 Mar 2016 02:46:37 +0100
From: Mickaël Salaün <mic@...ikod.net>
To: linux-security-module@...r.kernel.org
Cc: Mickaël Salaün <mic@...ikod.net>,
Andreas Gruenbacher <agruenba@...hat.com>,
Andy Lutomirski <luto@...capital.net>,
Andy Lutomirski <luto@...nel.org>,
Arnd Bergmann <arnd@...db.de>,
Casey Schaufler <casey@...aufler-ca.com>,
Daniel Borkmann <daniel@...earbox.net>,
David Drysdale <drysdale@...gle.com>,
Eric Paris <eparis@...hat.com>,
James Morris <james.l.morris@...cle.com>,
Jeff Dike <jdike@...toit.com>,
Julien Tinnes <jln@...gle.com>,
Kees Cook <keescook@...omium.org>,
Michael Kerrisk <mtk@...7.org>,
Paul Moore <pmoore@...hat.com>,
Richard Weinberger <richard@....at>,
"Serge E . Hallyn" <serge@...lyn.com>,
Stephen Smalley <sds@...ho.nsa.gov>,
Tetsuo Handa <penguin-kernel@...ove.SAKURA.ne.jp>,
Will Drewry <wad@...omium.org>,
linux-api@...r.kernel.org,
kernel-hardening@...ts.openwall.com
Subject: [RFC v1 06/17] seccomp: Add the SECCOMP_ADD_CHECKER_GROUP command
A new command SECCOMP_ADD_CHECKER_GROUP allows userland seccomp filters
to reference kernel objects with checkers in a batch.
Each checker is autonomous and new ones can easily be added in the
future. There is currently two checkers for path objects:
* SECCOMP_CHECK_FS_LITERAL checks if a string match a defined path;
* SECCOMP_CHECK_FS_BENEATH checks if the path representation of a string
is equal or equivalent to a file belonging to a defined path.
These checkers can use a bitmask of flags to match a path:
* SECCOMP_OBJFLAG_FS_DENTRY match a unique file;
* SECCOMP_OBJFLAG_FS_INODE only match a file inode (must be used with
the device flag);
* SECCOMP_OBJFLAG_FS_DEVICE match the device of a file;
* SECCOMP_OBJFLAG_FS_MOUNT match the mount point of a file;
* SECCOMP_OBJFLAG_FS_NOFOLLOW do not follow a symlink for the
initial checker evaluation.
Signed-off-by: Mickaël Salaün <mic@...ikod.net>
Cc: Andy Lutomirski <luto@...nel.org>
Cc: David Drysdale <drysdale@...gle.com>
Cc: James Morris <james.l.morris@...cle.com>
Cc: Kees Cook <keescook@...omium.org>
Cc: Michael Kerrisk <mtk@...7.org>
Cc: Paul Moore <pmoore@...hat.com>
Cc: Serge E. Hallyn <serge@...lyn.com>
Cc: Will Drewry <wad@...omium.org>
---
include/linux/seccomp.h | 32 ++++++
include/uapi/linux/seccomp.h | 81 ++++++++++++++++
kernel/seccomp.c | 221 ++++++++++++++++++++++++++++++++++++++++++
security/seccomp/Makefile | 2 +-
security/seccomp/checker_fs.c | 102 +++++++++++++++++++
5 files changed, 437 insertions(+), 1 deletion(-)
create mode 100644 security/seccomp/checker_fs.c
diff --git a/include/linux/seccomp.h b/include/linux/seccomp.h
index 2296e6b2f690..78f5861a0328 100644
--- a/include/linux/seccomp.h
+++ b/include/linux/seccomp.h
@@ -9,8 +9,10 @@
#include <linux/thread_info.h>
#include <asm/seccomp.h>
+#include <linux/path.h>
struct seccomp_filter;
+struct seccomp_filter_checker_group;
/**
* struct seccomp - the state of a seccomp'ed process
*
@@ -19,12 +21,20 @@ struct seccomp_filter;
* @filter: must always point to a valid seccomp-filter or NULL as it is
* accessed without locking during system call entry.
*
+ * @checker_group: an append-only list of argument checkers usable by filters
+ * created after the last update.
+ *
* @filter must only be accessed from the context of current as there
* is no read locking.
*/
struct seccomp {
int mode;
struct seccomp_filter *filter;
+
+#ifdef CONFIG_SECURITY_SECCOMP
+ /* @checker_group is only used for filter creation and unique per thread */
+ struct seccomp_filter_checker_group *checker_group;
+#endif
};
#ifdef CONFIG_HAVE_ARCH_SECCOMP_FILTER
@@ -85,6 +95,28 @@ static inline int seccomp_mode(struct seccomp *s)
#ifdef CONFIG_SECCOMP_FILTER
extern void put_seccomp_filter(struct task_struct *tsk);
extern void get_seccomp_filter(struct task_struct *tsk);
+
+#ifdef CONFIG_SECURITY_SECCOMP
+struct seccomp_filter_object_path {
+ u32 flags;
+ struct path path;
+};
+
+struct seccomp_filter_checker {
+ /* e.g. SECCOMP_ARGCHECK_FS_LITERAL */
+ u32 check;
+ /* e.g. SECCOMP_ARGTYPE_PATH */
+ u32 type;
+ union {
+ struct seccomp_filter_object_path object_path;
+ };
+};
+
+
+long seccomp_set_argcheck_fs(const struct seccomp_checker *,
+ struct seccomp_filter_checker *);
+#endif /* CONFIG_SECURITY_SECCOMP */
+
#else /* CONFIG_SECCOMP_FILTER */
static inline void put_seccomp_filter(struct task_struct *tsk)
{
diff --git a/include/uapi/linux/seccomp.h b/include/uapi/linux/seccomp.h
index 0f238a43ff1e..ca7e9343f3d7 100644
--- a/include/uapi/linux/seccomp.h
+++ b/include/uapi/linux/seccomp.h
@@ -13,6 +13,7 @@
/* Valid operations for seccomp syscall. */
#define SECCOMP_SET_MODE_STRICT 0
#define SECCOMP_SET_MODE_FILTER 1
+#define SECCOMP_ADD_CHECKER_GROUP 2 /* add a group of checkers */
/* Valid flags for SECCOMP_SET_MODE_FILTER */
#define SECCOMP_FILTER_FLAG_TSYNC 1
@@ -35,6 +36,25 @@
#define SECCOMP_RET_ACTION 0x7fff0000U
#define SECCOMP_RET_DATA 0x0000ffffU
+/* Object checks */
+#define SECCOMP_CHECK_FS_LITERAL 1
+#define SECCOMP_CHECK_FS_BENEATH 2
+
+/* Object flags */
+#define SECCOMP_OBJFLAG_FS_DENTRY (1 << 0)
+#define SECCOMP_OBJFLAG_FS_INODE (1 << 1)
+#define SECCOMP_OBJFLAG_FS_DEVICE (1 << 2)
+#define SECCOMP_OBJFLAG_FS_MOUNT (1 << 3)
+/* Do the evaluation follow the argument path? (cf. fs/namei.c)
+ * This flag is only used for the seccomp filter but not by the LSM check to
+ * enforce access control. You need to take care of the different path
+ * interpretation per syscall (e.g. rename(2) or open(2) with O_NOFOLLOW).
+ */
+#define SECCOMP_OBJFLAG_FS_NOFOLLOW (1 << 4)
+
+/* Argument types */
+#define SECCOMP_OBJTYPE_PATH 1
+
/**
* struct seccomp_data - the format the BPF program executes over.
* @nr: the system call number
@@ -51,4 +71,65 @@ struct seccomp_data {
__u64 args[6];
};
+/* TODO: Add a "at" field (default to AT_FDCWD) */
+struct seccomp_object_path {
+ /* e.g. SECCOMP_OBJFLAG_FS_DENTRY */
+ __u32 flags;
+ const char *path;
+};
+
+struct seccomp_checker {
+ __u32 check;
+ __u32 type;
+ /* Must match the checker extra size, if any */
+ unsigned int len;
+ /* Checkers must be pointers to allow futur additions */
+ union {
+ const struct seccomp_object_path *object_path;
+ };
+};
+
+#define SECCOMP_MAKE_PATH_DENTRY(_p) \
+ { \
+ .flags = SECCOMP_OBJFLAG_FS_DENTRY, \
+ .path = _p, \
+ }
+
+#define SECCOMP_MAKE_PATH_INODE(_p) \
+ { \
+ .flags = SECCOMP_OBJFLAG_FS_INODE | \
+ SECCOMP_OBJFLAG_FS_DEVICE, \
+ .path = _p, \
+ }
+
+#define SECCOMP_MAKE_PATH_MOUNT(_p) \
+ { \
+ .flags = SECCOMP_OBJFLAG_FS_MOUNT, \
+ .path = _p, \
+ }
+
+#define SECCOMP_MAKE_PATH_ALL(_p) \
+ { \
+ .flags = SECCOMP_OBJFLAG_FS_DENTRY | \
+ SECCOMP_OBJFLAG_FS_INODE | \
+ SECCOMP_OBJFLAG_FS_DEVICE | \
+ SECCOMP_OBJFLAG_FS_MOUNT, \
+ .path = _p, \
+ }
+
+#define SECCOMP_MAKE_OBJ_PATH(_c, _p) \
+ { \
+ .check = SECCOMP_CHECK_##_c, \
+ .type = SECCOMP_OBJTYPE_PATH, \
+ .len = 0, \
+ .object_path = _p, \
+ }
+
+struct seccomp_checker_group {
+ __u8 version;
+ __u8 id;
+ unsigned int len;
+ const struct seccomp_checker (*checkers)[];
+};
+
#endif /* _UAPI_LINUX_SECCOMP_H */
diff --git a/kernel/seccomp.c b/kernel/seccomp.c
index 2c94693e4163..0e5471d2891c 100644
--- a/kernel/seccomp.c
+++ b/kernel/seccomp.c
@@ -6,6 +6,8 @@
* Copyright (C) 2012 Google, Inc.
* Will Drewry <wad@...omium.org>
*
+ * Copyright (C) 2016 Mickaël Salaün <mic@...ikod.net>
+ *
* This defines a simple but solid secure-computing facility.
*
* Mode 1 uses a fixed list of allowed system calls.
@@ -60,6 +62,34 @@ struct seccomp_filter {
struct bpf_prog *prog;
};
+/* Argument group attached to seccomp filters
+ *
+ * @usage keep track of the references
+ * @prev link to the previous checker_group
+ * @id is given by userland to easely check a filter statically and not
+ * leak data from the kernel
+ * @checkers_len is the number of @checkers elements
+ * @checkers contains the checkers
+ *
+ * seccomp_filter_checker_group checkers are organized in a tree linked via the
+ * @prev pointer. For any task, it appears to be a singly-linked list starting
+ * with current->seccomp.filter->checker_group, the most recently added argument
+ * group. All filters created by a process share the argument groups created by
+ * this process until the filter creation but they can not be changed. However,
+ * multiple argument groups may share a @prev node, which results in a
+ * unidirectional tree existing in memory. They are not inherited through
+ * fork().
+ */
+#ifdef CONFIG_SECURITY_SECCOMP
+struct seccomp_filter_checker_group {
+ atomic_t usage;
+ struct seccomp_filter_checker_group *prev;
+ u8 id;
+ unsigned int checkers_len;
+ struct seccomp_filter_checker checkers[];
+};
+#endif /* CONFIG_SECURITY_SECCOMP */
+
/* Limit any path through the tree to 256KB worth of instructions. */
#define MAX_INSNS_PER_PATH ((1 << 18) / sizeof(struct sock_filter))
@@ -467,6 +497,38 @@ void get_seccomp_filter(struct task_struct *tsk)
atomic_inc(&orig->usage);
}
+#ifdef CONFIG_SECURITY_SECCOMP
+/* Do not free @checker */
+static void put_seccomp_obj(struct seccomp_filter_checker *checker)
+{
+ switch (checker->type) {
+ case SECCOMP_OBJTYPE_PATH:
+ /* Pointer checks done in path_put() */
+ path_put(&checker->object_path.path);
+ break;
+ default:
+ WARN_ON(1);
+ }
+}
+
+/* Free @checker_group */
+static void put_seccomp_checker_group(struct seccomp_filter_checker_group *checker_group)
+{
+ int i;
+ struct seccomp_filter_checker_group *orig = checker_group;
+
+ /* Clean up single-reference branches iteratively. */
+ while (orig && atomic_dec_and_test(&orig->usage)) {
+ struct seccomp_filter_checker_group *freeme = orig;
+
+ for (i = 0; i < freeme->checkers_len; i++)
+ put_seccomp_obj(&freeme->checkers[i]);
+ orig = orig->prev;
+ kfree(freeme);
+ }
+}
+#endif /* CONFIG_SECURITY_SECCOMP */
+
static inline void seccomp_filter_free(struct seccomp_filter *filter)
{
if (filter) {
@@ -485,6 +547,9 @@ void put_seccomp_filter(struct task_struct *tsk)
orig = orig->prev;
seccomp_filter_free(freeme);
}
+#ifdef CONFIG_SECURITY_SECCOMP
+ put_seccomp_checker_group(tsk->seccomp.checker_group);
+#endif
}
/**
@@ -813,6 +878,158 @@ static inline long seccomp_set_mode_filter(unsigned int flags,
}
#endif
+#ifdef CONFIG_SECURITY_SECCOMP
+
+/* Limit checkers number to 64 to be able to show matches with a bitmask. */
+#define SECCOMP_CHECKERS_MAX 64
+
+/* Limit arg group list and their checkers to 256KB. */
+#define SECCOMP_GROUP_CHECKERS_MAX_SIZE (1 << 18)
+
+static long seccomp_add_checker_group(unsigned int flags, const char __user *group)
+{
+ struct seccomp_checker_group kgroup;
+ struct seccomp_checker (*kcheckers)[], *user_checker;
+ struct seccomp_filter_checker_group *filter_group, *walker;
+ struct seccomp_filter_checker *kernel_obj;
+ unsigned int i;
+ unsigned long group_size, kcheckers_size, full_group_size;
+ long result;
+
+ if (!task_no_new_privs(current) &&
+ security_capable_noaudit(current_cred(),
+ current_user_ns(), CAP_SYS_ADMIN) != 0)
+ return -EACCES;
+ if (flags != 0 || !group)
+ return -EINVAL;
+
+#ifdef CONFIG_COMPAT
+ if (is_compat_task()) {
+ struct compat_seccomp_checker_group kgroup32;
+
+ if (copy_from_user(&kgroup32, group, sizeof(kgroup32)))
+ return -EFAULT;
+ kgroup.version = kgroup32.version;
+ kgroup.id = kgroup32.id;
+ kgroup.len = kgroup32.len;
+ kgroup.checkers = compat_ptr(kgroup32.checkers);
+ } else /* Falls through to the if below */
+#endif /* CONFIG_COMPAT */
+ if (copy_from_user(&kgroup, group, sizeof(kgroup)))
+ return -EFAULT;
+
+ if (kgroup.version != 1)
+ return -EINVAL;
+ /* The group ID 0 means no evaluated checkers */
+ if (kgroup.id == 0)
+ return -EINVAL;
+ if (kgroup.len == 0)
+ return -EINVAL;
+ if (kgroup.len > SECCOMP_CHECKERS_MAX)
+ return -E2BIG;
+
+ /* Validate resulting checker_group ID and length. */
+ group_size = sizeof(*filter_group) +
+ kgroup.len * sizeof(filter_group->checkers[0]);
+ full_group_size = group_size;
+ for (walker = current->seccomp.checker_group;
+ walker; walker = walker->prev) {
+ if (walker->id == kgroup.id)
+ return -EINVAL;
+ /* TODO: add penalty? */
+ full_group_size += sizeof(*walker) +
+ walker->checkers_len * sizeof(walker->checkers[0]);
+ }
+ if (full_group_size > SECCOMP_GROUP_CHECKERS_MAX_SIZE)
+ return -ENOMEM;
+
+ kcheckers_size = kgroup.len * sizeof((*kcheckers)[0]);
+ kcheckers = kmalloc(kcheckers_size, GFP_KERNEL);
+ if (!kcheckers)
+ return -ENOMEM;
+
+#ifdef CONFIG_COMPAT
+ if (is_compat_task()) {
+ unsigned int i, kcheckers32_size;
+ struct compat_seccomp_checker (*kcheckers32)[];
+
+ kcheckers32_size = kgroup.len * sizeof((*kcheckers32)[0]);
+ kcheckers32 = kmalloc(kcheckers32_size, GFP_KERNEL);
+ if (!kcheckers32) {
+ result = -ENOMEM;
+ goto free_kcheckers;
+ }
+ if (copy_from_user(kcheckers32, kgroup.checkers, kcheckers32_size)) {
+ kfree(kcheckers32);
+ result = -EFAULT;
+ goto free_kcheckers;
+ }
+ for (i = 0; i < kgroup.len; i++) {
+ (*kcheckers)[i].check = (*kcheckers32)[i].check;
+ (*kcheckers)[i].type = (*kcheckers32)[i].type;
+ (*kcheckers)[i].len = (*kcheckers32)[i].len;
+ (*kcheckers)[i].object_path = compat_ptr((*kcheckers32)[i].checker);
+ }
+ kfree(kcheckers32);
+ } else /* Falls through to the if below */
+#endif /* CONFIG_COMPAT */
+ if (copy_from_user(kcheckers, kgroup.checkers, kcheckers_size)) {
+ result = -EFAULT;
+ goto free_kcheckers;
+ }
+
+ /* filter_group->checkers must be zeroed to correctly be freed on error */
+ filter_group = kzalloc(group_size, GFP_KERNEL);
+ if (!filter_group) {
+ result = -ENOMEM;
+ goto free_kcheckers;
+ }
+ filter_group->prev = NULL;
+ filter_group->id = kgroup.id;
+ filter_group->checkers_len = kgroup.len;
+ for (i = 0; i < filter_group->checkers_len; i++) {
+ user_checker = &(*kcheckers)[i];
+ kernel_obj = &filter_group->checkers[i];
+ switch (user_checker->check) {
+ case SECCOMP_CHECK_FS_LITERAL:
+ case SECCOMP_CHECK_FS_BENEATH:
+ kernel_obj->check = user_checker->check;
+ result =
+ seccomp_set_argcheck_fs(user_checker, kernel_obj);
+ if (result)
+ goto free_group;
+ break;
+ default:
+ result = -EINVAL;
+ goto free_group;
+ }
+ }
+
+ atomic_set(&filter_group->usage, 1);
+ filter_group->prev = current->seccomp.checker_group;
+ /* No need to update filter_group->prev->usage because it get one
+ * reference from this filter but lose one from
+ * current->seccomp.checker_group.
+ */
+ current->seccomp.checker_group = filter_group;
+ /* XXX: Return the number of groups? */
+ result = 0;
+ goto free_kcheckers;
+
+free_group:
+ for (i = 0; i < filter_group->checkers_len; i++) {
+ kernel_obj = &filter_group->checkers[i];
+ if (kernel_obj->type)
+ put_seccomp_obj(kernel_obj);
+ }
+ kfree(filter_group);
+
+free_kcheckers:
+ kfree(kcheckers);
+ return result;
+}
+#endif /* CONFIG_SECURITY_SECCOMP */
+
/* Common entry point for both prctl and syscall. */
static long do_seccomp(unsigned int op, unsigned int flags,
const char __user *uargs)
@@ -824,6 +1041,10 @@ static long do_seccomp(unsigned int op, unsigned int flags,
return seccomp_set_mode_strict();
case SECCOMP_SET_MODE_FILTER:
return seccomp_set_mode_filter(flags, uargs);
+#ifdef CONFIG_SECURITY_SECCOMP
+ case SECCOMP_ADD_CHECKER_GROUP:
+ return seccomp_add_checker_group(flags, uargs);
+#endif /* CONFIG_SECURITY_SECCOMP */
default:
return -EINVAL;
}
diff --git a/security/seccomp/Makefile b/security/seccomp/Makefile
index f2e848d81138..1ed68b23a922 100644
--- a/security/seccomp/Makefile
+++ b/security/seccomp/Makefile
@@ -1,3 +1,3 @@
obj-$(CONFIG_SECURITY_SECCOMP) := seccomp.o
-seccomp-y := lsm.o
+seccomp-y := lsm.o checker_fs.o
diff --git a/security/seccomp/checker_fs.c b/security/seccomp/checker_fs.c
new file mode 100644
index 000000000000..c11efc892de5
--- /dev/null
+++ b/security/seccomp/checker_fs.c
@@ -0,0 +1,102 @@
+/*
+ * Seccomp Linux Security Module - File System Checkers
+ *
+ * Copyright (C) 2016 Mickaël Salaün <mic@...ikod.net>
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License version 2, as
+ * published by the Free Software Foundation.
+ */
+
+#include <linux/compat.h>
+#include <linux/namei.h> /* user_lpath() */
+#include <linux/path.h>
+#include <linux/seccomp.h>
+#include <linux/slab.h>
+#include <linux/uaccess.h> /* copy_from_user() */
+
+#ifdef CONFIG_COMPAT
+/* struct seccomp_object_path */
+struct compat_seccomp_object_path {
+ __u32 flags;
+ compat_uptr_t path; /* const char * */
+};
+#endif
+
+static const u32 path_flags_mask_literal =
+ SECCOMP_OBJFLAG_FS_DENTRY |
+ SECCOMP_OBJFLAG_FS_INODE |
+ SECCOMP_OBJFLAG_FS_DEVICE |
+ SECCOMP_OBJFLAG_FS_MOUNT |
+ SECCOMP_OBJFLAG_FS_NOFOLLOW;
+
+static const u32 path_flags_mask_beneath =
+ SECCOMP_OBJFLAG_FS_DENTRY |
+ SECCOMP_OBJFLAG_FS_INODE |
+ SECCOMP_OBJFLAG_FS_NOFOLLOW;
+
+/* Return true for any error, or false if flags are OK. */
+static bool wrong_check_flags(u32 check, u32 flags)
+{
+ u32 path_flags_mask;
+
+ /* Do not allow insecure check: inode without device */
+ if ((flags & SECCOMP_OBJFLAG_FS_INODE) &&
+ !(flags & SECCOMP_OBJFLAG_FS_DEVICE))
+ return true;
+
+ switch (check) {
+ case SECCOMP_CHECK_FS_LITERAL:
+ path_flags_mask = path_flags_mask_literal;
+ break;
+ case SECCOMP_CHECK_FS_BENEATH:
+ path_flags_mask = path_flags_mask_beneath;
+ break;
+ default:
+ WARN_ON(1);
+ return true;
+ }
+ /* Need at least one flag, but only in the allowed mask */
+ return !(flags & path_flags_mask) ||
+ ((flags | path_flags_mask) != path_flags_mask);
+}
+
+static long set_argtype_path(const struct seccomp_checker *user_checker,
+ struct seccomp_filter_checker *kernel_checker)
+{
+ struct seccomp_object_path user_cp;
+
+ /* @len is not used for @object_path */
+ if (user_checker->len != 0)
+ return -EINVAL;
+
+#ifdef CONFIG_COMPAT
+ if (is_compat_task()) {
+ struct compat_seccomp_object_path user_cp32;
+
+ if (copy_from_user(&user_cp32, user_checker->object_path, sizeof(user_cp32)))
+ return -EFAULT;
+ user_cp.flags = user_cp32.flags;
+ user_cp.path = compat_ptr(user_cp32.path);
+ } else /* Falls through to the if below */
+#endif
+ if (copy_from_user(&user_cp, user_checker->object_path, sizeof(user_cp)))
+ return -EFAULT;
+
+ if (wrong_check_flags(kernel_checker->check, user_cp.flags))
+ return -EINVAL;
+ kernel_checker->object_path.flags = user_cp.flags;
+ /* Do not follow symlinks for objects */
+ return user_lpath(user_cp.path, &kernel_checker->object_path.path);
+}
+
+long seccomp_set_argcheck_fs(const struct seccomp_checker *user_checker,
+ struct seccomp_filter_checker *kernel_checker)
+{
+ switch (user_checker->type) {
+ case SECCOMP_OBJTYPE_PATH:
+ kernel_checker->type = user_checker->type;
+ return set_argtype_path(user_checker, kernel_checker);
+ }
+ return -EINVAL;
+}
--
2.8.0.rc3
Powered by blists - more mailing lists
Confused about mailing lists and their use? Read about mailing lists on Wikipedia and check out these guidelines on proper formatting of your messages.