[PATCH bpf-next v2 2/3] bpf: Fix prog UAF in bpf_trampoline_multi_attach() register-path rollback
"Hui Zhu" <[email protected]> Wed, 5 Aug 2026 12:04:07 +0800
| Newsgroups | org.kernel.vger.linux-trace-kernel,org.kernel.vger.bpf,org.kernel.vger.linux-kernel |
|---|---|
| Message-ID | <6edcc3d19ab91511372a9c3f00d90fdc66a31d83.1785902527.git.zhuhui@kylinos.cn> |
From: Hui Zhu <[email protected]> In bpf_trampoline_multi_attach(), if update_ftrace_direct_mod() fails, the rollback tries to undo update_ftrace_direct_add() for register-path mnodes via update_ftrace_direct_del(). If that undo also fails, ftrace still calls into rtr->cur_image, but the unconditional rollback frees it -- a UAF of both the image and the prog baked into it. Fix: for register-path mnodes (old_image == NULL) whose undo failed while cur_image is set, pin the prog on cur_image instead of rolling back, reusing the pinned_prog mechanism from the detach path. Fixes: aef4dfa790b2 ("bpf: Add bpf_trampoline_multi_attach/detach functions") Signed-off-by: Hui Zhu <[email protected]> --- kernel/bpf/trampoline.c | 51 +++++++++++++++++++++++++++++++++++++---- 1 file changed, 46 insertions(+), 5 deletions(-) diff --git a/kernel/bpf/trampoline.c b/kernel/bpf/trampoline.c index c08d1a09e638..7fac27374ece 100644 --- a/kernel/bpf/trampoline.c +++ b/kernel/bpf/trampoline.c @@ -1668,7 +1668,7 @@ int bpf_trampoline_multi_attach(struct bpf_prog *prog, u32 *ids, struct btf *btf = prog->aux->attach_btf; struct bpf_tracing_multi_node *mnode; struct bpf_trampoline *tr; - int i, err, rollback_cnt; + int i, err, rollback_cnt, err_undo_reg = 0; u64 key; for_each_mnode(mnode, link) { @@ -1728,8 +1728,10 @@ int bpf_trampoline_multi_attach(struct bpf_prog *prog, u32 *ids, if (ftrace_hash_count(data->modify)) { err = update_ftrace_direct_mod(&direct_ops, data->modify, true); if (err) { - if (ftrace_hash_count(data->reg)) - WARN_ON_ONCE(update_ftrace_direct_del(&direct_ops, data->reg)); + if (ftrace_hash_count(data->reg)) { + err_undo_reg = update_ftrace_direct_del(&direct_ops, data->reg); + WARN_ON_ONCE(err_undo_reg); + } goto rollback_unlink; } } @@ -1744,8 +1746,47 @@ int bpf_trampoline_multi_attach(struct bpf_prog *prog, u32 *ids, rollback_unlink: for_each_mnode_cnt(mnode, link, rollback_cnt) { - bpf_trampoline_remove_prog(mnode->trampoline, &mnode->node); - bpf_trampoline_multi_attach_rollback(mnode->trampoline); + struct bpf_trampoline *rtr = mnode->trampoline; + /* + * register_fentry_multi()/modify_fentry_multi() set + * rtr->cur_image before any ftrace call is made, and + * bpf_trampoline_multi_attach_init() captured whatever was + * live before that into rtr->multi_attach.old_image. A NULL + * old_image means this ip had no prior direct caller, i.e. + * this mnode went through the "register" (data->reg) path + * rather than "modify" (data->modify). + */ + bool via_register = !rtr->multi_attach.old_image; + + bpf_trampoline_remove_prog(rtr, &mnode->node); + + /* + * If this mnode used the register path and the + * update_ftrace_direct_del() above meant to undo its + * earlier, successful update_ftrace_direct_add() failed, + * ftrace is still actually calling into rtr->cur_image + * (which has @prog's call baked into its machine code) even + * though this attach is being reported as failed. Freeing + * rtr->cur_image via the normal rollback (which would also + * let the caller free @prog once this function returns its + * error) would be a use-after-free, so instead pin @prog on + * it and leave rtr->cur_image untouched: rtr->multi_attach + * is a scratch area only meaningful between _init() and + * _free()/_rollback(), so skipping _rollback() here does + * not leave it in an inconsistent state (old_image is NULL + * on the register path anyway). This image (and the pinned + * prog reference) is subsequently either properly retired by + * a later, successful update on the same trampoline, or + * safely leaked when the trampoline is torn down - see + * bpf_trampoline_multi_attach_free() and bpf_trampoline_put(). + */ + if (via_register && err_undo_reg && rtr->cur_image) { + WARN_ON_ONCE(rtr->cur_image->pinned_prog); + bpf_prog_inc(prog); + rtr->cur_image->pinned_prog = prog; + } else { + bpf_trampoline_multi_attach_rollback(rtr); + } } trampoline_unlock_all(); -- 2.53.0