Skip to content

Commit bcfcb15

Browse files
ameryhungAlexei Starovoitov
authored andcommitted
bpf: Unify release handling for helpers and kfuncs
Introduce release_reg() to consolidate the release logic shared by both helpers and kfuncs: dynptr release, kptr_xchg percpu-to-RCU conversion, regular reference release, and NULL pass-through. NULL pass-through is only allowed if the prototype indicates the argument may be null. Determine release_regno from the function prototype/metadata before argument checking, rather than discovering it dynamically during argument processing. For helpers, scan the arg_type array in check_func_proto() via check_proto_release_reg(). For kfuncs, set release_regno to BPF_REG_1 in bpf_fetch_kfunc_arg_meta() when KF_RELEASE is set. In the future when we start adding decl_tag to kfunc arguments, we can just look at the function prototype instead of a release_regno. Extract ref_convert_alloc_rcu_protected() and invalidate_rcu_protected_refs() to make it more clear what the code is doing. For ref_convert_alloc_rcu_protected(), it pre-converts MEM_ALLOC | MEM_PERCPU registers to MEM_RCU (clearing id so they survive), then calls release_reference() to invalidate the remaining registers and release the reference state. Add KF_RELEASE to bpf_dynptr_file_discard() so its release_regno is set via fetch_kfunc_meta rather than being assigned manually in the dynptr argument processing. Set arg_type to ARG_PTR_TO_DYNPTR for KF_ARG_PTR_TO_DYNPTR so that check_func_arg_reg_off() correctly allows non-zero stack offsets for dynptr release arguments same as helper. Acked-by: Eduard Zingerman <eddyz87@gmail.com> Signed-off-by: Amery Hung <ameryhung@gmail.com> Link: https://lore.kernel.org/r/20260529014936.2811085-9-ameryhung@gmail.com Signed-off-by: Alexei Starovoitov <ast@kernel.org>
1 parent b7dd2b3 commit bcfcb15

12 files changed

Lines changed: 122 additions & 114 deletions

File tree

include/linux/bpf_verifier.h

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1426,9 +1426,9 @@ struct bpf_dynptr_desc {
14261426

14271427
/*
14281428
* The last seen rereferenced object; Updated by update_ref_obj() when a register refers to a
1429-
* referenced object. Used when the helper or kfunc is releasing a referenced object, casting
1430-
* a referenced object, returning allocated memory derived from referenced object or creating
1431-
* a dynptr with a referenced object as parent.
1429+
* referenced object. Used when the helper or kfunc is casting a referenced object, returning
1430+
* allocated memory derived from referenced object or creating a dynptr with a referenced
1431+
* object as parent.
14321432
*/
14331433
struct ref_obj_desc {
14341434
u32 id;

kernel/bpf/helpers.c

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -4957,7 +4957,7 @@ BTF_ID_FLAGS(func, bpf_stream_print_stack, KF_IMPLICIT_ARGS)
49574957
BTF_ID_FLAGS(func, bpf_task_work_schedule_signal, KF_IMPLICIT_ARGS)
49584958
BTF_ID_FLAGS(func, bpf_task_work_schedule_resume, KF_IMPLICIT_ARGS)
49594959
BTF_ID_FLAGS(func, bpf_dynptr_from_file)
4960-
BTF_ID_FLAGS(func, bpf_dynptr_file_discard)
4960+
BTF_ID_FLAGS(func, bpf_dynptr_file_discard, KF_RELEASE)
49614961
BTF_ID_FLAGS(func, bpf_timer_cancel_async)
49624962
BTF_KFUNCS_END(common_btf_ids)
49634963

kernel/bpf/verifier.c

Lines changed: 103 additions & 95 deletions
Original file line numberDiff line numberDiff line change
@@ -8225,17 +8225,11 @@ static int check_func_arg(struct bpf_verifier_env *env, u32 arg,
82258225
return err;
82268226

82278227
skip_type_check:
8228-
if (arg_type_is_release(arg_type)) {
8229-
if (!arg_type_is_dynptr(arg_type) && !reg_is_referenced(env, reg) && !bpf_register_is_null(reg)) {
8230-
verbose(env, "R%d must be referenced when passed to release function\n",
8231-
regno);
8232-
return -EINVAL;
8233-
}
8234-
if (meta->release_regno) {
8235-
verifier_bug(env, "more than one release argument");
8236-
return -EFAULT;
8237-
}
8238-
meta->release_regno = regno;
8228+
if (arg_type_is_release(arg_type) && !arg_type_is_dynptr(arg_type) &&
8229+
!reg_is_referenced(env, reg) && !bpf_register_is_null(reg)) {
8230+
verbose(env, "release helper %s expects referenced PTR_TO_BTF_ID passed to %s\n",
8231+
func_id_name(meta->func_id), reg_arg_name(env, argno));
8232+
return -EINVAL;
82398233
}
82408234

82418235
if (reg_is_referenced(env, reg))
@@ -8798,11 +8792,29 @@ static bool check_mem_arg_rw_flag_ok(const struct bpf_func_proto *fn)
87988792
return true;
87998793
}
88008794

8801-
static int check_func_proto(const struct bpf_func_proto *fn)
8795+
static bool check_proto_release_reg(const struct bpf_func_proto *fn, struct bpf_call_arg_meta *meta)
8796+
{
8797+
int i;
8798+
8799+
for (i = 0; i < ARRAY_SIZE(fn->arg_type); i++) {
8800+
enum bpf_arg_type arg_type = fn->arg_type[i];
8801+
8802+
if (arg_type_is_release(arg_type)) {
8803+
if (meta->release_regno)
8804+
return false;
8805+
meta->release_regno = i + 1;
8806+
}
8807+
}
8808+
8809+
return true;
8810+
}
8811+
8812+
static int check_func_proto(const struct bpf_func_proto *fn, struct bpf_call_arg_meta *meta)
88028813
{
88038814
return check_raw_mode_ok(fn) &&
88048815
check_arg_pair_ok(fn) &&
88058816
check_mem_arg_rw_flag_ok(fn) &&
8817+
check_proto_release_reg(fn, meta) &&
88068818
check_btf_id_ok(fn) ? 0 : -EINVAL;
88078819
}
88088820

@@ -8956,6 +8968,42 @@ static void invalidate_non_owning_refs(struct bpf_verifier_env *env)
89568968
}));
89578969
}
89588970

