bpf: Introduce allocated objects support
authorKumar Kartikeya Dwivedi <memxor@gmail.com>
Fri, 18 Nov 2022 01:55:55 +0000 (07:25 +0530)
committerAlexei Starovoitov <ast@kernel.org>
Fri, 18 Nov 2022 03:11:32 +0000 (19:11 -0800)
Introduce support for representing pointers to objects allocated by the
BPF program, i.e. PTR_TO_BTF_ID that point to a type in program BTF.
This is indicated by the presence of MEM_ALLOC type flag in reg->type to
avoid having to check btf_is_kernel when trying to match argument types
in helpers.

Whenever walking such types, any pointers being walked will always yield
a SCALAR instead of pointer. In the future we might permit kptr inside
such allocated objects (either kernel or program allocated), and it will
then form a PTR_TO_BTF_ID of the respective type.

For now, such allocated objects will always be referenced in verifier
context, hence ref_obj_id == 0 for them is a bug. It is allowed to write
to such objects, as long fields that are special are not touched
(support for which will be added in subsequent patches). Note that once
such a pointer is marked PTR_UNTRUSTED, it is no longer allowed to write
to it.

No PROBE_MEM handling is therefore done for loads into this type unless
PTR_UNTRUSTED is part of the register type, since they can never be in
an undefined state, and their lifetime will always be valid.

Signed-off-by: Kumar Kartikeya Dwivedi <memxor@gmail.com>
Link: https://lore.kernel.org/r/20221118015614.2013203-6-memxor@gmail.com
Signed-off-by: Alexei Starovoitov <ast@kernel.org>
include/linux/bpf.h
kernel/bpf/btf.c
kernel/bpf/verifier.c

index e60a5c052473cd679e8c75502eaa54c4c6b589a7..7440c20c41923911bf8709aa05c94cf44909f4b4 100644 (file)
@@ -525,6 +525,11 @@ enum bpf_type_flag {
        /* Size is known at compile time. */
        MEM_FIXED_SIZE          = BIT(10 + BPF_BASE_TYPE_BITS),
 
+       /* MEM is of an allocated object of type in program BTF. This is used to
+        * tag PTR_TO_BTF_ID allocated using bpf_obj_new.
+        */
+       MEM_ALLOC               = BIT(11 + BPF_BASE_TYPE_BITS),
+
        __BPF_TYPE_FLAG_MAX,
        __BPF_TYPE_LAST_FLAG    = __BPF_TYPE_FLAG_MAX - 1,
 };
@@ -2792,4 +2797,10 @@ struct bpf_key {
        bool has_ref;
 };
 #endif /* CONFIG_KEYS */
+
+static inline bool type_is_alloc(u32 type)
+{
+       return type & MEM_ALLOC;
+}
+
 #endif /* _LINUX_BPF_H */
index 875355ff371816c65b15663fe5c1304957182d36..9a596f430558691848bdce1630c643c2dc78d7d0 100644 (file)
@@ -6034,6 +6034,11 @@ int btf_struct_access(struct bpf_verifier_log *log,
 
                switch (err) {
                case WALK_PTR:
+                       /* For local types, the destination register cannot
+                        * become a pointer again.
+                        */
+                       if (type_is_alloc(reg->type))
+                               return SCALAR_VALUE;
                        /* If we found the pointer or scalar on t+off,
                         * we're done.
                         */
index 0312d9ce292ffb4afbe7eb59bf052953bff42b1f..49e08c1c2c6193f89c97ce359d468318da6d3771 100644 (file)
@@ -4687,14 +4687,27 @@ static int check_ptr_to_btf_access(struct bpf_verifier_env *env,
                return -EACCES;
        }
 
-       if (env->ops->btf_struct_access) {
+       if (env->ops->btf_struct_access && !type_is_alloc(reg->type)) {
+               if (!btf_is_kernel(reg->btf)) {
+                       verbose(env, "verifier internal error: reg->btf must be kernel btf\n");
+                       return -EFAULT;
+               }
                ret = env->ops->btf_struct_access(&env->log, reg, off, size, atype, &btf_id, &flag);
        } else {
-               if (atype != BPF_READ) {
+               /* Writes are permitted with default btf_struct_access for
+                * program allocated objects (which always have ref_obj_id > 0),
+                * but not for untrusted PTR_TO_BTF_ID | MEM_ALLOC.
+                */
+               if (atype != BPF_READ && reg->type != (PTR_TO_BTF_ID | MEM_ALLOC)) {
                        verbose(env, "only read is supported\n");
                        return -EACCES;
                }
 
+               if (type_is_alloc(reg->type) && !reg->ref_obj_id) {
+                       verbose(env, "verifier internal error: ref_obj_id for allocated object must be non-zero\n");
+                       return -EFAULT;
+               }
+
                ret = btf_struct_access(&env->log, reg, off, size, atype, &btf_id, &flag);
        }
 
@@ -5973,6 +5986,7 @@ int check_func_arg_reg_off(struct bpf_verifier_env *env,
         * fixed offset.
         */
        case PTR_TO_BTF_ID:
+       case PTR_TO_BTF_ID | MEM_ALLOC:
                /* When referenced PTR_TO_BTF_ID is passed to release function,
                 * it's fixed offset must be 0. In the other cases, fixed offset
                 * can be non-zero.
@@ -13690,6 +13704,13 @@ static int convert_ctx_accesses(struct bpf_verifier_env *env)
                        break;
                case PTR_TO_BTF_ID:
                case PTR_TO_BTF_ID | PTR_UNTRUSTED:
+               /* PTR_TO_BTF_ID | MEM_ALLOC always has a valid lifetime, unlike
+                * PTR_TO_BTF_ID, and an active ref_obj_id, but the same cannot
+                * be said once it is marked PTR_UNTRUSTED, hence we must handle
+                * any faults for loads into such types. BPF_WRITE is disallowed
+                * for this case.
+                */
+               case PTR_TO_BTF_ID | MEM_ALLOC | PTR_UNTRUSTED:
                        if (type == BPF_READ) {
                                insn->code = BPF_LDX | BPF_PROBE_MEM |
                                        BPF_SIZE((insn)->code);