> diff --git a/kernel/bpf/trampoline.c b/kernel/bpf/trampoline.c
> index 2d701bc6e1a5..c32205adfebe 100644
> --- a/kernel/bpf/trampoline.c
> +++ b/kernel/bpf/trampoline.c
[ ... ]
> +static int unregister_fentry_multi(struct bpf_trampoline *tr, u32
> orig_flags, void *old_addr,
> + void *ptr)
> +{
> + unsigned long addr = (unsigned long) old_addr;
> + unsigned long ip = ftrace_location(tr->ip);
> + struct fentry_multi_data *data = ptr;
> +
> + if (bpf_trampoline_use_jmp(tr->flags))
> + addr = ftrace_jmp_set(addr);
> + return add_ftrace_hash_entry_direct(data->unreg, ip, addr) ? 0 :
> -ENOMEM;
> +}
[ ... ]
> +int bpf_trampoline_multi_attach(struct bpf_prog *prog, u32 *ids,
> + struct bpf_tracing_multi_link *link)
> +{
> + struct bpf_attach_target_info tgt_info = {};
> + struct bpf_tracing_multi_node *mnode;
> + int j, i, err, cnt = link->nodes_cnt;
> + struct fentry_multi_data data = {};
> + struct bpf_trampoline *tr;
> + u64 key;
> +
> + data.reg = alloc_ftrace_hash(FTRACE_HASH_DEFAULT_BITS);
> + if (!data.reg)
> + return -ENOMEM;
> +
> + data.modify = alloc_ftrace_hash(FTRACE_HASH_DEFAULT_BITS);
> + if (!data.modify) {
> + free_ftrace_hash(data.reg);
> + return -ENOMEM;
> + }
data.unreg is left as NULL here. In the forward path this is
fine since only data.reg and data.modify are used, but the
rollback path can reach unregister_fentry_multi() which calls
add_ftrace_hash_entry_direct(data->unreg, ...) with a NULL hash.
This happens when rollback_unlink calls
__bpf_trampoline_unlink_prog() on a trampoline that had no
other BPF programs before this attach. Removing the only
program brings total to 0, so bpf_trampoline_update_ops()
calls ops->unregister_fentry, reaching:
unregister_fentry_multi()
add_ftrace_hash_entry_direct(data->unreg, ip, addr)
__add_hash_entry(hash, entry)
ftrace_hash_key(hash, ...) <-- NULL dereference
Should data.unreg also be allocated here to cover the rollback
path?
> +
> + for (i = 0; i < cnt; i++) {
> + mnode = &link->nodes[i];
> + err = bpf_check_attach_target(NULL, prog, NULL, ids[i],
> &tgt_info);
> + if (err)
> + goto rollback_put;
> +
> + key = bpf_trampoline_compute_key(NULL, prog->aux->attach_btf,
> ids[i]);
> +
> + tr = bpf_trampoline_get(key, &tgt_info);
> + if (!tr) {
> + err = -ENOMEM;
> + goto rollback_put;
> + }
> +
> + mnode->trampoline = tr;
> + mnode->node.link = &link->link;
> + }
After this loop completes, all cnt trampolines have been
obtained via bpf_trampoline_get() with refcounts incremented.
> +
> + trampoline_lock_all();
> +
> + for (i = 0; i < cnt; i++) {
> + mnode = &link->nodes[i];
> + err = __bpf_trampoline_link_prog(&mnode->node,
> mnode->trampoline, NULL,
> + &trampoline_multi_ops, &data);
> + if (err)
> + goto rollback_unlink;
> + }
[ ... ]
> +rollback_unlink:
> + for (j = 0; j < i; j++) {
> + mnode = &link->nodes[j];
> + WARN_ON_ONCE(__bpf_trampoline_unlink_prog(&mnode->node,
> mnode->trampoline,
> + NULL, &trampoline_multi_ops, &data));
> + }
> + trampoline_unlock_all();
> +
> +rollback_put:
> + for (j = 0; j < i; j++)
> + bpf_trampoline_put(link->nodes[j].trampoline);
When __bpf_trampoline_link_prog() fails at index i in the
second loop, this falls through from rollback_unlink to
rollback_put with i still set to the failing index. But all
cnt trampolines were obtained in the first loop above.
The rollback_put loop only puts trampolines 0..i-1, leaving
trampolines i..cnt-1 with elevated refcounts that are never
released.
When rollback_put is reached directly from the first loop
(bpf_trampoline_get or bpf_check_attach_target failure), the
j < i bound is correct since only 0..i-1 were obtained.
Could the rollback_unlink path set i = cnt before falling
through, or use a separate loop bound for the put?
> +
> + free_fentry_multi_data(&data);
> + return err;
> +}
[ ... ]
---
AI reviewed your patch. Please fix the bug or email reply why it's not a bug.
See: https://github.com/kernel-patches/vmtest/blob/master/ci/claude/README.md
CI run summary: https://github.com/kernel-patches/bpf/actions/runs/22220470181
AI-authorship-score: low
AI-authorship-explanation: Natural kernel development style from a known BPF
maintainer, with human-typical error handling oversights in batch operation
code.
issues-found: 2
issue-severity-score: high
issue-severity-explanation: NULL pointer dereference in error rollback path
causes kernel crash, combined with trampoline refcount leak preventing resource
cleanup.