From: sashiko-bot@kernel.org
To: "Kumar Kartikeya Dwivedi" <memxor@gmail.com>, bpf@vger.kernel.org
Subject: Re: [RFC PATCH bpf-next v1 09/11] bpf: Let the comparator of bpf_rbtree_add unwind
Date: Wed, 28 Oct 2026 01:21:21 +0000 [thread overview]
Message-ID: <sashiko-outbox-187313@kernel.org> (raw)
In-Reply-To: <d81d996f09e0fd0b07816dcd0541f39c0f451a23.1793147364.git.memxor@gmail.com>
Thank you for your contribution! Sashiko AI review found 2 potential issue(s) to consider:
- [Critical] bpf: clobbered R2 on bpf_rbtree_add() callback unwind causes UAF
- [High] bpf: __bpf_rbtree_add() leaves node->owner poisoned on comparator unwind
--
commit 55e1518aab2a62c50b8098afe20794d027a69689
Author: Kumar Kartikeya Dwivedi <memxor@gmail.com>
bpf: Let the comparator of bpf_rbtree_add unwind
This commit allows the comparator callback of bpf_rbtree_add() and
bpf_rbtree_add_impl() to unwind via unwind_exit. When the comparator
unwinds, the kfunc drops the uninserted node before leaving to the landing
pad, and the verifier invalidates non-owning references and releases the
caller's owning reference on revisit.
> diff --git a/kernel/bpf/helpers.c b/kernel/bpf/helpers.c
> index 648abe1ba63ae..f8708b7181d72 100644
> --- a/kernel/bpf/helpers.c
> +++ b/kernel/bpf/helpers.c
[ ... ]
> @@ -2784,7 +2788,8 @@ static int __bpf_rbtree_add(struct bpf_rb_root *root,
>
> while (*link) {
> parent = *link;
> - if (cb((uintptr_t)node, (uintptr_t)parent, 0, 0, 0)) {
> + bpf_call_cb(cb, node, parent, 0, 0, 0, r0, unwound);
> + if (r0) {
> link = &parent->rb_left;
> } else {
> link = &parent->rb_right;
> @@ -2796,6 +2801,15 @@ static int __bpf_rbtree_add(struct bpf_rb_root *root,
> rb_insert_color_cached(n, (struct rb_root_cached *)root, leftmost);
> WRITE_ONCE(node->owner, root);
> return 0;
> +
> +unwound:
> + /*
> + * The comparator left with unwind_exit. The node is not in the tree, and
> + * the caller has let go of it: drop it, as the error path above does.
> + */
> + __bpf_obj_drop_impl((void *)n - off, rec, false);
> + bpf_unwind_leave();
> + return r0;
> }
[Severity: High]
Does the unwound path in __bpf_rbtree_add() leave node->owner poisoned with
BPF_PTR_POISON when the object survives __bpf_obj_drop_impl()?
At the start of __bpf_rbtree_add() in kernel/bpf/helpers.c, node->owner is
transitioned from NULL to BPF_PTR_POISON before the traversal loop:
if (cmpxchg(&node->owner, NULL, BPF_PTR_POISON)) {
/* Only called from BPF prog, no need to migrate_disable */
__bpf_obj_drop_impl((void *)n - off, rec, false);
return -EINVAL;
}
When the comparator callback cb exits via unwind_exit during the while (*link)
loop, control jumps to unwound after cmpxchg() has already set node->owner to
BPF_PTR_POISON, and before WRITE_ONCE(node->owner, root) is reached.
If the object contains a bpf_refcount field and another active reference to it
exists (for example via bpf_refcount_acquire() before calling bpf_rbtree_add()
or inside the comparator before unwinding), __bpf_obj_drop_impl() decrements
the refcount and returns without freeing the object:
if (rec && rec->refcount_off >= 0 &&
!refcount_dec_and_test((refcount_t *)(p + rec->refcount_off))) {
return;
}
Unlike bpf_rb_root_free(), which clears node->owner back to NULL before
dropping the reference:
/* Ensure __bpf_rbtree_add() sees the node as unlinked. */
smp_store_release(&node->owner, NULL);
__bpf_obj_drop_impl(obj, field->graph_root.value_rec, false);
the unwound path in __bpf_rbtree_add() leaves node->owner set to
BPF_PTR_POISON on the surviving unlinked object, causing any subsequent
bpf_rbtree_add() or bpf_rbtree_add_impl() call on that object to fail the
cmpxchg() check with -EINVAL. Should node->owner be reset to NULL before
calling __bpf_obj_drop_impl() in the unwound path?
> diff --git a/kernel/bpf/verifier.c b/kernel/bpf/verifier.c
> index fd63ff36eb909..50c3d44c37e15 100644
> --- a/kernel/bpf/verifier.c
> +++ b/kernel/bpf/verifier.c
[ ... ]
> @@ -15738,7 +15738,21 @@ static int check_kfunc_call(struct bpf_verifier_env *env, struct bpf_insn *insn,
> id = regs[BPF_REG_2].id;
> insn_aux->insert_off = regs[BPF_REG_2].var_off.value;
> insn_aux->kptr_struct_meta = btf_find_struct_meta(meta.arg_btf, meta.arg_btf_id);
> - ref_convert_owning_non_owning(env, id);
> + /*
> + * The comparator unwound: the node never went in, and the
> + * kfunc dropped it on its way to the pad. The comparator was
> + * handed the node too, and may have unwound with it in r0, so
> + * every non-owning reference goes. Otherwise the node is in
> + * the tree now.
> + */
> + if (env->cur_state->unwound) {
> + invalidate_non_owning_refs(env);
> + err = release_reference(env, id);
> + if (err)
> + return err;
> + } else {
> + ref_convert_owning_non_owning(env, id);
> + }
> }
[Severity: Critical]
Can an unwinding comparator callback with a two-register return type clobber
caller->regs[BPF_REG_2] in prepare_func_exit(), causing check_kfunc_call() to
release the wrong reference ID and leave a dangling owning pointer to the
dropped node?
During verifier setup, bpf_compute_subprog_ret_regs() sets ret_reg_pair = true
for any subprogram whose BTF return type is a 9-16 byte struct, and
btf_validate_return_type() allows static subprograms to return structs up to
16 bytes. Neither check_func_arg(), push_callback_call(), nor
check_cb_unwind() prevents the less callback passed to bpf_rbtree_add() or
bpf_rbtree_add_impl() from having ret_reg_pair == true.
When such a callback executes unwind_exit, prepare_func_exit() in
kernel/bpf/verifier.c computes nregs = 2 (where ret_regs[] = { BPF_REG_0,
BPF_REG_2 }) and copies both registers from the callee frame into the caller
frame before rewinding *insn_idx to callee->callsite:
callee = state->frame[state->curframe];
r0 = &callee->regs[BPF_REG_0];
nregs = bpf_ret_reg_pair(env, callee->subprogno) ? 2 : 1;
...
if (callee->in_callback_fn && !unwinds) {
...
} else {
for (i = 0; i < nregs; i++) {
u32 regno = ret_regs[i];
bpf_diag_mod_begin(env, &caller->regs[regno], &callee->regs[regno],
BPF_DIAG_MOD_WRITE);
caller->regs[regno] = callee->regs[regno];
bpf_diag_mod_end(env);
}
}
If the caller passed &n1->node (with id = n1_id) in BPF_REG_2 while keeping a
copy of n1 in a callee-saved register (R6-R9) or stack spill, and the
comparator callback acquires an owning reference to a second node n2 of the
same BTF type (for example via bpf_refcount_acquire() or
bpf_list_pop_front()), places &n2->node (with id = n2_id) into BPF_REG_2, and
executes unwind_exit:
1. prepare_func_exit() overwrites caller->regs[BPF_REG_2] with &n2->node
(id = n2_id).
2. On the revisit of check_kfunc_call() at bpf_rbtree_add() with
env->cur_state->unwound == true, check_func_args() succeeds because
caller->regs[BPF_REG_2] holds a valid owning pointer of the expected
node type, and release_reference(env, id) releases n2_id instead of n1_id.
3. At runtime, __bpf_rbtree_add() drops the caller's original node n1 via
__bpf_obj_drop_impl(), while in the verifier state at the landing pad after
unwind_goto, n1_id was never released and the caller's saved pointer to n1
remains a valid owning PTR_TO_BTF_ID | MEM_ALLOC pointer to freed memory.
--
Sashiko AI review · https://sashiko.dev/#/patchset/cover.1793147364.git.memxor@gmail.com?part=9
next prev parent reply other threads:[~2026-10-28 1:21 UTC|newest]
Thread overview: 46+ messages / expand[flat|nested] mbox.gz Atom feed top
2026-10-28 0:44 [RFC PATCH bpf-next v1 00/11] Unwinding for synchronous callbacks Kumar Kartikeya Dwivedi
2026-10-28 0:44 ` [RFC PATCH bpf-next v1 01/11] bpf: Give a helper a way to call its callback with a pad Kumar Kartikeya Dwivedi
2026-10-28 1:20 ` sashiko-bot
2026-10-28 2:15 ` Kumar Kartikeya Dwivedi
2026-10-28 1:39 ` bot+bpf-ci
2026-10-28 2:15 ` Kumar Kartikeya Dwivedi
2026-10-28 0:44 ` [RFC PATCH bpf-next v1 02/11] bpf, arm64: Let the pad after a helper call have r0 Kumar Kartikeya Dwivedi
2026-10-28 1:02 ` sashiko-bot
2026-10-28 2:15 ` Kumar Kartikeya Dwivedi
2026-10-28 0:44 ` [RFC PATCH bpf-next v1 03/11] bpf: Let a callback unwind into the pad of its helper Kumar Kartikeya Dwivedi
2026-10-28 1:31 ` sashiko-bot
2026-10-28 2:15 ` Kumar Kartikeya Dwivedi
2026-10-28 1:39 ` bot+bpf-ci
2026-10-28 2:15 ` Kumar Kartikeya Dwivedi
2026-10-28 0:44 ` [RFC PATCH bpf-next v1 04/11] bpf: Give the inlined bpf_loop a pad Kumar Kartikeya Dwivedi
2026-10-28 1:10 ` sashiko-bot
2026-10-28 2:15 ` Kumar Kartikeya Dwivedi
2026-10-28 0:44 ` [RFC PATCH bpf-next v1 05/11] bpf: Let the callback of bpf_loop unwind Kumar Kartikeya Dwivedi
2026-10-28 1:19 ` sashiko-bot
2026-10-28 2:15 ` Kumar Kartikeya Dwivedi
2026-10-28 1:25 ` bot+bpf-ci
2026-10-28 2:15 ` Kumar Kartikeya Dwivedi
2026-10-28 0:44 ` [RFC PATCH bpf-next v1 06/11] bpf: Let the callback of bpf_for_each_map_elem unwind Kumar Kartikeya Dwivedi
2026-10-28 1:25 ` bot+bpf-ci
2026-10-28 2:15 ` Kumar Kartikeya Dwivedi
2026-10-28 0:44 ` [RFC PATCH bpf-next v1 07/11] bpf: Let the callback of bpf_find_vma unwind Kumar Kartikeya Dwivedi
2026-10-28 1:08 ` sashiko-bot
2026-10-28 2:15 ` Kumar Kartikeya Dwivedi
2026-10-28 0:44 ` [RFC PATCH bpf-next v1 08/11] bpf: Let the callback of bpf_user_ringbuf_drain unwind Kumar Kartikeya Dwivedi
2026-10-28 1:01 ` sashiko-bot
2026-10-28 1:04 ` Kumar Kartikeya Dwivedi
2026-10-28 0:44 ` [RFC PATCH bpf-next v1 09/11] bpf: Let the comparator of bpf_rbtree_add unwind Kumar Kartikeya Dwivedi
2026-10-28 1:21 ` sashiko-bot [this message]
2026-10-28 2:15 ` Kumar Kartikeya Dwivedi
2026-10-28 1:25 ` bot+bpf-ci
2026-10-28 2:15 ` Kumar Kartikeya Dwivedi
2026-10-28 0:44 ` [RFC PATCH bpf-next v1 10/11] selftests/bpf: Test callbacks that unwind Kumar Kartikeya Dwivedi
2026-10-28 1:25 ` bot+bpf-ci
2026-10-28 2:15 ` Kumar Kartikeya Dwivedi
2026-10-28 0:44 ` [RFC PATCH bpf-next v1 11/11] selftests/bpf: Test what a callback unwinds with Kumar Kartikeya Dwivedi
2026-10-28 5:06 ` [RFC PATCH bpf-next v1 00/11] Unwinding for synchronous callbacks Alexei Starovoitov
2026-10-28 10:20 ` Kumar Kartikeya Dwivedi
2026-10-28 20:17 ` Alexei Starovoitov
2026-10-28 21:42 ` Kumar Kartikeya Dwivedi
2026-10-28 23:12 ` Eduard Zingerman
2026-10-29 0:14 ` Alexei Starovoitov
Reply instructions:
You may reply publicly to this message via plain-text email
using any one of the following methods:
* Save the following mbox file, import it into your mail client,
and reply-to-all from there: mbox
Avoid top-posting and favor interleaved quoting:
https://en.wikipedia.org/wiki/Posting_style#Interleaved_style
* Reply using the --to, --cc, and --in-reply-to
switches of git-send-email(1):
git send-email \
--in-reply-to=sashiko-outbox-187313@kernel.org \
--to=sashiko-bot@kernel.org \
--cc=bpf@vger.kernel.org \
--cc=memxor@gmail.com \
--cc=sashiko-reviews@lists.linux.dev \
/path/to/YOUR_REPLY
https://kernel.org/pub/software/scm/git/docs/git-send-email.html
* If your mail client supports setting the In-Reply-To header
via mailto: links, try the mailto: link
Be sure your reply has a Subject: header at the top and a blank line
before the message body.
This is an external index of several public inboxes,
see mirroring instructions on how to clone and mirror
all data and code used by this external index.