]> git.ipfire.org Git - thirdparty/linux.git/commitdiff
bpf: Allow void global functions in the verifier
authorEmil Tsalapatis <emil@etsalapatis.com>
Sat, 28 Feb 2026 18:47:58 +0000 (13:47 -0500)
committerAlexei Starovoitov <ast@kernel.org>
Tue, 3 Mar 2026 16:47:23 +0000 (08:47 -0800)
Global subprogs are currently not allowed to return void. Adjust
verifier logic to allow global functions with a void return type.

Acked-by: Eduard Zingerman <eddyz87@gmail.com>
Signed-off-by: Emil Tsalapatis <emil@etsalapatis.com>
Link: https://lore.kernel.org/r/20260228184759.108145-5-emil@etsalapatis.com
Signed-off-by: Alexei Starovoitov <ast@kernel.org>
kernel/bpf/btf.c
kernel/bpf/verifier.c
tools/testing/selftests/bpf/progs/exceptions_fail.c
tools/testing/selftests/bpf/progs/test_global_func7.c

index 4872d2a6c42d3acac4c84a6bc14bd239da8e5fed..09fcbb125155609f42b27e2b2b048962f64ed0bd 100644 (file)
@@ -7836,15 +7836,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 returns int, exception cb also requires this */
+       /* 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_int(t) && !btf_is_any_enum(t)) {
+       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() doesn't return scalar. Only those are supported.\n",
+                       "Global function %s() return value not void or scalar. "
+                       "Only those are supported.\n",
                        tname);
                return -EINVAL;
        }
index f66080e88187d95fce747e306cd6e78221446c83..d92cf282165791fdf6171c91485d6bd094866d07 100644 (file)
@@ -444,6 +444,29 @@ static bool subprog_is_global(const struct bpf_verifier_env *env, int subprog)
        return aux && aux[subprog].linkage == BTF_FUNC_GLOBAL;
 }
 
+static bool subprog_returns_void(struct bpf_verifier_env *env, int subprog)
+{
+       const struct btf_type *type, *func, *func_proto;
+       const struct btf *btf = env->prog->aux->btf;
+       u32 btf_id;
+
+       btf_id = env->prog->aux->func_info[subprog].type_id;
+
+       func = btf_type_by_id(btf, btf_id);
+       if (verifier_bug_if(!func, env, "btf_id %u not found", btf_id))
+               return false;
+
+       func_proto = btf_type_by_id(btf, func->type);
+       if (!func_proto)
+               return false;
+
+       type = btf_type_skip_modifiers(btf, func_proto->type, NULL);
+       if (!type)
+               return false;
+
+       return btf_type_is_void(type);
+}
+
 static const char *subprog_name(const struct bpf_verifier_env *env, int subprog)
 {
        struct bpf_func_info *info;
@@ -10889,9 +10912,11 @@ static int check_func_call(struct bpf_verifier_env *env, struct bpf_insn *insn,
                subprog_aux(env, subprog)->called = true;
                clear_caller_saved_regs(env, caller->regs);
 
-               /* All global functions return a 64-bit SCALAR_VALUE */
-               mark_reg_unknown(env, caller->regs, BPF_REG_0);
-               caller->regs[BPF_REG_0].subreg_def = DEF_NOT_SUBREG;
+               /* All non-void global functions return a 64-bit SCALAR_VALUE. */
+               if (!subprog_returns_void(env, subprog)) {
+                       mark_reg_unknown(env, caller->regs, BPF_REG_0);
+                       caller->regs[BPF_REG_0].subreg_def = DEF_NOT_SUBREG;
+               }
 
                /* continue with next insn after call */
                return 0;
@@ -17956,7 +17981,7 @@ static bool return_retval_range(struct bpf_verifier_env *env, struct bpf_retval_
 static bool program_returns_void(struct bpf_verifier_env *env)
 {
        const struct bpf_prog *prog = env->prog;
-       enum bpf_prog_type prog_type = resolve_prog_type(prog);
+       enum bpf_prog_type prog_type = prog->type;
 
        switch (prog_type) {
        case BPF_PROG_TYPE_LSM:
@@ -17969,6 +17994,16 @@ static bool program_returns_void(struct bpf_verifier_env *env)
                if (!prog->aux->attach_func_proto->type)
                        return true;
                break;
+       case BPF_PROG_TYPE_EXT:
+               /*
+                * If the actual program is an extension, let it
+                * return void - attaching will succeed only if the
+                * program being replaced also returns void, and since
+                * it has passed verification its actual type doesn't matter.
+                */
+               if (subprog_returns_void(env, 0))
+                       return true;
+               break;
        default:
                break;
        }
@@ -18063,8 +18098,12 @@ enforce_retval:
 static int check_global_subprog_return_code(struct bpf_verifier_env *env)
 {
        struct bpf_reg_state *reg = reg_state(env, BPF_REG_0);
+       struct bpf_func_state *cur_frame = cur_func(env);
        int err;
 
+       if (subprog_returns_void(env, cur_frame->subprogno))
+               return 0;
+
        err = check_reg_arg(env, BPF_REG_0, SRC_OP);
        if (err)
                return err;
@@ -24564,10 +24603,18 @@ static int do_check_common(struct bpf_verifier_env *env, int subprog)
 
                if (subprog_is_exc_cb(env, subprog)) {
                        state->frame[0]->in_exception_callback_fn = true;
-                       /* We have already ensured that the callback returns an integer, just
-                        * like all global subprogs. We need to determine it only has a single
-                        * scalar argument.
+
+                       /*
+                        * Global functions are scalar or void, make sure
+                        * we return a scalar.
                         */
+                       if (subprog_returns_void(env, subprog)) {
+                               verbose(env, "exception cb cannot return void\n");
+                               ret = -EINVAL;
+                               goto out;
+                       }
+
+                       /* Also ensure the callback only has a single scalar argument. */
                        if (sub->arg_cnt != 1 || sub->args[0].arg_type != ARG_ANYTHING) {
                                verbose(env, "exception cb only supports single integer argument\n");
                                ret = -EINVAL;
index 8a0fdff899271d615a0c0e2875702b29c5c9df77..d28ecc4ee2d03ab4222e5a953f37cf5721c97aaa 100644 (file)
@@ -51,7 +51,7 @@ __noinline int exception_cb_ok_arg_small(int a)
 
 SEC("?tc")
 __exception_cb(exception_cb_bad_ret_type)
-__failure __msg("Global function exception_cb_bad_ret_type() doesn't return scalar.")
+__failure __msg("Global function exception_cb_bad_ret_type() return value not void or scalar.")
 int reject_exception_cb_type_1(struct __sk_buff *ctx)
 {
        bpf_throw(0);
index f182febfde3c08da5e8e8bab245c6268c8a804c2..9e59625c1c9220fc95cc80f141deced3953a8e3a 100644 (file)
@@ -12,7 +12,7 @@ void foo(struct __sk_buff *skb)
 }
 
 SEC("tc")
-__failure __msg("foo() doesn't return scalar")
+__success
 int global_func7(struct __sk_buff *skb)
 {
        foo(skb);