diff options
Diffstat (limited to 'kernel/seccomp.c')
| -rw-r--r-- | kernel/seccomp.c | 224 | 
1 files changed, 115 insertions, 109 deletions
diff --git a/kernel/seccomp.c b/kernel/seccomp.c index b7a10048a32..301bbc24739 100644 --- a/kernel/seccomp.c +++ b/kernel/seccomp.c @@ -39,7 +39,7 @@   *         is only needed for handling filters shared across tasks.   * @prev: points to a previously installed, or inherited, filter   * @len: the number of instructions in the program - * @insns: the BPF program instructions to evaluate + * @insnsi: the BPF program instructions to evaluate   *   * seccomp_filter objects are organized in a tree linked via the @prev   * pointer.  For any task, it appears to be a singly-linked list starting @@ -54,61 +54,32 @@  struct seccomp_filter {  	atomic_t usage;  	struct seccomp_filter *prev; -	unsigned short len;  /* Instruction count */ -	struct sock_filter insns[]; +	struct sk_filter *prog;  };  /* Limit any path through the tree to 256KB worth of instructions. */  #define MAX_INSNS_PER_PATH ((1 << 18) / sizeof(struct sock_filter)) -/** - * get_u32 - returns a u32 offset into data - * @data: a unsigned 64 bit value - * @index: 0 or 1 to return the first or second 32-bits - * - * This inline exists to hide the length of unsigned long.  If a 32-bit - * unsigned long is passed in, it will be extended and the top 32-bits will be - * 0. If it is a 64-bit unsigned long, then whatever data is resident will be - * properly returned. - * +/*   * Endianness is explicitly ignored and left for BPF program authors to manage   * as per the specific architecture.   */ -static inline u32 get_u32(u64 data, int index) +static void populate_seccomp_data(struct seccomp_data *sd)  { -	return ((u32 *)&data)[index]; -} - -/* Helper for bpf_load below. */ -#define BPF_DATA(_name) offsetof(struct seccomp_data, _name) -/** - * bpf_load: checks and returns a pointer to the requested offset - * @off: offset into struct seccomp_data to load from - * - * Returns the requested 32-bits of data. - * seccomp_check_filter() should assure that @off is 32-bit aligned - * and not out of bounds.  Failure to do so is a BUG. - */ -u32 seccomp_bpf_load(int off) -{ -	struct pt_regs *regs = task_pt_regs(current); -	if (off == BPF_DATA(nr)) -		return syscall_get_nr(current, regs); -	if (off == BPF_DATA(arch)) -		return syscall_get_arch(current, regs); -	if (off >= BPF_DATA(args[0]) && off < BPF_DATA(args[6])) { -		unsigned long value; -		int arg = (off - BPF_DATA(args[0])) / sizeof(u64); -		int index = !!(off % sizeof(u64)); -		syscall_get_arguments(current, regs, arg, 1, &value); -		return get_u32(value, index); -	} -	if (off == BPF_DATA(instruction_pointer)) -		return get_u32(KSTK_EIP(current), 0); -	if (off == BPF_DATA(instruction_pointer) + sizeof(u32)) -		return get_u32(KSTK_EIP(current), 1); -	/* seccomp_check_filter should make this impossible. */ -	BUG(); +	struct task_struct *task = current; +	struct pt_regs *regs = task_pt_regs(task); +	unsigned long args[6]; + +	sd->nr = syscall_get_nr(task, regs); +	sd->arch = syscall_get_arch(); +	syscall_get_arguments(task, regs, 0, 6, args); +	sd->args[0] = args[0]; +	sd->args[1] = args[1]; +	sd->args[2] = args[2]; +	sd->args[3] = args[3]; +	sd->args[4] = args[4]; +	sd->args[5] = args[5]; +	sd->instruction_pointer = KSTK_EIP(task);  }  /** @@ -132,59 +103,59 @@ static int seccomp_check_filter(struct sock_filter *filter, unsigned int flen)  		u32 k = ftest->k;  		switch (code) { -		case BPF_S_LD_W_ABS: -			ftest->code = BPF_S_ANC_SECCOMP_LD_W; +		case BPF_LD | BPF_W | BPF_ABS: +			ftest->code = BPF_LDX | BPF_W | BPF_ABS;  			/* 32-bit aligned and not out of bounds. */  			if (k >= sizeof(struct seccomp_data) || k & 3)  				return -EINVAL;  			continue; -		case BPF_S_LD_W_LEN: -			ftest->code = BPF_S_LD_IMM; +		case BPF_LD | BPF_W | BPF_LEN: +			ftest->code = BPF_LD | BPF_IMM;  			ftest->k = sizeof(struct seccomp_data);  			continue; -		case BPF_S_LDX_W_LEN: -			ftest->code = BPF_S_LDX_IMM; +		case BPF_LDX | BPF_W | BPF_LEN: +			ftest->code = BPF_LDX | BPF_IMM;  			ftest->k = sizeof(struct seccomp_data);  			continue;  		/* Explicitly include allowed calls. */ -		case BPF_S_RET_K: -		case BPF_S_RET_A: -		case BPF_S_ALU_ADD_K: -		case BPF_S_ALU_ADD_X: -		case BPF_S_ALU_SUB_K: -		case BPF_S_ALU_SUB_X: -		case BPF_S_ALU_MUL_K: -		case BPF_S_ALU_MUL_X: -		case BPF_S_ALU_DIV_X: -		case BPF_S_ALU_AND_K: -		case BPF_S_ALU_AND_X: -		case BPF_S_ALU_OR_K: -		case BPF_S_ALU_OR_X: -		case BPF_S_ALU_XOR_K: -		case BPF_S_ALU_XOR_X: -		case BPF_S_ALU_LSH_K: -		case BPF_S_ALU_LSH_X: -		case BPF_S_ALU_RSH_K: -		case BPF_S_ALU_RSH_X: -		case BPF_S_ALU_NEG: -		case BPF_S_LD_IMM: -		case BPF_S_LDX_IMM: -		case BPF_S_MISC_TAX: -		case BPF_S_MISC_TXA: -		case BPF_S_ALU_DIV_K: -		case BPF_S_LD_MEM: -		case BPF_S_LDX_MEM: -		case BPF_S_ST: -		case BPF_S_STX: -		case BPF_S_JMP_JA: -		case BPF_S_JMP_JEQ_K: -		case BPF_S_JMP_JEQ_X: -		case BPF_S_JMP_JGE_K: -		case BPF_S_JMP_JGE_X: -		case BPF_S_JMP_JGT_K: -		case BPF_S_JMP_JGT_X: -		case BPF_S_JMP_JSET_K: -		case BPF_S_JMP_JSET_X: +		case BPF_RET | BPF_K: +		case BPF_RET | BPF_A: +		case BPF_ALU | BPF_ADD | BPF_K: +		case BPF_ALU | BPF_ADD | BPF_X: +		case BPF_ALU | BPF_SUB | BPF_K: +		case BPF_ALU | BPF_SUB | BPF_X: +		case BPF_ALU | BPF_MUL | BPF_K: +		case BPF_ALU | BPF_MUL | BPF_X: +		case BPF_ALU | BPF_DIV | BPF_K: +		case BPF_ALU | BPF_DIV | BPF_X: +		case BPF_ALU | BPF_AND | BPF_K: +		case BPF_ALU | BPF_AND | BPF_X: +		case BPF_ALU | BPF_OR | BPF_K: +		case BPF_ALU | BPF_OR | BPF_X: +		case BPF_ALU | BPF_XOR | BPF_K: +		case BPF_ALU | BPF_XOR | BPF_X: +		case BPF_ALU | BPF_LSH | BPF_K: +		case BPF_ALU | BPF_LSH | BPF_X: +		case BPF_ALU | BPF_RSH | BPF_K: +		case BPF_ALU | BPF_RSH | BPF_X: +		case BPF_ALU | BPF_NEG: +		case BPF_LD | BPF_IMM: +		case BPF_LDX | BPF_IMM: +		case BPF_MISC | BPF_TAX: +		case BPF_MISC | BPF_TXA: +		case BPF_LD | BPF_MEM: +		case BPF_LDX | BPF_MEM: +		case BPF_ST: +		case BPF_STX: +		case BPF_JMP | BPF_JA: +		case BPF_JMP | BPF_JEQ | BPF_K: +		case BPF_JMP | BPF_JEQ | BPF_X: +		case BPF_JMP | BPF_JGE | BPF_K: +		case BPF_JMP | BPF_JGE | BPF_X: +		case BPF_JMP | BPF_JGT | BPF_K: +		case BPF_JMP | BPF_JGT | BPF_X: +		case BPF_JMP | BPF_JSET | BPF_K: +		case BPF_JMP | BPF_JSET | BPF_X:  			continue;  		default:  			return -EINVAL; @@ -202,18 +173,22 @@ static int seccomp_check_filter(struct sock_filter *filter, unsigned int flen)  static u32 seccomp_run_filters(int syscall)  {  	struct seccomp_filter *f; +	struct seccomp_data sd;  	u32 ret = SECCOMP_RET_ALLOW;  	/* Ensure unexpected behavior doesn't result in failing open. */  	if (WARN_ON(current->seccomp.filter == NULL))  		return SECCOMP_RET_KILL; +	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 = current->seccomp.filter; f; f = f->prev) { -		u32 cur_ret = sk_run_filter(NULL, f->insns); +		u32 cur_ret = SK_RUN_FILTER(f->prog, (void *)&sd); +  		if ((cur_ret & SECCOMP_RET_ACTION) < (ret & SECCOMP_RET_ACTION))  			ret = cur_ret;  	} @@ -231,18 +206,20 @@ static long seccomp_attach_filter(struct sock_fprog *fprog)  	struct seccomp_filter *filter;  	unsigned long fp_size = fprog->len * sizeof(struct sock_filter);  	unsigned long total_insns = fprog->len; +	struct sock_filter *fp; +	int new_len;  	long ret;  	if (fprog->len == 0 || fprog->len > BPF_MAXINSNS)  		return -EINVAL;  	for (filter = current->seccomp.filter; filter; filter = filter->prev) -		total_insns += filter->len + 4;  /* include a 4 instr penalty */ +		total_insns += filter->prog->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 +	 * Installing a seccomp filter requires that the task has  	 * CAP_SYS_ADMIN in its namespace or be running with no_new_privs.  	 * This avoids scenarios where unprivileged tasks can affect the  	 * behavior of privileged children. @@ -252,28 +229,51 @@ static long seccomp_attach_filter(struct sock_fprog *fprog)  				     CAP_SYS_ADMIN) != 0)  		return -EACCES; -	/* Allocate a new seccomp_filter */ -	filter = kzalloc(sizeof(struct seccomp_filter) + fp_size, -			 GFP_KERNEL|__GFP_NOWARN); -	if (!filter) +	fp = kzalloc(fp_size, GFP_KERNEL|__GFP_NOWARN); +	if (!fp)  		return -ENOMEM; -	atomic_set(&filter->usage, 1); -	filter->len = fprog->len;  	/* Copy the instructions from fprog. */  	ret = -EFAULT; -	if (copy_from_user(filter->insns, fprog->filter, fp_size)) -		goto fail; +	if (copy_from_user(fp, fprog->filter, fp_size)) +		goto free_prog;  	/* Check and rewrite the fprog via the skb checker */ -	ret = sk_chk_filter(filter->insns, filter->len); +	ret = sk_chk_filter(fp, fprog->len);  	if (ret) -		goto fail; +		goto free_prog;  	/* Check and rewrite the fprog for seccomp use */ -	ret = seccomp_check_filter(filter->insns, filter->len); +	ret = seccomp_check_filter(fp, fprog->len); +	if (ret) +		goto free_prog; + +	/* Convert 'sock_filter' insns to 'sock_filter_int' insns */ +	ret = sk_convert_filter(fp, fprog->len, NULL, &new_len);  	if (ret) -		goto fail; +		goto free_prog; + +	/* Allocate a new seccomp_filter */ +	ret = -ENOMEM; +	filter = kzalloc(sizeof(struct seccomp_filter), +			 GFP_KERNEL|__GFP_NOWARN); +	if (!filter) +		goto free_prog; + +	filter->prog = kzalloc(sk_filter_size(new_len), +			       GFP_KERNEL|__GFP_NOWARN); +	if (!filter->prog) +		goto free_filter; + +	ret = sk_convert_filter(fp, fprog->len, filter->prog->insnsi, &new_len); +	if (ret) +		goto free_filter_prog; +	kfree(fp); + +	atomic_set(&filter->usage, 1); +	filter->prog->len = new_len; + +	sk_filter_select_runtime(filter->prog);  	/*  	 * If there is an existing filter, make it the prev and don't drop its @@ -282,8 +282,13 @@ static long seccomp_attach_filter(struct sock_fprog *fprog)  	filter->prev = current->seccomp.filter;  	current->seccomp.filter = filter;  	return 0; -fail: + +free_filter_prog: +	kfree(filter->prog); +free_filter:  	kfree(filter); +free_prog: +	kfree(fp);  	return ret;  } @@ -293,7 +298,7 @@ fail:   *   * Returns 0 on success and non-zero otherwise.   */ -long seccomp_attach_user_filter(char __user *user_filter) +static long seccomp_attach_user_filter(char __user *user_filter)  {  	struct sock_fprog fprog;  	long ret = -EFAULT; @@ -332,6 +337,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; +		sk_filter_free(freeme->prog);  		kfree(freeme);  	}  } @@ -351,7 +357,7 @@ static void seccomp_send_sigsys(int syscall, int reason)  	info.si_code = SYS_SECCOMP;  	info.si_call_addr = (void __user *)KSTK_EIP(current);  	info.si_errno = reason; -	info.si_arch = syscall_get_arch(current, task_pt_regs(current)); +	info.si_arch = syscall_get_arch();  	info.si_syscall = syscall;  	force_sig_info(SIGSYS, &info, current);  }  | 
