Commit 34d4ef57 authored by Joanne Koong's avatar Joanne Koong Committed by Andrii Nakryiko
Browse files

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: default avatarJoanne Koong <joannelkoong@gmail.com>
Signed-off-by: default avatarAndrii Nakryiko <andrii@kernel.org>
Acked-by: default avatarYonghong Song <yhs@fb.com>
Link: https://lore.kernel.org/bpf/20220523210712.3641569-6-joannelkoong@gmail.com
parent 13bbbfbe
Loading
Loading
Loading
Loading
+1 −0
Original line number Diff line number Diff line
@@ -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_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_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,

	/* This must be the last entry. Its purpose is to ensure the enum is
+12 −0
Original line number Diff line number Diff line
@@ -5238,6 +5238,17 @@ union bpf_attr {
 *		0 on success, -E2BIG if *offset* + *len* exceeds the length
 *		of *dst*'s data, -EINVAL if *dst* is an invalid dynptr or if *dst*
 *		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)		\
	FN(unspec),			\
@@ -5443,6 +5454,7 @@ union bpf_attr {
	FN(ringbuf_discard_dynptr),	\
	FN(dynptr_read),		\
	FN(dynptr_write),		\
	FN(dynptr_data),		\
	/* */

/* integer value in 'imm' field of BPF_CALL instruction selects which helper
+28 −0
Original line number Diff line number Diff line
@@ -1549,6 +1549,32 @@ const struct bpf_func_proto bpf_dynptr_write_proto = {
	.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_btf_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;
	case BPF_FUNC_dynptr_write:
		return &bpf_dynptr_write_proto;
	case BPF_FUNC_dynptr_data:
		return &bpf_dynptr_data_proto;
	default:
		break;
	}
+23 −0
Original line number Diff line number Diff line
@@ -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);
}

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,
			  struct bpf_call_arg_meta *meta,
			  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;
		/* For release_reference() */
		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, &regs[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);
+12 −0
Original line number Diff line number Diff line
@@ -5238,6 +5238,17 @@ union bpf_attr {
 *		0 on success, -E2BIG if *offset* + *len* exceeds the length
 *		of *dst*'s data, -EINVAL if *dst* is an invalid dynptr or if *dst*
 *		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)		\
	FN(unspec),			\
@@ -5443,6 +5454,7 @@ union bpf_attr {
	FN(ringbuf_discard_dynptr),	\
	FN(dynptr_read),		\
	FN(dynptr_write),		\
	FN(dynptr_data),		\
	/* */

/* integer value in 'imm' field of BPF_CALL instruction selects which helper