forked from mirrors/linux
		
	bpf: Add dynptr data slices
This patch adds a new helper function void *bpf_dynptr_data(struct bpf_dynptr *ptr, u32 offset, u32 len); which returns a pointer to the underlying data of a dynptr. *len* must be a statically known value. The bpf program may access the returned data slice as a normal buffer (eg can do direct reads and writes), since the verifier associates the length with the returned pointer, and enforces that no out of bounds accesses occur. Signed-off-by: Joanne Koong <joannelkoong@gmail.com> Signed-off-by: Andrii Nakryiko <andrii@kernel.org> Acked-by: Yonghong Song <yhs@fb.com> Link: https://lore.kernel.org/bpf/20220523210712.3641569-6-joannelkoong@gmail.com
This commit is contained in:
		
							parent
							
								
									13bbbfbea7
								
							
						
					
					
						commit
						34d4ef5775
					
				
					 5 changed files with 76 additions and 0 deletions
				
			
		|  | @ -488,6 +488,7 @@ enum bpf_return_type { | ||||||
| 	RET_PTR_TO_TCP_SOCK_OR_NULL	= PTR_MAYBE_NULL | RET_PTR_TO_TCP_SOCK, | 	RET_PTR_TO_TCP_SOCK_OR_NULL	= PTR_MAYBE_NULL | RET_PTR_TO_TCP_SOCK, | ||||||
| 	RET_PTR_TO_SOCK_COMMON_OR_NULL	= PTR_MAYBE_NULL | RET_PTR_TO_SOCK_COMMON, | 	RET_PTR_TO_SOCK_COMMON_OR_NULL	= PTR_MAYBE_NULL | RET_PTR_TO_SOCK_COMMON, | ||||||
| 	RET_PTR_TO_ALLOC_MEM_OR_NULL	= PTR_MAYBE_NULL | MEM_ALLOC | RET_PTR_TO_ALLOC_MEM, | 	RET_PTR_TO_ALLOC_MEM_OR_NULL	= PTR_MAYBE_NULL | MEM_ALLOC | RET_PTR_TO_ALLOC_MEM, | ||||||
|  | 	RET_PTR_TO_DYNPTR_MEM_OR_NULL	= PTR_MAYBE_NULL | RET_PTR_TO_ALLOC_MEM, | ||||||
| 	RET_PTR_TO_BTF_ID_OR_NULL	= PTR_MAYBE_NULL | RET_PTR_TO_BTF_ID, | 	RET_PTR_TO_BTF_ID_OR_NULL	= PTR_MAYBE_NULL | RET_PTR_TO_BTF_ID, | ||||||
| 
 | 
 | ||||||
| 	/* This must be the last entry. Its purpose is to ensure the enum is
 | 	/* This must be the last entry. Its purpose is to ensure the enum is
 | ||||||
|  |  | ||||||
|  | @ -5238,6 +5238,17 @@ union bpf_attr { | ||||||
|  *		0 on success, -E2BIG if *offset* + *len* exceeds the length |  *		0 on success, -E2BIG if *offset* + *len* exceeds the length | ||||||
|  *		of *dst*'s data, -EINVAL if *dst* is an invalid dynptr or if *dst* |  *		of *dst*'s data, -EINVAL if *dst* is an invalid dynptr or if *dst* | ||||||
|  *		is a read-only dynptr. |  *		is a read-only dynptr. | ||||||
|  |  * | ||||||
|  |  * void *bpf_dynptr_data(struct bpf_dynptr *ptr, u32 offset, u32 len) | ||||||
|  |  *	Description | ||||||
|  |  *		Get a pointer to the underlying dynptr data. | ||||||
|  |  * | ||||||
|  |  *		*len* must be a statically known value. The returned data slice | ||||||
|  |  *		is invalidated whenever the dynptr is invalidated. | ||||||
|  |  *	Return | ||||||
|  |  *		Pointer to the underlying dynptr data, NULL if the dynptr is | ||||||
|  |  *		read-only, if the dynptr is invalid, or if the offset and length | ||||||
|  |  *		is out of bounds. | ||||||
|  */ |  */ | ||||||
| #define __BPF_FUNC_MAPPER(FN)		\ | #define __BPF_FUNC_MAPPER(FN)		\ | ||||||
| 	FN(unspec),			\ | 	FN(unspec),			\ | ||||||
|  | @ -5443,6 +5454,7 @@ union bpf_attr { | ||||||
| 	FN(ringbuf_discard_dynptr),	\ | 	FN(ringbuf_discard_dynptr),	\ | ||||||
| 	FN(dynptr_read),		\ | 	FN(dynptr_read),		\ | ||||||
| 	FN(dynptr_write),		\ | 	FN(dynptr_write),		\ | ||||||
|  | 	FN(dynptr_data),		\ | ||||||
| 	/* */ | 	/* */ | ||||||
| 
 | 
 | ||||||
| /* integer value in 'imm' field of BPF_CALL instruction selects which helper
 | /* integer value in 'imm' field of BPF_CALL instruction selects which helper
 | ||||||
|  |  | ||||||
|  | @ -1549,6 +1549,32 @@ const struct bpf_func_proto bpf_dynptr_write_proto = { | ||||||
| 	.arg4_type	= ARG_CONST_SIZE_OR_ZERO, | 	.arg4_type	= ARG_CONST_SIZE_OR_ZERO, | ||||||
| }; | }; | ||||||
| 
 | 
 | ||||||
|  | BPF_CALL_3(bpf_dynptr_data, struct bpf_dynptr_kern *, ptr, u32, offset, u32, len) | ||||||
|  | { | ||||||
|  | 	int err; | ||||||
|  | 
 | ||||||
|  | 	if (!ptr->data) | ||||||
|  | 		return 0; | ||||||
|  | 
 | ||||||
|  | 	err = bpf_dynptr_check_off_len(ptr, offset, len); | ||||||
|  | 	if (err) | ||||||
|  | 		return 0; | ||||||
|  | 
 | ||||||
|  | 	if (bpf_dynptr_is_rdonly(ptr)) | ||||||
|  | 		return 0; | ||||||
|  | 
 | ||||||
|  | 	return (unsigned long)(ptr->data + ptr->offset + offset); | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | const struct bpf_func_proto bpf_dynptr_data_proto = { | ||||||
|  | 	.func		= bpf_dynptr_data, | ||||||
|  | 	.gpl_only	= false, | ||||||
|  | 	.ret_type	= RET_PTR_TO_DYNPTR_MEM_OR_NULL, | ||||||
|  | 	.arg1_type	= ARG_PTR_TO_DYNPTR, | ||||||
|  | 	.arg2_type	= ARG_ANYTHING, | ||||||
|  | 	.arg3_type	= ARG_CONST_ALLOC_SIZE_OR_ZERO, | ||||||
|  | }; | ||||||
|  | 
 | ||||||
| const struct bpf_func_proto bpf_get_current_task_proto __weak; | const struct bpf_func_proto bpf_get_current_task_proto __weak; | ||||||
| const struct bpf_func_proto bpf_get_current_task_btf_proto __weak; | const struct bpf_func_proto bpf_get_current_task_btf_proto __weak; | ||||||
| const struct bpf_func_proto bpf_probe_read_user_proto __weak; | const struct bpf_func_proto bpf_probe_read_user_proto __weak; | ||||||
|  | @ -1615,6 +1641,8 @@ bpf_base_func_proto(enum bpf_func_id func_id) | ||||||
| 		return &bpf_dynptr_read_proto; | 		return &bpf_dynptr_read_proto; | ||||||
| 	case BPF_FUNC_dynptr_write: | 	case BPF_FUNC_dynptr_write: | ||||||
| 		return &bpf_dynptr_write_proto; | 		return &bpf_dynptr_write_proto; | ||||||
|  | 	case BPF_FUNC_dynptr_data: | ||||||
|  | 		return &bpf_dynptr_data_proto; | ||||||
| 	default: | 	default: | ||||||
| 		break; | 		break; | ||||||
| 	} | 	} | ||||||
|  |  | ||||||
|  | @ -5832,6 +5832,14 @@ int check_func_arg_reg_off(struct bpf_verifier_env *env, | ||||||
| 	return __check_ptr_off_reg(env, reg, regno, fixed_off_ok); | 	return __check_ptr_off_reg(env, reg, regno, fixed_off_ok); | ||||||
| } | } | ||||||
| 
 | 
 | ||||||
|  | static u32 stack_slot_get_id(struct bpf_verifier_env *env, struct bpf_reg_state *reg) | ||||||
|  | { | ||||||
|  | 	struct bpf_func_state *state = func(env, reg); | ||||||
|  | 	int spi = get_spi(reg->off); | ||||||
|  | 
 | ||||||
|  | 	return state->stack[spi].spilled_ptr.id; | ||||||
|  | } | ||||||
|  | 
 | ||||||
| static int check_func_arg(struct bpf_verifier_env *env, u32 arg, | static int check_func_arg(struct bpf_verifier_env *env, u32 arg, | ||||||
| 			  struct bpf_call_arg_meta *meta, | 			  struct bpf_call_arg_meta *meta, | ||||||
| 			  const struct bpf_func_proto *fn) | 			  const struct bpf_func_proto *fn) | ||||||
|  | @ -7384,6 +7392,21 @@ static int check_helper_call(struct bpf_verifier_env *env, struct bpf_insn *insn | ||||||
| 		regs[BPF_REG_0].id = id; | 		regs[BPF_REG_0].id = id; | ||||||
| 		/* For release_reference() */ | 		/* For release_reference() */ | ||||||
| 		regs[BPF_REG_0].ref_obj_id = id; | 		regs[BPF_REG_0].ref_obj_id = id; | ||||||
|  | 	} else if (func_id == BPF_FUNC_dynptr_data) { | ||||||
|  | 		int dynptr_id = 0, i; | ||||||
|  | 
 | ||||||
|  | 		/* Find the id of the dynptr we're acquiring a reference to */ | ||||||
|  | 		for (i = 0; i < MAX_BPF_FUNC_REG_ARGS; i++) { | ||||||
|  | 			if (arg_type_is_dynptr(fn->arg_type[i])) { | ||||||
|  | 				if (dynptr_id) { | ||||||
|  | 					verbose(env, "verifier internal error: multiple dynptr args in func\n"); | ||||||
|  | 					return -EFAULT; | ||||||
|  | 				} | ||||||
|  | 				dynptr_id = stack_slot_get_id(env, ®s[BPF_REG_1 + i]); | ||||||
|  | 			} | ||||||
|  | 		} | ||||||
|  | 		/* For release_reference() */ | ||||||
|  | 		regs[BPF_REG_0].ref_obj_id = dynptr_id; | ||||||
| 	} | 	} | ||||||
| 
 | 
 | ||||||
