bpf: Add dynptr data slices
authorJoanne Koong <joannelkoong@gmail.com>
Mon, 23 May 2022 21:07:11 +0000 (14:07 -0700)
committerAndrii Nakryiko <andrii@kernel.org>
Mon, 23 May 2022 21:31:28 +0000 (14:31 -0700)
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
include/linux/bpf.h
include/uapi/linux/bpf.h
kernel/bpf/helpers.c
kernel/bpf/verifier.c
tools/include/uapi/linux/bpf.h

index c72321b..a7080c8 100644 (file)
@@ -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
index efe2505..f4009db 100644 (file)
@@ -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
index 8cef3fb..225806a 100644 (file)
@@ -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;
        }
index 8be1403..aedac2a 100644 (file)
@@ -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);
index efe2505..f4009db 100644 (file)
@@ -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