8971+
static void invalidate_rcu_protected_refs(struct bpf_verifier_env *env)
8972+
{
8973+
struct bpf_stack_state *stack;
8974+
struct bpf_func_state *state;
8975+
struct bpf_reg_state *reg;
8976+
u32 clear_mask = (1 << STACK_SPILL) | (1 << STACK_ITER);
8977+
8978+
bpf_for_each_reg_in_vstate_mask(env->cur_state, state, reg, stack, clear_mask, ({
8979+
if (reg->type & MEM_RCU) {
8980+
reg->type &= ~(MEM_RCU | PTR_MAYBE_NULL);
8981+
reg->type |= PTR_UNTRUSTED;
8982+
}
8983+
}));
8984+
}
8985+
8986+
static int ref_convert_alloc_rcu_protected(struct bpf_verifier_env *env, u32 id)
8987+
{
8988+
struct bpf_func_state *state;
8989+
struct bpf_reg_state *reg;
8990+
int err;
8991+
8992+
err = release_reference_nomark(env->cur_state, id);
8993+
8994+
bpf_for_each_reg_in_vstate(env->cur_state, state, reg, ({
8995+
if (reg->id != id)
8996+
continue;
8997+
if ((reg->type & MEM_ALLOC) && (reg->type & MEM_PERCPU)) {
8998+
reg->id = 0;
8999+
reg->type &= ~MEM_ALLOC;
9000+
reg->type |= MEM_RCU;
9001+
}
9002+
}));
9003+
9004+
return err;
9005+
}
9006+
89599007
static void clear_caller_saved_regs(struct bpf_verifier_env *env,
89609008
struct bpf_reg_state *regs)
89619009
{
@@ -10028,6 +10076,24 @@ static const char *non_sleepable_context_description(struct bpf_verifier_env *en
1002810076
return "non-sleepable prog";
1002910077
}
1003010078

10079+
static int release_reg(struct bpf_verifier_env *env, struct bpf_reg_state *reg,
10080+
bool convert_rcu, bool release_dynptr)
10081+
{
10082+
int err = -EINVAL;
10083+
10084+
if (bpf_register_is_null(reg))
10085+
return 0;
10086+
10087+
if (release_dynptr)
10088+
err = unmark_stack_slots_dynptr(env, reg);
10089+
else if (convert_rcu)
10090+
err = ref_convert_alloc_rcu_protected(env, reg->id);
10091+
else if (reg_is_referenced(env, reg))
10092+
err = release_reference(env, reg->id);
10093+
10094+
return err;
10095+
}
10096+
1003110097
static int check_helper_call(struct bpf_verifier_env *env, struct bpf_insn *insn,
1003210098
int *insn_idx_p)
1003310099
{
@@ -10077,7 +10143,7 @@ static int check_helper_call(struct bpf_verifier_env *env, struct bpf_insn *insn
1007710143
memset(&meta, 0, sizeof(meta));
1007810144
meta.pkt_access = fn->pkt_access;
1007910145

10080-
err = check_func_proto(fn);
10146+
err = check_func_proto(fn, &meta);
1008110147
if (err) {
1008210148
verifier_bug(env, "incorrect func proto %s#%d", func_id_name(func_id), func_id);
1008310149
return err;
@@ -10122,37 +10188,11 @@ static int check_helper_call(struct bpf_verifier_env *env, struct bpf_insn *insn
1012210188
}
1012310189

1012410190
if (meta.release_regno) {
10125-
err = -EINVAL;
10126-
if (arg_type_is_dynptr(fn->arg_type[meta.release_regno - BPF_REG_1])) {
10127-
err = unmark_stack_slots_dynptr(env, &regs[meta.release_regno]);
10128-
} else if (func_id == BPF_FUNC_kptr_xchg && meta.ref_obj.id) {
10129-
u32 id = meta.ref_obj.id;
10130-
bool in_rcu = in_rcu_cs(env);
10131-
struct bpf_func_state *state;
10132-
struct bpf_reg_state *reg;
10133-
10134-
err = release_reference_nomark(env->cur_state, id);
10135-
if (!err) {
10136-
bpf_for_each_reg_in_vstate(env->cur_state, state, reg, ({
10137-
if (reg->id == id) {
10138-
if (in_rcu && (reg->type & MEM_ALLOC) && (reg->type & MEM_PERCPU)) {
10139-
reg->id = 0;
10140-
reg->type &= ~MEM_ALLOC;
10141-
reg->type |= MEM_RCU;
10142-
} else {
10143-
mark_reg_invalid(env, reg);
10144-
}
10145-
}
10146-
}));
10147-
}
10148-
} else if (meta.ref_obj.id) {
10149-
err = release_reference(env, meta.ref_obj.id);
10150-
} else if (bpf_register_is_null(&regs[meta.release_regno])) {
10151-
/* meta.ref_obj.id can only be 0 if register that is meant to be
10152-
* released is NULL, which must be > R0.
10153-
*/
10154-
err = 0;
10155-
}
10191+
struct bpf_reg_state *reg = &regs[meta.release_regno];
10192+
bool convert_rcu = (func_id == BPF_FUNC_kptr_xchg) && in_rcu_cs(env) &&
10193+
(reg->type & MEM_ALLOC) && (reg->type & MEM_PERCPU);
10194+
10195+
err = release_reg(env, reg, convert_rcu, !!meta.dynptr.id);
1015610196
if (err)
1015710197
return err;
1015810198
}
@@ -10547,7 +10587,6 @@ static bool is_kfunc_release(struct bpf_kfunc_call_arg_meta *meta)
1054710587
return meta->kfunc_flags & KF_RELEASE;
1054810588
}
1054910589

10550-
1055110590
static bool is_kfunc_destructive(struct bpf_kfunc_call_arg_meta *meta)
1055210591
{
1055310592
return meta->kfunc_flags & KF_DESTRUCTIVE;
@@ -11912,24 +11951,16 @@ static int check_kfunc_args(struct bpf_verifier_env *env, struct bpf_kfunc_call_
1191211951
return -EACCES;
1191311952
}
1191411953

11915-
if (reg_is_referenced(env, reg)) {
11916-
if (is_kfunc_release(meta) && meta->ref_obj.cnt) {
11917-
verbose(env, "more than one arg with referenced id %s %u %u",
11918-
reg_arg_name(env, argno), reg->id,
11919-
meta->ref_obj.id);
11920-
return -EFAULT;
11921-
}
11922-
update_ref_obj(&meta->ref_obj, reg);
11923-
if (is_kfunc_release(meta)) {
11924-
if (regno < 0) {
11925-
verbose(env, "%s release arg cannot be a stack argument\n",
11926-
reg_arg_name(env, argno));
11927-
return -EINVAL;
11928-
}
11929-
meta->release_regno = regno;
11930-
}
11954+
if (regno == meta->release_regno && !is_kfunc_arg_dynptr(meta->btf, &args[i]) &&
11955+
!reg_is_referenced(env, reg) && !bpf_register_is_null(reg)) {
11956+
verbose(env, "release kfunc %s expects referenced PTR_TO_BTF_ID passed to %s\n",
11957+
func_name, reg_arg_name(env, argno));
11958+
return -EINVAL;
1193111959
}
1193211960

11961+
if (reg_is_referenced(env, reg))
11962+
update_ref_obj(&meta->ref_obj, reg);
11963+
1193311964
ref_t = btf_type_skip_modifiers(btf, t->type, &ref_id);
1193411965
ref_tname = btf_name_by_offset(btf, ref_t->name_off);
1193511966

@@ -11993,7 +12024,6 @@ static int check_kfunc_args(struct bpf_verifier_env *env, struct bpf_kfunc_call_
1199312024
}
1199412025
}
1199512026
fallthrough;
11996-
case KF_ARG_PTR_TO_DYNPTR:
1199712027
case KF_ARG_PTR_TO_ITER:
1199812028
case KF_ARG_PTR_TO_LIST_HEAD:
1199912029
case KF_ARG_PTR_TO_LIST_NODE:
@@ -12010,6 +12040,9 @@ static int check_kfunc_args(struct bpf_verifier_env *env, struct bpf_kfunc_call_
1201012040
case KF_ARG_PTR_TO_IRQ_FLAG:
1201112041
case KF_ARG_PTR_TO_RES_SPIN_LOCK:
1201212042
break;
12043+
case KF_ARG_PTR_TO_DYNPTR:
12044+
arg_type = ARG_PTR_TO_DYNPTR;
12045+
break;
1201312046
case KF_ARG_PTR_TO_CTX:
1201412047
arg_type = ARG_PTR_TO_CTX;
1201512048
break;
@@ -12018,7 +12051,7 @@ static int check_kfunc_args(struct bpf_verifier_env *env, struct bpf_kfunc_call_
1201812051
return -EFAULT;
1201912052
}
1202012053

12021-
if (is_kfunc_release(meta) && reg_is_referenced(env, reg))
12054+
if (regno == meta->release_regno)
1202212055
arg_type |= OBJ_RELEASE;
1202312056
ret = check_func_arg_reg_off(env, reg, argno, arg_type);
1202412057
if (ret < 0)
@@ -12083,12 +12116,6 @@ static int check_kfunc_args(struct bpf_verifier_env *env, struct bpf_kfunc_call_
1208312116
dynptr_arg_type |= DYNPTR_TYPE_FILE;
1208412117
} else if (meta->func_id == special_kfunc_list[KF_bpf_dynptr_file_discard]) {
1208512118
dynptr_arg_type |= DYNPTR_TYPE_FILE | OBJ_RELEASE;
12086-
if (regno < 0) {
12087-
verbose(env, "%s release arg cannot be a stack argument\n",
12088-
reg_arg_name(env, argno));
12089-
return -EINVAL;
12090-
}
12091-
meta->release_regno = regno;
1209212119
} else if (meta->func_id == special_kfunc_list[KF_bpf_dynptr_clone] &&
1209312120
(dynptr_arg_type & MEM_UNINIT)) {
1209412121
enum bpf_dynptr_type parent_type = meta->dynptr.type;
@@ -12377,12 +12404,6 @@ static int check_kfunc_args(struct bpf_verifier_env *env, struct bpf_kfunc_call_
1237712404
}
1237812405
}
1237912406

12380-
if (is_kfunc_release(meta) && !meta->release_regno) {
12381-
verbose(env, "release kernel function %s expects refcounted PTR_TO_BTF_ID\n",
12382-
func_name);
12383-
return -EINVAL;
12384-
}
12385-
1238612407
return 0;
1238712408
}
1238812409

@@ -12409,6 +12430,10 @@ int bpf_fetch_kfunc_arg_meta(struct bpf_verifier_env *env,
1240912430

1241012431
meta->kfunc_flags = *kfunc.flags;
1241112432

12433+
/* Only support release referenced argument passed by register */
12434+
if (is_kfunc_release(meta))
12435+
meta->release_regno = BPF_REG_1;
12436+
1241212437
return 0;
1241312438
}
1241412439

@@ -12899,23 +12924,12 @@ static int check_kfunc_call(struct bpf_verifier_env *env, struct bpf_insn *insn,
1289912924
if (rcu_lock) {
1290012925
env->cur_state->active_rcu_locks++;
1290112926
} else if (rcu_unlock) {
12902-
struct bpf_stack_state *stack;
12903-
struct bpf_func_state *state;
12904-
struct bpf_reg_state *reg;
12905-
u32 clear_mask = (1 << STACK_SPILL) | (1 << STACK_ITER);
12906-
1290712927
if (env->cur_state->active_rcu_locks == 0) {
1290812928
verbose(env, "unmatched rcu read unlock (kernel function %s)\n", func_name);
1290912929
return -EINVAL;
1291012930
}
12911-
if (--env->cur_state->active_rcu_locks == 0) {
12912-
bpf_for_each_reg_in_vstate_mask(env->cur_state, state, reg, stack, clear_mask, ({
12913-
if (reg->type & MEM_RCU) {
12914-
reg->type &= ~(MEM_RCU | PTR_MAYBE_NULL);
12915-
reg->type |= PTR_UNTRUSTED;
12916-
}
12917-
}));
12918-
}
12931+
if (--env->cur_state->active_rcu_locks == 0)
12932+
invalidate_rcu_protected_refs(env);
1291912933
} else if (preempt_disable) {
1292012934
env->cur_state->active_preempt_locks++;
1292112935
} else if (preempt_enable) {
@@ -12946,13 +12960,7 @@ static int check_kfunc_call(struct bpf_verifier_env *env, struct bpf_insn *insn,
1294612960
* PTR_TO_BTF_ID in bpf_kfunc_arg_meta, do the release now.
1294712961
*/
1294812962
if (meta.release_regno) {
12949-
struct bpf_reg_state *reg = &regs[meta.release_regno];
12950-
12951-
if (meta.dynptr.id) {
12952-
err = unmark_stack_slots_dynptr(env, reg);
12953-
} else {
12954-
err = release_reference(env, reg->id);
12955-
}
12963+
err = release_reg(env, &regs[meta.release_regno], false, !!meta.dynptr.id);
1295612964
if (err)
1295712965
return err;
1295812966
}

tools/testing/selftests/bpf/prog_tests/cb_refs.c

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -11,7 +11,7 @@ struct {
1111
const char *prog_name;
1212
const char *err_msg;
1313
} cb_refs_tests[] = {
14-
{ "underflow_prog", "must point to scalar, or struct with scalar" },
14+
{ "underflow_prog", "release kfunc bpf_kfunc_call_test_release expects referenced PTR_TO_BTF_ID passed to R1" },
1515
{ "leak_prog", "Possibly NULL pointer passed to helper R2" },
1616
{ "nested_cb", "Unreleased reference id=4 alloc_insn=2" }, /* alloc_insn=2{4,5} */
1717
{ "non_cb_transfer_ref", "Unreleased reference id=4 alloc_insn=1" }, /* alloc_insn=1{1,2} */

tools/testing/selftests/bpf/progs/cgrp_kfunc_failure.c

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -154,7 +154,7 @@ int BPF_PROG(cgrp_kfunc_xchg_unreleased, struct cgroup *cgrp, const char *path)
154154
}
155155

156156
SEC("tp_btf/cgroup_mkdir")
157-
__failure __msg("must be referenced or trusted")
157+
__failure __msg("release kfunc bpf_cgroup_release expects referenced PTR_TO_BTF_ID passed to R1")
158158
int BPF_PROG(cgrp_kfunc_rcu_get_release, struct cgroup *cgrp, const char *path)
159159
{
160160
struct cgroup *kptr;
@@ -191,7 +191,7 @@ int BPF_PROG(cgrp_kfunc_release_untrusted, struct cgroup *cgrp, const char *path
191191
}
192192

193193
SEC("tp_btf/cgroup_mkdir")
194-
__failure __msg("R1 pointer type STRUCT cgroup must point")
194+
__failure __msg("release kfunc bpf_cgroup_release expects referenced PTR_TO_BTF_ID passed to R1")
195195
int BPF_PROG(cgrp_kfunc_release_fp, struct cgroup *cgrp, const char *path)
196196
{
197197
struct cgroup *acquired = (struct cgroup *)&path;
@@ -237,7 +237,7 @@ int BPF_PROG(cgrp_kfunc_release_null, struct cgroup *cgrp, const char *path)
237237
}
238238

239239
SEC("tp_btf/cgroup_mkdir")
240-
__failure __msg("release kernel function bpf_cgroup_release expects")
240+
__failure __msg("release kfunc bpf_cgroup_release expects referenced PTR_TO_BTF_ID passed to R1")
241241
int BPF_PROG(cgrp_kfunc_release_unacquired, struct cgroup *cgrp, const char *path)
242242
{
243243
/* Cannot release trusted cgroup pointer which was not acquired. */

tools/testing/selftests/bpf/progs/map_kptr_fail.c

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -252,7 +252,7 @@ int reject_untrusted_store_to_ref(struct __sk_buff *ctx)
252252
}
253253

254254
SEC("?tc")
255-
__failure __msg("R2 must be referenced")
255+
__failure __msg("release helper bpf_kptr_xchg expects referenced PTR_TO_BTF_ID passed to R2")
256256
int reject_untrusted_xchg(struct __sk_buff *ctx)
257257
{
258258
struct prog_test_ref_kfunc *p;

0 commit comments

Comments
 (0)