| 	do_refine_retval_range(regs, fn->ret_type, func_id, &meta); | 	do_refine_retval_range(regs, fn->ret_type, func_id, &meta); | ||||||
|  |  | ||||||
|  | @ -5238,6 +5238,17 @@ union bpf_attr { | ||||||
|  *		0 on success, -E2BIG if *offset* + *len* exceeds the length |  *		0 on success, -E2BIG if *offset* + *len* exceeds the length | ||||||
|  *		of *dst*'s data, -EINVAL if *dst* is an invalid dynptr or if *dst* |  *		of *dst*'s data, -EINVAL if *dst* is an invalid dynptr or if *dst* | ||||||
|  *		is a read-only dynptr. |  *		is a read-only dynptr. | ||||||
|  |  * | ||||||
|  |  * void *bpf_dynptr_data(struct bpf_dynptr *ptr, u32 offset, u32 len) | ||||||
|  |  *	Description | ||||||
|  |  *		Get a pointer to the underlying dynptr data. | ||||||
|  |  * | ||||||
|  |  *		*len* must be a statically known value. The returned data slice | ||||||
|  |  *		is invalidated whenever the dynptr is invalidated. | ||||||
|  |  *	Return | ||||||
|  |  *		Pointer to the underlying dynptr data, NULL if the dynptr is | ||||||
|  |  *		read-only, if the dynptr is invalid, or if the offset and length | ||||||
|  |  *		is out of bounds. | ||||||
|  */ |  */ | ||||||
| #define __BPF_FUNC_MAPPER(FN)		\ | #define __BPF_FUNC_MAPPER(FN)		\ | ||||||
| 	FN(unspec),			\ | 	FN(unspec),			\ | ||||||
|  | @ -5443,6 +5454,7 @@ union bpf_attr { | ||||||
| 	FN(ringbuf_discard_dynptr),	\ | 	FN(ringbuf_discard_dynptr),	\ | ||||||
| 	FN(dynptr_read),		\ | 	FN(dynptr_read),		\ | ||||||
| 	FN(dynptr_write),		\ | 	FN(dynptr_write),		\ | ||||||
|  | 	FN(dynptr_data),		\ | ||||||
| 	/* */ | 	/* */ | ||||||
| 
 | 
 | ||||||
| /* integer value in 'imm' field of BPF_CALL instruction selects which helper
 | /* integer value in 'imm' field of BPF_CALL instruction selects which helper
 | ||||||
|  |  | ||||||
		Loading…
	
		Reference in a new issue
	
	 Joanne Koong
						Joanne Koong