]> git.ipfire.org Git - thirdparty/linux.git/commitdiff
bpf: Allow subprogs to return arena pointers
authorEmil Tsalapatis <emil@etsalapatis.com>
Tue, 2 Jun 2026 00:41:17 +0000 (20:41 -0400)
committerAlexei Starovoitov <ast@kernel.org>
Tue, 2 Jun 2026 01:42:33 +0000 (18:42 -0700)
BPF subprogs currently only return void or scalar values. However,
it is also safe to return arena pointers between subprogs in the same
BPF program: Arena pointers are guaranteed to be safe for both programs
at any point. Expand the verifier to permit returning an arena pointer
to the caller.

The main subprog is still not allowed to return an arena pointer because
arena pointers are internal to the BPF program, and the return values
permitted for each main subprog depend on the program type anyway.

Acked-by: Eduard Zingerman <eddyz87@gmail.com>
Signed-off-by: Emil Tsalapatis <emil@etsalapatis.com>
Link: https://lore.kernel.org/r/20260602004120.17087-4-emil@etsalapatis.com
Signed-off-by: Alexei Starovoitov <ast@kernel.org>
kernel/bpf/btf.c
kernel/bpf/verifier.c

index 6fb3461e3ac2b3cdad54c5357b2664fbd3ea7d1c..68921d9172b562db9b33964f29b65a3a895eda85 100644 (file)
@@ -7858,12 +7858,22 @@ static int btf_scan_type_tags(struct bpf_verifier_env *env,
 
        /* Find the first pointer type in the chain. */
        t = btf_type_skip_modifiers(btf, type_id, NULL);
+
+       /*
+        * We currently reject type tags on non-pointer types,
+        * which neither LLVM nor GCC support anyway.
+        */
        if (!t || !btf_type_is_ptr(t))
                return 0;
 
        /* We got a pointer, get all associated type tags. */
-       t = btf_type_by_id(btf, t->type);
-       while (t && btf_type_is_type_tag(t)) {
+       for (t = btf_type_by_id(btf, t->type); t && btf_type_is_modifier(t);
+               t = btf_type_by_id(btf, t->type)) {
+
+               /* Skip non-type tag modifiers. */
+               if (!btf_type_is_type_tag(t))
+                       continue;
+
                const char *tag = __btf_name_by_offset(btf, t->name_off);
 
                if (strcmp(tag, "arena") == 0) {
@@ -7873,13 +7883,39 @@ static int btf_scan_type_tags(struct bpf_verifier_env *env,
                                tag);
                        return -EOPNOTSUPP;
                }
-
-               t = btf_type_by_id(btf, t->type);
        }
 
        return 0;
 }
 
+/* Check whether the type is a valid return type. */
+static int btf_validate_return_type(struct bpf_verifier_env *env, struct btf *btf,
+               const struct btf_type *t, int subprog)
+{
+       u32 tags = 0;
+       int err;
+
+       err = btf_scan_type_tags(env, btf, t->type, &tags);
+       if (err)
+               return err;
+
+       t = btf_type_skip_modifiers(btf, t->type, NULL);
+
+       /*
+        * We allow all subprogs except for the main one to return any kind of arena pointer.
+        * General arena variables are not allowed, since it makes no sense to return by value
+        * a variable that's on the heap in the first place.
+        */
+       if (subprog && (tags & ARG_TAG_ARENA) && btf_type_is_ptr(t))
+               return 0;
+
+       /* We always accept void or scalars. */
+       if (btf_type_is_void(t) || btf_type_is_int(t) || btf_is_any_enum(t))
+               return 0;
+
+       return -EOPNOTSUPP;
+}
+
 /* Process BTF of a function to produce high-level expectation of function
  * arguments (like ARG_PTR_TO_CTX, or ARG_PTR_TO_MEM, etc). This information
  * is cached in subprog info for reuse.
@@ -7963,18 +7999,16 @@ int btf_prepare_func_args(struct bpf_verifier_env *env, int subprog)
                        tname, nargs, MAX_BPF_FUNC_REG_ARGS);
                return -EINVAL;
        }
-       /* check that function is void or returns int, exception cb also requires this */
-       t = btf_type_by_id(btf, t->type);
-       while (btf_type_is_modifier(t))
-               t = btf_type_by_id(btf, t->type);
-       if (!btf_type_is_void(t) && !btf_type_is_int(t) && !btf_is_any_enum(t)) {
-               if (!is_global)
-                       return -EINVAL;
-               bpf_log(log,
-                       "Global function %s() return value not void or scalar. "
-                       "Only those are supported.\n",
-                       tname);
-               return -EINVAL;
+
+       err = btf_validate_return_type(env, btf, t, subprog);
+       if (err) {
+               if (is_global) {
+                       bpf_log(log,
+                               "Global function %s() return value not void or scalar. "
+                               "Only those are supported.\n",
+                               tname);
+               }
+               return err;
        }
 
        /* Convert BTF function arguments into verifier types.
index 5d8f2656dbfd43a2f573f4239dd6dd860720a3a9..8ed484cb1a8a4b41fc0298735ea31a1a0d885170 100644 (file)
@@ -16503,6 +16503,10 @@ static int check_global_subprog_return_code(struct bpf_verifier_env *env)
        if (err)
                return err;
 
+       /* Pointers to arena are safe to pass between subprograms. */
+       if (is_arena_reg(env, BPF_REG_0))
+               return 0;
+
        if (is_pointer_value(env, BPF_REG_0)) {
                verbose(env, "R%d leaks addr as return value\n", BPF_REG_0);
                return -EACCES;