Linux kernel mirror (for testing) git.kernel.org/pub/scm/linux/kernel/git/torvalds/linux.git
kernel os linux
1
fork

Configure Feed

Select the types of activity you want to include in your feed.

Merge branch 'bpf-allow-void-return-type-for-global-subprogs'

Emil Tsalapatis says:

====================
bpf: Allow void return type for global subprogs

Global subprogs are currently not allowed to return void. Adjust
verifier logic to allow global functions with a void return type.
Exception callbacks are excluded from this change, and still require
a scalar return type.

Adding additional logic for void globals significantly complicates
check_return_code(), mainly because of how the new feature interacts
with bpf_throw(). To avoid complicating the code, refactor the return
value range validation logic into two separate functions, one for
program exits through BPF_EXIT and one for program exits through
bpf_throw(). The changes are non-functional.

Also adjust existing selftests that were ensuring that global subprogs
with void return types fail to verify. These tests should now load
successfully.

Patches 1-3 refactor check_return_code into two separate functions, one
for BPF_EXIT and one for bpf_throw(). No functional changes.

Patches 4-5 enable void globals in the verifier and add additional
selftests.

v5 -> v6
(https://lore.kernel.org/bpf/20260227154616.6846-1-emil@etsalapatis.com/)

Patch 1:

- Removed unused variables (kernel test robot)
- Add break; on empty default to suppress warning (kernel test robot)
- Added explanation on why change in switch statement for
BPF_PROG_TYPE_TRACING in check_return_code is a no-op

Patches 2/3:

- Added From: tags to Eduard's patches and changed my Reviewed-by to
Signed-off-by to properly denote patch route (submitting-patches.rst)
- Renamed check_subprogram_return_code for into check_global_subprog_return_code
(Eduard)

Patch 4:

- Remove unnecessary subprog_is_global() check in check_return_code (Eduard)
- Fix indentation on patch 4 (AI)

Patch 5:

- Fix unused data in selftests (Eduard)

v4 -> v5
(https://lore.kernel.org/bpf/20260225033356.518313-1-emil@etsalapatis.com/)
- Split value validation logic from throw/nonthrow to main prog/subprog (Eduard)
- Turn return_retval_range() to bool and incorporate return_32bit in retval (Eduard)
- Adjust if-else chain in return_retval_range for cgroup programs into switch
statement to bring it in line with the other program types

v3 -> v4
(https://lore.kernel.org/bpf/20260223215046.1706110-1-emil@etsalapatis.com/)
- Factor out bpf_throw() return value validation logic into its own
function (Eduard)
- Validate the range of bpf_throw() return values from void globals (Eduard)
- Move new selftest to verifier_global_subprogs.c (Eduard)
- Remove unnecessary verifier_bug_if() statements (Eduard)
- Add new test to ensure void globals can call bpf_throw

v2 -> v3
(https://lore.kernel.org/bpf/20260210183257.217285-1-emil@etsalapatis.com/)
- Allow freplace for global programs that return void and add selftests (Kumar)

v1 -> v2
(https://lore.kernel.org/bpf/20260127231414.359283-1-emil@etsalapatis.com/)

- Only mark R0 as valid for non-void global functions (AI)
- Rename subprog_is_void to subprog_returns_void (Kumar)
- Add test that R0 is invalid after calling a void global (Alexei)
- Add check to ensure exception callbacks do not return void
and associated selftest

Cc: Kumar Kartikeya Dwivedi <memxor@gmail.com>
====================

Link: https://patch.msgid.link/20260228184759.108145-1-emil@etsalapatis.com
Signed-off-by: Alexei Starovoitov <ast@kernel.org>

+369 -154
+1
include/linux/bpf_verifier.h
··· 265 265 struct bpf_retval_range { 266 266 s32 minval; 267 267 s32 maxval; 268 + bool return_32bit; 268 269 }; 269 270 270 271 /* state of the program:
+4 -3
kernel/bpf/btf.c
··· 7836 7836 tname, nargs, MAX_BPF_FUNC_REG_ARGS); 7837 7837 return -EINVAL; 7838 7838 } 7839 - /* check that function returns int, exception cb also requires this */ 7839 + /* check that function is void or returns int, exception cb also requires this */ 7840 7840 t = btf_type_by_id(btf, t->type); 7841 7841 while (btf_type_is_modifier(t)) 7842 7842 t = btf_type_by_id(btf, t->type); 7843 - if (!btf_type_is_int(t) && !btf_is_any_enum(t)) { 7843 + if (!btf_type_is_void(t) && !btf_type_is_int(t) && !btf_is_any_enum(t)) { 7844 7844 if (!is_global) 7845 7845 return -EINVAL; 7846 7846 bpf_log(log, 7847 - "Global function %s() doesn't return scalar. Only those are supported.\n", 7847 + "Global function %s() return value not void or scalar. " 7848 + "Only those are supported.\n", 7848 7849 tname); 7849 7850 return -EINVAL; 7850 7851 }
+252 -147
kernel/bpf/verifier.c
··· 444 444 return aux && aux[subprog].linkage == BTF_FUNC_GLOBAL; 445 445 } 446 446 447 + static bool subprog_returns_void(struct bpf_verifier_env *env, int subprog) 448 + { 449 + const struct btf_type *type, *func, *func_proto; 450 + const struct btf *btf = env->prog->aux->btf; 451 + u32 btf_id; 452 + 453 + btf_id = env->prog->aux->func_info[subprog].type_id; 454 + 455 + func = btf_type_by_id(btf, btf_id); 456 + if (verifier_bug_if(!func, env, "btf_id %u not found", btf_id)) 457 + return false; 458 + 459 + func_proto = btf_type_by_id(btf, func->type); 460 + if (!func_proto) 461 + return false; 462 + 463 + type = btf_type_skip_modifiers(btf, func_proto->type, NULL); 464 + if (!type) 465 + return false; 466 + 467 + return btf_type_is_void(type); 468 + } 469 + 447 470 static const char *subprog_name(const struct bpf_verifier_env *env, int subprog) 448 471 { 449 472 struct bpf_func_info *info; ··· 2976 2953 2977 2954 static struct bpf_retval_range retval_range(s32 minval, s32 maxval) 2978 2955 { 2979 - return (struct bpf_retval_range){ minval, maxval }; 2956 + /* 2957 + * return_32bit is set to false by default and set explicitly 2958 + * by the caller when necessary. 2959 + */ 2960 + return (struct bpf_retval_range){ minval, maxval, false }; 2980 2961 } 2981 2962 2982 2963 #define BPF_MAIN_FUNC (-1) ··· 10912 10885 subprog_aux(env, subprog)->called = true; 10913 10886 clear_caller_saved_regs(env, caller->regs); 10914 10887 10915 - /* All global functions return a 64-bit SCALAR_VALUE */ 10916 - mark_reg_unknown(env, caller->regs, BPF_REG_0); 10917 - caller->regs[BPF_REG_0].subreg_def = DEF_NOT_SUBREG; 10888 + /* All non-void global functions return a 64-bit SCALAR_VALUE. */ 10889 + if (!subprog_returns_void(env, subprog)) { 10890 + mark_reg_unknown(env, caller->regs, BPF_REG_0); 10891 + caller->regs[BPF_REG_0].subreg_def = DEF_NOT_SUBREG; 10892 + } 10918 10893 10919 10894 /* continue with next insn after call */ 10920 10895 return 0; ··· 11204 11175 return is_rbtree_lock_required_kfunc(kfunc_btf_id); 11205 11176 } 11206 11177 11207 - static bool retval_range_within(struct bpf_retval_range range, const struct bpf_reg_state *reg, 11208 - bool return_32bit) 11178 + static bool retval_range_within(struct bpf_retval_range range, const struct bpf_reg_state *reg) 11209 11179 { 11210 - if (return_32bit) 11180 + if (range.return_32bit) 11211 11181 return range.minval <= reg->s32_min_value && reg->s32_max_value <= range.maxval; 11212 11182 else 11213 11183 return range.minval <= reg->smin_value && reg->smax_value <= range.maxval; ··· 11250 11222 return err; 11251 11223 11252 11224 /* enforce R0 return value range, and bpf_callback_t returns 64bit */ 11253 - if (!retval_range_within(callee->callback_ret_range, r0, false)) { 11225 + if (!retval_range_within(callee->callback_ret_range, r0)) { 11254 11226 verbose_invalid_scalar(env, r0, callee->callback_ret_range, 11255 11227 "At callback return", "R0"); 11256 11228 return -EINVAL; ··· 17869 17841 return 0; 17870 17842 } 17871 17843 17844 + 17845 + static bool return_retval_range(struct bpf_verifier_env *env, struct bpf_retval_range *range) 17846 + { 17847 + enum bpf_prog_type prog_type = resolve_prog_type(env->prog); 17848 + 17849 + /* Default return value range. */ 17850 + *range = retval_range(0, 1); 17851 + 17852 + switch (prog_type) { 17853 + case BPF_PROG_TYPE_CGROUP_SOCK_ADDR: 17854 + switch (env->prog->expected_attach_type) { 17855 + case BPF_CGROUP_UDP4_RECVMSG: 17856 + case BPF_CGROUP_UDP6_RECVMSG: 17857 + case BPF_CGROUP_UNIX_RECVMSG: 17858 + case BPF_CGROUP_INET4_GETPEERNAME: 17859 + case BPF_CGROUP_INET6_GETPEERNAME: 17860 + case BPF_CGROUP_UNIX_GETPEERNAME: 17861 + case BPF_CGROUP_INET4_GETSOCKNAME: 17862 + case BPF_CGROUP_INET6_GETSOCKNAME: 17863 + case BPF_CGROUP_UNIX_GETSOCKNAME: 17864 + *range = retval_range(1, 1); 17865 + break; 17866 + case BPF_CGROUP_INET4_BIND: 17867 + case BPF_CGROUP_INET6_BIND: 17868 + *range = retval_range(0, 3); 17869 + break; 17870 + default: 17871 + break; 17872 + } 17873 + break; 17874 + case BPF_PROG_TYPE_CGROUP_SKB: 17875 + if (env->prog->expected_attach_type == BPF_CGROUP_INET_EGRESS) 17876 + *range = retval_range(0, 3); 17877 + break; 17878 + case BPF_PROG_TYPE_CGROUP_SOCK: 17879 + case BPF_PROG_TYPE_SOCK_OPS: 17880 + case BPF_PROG_TYPE_CGROUP_DEVICE: 17881 + case BPF_PROG_TYPE_CGROUP_SYSCTL: 17882 + case BPF_PROG_TYPE_CGROUP_SOCKOPT: 17883 + break; 17884 + case BPF_PROG_TYPE_RAW_TRACEPOINT: 17885 + if (!env->prog->aux->attach_btf_id) 17886 + return false; 17887 + *range = retval_range(0, 0); 17888 + break; 17889 + case BPF_PROG_TYPE_TRACING: 17890 + switch (env->prog->expected_attach_type) { 17891 + case BPF_TRACE_FENTRY: 17892 + case BPF_TRACE_FEXIT: 17893 + case BPF_TRACE_FSESSION: 17894 + *range = retval_range(0, 0); 17895 + break; 17896 + case BPF_TRACE_RAW_TP: 17897 + case BPF_MODIFY_RETURN: 17898 + return false; 17899 + case BPF_TRACE_ITER: 17900 + default: 17901 + break; 17902 + } 17903 + break; 17904 + case BPF_PROG_TYPE_KPROBE: 17905 + switch (env->prog->expected_attach_type) { 17906 + case BPF_TRACE_KPROBE_SESSION: 17907 + case BPF_TRACE_UPROBE_SESSION: 17908 + break; 17909 + default: 17910 + return false; 17911 + } 17912 + break; 17913 + case BPF_PROG_TYPE_SK_LOOKUP: 17914 + *range = retval_range(SK_DROP, SK_PASS); 17915 + break; 17916 + 17917 + case BPF_PROG_TYPE_LSM: 17918 + if (env->prog->expected_attach_type != BPF_LSM_CGROUP) { 17919 + /* no range found, any return value is allowed */ 17920 + if (!get_func_retval_range(env->prog, range)) 17921 + return false; 17922 + /* no restricted range, any return value is allowed */ 17923 + if (range->minval == S32_MIN && range->maxval == S32_MAX) 17924 + return false; 17925 + range->return_32bit = true; 17926 + } else if (!env->prog->aux->attach_func_proto->type) { 17927 + /* Make sure programs that attach to void 17928 + * hooks don't try to modify return value. 17929 + */ 17930 + *range = retval_range(1, 1); 17931 + } 17932 + break; 17933 + 17934 + case BPF_PROG_TYPE_NETFILTER: 17935 + *range = retval_range(NF_DROP, NF_ACCEPT); 17936 + break; 17937 + case BPF_PROG_TYPE_STRUCT_OPS: 17938 + *range = retval_range(0, 0); 17939 + break; 17940 + case BPF_PROG_TYPE_EXT: 17941 + /* freplace program can return anything as its return value 17942 + * depends on the to-be-replaced kernel func or bpf program. 17943 + */ 17944 + default: 17945 + return false; 17946 + } 17947 + 17948 + /* Continue calculating. */ 17949 + 17950 + return true; 17951 + } 17952 + 17953 + static bool program_returns_void(struct bpf_verifier_env *env) 17954 + { 17955 + const struct bpf_prog *prog = env->prog; 17956 + enum bpf_prog_type prog_type = prog->type; 17957 + 17958 + switch (prog_type) { 17959 + case BPF_PROG_TYPE_LSM: 17960 + /* See return_retval_range, for BPF_LSM_CGROUP can be 0 or 0-1 depending on hook. */ 17961 + if (prog->expected_attach_type != BPF_LSM_CGROUP && 17962 + !prog->aux->attach_func_proto->type) 17963 + return true; 17964 + break; 17965 + case BPF_PROG_TYPE_STRUCT_OPS: 17966 + if (!prog->aux->attach_func_proto->type) 17967 + return true; 17968 + break; 17969 + case BPF_PROG_TYPE_EXT: 17970 + /* 17971 + * If the actual program is an extension, let it 17972 + * return void - attaching will succeed only if the 17973 + * program being replaced also returns void, and since 17974 + * it has passed verification its actual type doesn't matter. 17975 + */ 17976 + if (subprog_returns_void(env, 0)) 17977 + return true; 17978 + break; 17979 + default: 17980 + break; 17981 + } 17982 + return false; 17983 + } 17984 + 17872 17985 static int check_return_code(struct bpf_verifier_env *env, int regno, const char *reg_name) 17873 17986 { 17874 17987 const char *exit_ctx = "At program exit"; ··· 18018 17849 struct bpf_reg_state *reg = reg_state(env, regno); 18019 17850 struct bpf_retval_range range = retval_range(0, 1); 18020 17851 enum bpf_prog_type prog_type = resolve_prog_type(env->prog); 18021 - int err; 18022 17852 struct bpf_func_state *frame = env->cur_state->frame[0]; 18023 - const bool is_subprog = frame->subprogno; 18024 - bool return_32bit = false; 18025 17853 const struct btf_type *reg_type, *ret_type = NULL; 17854 + int err; 18026 17855 18027 17856 /* LSM and struct_ops func-ptr's return type could be "void" */ 18028 - if (!is_subprog || frame->in_exception_callback_fn) { 18029 - switch (prog_type) { 18030 - case BPF_PROG_TYPE_LSM: 18031 - if (prog->expected_attach_type == BPF_LSM_CGROUP) 18032 - /* See below, can be 0 or 0-1 depending on hook. */ 18033 - break; 18034 - if (!prog->aux->attach_func_proto->type) 18035 - return 0; 18036 - break; 18037 - case BPF_PROG_TYPE_STRUCT_OPS: 18038 - if (!prog->aux->attach_func_proto->type) 18039 - return 0; 17857 + if (!frame->in_async_callback_fn && program_returns_void(env)) 17858 + return 0; 18040 17859 18041 - if (frame->in_exception_callback_fn) 18042 - break; 18043 - 18044 - /* Allow a struct_ops program to return a referenced kptr if it 18045 - * matches the operator's return type and is in its unmodified 18046 - * form. A scalar zero (i.e., a null pointer) is also allowed. 18047 - */ 18048 - reg_type = reg->btf ? btf_type_by_id(reg->btf, reg->btf_id) : NULL; 18049 - ret_type = btf_type_resolve_ptr(prog->aux->attach_btf, 18050 - prog->aux->attach_func_proto->type, 18051 - NULL); 18052 - if (ret_type && ret_type == reg_type && reg->ref_obj_id) 18053 - return __check_ptr_off_reg(env, reg, regno, false); 18054 - break; 18055 - default: 18056 - break; 18057 - } 17860 + if (prog_type == BPF_PROG_TYPE_STRUCT_OPS) { 17861 + /* Allow a struct_ops program to return a referenced kptr if it 17862 + * matches the operator's return type and is in its unmodified 17863 + * form. A scalar zero (i.e., a null pointer) is also allowed. 17864 + */ 17865 + reg_type = reg->btf ? btf_type_by_id(reg->btf, reg->btf_id) : NULL; 17866 + ret_type = btf_type_resolve_ptr(prog->aux->attach_btf, 17867 + prog->aux->attach_func_proto->type, 17868 + NULL); 17869 + if (ret_type && ret_type == reg_type && reg->ref_obj_id) 17870 + return __check_ptr_off_reg(env, reg, regno, false); 18058 17871 } 18059 17872 18060 17873 /* eBPF calling convention is such that R0 is used ··· 18060 17909 goto enforce_retval; 18061 17910 } 18062 17911 18063 - if (is_subprog && !frame->in_exception_callback_fn) { 18064 - if (reg->type != SCALAR_VALUE) { 18065 - verbose(env, "At subprogram exit the register R%d is not a scalar value (%s)\n", 18066 - regno, reg_type_str(env, reg->type)); 18067 - return -EINVAL; 18068 - } 17912 + if (prog_type == BPF_PROG_TYPE_STRUCT_OPS && !ret_type) 18069 17913 return 0; 18070 - } 18071 17914 18072 - switch (prog_type) { 18073 - case BPF_PROG_TYPE_CGROUP_SOCK_ADDR: 18074 - if (env->prog->expected_attach_type == BPF_CGROUP_UDP4_RECVMSG || 18075 - env->prog->expected_attach_type == BPF_CGROUP_UDP6_RECVMSG || 18076 - env->prog->expected_attach_type == BPF_CGROUP_UNIX_RECVMSG || 18077 - env->prog->expected_attach_type == BPF_CGROUP_INET4_GETPEERNAME || 18078 - env->prog->expected_attach_type == BPF_CGROUP_INET6_GETPEERNAME || 18079 - env->prog->expected_attach_type == BPF_CGROUP_UNIX_GETPEERNAME || 18080 - env->prog->expected_attach_type == BPF_CGROUP_INET4_GETSOCKNAME || 18081 - env->prog->expected_attach_type == BPF_CGROUP_INET6_GETSOCKNAME || 18082 - env->prog->expected_attach_type == BPF_CGROUP_UNIX_GETSOCKNAME) 18083 - range = retval_range(1, 1); 18084 - if (env->prog->expected_attach_type == BPF_CGROUP_INET4_BIND || 18085 - env->prog->expected_attach_type == BPF_CGROUP_INET6_BIND) 18086 - range = retval_range(0, 3); 18087 - break; 18088 - case BPF_PROG_TYPE_CGROUP_SKB: 18089 - if (env->prog->expected_attach_type == BPF_CGROUP_INET_EGRESS) { 18090 - range = retval_range(0, 3); 18091 - enforce_attach_type_range = tnum_range(2, 3); 18092 - } 18093 - break; 18094 - case BPF_PROG_TYPE_CGROUP_SOCK: 18095 - case BPF_PROG_TYPE_SOCK_OPS: 18096 - case BPF_PROG_TYPE_CGROUP_DEVICE: 18097 - case BPF_PROG_TYPE_CGROUP_SYSCTL: 18098 - case BPF_PROG_TYPE_CGROUP_SOCKOPT: 18099 - break; 18100 - case BPF_PROG_TYPE_RAW_TRACEPOINT: 18101 - if (!env->prog->aux->attach_btf_id) 18102 - return 0; 18103 - range = retval_range(0, 0); 18104 - break; 18105 - case BPF_PROG_TYPE_TRACING: 18106 - switch (env->prog->expected_attach_type) { 18107 - case BPF_TRACE_FENTRY: 18108 - case BPF_TRACE_FEXIT: 18109 - case BPF_TRACE_FSESSION: 18110 - range = retval_range(0, 0); 18111 - break; 18112 - case BPF_TRACE_RAW_TP: 18113 - case BPF_MODIFY_RETURN: 18114 - return 0; 18115 - case BPF_TRACE_ITER: 18116 - break; 18117 - default: 18118 - return -ENOTSUPP; 18119 - } 18120 - break; 18121 - case BPF_PROG_TYPE_KPROBE: 18122 - switch (env->prog->expected_attach_type) { 18123 - case BPF_TRACE_KPROBE_SESSION: 18124 - case BPF_TRACE_UPROBE_SESSION: 18125 - range = retval_range(0, 1); 18126 - break; 18127 - default: 18128 - return 0; 18129 - } 18130 - break; 18131 - case BPF_PROG_TYPE_SK_LOOKUP: 18132 - range = retval_range(SK_DROP, SK_PASS); 18133 - break; 17915 + if (prog_type == BPF_PROG_TYPE_CGROUP_SKB && (env->prog->expected_attach_type == BPF_CGROUP_INET_EGRESS)) 17916 + enforce_attach_type_range = tnum_range(2, 3); 18134 17917 18135 - case BPF_PROG_TYPE_LSM: 18136 - if (env->prog->expected_attach_type != BPF_LSM_CGROUP) { 18137 - /* no range found, any return value is allowed */ 18138 - if (!get_func_retval_range(env->prog, &range)) 18139 - return 0; 18140 - /* no restricted range, any return value is allowed */ 18141 - if (range.minval == S32_MIN && range.maxval == S32_MAX) 18142 - return 0; 18143 - return_32bit = true; 18144 - } else if (!env->prog->aux->attach_func_proto->type) { 18145 - /* Make sure programs that attach to void 18146 - * hooks don't try to modify return value. 18147 - */ 18148 - range = retval_range(1, 1); 18149 - } 18150 - break; 18151 - 18152 - case BPF_PROG_TYPE_NETFILTER: 18153 - range = retval_range(NF_DROP, NF_ACCEPT); 18154 - break; 18155 - case BPF_PROG_TYPE_STRUCT_OPS: 18156 - if (!ret_type) 18157 - return 0; 18158 - range = retval_range(0, 0); 18159 - break; 18160 - case BPF_PROG_TYPE_EXT: 18161 - /* freplace program can return anything as its return value 18162 - * depends on the to-be-replaced kernel func or bpf program. 18163 - */ 18164 - default: 17918 + if (!return_retval_range(env, &range)) 18165 17919 return 0; 18166 - } 18167 17920 18168 17921 enforce_retval: 18169 17922 if (reg->type != SCALAR_VALUE) { ··· 18080 18025 if (err) 18081 18026 return err; 18082 18027 18083 - if (!retval_range_within(range, reg, return_32bit)) { 18028 + if (!retval_range_within(range, reg)) { 18084 18029 verbose_invalid_scalar(env, reg, range, exit_ctx, reg_name); 18085 - if (!is_subprog && 18086 - prog->expected_attach_type == BPF_LSM_CGROUP && 18030 + if (prog->expected_attach_type == BPF_LSM_CGROUP && 18087 18031 prog_type == BPF_PROG_TYPE_LSM && 18088 18032 !prog->aux->attach_func_proto->type) 18089 18033 verbose(env, "Note, BPF_LSM_CGROUP that attach to void LSM hooks can't modify return value!\n"); ··· 18092 18038 if (!tnum_is_unknown(enforce_attach_type_range) && 18093 18039 tnum_in(enforce_attach_type_range, reg->var_off)) 18094 18040 env->prog->enforce_expected_attach_type = 1; 18041 + return 0; 18042 + } 18043 + 18044 + static int check_global_subprog_return_code(struct bpf_verifier_env *env) 18045 + { 18046 + struct bpf_reg_state *reg = reg_state(env, BPF_REG_0); 18047 + struct bpf_func_state *cur_frame = cur_func(env); 18048 + int err; 18049 + 18050 + if (subprog_returns_void(env, cur_frame->subprogno)) 18051 + return 0; 18052 + 18053 + err = check_reg_arg(env, BPF_REG_0, SRC_OP); 18054 + if (err) 18055 + return err; 18056 + 18057 + if (is_pointer_value(env, BPF_REG_0)) { 18058 + verbose(env, "R%d leaks addr as return value\n", BPF_REG_0); 18059 + return -EACCES; 18060 + } 18061 + 18062 + if (reg->type != SCALAR_VALUE) { 18063 + verbose(env, "At subprogram exit the register R0 is not a scalar value (%s)\n", 18064 + reg_type_str(env, reg->type)); 18065 + return -EINVAL; 18066 + } 18067 + 18095 18068 return 0; 18096 18069 } 18097 18070 ··· 20915 20834 bool *do_print_state, 20916 20835 bool exception_exit) 20917 20836 { 20837 + struct bpf_func_state *cur_frame = cur_func(env); 20838 + 20918 20839 /* We must do check_reference_leak here before 20919 20840 * prepare_func_exit to handle the case when 20920 20841 * state->curframe > 0, it may be a callback function, ··· 20950 20867 return 0; 20951 20868 } 20952 20869 20953 - err = check_return_code(env, BPF_REG_0, "R0"); 20870 + /* 20871 + * Return from a regular global subprogram differs from return 20872 + * from the main program or async/exception callback. 20873 + * Main program exit implies return code restrictions 20874 + * that depend on program type. 20875 + * Exit from exception callback is equivalent to main program exit. 20876 + * Exit from async callback implies return code restrictions 20877 + * that depend on async scheduling mechanism. 20878 + */ 20879 + if (cur_frame->subprogno && 20880 + !cur_frame->in_async_callback_fn && 20881 + !cur_frame->in_exception_callback_fn) 20882 + err = check_global_subprog_return_code(env); 20883 + else 20884 + err = check_return_code(env, BPF_REG_0, "R0"); 20954 20885 if (err) 20955 20886 return err; 20956 20887 return PROCESS_BPF_EXIT; ··· 24603 24506 24604 24507 if (subprog_is_exc_cb(env, subprog)) { 24605 24508 state->frame[0]->in_exception_callback_fn = true; 24606 - /* We have already ensured that the callback returns an integer, just 24607 - * like all global subprogs. We need to determine it only has a single 24608 - * scalar argument. 24509 + 24510 + /* 24511 + * Global functions are scalar or void, make sure 24512 + * we return a scalar. 24609 24513 */ 24514 + if (subprog_returns_void(env, subprog)) { 24515 + verbose(env, "exception cb cannot return void\n"); 24516 + ret = -EINVAL; 24517 + goto out; 24518 + } 24519 + 24520 + /* Also ensure the callback only has a single scalar argument. */ 24610 24521 if (sub->arg_cnt != 1 || sub->args[0].arg_type != ARG_ANYTHING) { 24611 24522 verbose(env, "exception cb only supports single integer argument\n"); 24612 24523 ret = -EINVAL;
+1
tools/testing/selftests/bpf/prog_tests/exceptions.c
··· 83 83 RUN_SUCCESS(exception_assert_range_with, 1); 84 84 RUN_SUCCESS(exception_bad_assert_range, 0); 85 85 RUN_SUCCESS(exception_bad_assert_range_with, 10); 86 + RUN_SUCCESS(exception_throw_from_void_global, 11); 86 87 87 88 #define RUN_EXT(load_ret, attach_err, expr, msg, after_link) \ 88 89 { \
+24
tools/testing/selftests/bpf/prog_tests/fexit_bpf2bpf.c
··· 347 347 prog_name, false, NULL); 348 348 } 349 349 350 + static void test_func_replace_void(void) 351 + { 352 + const char *prog_name[] = { 353 + "freplace/foo", 354 + }; 355 + test_fexit_bpf2bpf_common("./freplace_void.bpf.o", 356 + "./test_global_func7.bpf.o", 357 + ARRAY_SIZE(prog_name), 358 + prog_name, false, NULL); 359 + } 360 + 350 361 static void test_obj_load_failure_common(const char *obj_file, 351 362 const char *target_obj_file, 352 363 const char *exp_msg) ··· 441 430 "./test_pkt_access.bpf.o", 442 431 ARRAY_SIZE(prog_name), 443 432 prog_name, false, NULL); 433 + } 434 + 435 + static void test_func_replace_int_with_void(void) 436 + { 437 + /* Make sure we can't freplace with the wrong type */ 438 + test_obj_load_failure_common("freplace_int_with_void.bpf.o", 439 + "./test_global_func2.bpf.o", 440 + "Return type UNKNOWN of test_freplace_int_with_void()" 441 + " doesn't match type INT of global_func2()"); 444 442 } 445 443 446 444 static int find_prog_btf_id(const char *name, __u32 attach_prog_fd) ··· 617 597 test_fentry_to_cgroup_bpf(); 618 598 if (test__start_subtest("func_replace_progmap")) 619 599 test_func_replace_progmap(); 600 + if (test__start_subtest("freplace_int_with_void")) 601 + test_func_replace_int_with_void(); 602 + if (test__start_subtest("freplace_void")) 603 + test_func_replace_void(); 620 604 }
+14
tools/testing/selftests/bpf/progs/exceptions.c
··· 109 109 return ret + 8; 110 110 } 111 111 112 + __weak 113 + void throw_11(void) 114 + { 115 + bpf_throw(11); 116 + } 117 + 118 + SEC("tc") 119 + int exception_throw_from_void_global(struct __sk_buff *ctx) 120 + { 121 + throw_11(); 122 + 123 + return 0; 124 + } 125 + 112 126 __noinline int exception_ext_global(struct __sk_buff *ctx) 113 127 { 114 128 volatile int ret = 0;
+32 -3
tools/testing/selftests/bpf/progs/exceptions_fail.c
··· 29 29 private(A) struct bpf_spin_lock lock; 30 30 private(A) struct bpf_rb_root rbtree __contains(foo, node); 31 31 32 - __noinline void *exception_cb_bad_ret_type(u64 cookie) 32 + __noinline void *exception_cb_bad_ret_type1(u64 cookie) 33 33 { 34 34 return NULL; 35 + } 36 + 37 + __noinline void exception_cb_bad_ret_type2(u64 cookie) 38 + { 35 39 } 36 40 37 41 __noinline int exception_cb_bad_arg_0(void) ··· 54 50 } 55 51 56 52 SEC("?tc") 57 - __exception_cb(exception_cb_bad_ret_type) 58 - __failure __msg("Global function exception_cb_bad_ret_type() doesn't return scalar.") 53 + __exception_cb(exception_cb_bad_ret_type1) 54 + __failure __msg("Global function exception_cb_bad_ret_type1() return value not void or scalar.") 59 55 int reject_exception_cb_type_1(struct __sk_buff *ctx) 60 56 { 61 57 bpf_throw(0); ··· 84 80 __exception_cb(exception_cb_ok_arg_small) 85 81 __success 86 82 int reject_exception_cb_type_4(struct __sk_buff *ctx) 83 + { 84 + bpf_throw(0); 85 + return 0; 86 + } 87 + 88 + SEC("?tc") 89 + __exception_cb(exception_cb_bad_ret_type2) 90 + __failure __msg("exception cb cannot return void") 91 + int reject_exception_cb_type_5(struct __sk_buff *ctx) 87 92 { 88 93 bpf_throw(0); 89 94 return 0; ··· 358 345 bpf_loop(5, loop_cb2, NULL, 0); 359 346 return 0; 360 347 } 348 + 349 + __weak 350 + void foo(void) 351 + { 352 + bpf_throw(1); 353 + } 354 + 355 + SEC("?fentry/bpf_check") 356 + __failure __msg("At program exit the register R1 has smin=1 smax=1 should") 357 + int reject_out_of_range_global_throw(struct __sk_buff *skb) 358 + { 359 + foo(); 360 + 361 + return 0; 362 + } 363 + 361 364 362 365 char _license[] SEC("license") = "GPL";
+11
tools/testing/selftests/bpf/progs/freplace_int_with_void.c
··· 1 + // SPDX-License-Identifier: GPL-2.0 2 + #include <linux/bpf.h> 3 + #include <linux/pkt_cls.h> 4 + #include <bpf/bpf_helpers.h> 5 + 6 + SEC("freplace/global_func2") 7 + void test_freplace_int_with_void(struct __sk_buff *skb) 8 + { 9 + } 10 + 11 + char _license[] SEC("license") = "GPL";
+10
tools/testing/selftests/bpf/progs/freplace_void.c
··· 1 + // SPDX-License-Identifier: GPL-2.0 2 + #include <linux/bpf.h> 3 + #include <bpf/bpf_helpers.h> 4 + 5 + SEC("freplace/foo") 6 + void test_freplace_void(struct __sk_buff *skb) 7 + { 8 + } 9 + 10 + char _license[] SEC("license") = "GPL";
+1 -1
tools/testing/selftests/bpf/progs/test_global_func7.c
··· 12 12 } 13 13 14 14 SEC("tc") 15 - __failure __msg("foo() doesn't return scalar") 15 + __success 16 16 int global_func7(struct __sk_buff *skb) 17 17 { 18 18 foo(skb);
+19
tools/testing/selftests/bpf/progs/verifier_global_subprogs.c
··· 388 388 return subprog_dynptr(&dptr); 389 389 } 390 390 391 + __weak 392 + void foo(void) 393 + { 394 + } 395 + 396 + SEC("?tc") 397 + __failure __msg("R0 !read_ok") 398 + int return_from_void_global(struct __sk_buff *skb) 399 + { 400 + foo(); 401 + 402 + asm volatile( 403 + "r1 = r0;" 404 + ::: 405 + ); 406 + 407 + return 0; 408 + } 409 + 391 410 char _license[] SEC("license") = "GPL";