diff options
Diffstat (limited to 'kernel/seccomp.c')
-rw-r--r-- | kernel/seccomp.c | 450 |
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(¤t->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(¤t->signal->cred_guard_mutex)); - BUG_ON(!spin_is_locked(¤t->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(¤t->signal->cred_guard_mutex)); - BUG_ON(!spin_is_locked(¤t->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(¤t->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(¤t->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(¤t->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(¤t->signal->cred_guard_mutex)) - goto out_free; - - spin_lock_irq(¤t->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(¤t->sighand->siglock); - if (flags & SECCOMP_FILTER_FLAG_TSYNC) - mutex_unlock(¤t->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; } |