diff options
Diffstat (limited to 'kernel/seccomp.c')
| -rw-r--r-- | kernel/seccomp.c | 345 | 
1 files changed, 327 insertions, 18 deletions
| diff --git a/kernel/seccomp.c b/kernel/seccomp.c index 98b59b5db90b..418a1c045933 100644 --- a/kernel/seccomp.c +++ b/kernel/seccomp.c @@ -1,3 +1,4 @@ +// SPDX-License-Identifier: GPL-2.0  /*   * linux/kernel/seccomp.c   * @@ -17,11 +18,13 @@  #include <linux/audit.h>  #include <linux/compat.h>  #include <linux/coredump.h> +#include <linux/kmemleak.h>  #include <linux/sched.h>  #include <linux/sched/task_stack.h>  #include <linux/seccomp.h>  #include <linux/slab.h>  #include <linux/syscalls.h> +#include <linux/sysctl.h>  #ifdef CONFIG_HAVE_ARCH_SECCOMP_FILTER  #include <asm/syscall.h> @@ -42,6 +45,7 @@   *         get/put helpers should be used when accessing an instance   *         outside of a lifetime-guarded section.  In general, this   *         is only needed for handling filters shared across tasks. + * @log: true if all actions except for SECCOMP_RET_ALLOW should be logged   * @prev: points to a previously installed, or inherited, filter   * @prog: the BPF program to evaluate   * @@ -57,6 +61,7 @@   */  struct seccomp_filter {  	refcount_t usage; +	bool log;  	struct seccomp_filter *prev;  	struct bpf_prog *prog;  }; @@ -171,10 +176,15 @@ static int seccomp_check_filter(struct sock_filter *filter, unsigned int flen)  /**   * seccomp_run_filters - evaluates all seccomp filters against @sd   * @sd: optional seccomp data to be passed to filters + * @match: stores struct seccomp_filter that resulted in the return value, + *         unless filter returned SECCOMP_RET_ALLOW, in which case it will + *         be unchanged.   *   * Returns valid seccomp BPF response codes.   */ -static u32 seccomp_run_filters(const struct seccomp_data *sd) +#define ACTION_ONLY(ret) ((s32)((ret) & (SECCOMP_RET_ACTION_FULL))) +static u32 seccomp_run_filters(const struct seccomp_data *sd, +			       struct seccomp_filter **match)  {  	struct seccomp_data sd_local;  	u32 ret = SECCOMP_RET_ALLOW; @@ -184,7 +194,7 @@ static u32 seccomp_run_filters(const struct seccomp_data *sd)  	/* Ensure unexpected behavior doesn't result in failing open. */  	if (unlikely(WARN_ON(f == NULL))) -		return SECCOMP_RET_KILL; +		return SECCOMP_RET_KILL_PROCESS;  	if (!sd) {  		populate_seccomp_data(&sd_local); @@ -198,8 +208,10 @@ static u32 seccomp_run_filters(const struct seccomp_data *sd)  	for (; f; f = f->prev) {  		u32 cur_ret = BPF_PROG_RUN(f->prog, sd); -		if ((cur_ret & SECCOMP_RET_ACTION) < (ret & SECCOMP_RET_ACTION)) +		if (ACTION_ONLY(cur_ret) < ACTION_ONLY(ret)) {  			ret = cur_ret; +			*match = f; +		}  	}  	return ret;  } @@ -444,6 +456,10 @@ static long seccomp_attach_filter(unsigned int flags,  			return ret;  	} +	/* Set log flag, if present. */ +	if (flags & SECCOMP_FILTER_FLAG_LOG) +		filter->log = true; +  	/*  	 * If there is an existing filter, make it the prev and don't drop its  	 * task reference. @@ -458,14 +474,19 @@ static long seccomp_attach_filter(unsigned int flags,  	return 0;  } +static void __get_seccomp_filter(struct seccomp_filter *filter) +{ +	/* Reference count is bounded by the number of total processes. */ +	refcount_inc(&filter->usage); +} +  /* get_seccomp_filter - increments the reference count of the filter on @tsk */  void get_seccomp_filter(struct task_struct *tsk)  {  	struct seccomp_filter *orig = tsk->seccomp.filter;  	if (!orig)  		return; -	/* Reference count is bounded by the number of total processes. */ -	refcount_inc(&orig->usage); +	__get_seccomp_filter(orig);  }  static inline void seccomp_filter_free(struct seccomp_filter *filter) @@ -476,10 +497,8 @@ static inline void seccomp_filter_free(struct seccomp_filter *filter)  	}  } -/* put_seccomp_filter - decrements the ref count of tsk->seccomp.filter */ -void put_seccomp_filter(struct task_struct *tsk) +static void __put_seccomp_filter(struct seccomp_filter *orig)  { -	struct seccomp_filter *orig = tsk->seccomp.filter;  	/* Clean up single-reference branches iteratively. */  	while (orig && refcount_dec_and_test(&orig->usage)) {  		struct seccomp_filter *freeme = orig; @@ -488,6 +507,12 @@ void put_seccomp_filter(struct task_struct *tsk)  	}  } +/* put_seccomp_filter - decrements the ref count of tsk->seccomp.filter */ +void put_seccomp_filter(struct task_struct *tsk) +{ +	__put_seccomp_filter(tsk->seccomp.filter); +} +  static void seccomp_init_siginfo(siginfo_t *info, int syscall, int reason)  {  	memset(info, 0, sizeof(*info)); @@ -514,6 +539,65 @@ static void seccomp_send_sigsys(int syscall, int reason)  }  #endif	/* CONFIG_SECCOMP_FILTER */ +/* For use with seccomp_actions_logged */ +#define SECCOMP_LOG_KILL_PROCESS	(1 << 0) +#define SECCOMP_LOG_KILL_THREAD		(1 << 1) +#define SECCOMP_LOG_TRAP		(1 << 2) +#define SECCOMP_LOG_ERRNO		(1 << 3) +#define SECCOMP_LOG_TRACE		(1 << 4) +#define SECCOMP_LOG_LOG			(1 << 5) +#define SECCOMP_LOG_ALLOW		(1 << 6) + +static u32 seccomp_actions_logged = SECCOMP_LOG_KILL_PROCESS | +				    SECCOMP_LOG_KILL_THREAD  | +				    SECCOMP_LOG_TRAP  | +				    SECCOMP_LOG_ERRNO | +				    SECCOMP_LOG_TRACE | +				    SECCOMP_LOG_LOG; + +static inline void seccomp_log(unsigned long syscall, long signr, u32 action, +			       bool requested) +{ +	bool log = false; + +	switch (action) { +	case SECCOMP_RET_ALLOW: +		break; +	case SECCOMP_RET_TRAP: +		log = requested && seccomp_actions_logged & SECCOMP_LOG_TRAP; +		break; +	case SECCOMP_RET_ERRNO: +		log = requested && seccomp_actions_logged & SECCOMP_LOG_ERRNO; +		break; +	case SECCOMP_RET_TRACE: +		log = requested && seccomp_actions_logged & SECCOMP_LOG_TRACE; +		break; +	case SECCOMP_RET_LOG: +		log = seccomp_actions_logged & SECCOMP_LOG_LOG; +		break; +	case SECCOMP_RET_KILL_THREAD: +		log = seccomp_actions_logged & SECCOMP_LOG_KILL_THREAD; +		break; +	case SECCOMP_RET_KILL_PROCESS: +	default: +		log = seccomp_actions_logged & SECCOMP_LOG_KILL_PROCESS; +	} + +	/* +	 * Force an audit message to be emitted when the action is RET_KILL_*, +	 * RET_LOG, or the FILTER_FLAG_LOG bit was set and the action is +	 * allowed to be logged by the admin. +	 */ +	if (log) +		return __audit_seccomp(syscall, signr, action); + +	/* +	 * Let the audit subsystem decide if the action should be audited based +	 * on whether the current task itself is being audited. +	 */ +	return audit_seccomp(syscall, signr, action); +} +  /*   * Secure computing mode 1 allows only read/write/exit/sigreturn.   * To be fully secure this must be combined with rlimit @@ -539,7 +623,7 @@ static void __secure_computing_strict(int this_syscall)  #ifdef SECCOMP_DEBUG  	dump_stack();  #endif -	audit_seccomp(this_syscall, SIGKILL, SECCOMP_RET_KILL); +	seccomp_log(this_syscall, SIGKILL, SECCOMP_RET_KILL_THREAD, true);  	do_exit(SIGKILL);  } @@ -566,6 +650,7 @@ static int __seccomp_filter(int this_syscall, const struct seccomp_data *sd,  			    const bool recheck_after_trace)  {  	u32 filter_ret, action; +	struct seccomp_filter *match = NULL;  	int data;  	/* @@ -574,9 +659,9 @@ static int __seccomp_filter(int this_syscall, const struct seccomp_data *sd,  	 */  	rmb(); -	filter_ret = seccomp_run_filters(sd); +	filter_ret = seccomp_run_filters(sd, &match);  	data = filter_ret & SECCOMP_RET_DATA; -	action = filter_ret & SECCOMP_RET_ACTION; +	action = filter_ret & SECCOMP_RET_ACTION_FULL;  	switch (action) {  	case SECCOMP_RET_ERRNO: @@ -637,14 +722,25 @@ static int __seccomp_filter(int this_syscall, const struct seccomp_data *sd,  		return 0; +	case SECCOMP_RET_LOG: +		seccomp_log(this_syscall, 0, action, true); +		return 0; +  	case SECCOMP_RET_ALLOW: +		/* +		 * Note that the "match" filter will always be NULL for +		 * this action since SECCOMP_RET_ALLOW is the starting +		 * state in seccomp_run_filters(). +		 */  		return 0; -	case SECCOMP_RET_KILL: +	case SECCOMP_RET_KILL_THREAD: +	case SECCOMP_RET_KILL_PROCESS:  	default: -		audit_seccomp(this_syscall, SIGSYS, action); +		seccomp_log(this_syscall, SIGSYS, action, true);  		/* Dump core only if this is the last remaining thread. */ -		if (get_nr_threads(current) == 1) { +		if (action == SECCOMP_RET_KILL_PROCESS || +		    get_nr_threads(current) == 1) {  			siginfo_t info;  			/* Show the original registers in the dump. */ @@ -653,13 +749,16 @@ static int __seccomp_filter(int this_syscall, const struct seccomp_data *sd,  			seccomp_init_siginfo(&info, this_syscall, data);  			do_coredump(&info);  		} -		do_exit(SIGSYS); +		if (action == SECCOMP_RET_KILL_PROCESS) +			do_group_exit(SIGSYS); +		else +			do_exit(SIGSYS);  	}  	unreachable();  skip: -	audit_seccomp(this_syscall, 0, action); +	seccomp_log(this_syscall, 0, action, match ? match->log : false);  	return -1;  }  #else @@ -794,6 +893,29 @@ static inline long seccomp_set_mode_filter(unsigned int flags,  }  #endif +static long seccomp_get_action_avail(const char __user *uaction) +{ +	u32 action; + +	if (copy_from_user(&action, uaction, sizeof(action))) +		return -EFAULT; + +	switch (action) { +	case SECCOMP_RET_KILL_PROCESS: +	case SECCOMP_RET_KILL_THREAD: +	case SECCOMP_RET_TRAP: +	case SECCOMP_RET_ERRNO: +	case SECCOMP_RET_TRACE: +	case SECCOMP_RET_LOG: +	case SECCOMP_RET_ALLOW: +		break; +	default: +		return -EOPNOTSUPP; +	} + +	return 0; +} +  /* Common entry point for both prctl and syscall. */  static long do_seccomp(unsigned int op, unsigned int flags,  		       const char __user *uargs) @@ -805,6 +927,11 @@ 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); +	case SECCOMP_GET_ACTION_AVAIL: +		if (flags != 0) +			return -EINVAL; + +		return seccomp_get_action_avail(uargs);  	default:  		return -EINVAL;  	} @@ -908,13 +1035,13 @@ long seccomp_get_filter(struct task_struct *task, unsigned long filter_off,  	if (!data)  		goto out; -	get_seccomp_filter(task); +	__get_seccomp_filter(filter);  	spin_unlock_irq(&task->sighand->siglock);  	if (copy_to_user(data, fprog->filter, bpf_classic_proglen(fprog)))  		ret = -EFAULT; -	put_seccomp_filter(task); +	__put_seccomp_filter(filter);  	return ret;  out: @@ -922,3 +1049,185 @@ out:  	return ret;  }  #endif + +#ifdef CONFIG_SYSCTL + +/* Human readable action names for friendly sysctl interaction */ +#define SECCOMP_RET_KILL_PROCESS_NAME	"kill_process" +#define SECCOMP_RET_KILL_THREAD_NAME	"kill_thread" +#define SECCOMP_RET_TRAP_NAME		"trap" +#define SECCOMP_RET_ERRNO_NAME		"errno" +#define SECCOMP_RET_TRACE_NAME		"trace" +#define SECCOMP_RET_LOG_NAME		"log" +#define SECCOMP_RET_ALLOW_NAME		"allow" + +static const char seccomp_actions_avail[] = +				SECCOMP_RET_KILL_PROCESS_NAME	" " +				SECCOMP_RET_KILL_THREAD_NAME	" " +				SECCOMP_RET_TRAP_NAME		" " +				SECCOMP_RET_ERRNO_NAME		" " +				SECCOMP_RET_TRACE_NAME		" " +				SECCOMP_RET_LOG_NAME		" " +				SECCOMP_RET_ALLOW_NAME; + +struct seccomp_log_name { +	u32		log; +	const char	*name; +}; + +static const struct seccomp_log_name seccomp_log_names[] = { +	{ SECCOMP_LOG_KILL_PROCESS, SECCOMP_RET_KILL_PROCESS_NAME }, +	{ SECCOMP_LOG_KILL_THREAD, SECCOMP_RET_KILL_THREAD_NAME }, +	{ SECCOMP_LOG_TRAP, SECCOMP_RET_TRAP_NAME }, +	{ SECCOMP_LOG_ERRNO, SECCOMP_RET_ERRNO_NAME }, +	{ SECCOMP_LOG_TRACE, SECCOMP_RET_TRACE_NAME }, +	{ SECCOMP_LOG_LOG, SECCOMP_RET_LOG_NAME }, +	{ SECCOMP_LOG_ALLOW, SECCOMP_RET_ALLOW_NAME }, +	{ } +}; + +static bool seccomp_names_from_actions_logged(char *names, size_t size, +					      u32 actions_logged) +{ +	const struct seccomp_log_name *cur; +	bool append_space = false; + +	for (cur = seccomp_log_names; cur->name && size; cur++) { +		ssize_t ret; + +		if (!(actions_logged & cur->log)) +			continue; + +		if (append_space) { +			ret = strscpy(names, " ", size); +			if (ret < 0) +				return false; + +			names += ret; +			size -= ret; +		} else +			append_space = true; + +		ret = strscpy(names, cur->name, size); +		if (ret < 0) +			return false; + +		names += ret; +		size -= ret; +	} + +	return true; +} + +static bool seccomp_action_logged_from_name(u32 *action_logged, +					    const char *name) +{ +	const struct seccomp_log_name *cur; + +	for (cur = seccomp_log_names; cur->name; cur++) { +		if (!strcmp(cur->name, name)) { +			*action_logged = cur->log; +			return true; +		} +	} + +	return false; +} + +static bool seccomp_actions_logged_from_names(u32 *actions_logged, char *names) +{ +	char *name; + +	*actions_logged = 0; +	while ((name = strsep(&names, " ")) && *name) { +		u32 action_logged = 0; + +		if (!seccomp_action_logged_from_name(&action_logged, name)) +			return false; + +		*actions_logged |= action_logged; +	} + +	return true; +} + +static int seccomp_actions_logged_handler(struct ctl_table *ro_table, int write, +					  void __user *buffer, size_t *lenp, +					  loff_t *ppos) +{ +	char names[sizeof(seccomp_actions_avail)]; +	struct ctl_table table; +	int ret; + +	if (write && !capable(CAP_SYS_ADMIN)) +		return -EPERM; + +	memset(names, 0, sizeof(names)); + +	if (!write) { +		if (!seccomp_names_from_actions_logged(names, sizeof(names), +						       seccomp_actions_logged)) +			return -EINVAL; +	} + +	table = *ro_table; +	table.data = names; +	table.maxlen = sizeof(names); +	ret = proc_dostring(&table, write, buffer, lenp, ppos); +	if (ret) +		return ret; + +	if (write) { +		u32 actions_logged; + +		if (!seccomp_actions_logged_from_names(&actions_logged, +						       table.data)) +			return -EINVAL; + +		if (actions_logged & SECCOMP_LOG_ALLOW) +			return -EINVAL; + +		seccomp_actions_logged = actions_logged; +	} + +	return 0; +} + +static struct ctl_path seccomp_sysctl_path[] = { +	{ .procname = "kernel", }, +	{ .procname = "seccomp", }, +	{ } +}; + +static struct ctl_table seccomp_sysctl_table[] = { +	{ +		.procname	= "actions_avail", +		.data		= (void *) &seccomp_actions_avail, +		.maxlen		= sizeof(seccomp_actions_avail), +		.mode		= 0444, +		.proc_handler	= proc_dostring, +	}, +	{ +		.procname	= "actions_logged", +		.mode		= 0644, +		.proc_handler	= seccomp_actions_logged_handler, +	}, +	{ } +}; + +static int __init seccomp_sysctl_init(void) +{ +	struct ctl_table_header *hdr; + +	hdr = register_sysctl_paths(seccomp_sysctl_path, seccomp_sysctl_table); +	if (!hdr) +		pr_warn("seccomp: sysctl registration failed\n"); +	else +		kmemleak_not_leak(hdr); + +	return 0; +} + +device_initcall(seccomp_sysctl_init) + +#endif /* CONFIG_SYSCTL */ |