aboutsummaryrefslogtreecommitdiff
path: root/kernel/seccomp.c
diff options
context:
space:
mode:
Diffstat (limited to 'kernel/seccomp.c')
-rw-r--r--kernel/seccomp.c450
1 files changed, 66 insertions, 384 deletions
diff --git a/kernel/seccomp.c b/kernel/seccomp.c
index 2d13b264d85..b7a10048a32 100644
--- a/kernel/seccomp.c
+++ b/kernel/seccomp.c
@@ -18,17 +18,15 @@
#include <linux/compat.h>
#include <linux/sched.h>
#include <linux/seccomp.h>
-#include <linux/slab.h>
-#include <linux/syscalls.h>
/* #define SECCOMP_DEBUG 1 */
#ifdef CONFIG_SECCOMP_FILTER
#include <asm/syscall.h>
#include <linux/filter.h>
-#include <linux/pid.h>
#include <linux/ptrace.h>
#include <linux/security.h>
+#include <linux/slab.h>
#include <linux/tracehook.h>
#include <linux/uaccess.h>
@@ -203,184 +201,45 @@ static int seccomp_check_filter(struct sock_filter *filter, unsigned int flen)
*/
static u32 seccomp_run_filters(int syscall)
{
- struct seccomp_filter *f = ACCESS_ONCE(current->seccomp.filter);
- struct seccomp_data sd;
+ struct seccomp_filter *f;
u32 ret = SECCOMP_RET_ALLOW;
/* Ensure unexpected behavior doesn't result in failing open. */
- if (unlikely(WARN_ON(f == NULL)))
+ if (WARN_ON(current->seccomp.filter == NULL))
return SECCOMP_RET_KILL;
- /* Make sure cross-thread synced filter points somewhere sane. */
- smp_read_barrier_depends();
-
- populate_seccomp_data(&sd);
-
/*
* All filters in the list are evaluated and the lowest BPF return
* value always takes priority (ignoring the DATA).
*/
- for (; f; f = f->prev) {
- u32 cur_ret = SK_RUN_FILTER(f->prog, (void *)&sd);
-
+ for (f = current->seccomp.filter; f; f = f->prev) {
+ u32 cur_ret = sk_run_filter(NULL, f->insns);
if ((cur_ret & SECCOMP_RET_ACTION) < (ret & SECCOMP_RET_ACTION))
ret = cur_ret;
}
return ret;
}
-#endif /* CONFIG_SECCOMP_FILTER */
-
-static inline bool seccomp_may_assign_mode(unsigned long seccomp_mode)
-{
- BUG_ON(!spin_is_locked(&current->sighand->siglock));
-
- if (current->seccomp.mode && current->seccomp.mode != seccomp_mode)
- return false;
-
- return true;
-}
-
-static inline void seccomp_assign_mode(struct task_struct *task,
- unsigned long seccomp_mode)
-{
- BUG_ON(!spin_is_locked(&task->sighand->siglock));
-
- task->seccomp.mode = seccomp_mode;
- /*
- * Make sure TIF_SECCOMP cannot be set before the mode (and
- * filter) is set.
- */
- smp_mb__before_atomic();
- set_tsk_thread_flag(task, TIF_SECCOMP);
-}
-
-#ifdef CONFIG_SECCOMP_FILTER
-/* Returns 1 if the parent is an ancestor of the child. */
-static int is_ancestor(struct seccomp_filter *parent,
- struct seccomp_filter *child)
-{
- /* NULL is the root ancestor. */
- if (parent == NULL)
- return 1;
- for (; child; child = child->prev)
- if (child == parent)
- return 1;
- return 0;
-}
/**
- * seccomp_can_sync_threads: checks if all threads can be synchronized
- *
- * Expects sighand and cred_guard_mutex locks to be held.
- *
- * Returns 0 on success, -ve on error, or the pid of a thread which was
- * either not in the correct seccomp mode or it did not have an ancestral
- * seccomp filter.
- */
-static inline pid_t seccomp_can_sync_threads(void)
-{
- struct task_struct *thread, *caller;
-
- BUG_ON(!mutex_is_locked(&current->signal->cred_guard_mutex));
- BUG_ON(!spin_is_locked(&current->sighand->siglock));
-
- /* Validate all threads being eligible for synchronization. */
- caller = current;
- for_each_thread(caller, thread) {
- pid_t failed;
-
- /* Skip current, since it is initiating the sync. */
- if (thread == caller)
- continue;
-
- if (thread->seccomp.mode == SECCOMP_MODE_DISABLED ||
- (thread->seccomp.mode == SECCOMP_MODE_FILTER &&
- is_ancestor(thread->seccomp.filter,
- caller->seccomp.filter)))
- continue;
-
- /* Return the first thread that cannot be synchronized. */
- failed = task_pid_vnr(thread);
- /* If the pid cannot be resolved, then return -ESRCH */
- if (unlikely(WARN_ON(failed == 0)))
- failed = -ESRCH;
- return failed;
- }
-
- return 0;
-}
-
-/**
- * seccomp_sync_threads: sets all threads to use current's filter
- *
- * Expects sighand and cred_guard_mutex locks to be held, and for
- * seccomp_can_sync_threads() to have returned success already
- * without dropping the locks.
- *
- */
-static inline void seccomp_sync_threads(void)
-{
- struct task_struct *thread, *caller;
-
- BUG_ON(!mutex_is_locked(&current->signal->cred_guard_mutex));
- BUG_ON(!spin_is_locked(&current->sighand->siglock));
-
- /* Synchronize all threads. */
- caller = current;
- for_each_thread(caller, thread) {
- /* Skip current, since it needs no changes. */
- if (thread == caller)
- continue;
-
- /* Get a task reference for the new leaf node. */
- get_seccomp_filter(caller);
- /*
- * Drop the task reference to the shared ancestor since
- * current's path will hold a reference. (This also
- * allows a put before the assignment.)
- */
- put_seccomp_filter(thread);
- smp_store_release(&thread->seccomp.filter,
- caller->seccomp.filter);
- /*
- * Opt the other thread into seccomp if needed.
- * As threads are considered to be trust-realm
- * equivalent (see ptrace_may_access), it is safe to
- * allow one thread to transition the other.
- */
- if (thread->seccomp.mode == SECCOMP_MODE_DISABLED) {
- /*
- * Don't let an unprivileged task work around
- * the no_new_privs restriction by creating
- * a thread that sets it up, enters seccomp,
- * then dies.
- */
- if (task_no_new_privs(caller))
- task_set_no_new_privs(thread);
-
- seccomp_assign_mode(thread, SECCOMP_MODE_FILTER);
- }
- }
-}
-
-/**
- * seccomp_prepare_filter: Prepares a seccomp filter for use.
+ * seccomp_attach_filter: Attaches a seccomp filter to current.
* @fprog: BPF program to install
*
- * Returns filter on success or an ERR_PTR on failure.
+ * Returns 0 on success or an errno on failure.
*/
-static struct seccomp_filter *seccomp_prepare_filter(struct sock_fprog *fprog)
+static long seccomp_attach_filter(struct sock_fprog *fprog)
{
struct seccomp_filter *filter;
- unsigned long fp_size;
- struct sock_filter *fp;
- int new_len;
+ unsigned long fp_size = fprog->len * sizeof(struct sock_filter);
+ unsigned long total_insns = fprog->len;
long ret;
if (fprog->len == 0 || fprog->len > BPF_MAXINSNS)
- return ERR_PTR(-EINVAL);
- BUG_ON(INT_MAX / fprog->len < sizeof(struct sock_filter));
- fp_size = fprog->len * sizeof(struct sock_filter);
+ return -EINVAL;
+
+ for (filter = current->seccomp.filter; filter; filter = filter->prev)
+ total_insns += filter->len + 4; /* include a 4 instr penalty */
+ if (total_insns > MAX_INSNS_PER_PATH)
+ return -ENOMEM;
/*
* Installing a seccomp filter requires that the task have
@@ -391,11 +250,15 @@ static struct seccomp_filter *seccomp_prepare_filter(struct sock_fprog *fprog)
if (!current->no_new_privs &&
security_capable_noaudit(current_cred(), current_user_ns(),
CAP_SYS_ADMIN) != 0)
- return ERR_PTR(-EACCES);
+ return -EACCES;
- fp = kzalloc(fp_size, GFP_KERNEL|__GFP_NOWARN);
- if (!fp)
- return ERR_PTR(-ENOMEM);
+ /* Allocate a new seccomp_filter */
+ filter = kzalloc(sizeof(struct seccomp_filter) + fp_size,
+ GFP_KERNEL|__GFP_NOWARN);
+ if (!filter)
+ return -ENOMEM;
+ atomic_set(&filter->usage, 1);
+ filter->len = fprog->len;
/* Copy the instructions from fprog. */
ret = -EFAULT;
@@ -410,46 +273,30 @@ static struct seccomp_filter *seccomp_prepare_filter(struct sock_fprog *fprog)
/* Check and rewrite the fprog for seccomp use */
ret = seccomp_check_filter(filter->insns, filter->len);
if (ret)
- goto free_prog;
-
- /* Allocate a new seccomp_filter */
- ret = -ENOMEM;
- filter = kzalloc(sizeof(struct seccomp_filter) +
- sizeof(struct sock_filter_int) * new_len,
- GFP_KERNEL|__GFP_NOWARN);
- if (!filter)
- goto free_prog;
-
- ret = sk_convert_filter(fp, fprog->len, filter->insnsi, &new_len);
- if (ret)
- goto free_filter;
- kfree(fp);
-
- atomic_set(&filter->usage, 1);
- filter->len = new_len;
-
- return filter;
+ goto fail;
-free_filter_prog:
- kfree(filter->prog);
-free_filter:
+ /*
+ * If there is an existing filter, make it the prev and don't drop its
+ * task reference.
+ */
+ filter->prev = current->seccomp.filter;
+ current->seccomp.filter = filter;
+ return 0;
+fail:
kfree(filter);
-free_prog:
- kfree(fp);
- return ERR_PTR(ret);
+ return ret;
}
/**
- * seccomp_prepare_user_filter - prepares a user-supplied sock_fprog
+ * seccomp_attach_user_filter - attaches a user-supplied sock_fprog
* @user_filter: pointer to the user data containing a sock_fprog.
*
* Returns 0 on success and non-zero otherwise.
*/
-static struct seccomp_filter *
-seccomp_prepare_user_filter(const char __user *user_filter)
+long seccomp_attach_user_filter(char __user *user_filter)
{
struct sock_fprog fprog;
- struct seccomp_filter *filter = ERR_PTR(-EFAULT);
+ long ret = -EFAULT;
#ifdef CONFIG_COMPAT
if (is_compat_task()) {
@@ -462,56 +309,9 @@ seccomp_prepare_user_filter(const char __user *user_filter)
#endif
if (copy_from_user(&fprog, user_filter, sizeof(fprog)))
goto out;
- filter = seccomp_prepare_filter(&fprog);
+ ret = seccomp_attach_filter(&fprog);
out:
- return filter;
-}
-
-/**
- * seccomp_attach_filter: validate and attach filter
- * @flags: flags to change filter behavior
- * @filter: seccomp filter to add to the current process
- *
- * Caller must be holding current->sighand->siglock lock.
- *
- * Returns 0 on success, -ve on error.
- */
-static long seccomp_attach_filter(unsigned int flags,
- struct seccomp_filter *filter)
-{
- unsigned long total_insns;
- struct seccomp_filter *walker;
-
- BUG_ON(!spin_is_locked(&current->sighand->siglock));
-
- /* Validate resulting filter length. */
- total_insns = filter->prog->len;
- for (walker = current->seccomp.filter; walker; walker = walker->prev)
- total_insns += walker->prog->len + 4; /* 4 instr penalty */
- if (total_insns > MAX_INSNS_PER_PATH)
- return -ENOMEM;
-
- /* If thread sync has been requested, check that it is possible. */
- if (flags & SECCOMP_FILTER_FLAG_TSYNC) {
- int ret;
-
- ret = seccomp_can_sync_threads();
- if (ret)
- return ret;
- }
-
- /*
- * If there is an existing filter, make it the prev and don't drop its
- * task reference.
- */
- filter->prev = current->seccomp.filter;
- current->seccomp.filter = filter;
-
- /* Now that the new filter is in place, synchronize to all threads. */
- if (flags & SECCOMP_FILTER_FLAG_TSYNC)
- seccomp_sync_threads();
-
- return 0;
+ return ret;
}
/* get_seccomp_filter - increments the reference count of the filter on @tsk */
@@ -524,14 +324,6 @@ void get_seccomp_filter(struct task_struct *tsk)
atomic_inc(&orig->usage);
}
-static inline void seccomp_filter_free(struct seccomp_filter *filter)
-{
- if (filter) {
- sk_filter_free(filter->prog);
- kfree(filter);
- }
-}
-
/* put_seccomp_filter - decrements the ref count of tsk->seccomp.filter */
void put_seccomp_filter(struct task_struct *tsk)
{
@@ -540,7 +332,7 @@ void put_seccomp_filter(struct task_struct *tsk)
while (orig && atomic_dec_and_test(&orig->usage)) {
struct seccomp_filter *freeme = orig;
orig = orig->prev;
- seccomp_filter_free(freeme);
+ kfree(freeme);
}
}
@@ -584,17 +376,12 @@ static int mode1_syscalls_32[] = {
int __secure_computing(int this_syscall)
{
+ int mode = current->seccomp.mode;
int exit_sig = 0;
int *syscall;
u32 ret;
- /*
- * Make sure that any changes to mode from another thread have
- * been seen after TIF_SECCOMP was seen.
- */
- rmb();
-
- switch (current->seccomp.mode) {
+ switch (mode) {
case SECCOMP_MODE_STRICT:
syscall = mode1_syscalls;
#ifdef CONFIG_COMPAT
@@ -680,152 +467,47 @@ long prctl_get_seccomp(void)
}
/**
- * seccomp_set_mode_strict: internal function for setting strict seccomp
- *
- * Once current->seccomp.mode is non-zero, it may not be changed.
- *
- * Returns 0 on success or -EINVAL on failure.
- */
-static long seccomp_set_mode_strict(void)
-{
- const unsigned long seccomp_mode = SECCOMP_MODE_STRICT;
- long ret = -EINVAL;
-
- spin_lock_irq(&current->sighand->siglock);
-
- if (!seccomp_may_assign_mode(seccomp_mode))
- goto out;
-
-#ifdef TIF_NOTSC
- disable_TSC();
-#endif
- seccomp_assign_mode(current, seccomp_mode);
- ret = 0;
-
-out:
- spin_unlock_irq(&current->sighand->siglock);
-
- return ret;
-}
-
-#ifdef CONFIG_SECCOMP_FILTER
-/**
- * seccomp_set_mode_filter: internal function for setting seccomp filter
- * @flags: flags to change filter behavior
- * @filter: struct sock_fprog containing filter
+ * prctl_set_seccomp: configures current->seccomp.mode
+ * @seccomp_mode: requested mode to use
+ * @filter: optional struct sock_fprog for use with SECCOMP_MODE_FILTER
*
- * This function may be called repeatedly to install additional filters.
- * Every filter successfully installed will be evaluated (in reverse order)
- * for each system call the task makes.
+ * This function may be called repeatedly with a @seccomp_mode of
+ * SECCOMP_MODE_FILTER to install additional filters. Every filter
+ * successfully installed will be evaluated (in reverse order) for each system
+ * call the task makes.
*
* Once current->seccomp.mode is non-zero, it may not be changed.
*
* Returns 0 on success or -EINVAL on failure.
*/
-static long seccomp_set_mode_filter(unsigned int flags,
- const char __user *filter)
+long prctl_set_seccomp(unsigned long seccomp_mode, char __user *filter)
{
- const unsigned long seccomp_mode = SECCOMP_MODE_FILTER;
- struct seccomp_filter *prepared = NULL;
long ret = -EINVAL;
- /* Validate flags. */
- if (flags & ~SECCOMP_FILTER_FLAG_MASK)
- return -EINVAL;
-
- /* Prepare the new filter before holding any locks. */
- prepared = seccomp_prepare_user_filter(filter);
- if (IS_ERR(prepared))
- return PTR_ERR(prepared);
-
- /*
- * Make sure we cannot change seccomp or nnp state via TSYNC
- * while another thread is in the middle of calling exec.
- */
- if (flags & SECCOMP_FILTER_FLAG_TSYNC &&
- mutex_lock_killable(&current->signal->cred_guard_mutex))
- goto out_free;
-
- spin_lock_irq(&current->sighand->siglock);
-
- if (!seccomp_may_assign_mode(seccomp_mode))
+ if (current->seccomp.mode &&
+ current->seccomp.mode != seccomp_mode)
goto out;
- ret = seccomp_attach_filter(flags, prepared);
- if (ret)
- goto out;
- /* Do not free the successfully attached filter. */
- prepared = NULL;
-
- seccomp_assign_mode(current, seccomp_mode);
-out:
- spin_unlock_irq(&current->sighand->siglock);
- if (flags & SECCOMP_FILTER_FLAG_TSYNC)
- mutex_unlock(&current->signal->cred_guard_mutex);
-out_free:
- seccomp_filter_free(prepared);
- return ret;
-}
-#else
-static inline long seccomp_set_mode_filter(unsigned int flags,
- const char __user *filter)
-{
- return -EINVAL;
-}
-#endif
-
-/* Common entry point for both prctl and syscall. */
-static long do_seccomp(unsigned int op, unsigned int flags,
- const char __user *uargs)
-{
- switch (op) {
- case SECCOMP_SET_MODE_STRICT:
- if (flags != 0 || uargs != NULL)
- return -EINVAL;
- return seccomp_set_mode_strict();
- case SECCOMP_SET_MODE_FILTER:
- return seccomp_set_mode_filter(flags, uargs);
- default:
- return -EINVAL;
- }
-}
-
-SYSCALL_DEFINE3(seccomp, unsigned int, op, unsigned int, flags,
- const char __user *, uargs)
-{
- return do_seccomp(op, flags, uargs);
-}
-
-/**
- * prctl_set_seccomp: configures current->seccomp.mode
- * @seccomp_mode: requested mode to use
- * @filter: optional struct sock_fprog for use with SECCOMP_MODE_FILTER
- *
- * Returns 0 on success or -EINVAL on failure.
- */
-long prctl_set_seccomp(unsigned long seccomp_mode, char __user *filter)
-{
- unsigned int op;
- char __user *uargs;
-
switch (seccomp_mode) {
case SECCOMP_MODE_STRICT:
- op = SECCOMP_SET_MODE_STRICT;
- /*
- * Setting strict mode through prctl always ignored filter,
- * so make sure it is always NULL here to pass the internal
- * check in do_seccomp().
- */
- uargs = NULL;
+ ret = 0;
+#ifdef TIF_NOTSC
+ disable_TSC();
+#endif
break;
+#ifdef CONFIG_SECCOMP_FILTER
case SECCOMP_MODE_FILTER:
- op = SECCOMP_SET_MODE_FILTER;
- uargs = filter;
+ ret = seccomp_attach_user_filter(filter);
+ if (ret)
+ goto out;
break;
+#endif
default:
- return -EINVAL;
+ goto out;
}
- /* prctl interface doesn't have flags, so they are always zero. */
- return do_seccomp(op, 0, uargs);
+ current->seccomp.mode = seccomp_mode;
+ set_thread_flag(TIF_SECCOMP);
+out:
+ return ret;
}