* [PATCH v4] bpf: Fix UAF in bpf_trampoline_multi_attach_free on update failure
@ 2026-08-10 6:13 Hui Zhu
2026-08-10 6:27 ` sashiko-bot
2026-08-10 7:16 ` bot+bpf-ci
0 siblings, 2 replies; 3+ messages in thread
From: Hui Zhu @ 2026-08-10 6:13 UTC (permalink / raw)
To: Alexei Starovoitov, Daniel Borkmann, John Fastabend,
Andrii Nakryiko, Eduard Zingerman, Kumar Kartikeya Dwivedi,
Martin KaFai Lau, Song Liu, Yonghong Song, Jiri Olsa,
Emil Tsalapatis, Ihor Solodrai, KP Singh, Matt Bobrowski,
Steven Rostedt, Masami Hiramatsu, Mathieu Desnoyers, bpf,
linux-kernel, linux-trace-kernel
Cc: Hui Zhu
From: Hui Zhu <zhuhui@kylinos.cn>
When bpf_trampoline_update() fails before modify_fentry_multi()/
unregister_fentry_multi() is called, cur_image is unchanged
(cur_image == old_image) and ftrace still calls into it. Freeing
old_image in that case causes a UAF.
Only free old_image when it differs from cur_image.
Also change bpf_trampoline_multi_detach() to return void and drop
the WARN_ON_ONCE at its call site.
Fixes: aef4dfa790b2 ("bpf: Add bpf_trampoline_multi_attach/detach functions")
Signed-off-by: Hui Zhu <zhuhui@kylinos.cn>
---
Changelog:
v4:
According to the comments of bot+bpf-ci, add Fixes: and update comments
of bpf_trampoline_multi_attach_free.
v3:
According to the comments of Jiri Olsa, drop patches 2/3 and the
prog-side machinery.
keep only the simplified image-side fix in
bpf_trampoline_multi_attach_free() and make
bpf_trampoline_multi_detach() return void.
v2:
Folded v1's two detach patches into patch 1.
According to the comments of Jiri Olsa, Pin the prog (pinned_prog) on
cur_image so it stays alive while ftrace may still call into it.
Make bpf_trampoline_multi_detach() return void.
Fix the same UAF in standard (non-multi) trampolines.
According to the comments of sashiko, Fix the prog UAF in
bpf_trampoline_multi_attach() rollback.
Leak the trampoline in bpf_trampoline_put() when cur_image is left
by a rollback.
include/linux/bpf.h | 9 ++++-----
kernel/bpf/trampoline.c | 16 +++++++++++++---
kernel/trace/bpf_trace.c | 2 +-
3 files changed, 18 insertions(+), 9 deletions(-)
diff --git a/include/linux/bpf.h b/include/linux/bpf.h
index 73bacfc6444d..3255c6f5ea6b 100644
--- a/include/linux/bpf.h
+++ b/include/linux/bpf.h
@@ -1518,8 +1518,8 @@ int arch_prepare_bpf_dispatcher(void *image, void *buf, s64 *funcs, int num_func
int bpf_trampoline_multi_attach(struct bpf_prog *prog, u32 *ids,
struct bpf_tracing_multi_link *link);
-int bpf_trampoline_multi_detach(struct bpf_prog *prog,
- struct bpf_tracing_multi_link *link);
+void bpf_trampoline_multi_detach(struct bpf_prog *prog,
+ struct bpf_tracing_multi_link *link);
void bpf_trampoline_set_flags(struct bpf_trampoline *tr, u32 flags);
/*
@@ -1639,10 +1639,9 @@ static inline int bpf_trampoline_multi_attach(struct bpf_prog *prog, u32 *ids,
{
return -ENOTSUPP;
}
-static inline int bpf_trampoline_multi_detach(struct bpf_prog *prog,
- struct bpf_tracing_multi_link *link)
+static inline void bpf_trampoline_multi_detach(struct bpf_prog *prog,
+ struct bpf_tracing_multi_link *link)
{
- return -ENOTSUPP;
}
static inline void bpf_trampoline_set_flags(struct bpf_trampoline *tr, u32 flags) {}
#endif
diff --git a/kernel/bpf/trampoline.c b/kernel/bpf/trampoline.c
index ed7999ad6c66..e30425789487 100644
--- a/kernel/bpf/trampoline.c
+++ b/kernel/bpf/trampoline.c
@@ -1595,7 +1595,17 @@ static void bpf_trampoline_multi_attach_init(struct bpf_trampoline *tr)
static void bpf_trampoline_multi_attach_free(struct bpf_trampoline *tr)
{
- if (tr->multi_attach.old_image)
+ /*
+ * Only free old_image if it is no longer the active image.
+ * When bpf_trampoline_update() fails before modify_fentry_multi()/
+ * unregister_fentry_multi() is called, cur_image is unchanged
+ * (cur_image == old_image) and ftrace still points to it. Freeing
+ * it would cause a UAF when ftrace calls into the freed memory.
+ * On success, cur_image is either a new image or NULL, so
+ * old_image != cur_image means the image is stale.
+ */
+ if (tr->multi_attach.old_image &&
+ tr->multi_attach.old_image != tr->cur_image)
bpf_tramp_image_put(tr->multi_attach.old_image);
tr->multi_attach.old_image = NULL;
@@ -1719,7 +1729,8 @@ int bpf_trampoline_multi_attach(struct bpf_prog *prog, u32 *ids,
return err;
}
-int bpf_trampoline_multi_detach(struct bpf_prog *prog, struct bpf_tracing_multi_link *link)
+void bpf_trampoline_multi_detach(struct bpf_prog *prog,
+ struct bpf_tracing_multi_link *link)
{
struct bpf_tracing_multi_data *data = &link->data;
struct bpf_tracing_multi_node *mnode;
@@ -1749,7 +1760,6 @@ int bpf_trampoline_multi_detach(struct bpf_prog *prog, struct bpf_tracing_multi_
bpf_trampoline_put(mnode->trampoline);
clear_tracing_multi_data(data);
- return 0;
}
#undef for_each_mnode_cnt
diff --git a/kernel/trace/bpf_trace.c b/kernel/trace/bpf_trace.c
index 891897f8a1b3..29260951aa87 100644
--- a/kernel/trace/bpf_trace.c
+++ b/kernel/trace/bpf_trace.c
@@ -3687,7 +3687,7 @@ static void bpf_tracing_multi_link_release(struct bpf_link *link)
struct bpf_tracing_multi_link *tr_link =
container_of(link, struct bpf_tracing_multi_link, link);
- WARN_ON_ONCE(bpf_trampoline_multi_detach(link->prog, tr_link));
+ bpf_trampoline_multi_detach(link->prog, tr_link);
}
static void bpf_tracing_multi_link_dealloc(struct bpf_link *link)
--
2.53.0
^ permalink raw reply related [flat|nested] 3+ messages in thread
* Re: [PATCH v4] bpf: Fix UAF in bpf_trampoline_multi_attach_free on update failure
2026-08-10 6:13 [PATCH v4] bpf: Fix UAF in bpf_trampoline_multi_attach_free on update failure Hui Zhu
@ 2026-08-10 6:27 ` sashiko-bot
2026-08-10 7:16 ` bot+bpf-ci
1 sibling, 0 replies; 3+ messages in thread
From: sashiko-bot @ 2026-08-10 6:27 UTC (permalink / raw)
To: Hui Zhu; +Cc: bpf
Thank you for your contribution! Sashiko AI review found 2 potential issue(s) to consider:
Pre-existing issues:
- [High] Ignoring errors from `__bpf_trampoline_unlink_prog()` during detach leads to a Use-After-Free of the BPF program.
- [High] Ignoring errors from `update_ftrace_direct_del/mod()` during detach or attach rollback leads to a Use-After-Free of the trampoline image.
--
--- Patch [1]: [PATCH v4] bpf: Fix UAF in bpf_trampoline_multi_attach_free on update failure ---
commit 12049949b83ebddc54a6fdad57fa0b635706dbda
Author: Hui Zhu <zhuhui@kylinos.cn>
bpf: Fix UAF in bpf_trampoline_multi_attach_free on update failure
When bpf_trampoline_update() fails before modify_fentry_multi() or
unregister_fentry_multi() is called, cur_image is unchanged and ftrace still
calls into it. Freeing old_image in that case causes a UAF. This patch only
frees old_image when it differs from cur_image, and makes
bpf_trampoline_multi_detach() return void.
> diff --git a/kernel/bpf/trampoline.c b/kernel/bpf/trampoline.c
> index e07af35ed0402..90b70ea0d3709 100644
> --- a/kernel/bpf/trampoline.c
> +++ b/kernel/bpf/trampoline.c
> @@ -1632,7 +1632,17 @@ static void bpf_trampoline_multi_attach_init(struct bpf_trampoline *tr)
>
> static void bpf_trampoline_multi_attach_free(struct bpf_trampoline *tr)
> {
> - if (tr->multi_attach.old_image)
> + /*
> + * Only free old_image if it is no longer the active image.
> + * When bpf_trampoline_update() fails before modify_fentry_multi()/
> + * unregister_fentry_multi() is called, cur_image is unchanged
> + * (cur_image == old_image) and ftrace still points to it. Freeing
> + * it would cause a UAF when ftrace calls into the freed memory.
> + * On success, cur_image is either a new image or NULL, so
> + * old_image != cur_image means the image is stale.
> + */
> + if (tr->multi_attach.old_image &&
> + tr->multi_attach.old_image != tr->cur_image)
[Severity: High]
This is a pre-existing issue, but does this condition still allow a
use-after-free of the trampoline image if ftrace updates fail?
If __bpf_trampoline_unlink_prog() succeeds in bpf_trampoline_multi_detach(),
the active image is updated and old_image != cur_image will be true.
However, if the subsequent update to ftrace fails due to memory pressure:
if (ftrace_hash_count(data->unreg))
WARN_ON_ONCE(update_ftrace_direct_del(&direct_ops, data->unreg));
ftrace will return an error without updating its hooks, leaving ftrace
pointing to the original active image (old_image).
Since old_image != cur_image is true, won't bpf_trampoline_multi_attach_free()
then free old_image while ftrace is still executing it?
A similar issue appears to exist during error rollback in
kernel/bpf/trampoline.c:bpf_trampoline_multi_attach() if
update_ftrace_direct_del() fails, leaving ftrace executing newly allocated
images that are subsequently freed.
> bpf_tramp_image_put(tr->multi_attach.old_image);
>
> tr->multi_attach.old_image = NULL;
[ ... ]
> diff --git a/kernel/trace/bpf_trace.c b/kernel/trace/bpf_trace.c
> index 891897f8a1b35..29260951aa871 100644
> --- a/kernel/trace/bpf_trace.c
> +++ b/kernel/trace/bpf_trace.c
> @@ -3687,7 +3687,7 @@ static void bpf_tracing_multi_link_release(struct bpf_link *link)
> struct bpf_tracing_multi_link *tr_link =
> container_of(link, struct bpf_tracing_multi_link, link);
>
> - WARN_ON_ONCE(bpf_trampoline_multi_detach(link->prog, tr_link));
> + bpf_trampoline_multi_detach(link->prog, tr_link);
[Severity: High]
This is a pre-existing issue, but does dropping the program reference here
lead to a use-after-free if __bpf_trampoline_unlink_prog() fails?
In kernel/bpf/trampoline.c:bpf_trampoline_multi_detach(), if unlink fails:
err = __bpf_trampoline_unlink_prog(&mnode->node, mnode->trampoline, NULL,
&trampoline_multi_ops, data);
WARN_ONCE(err, "__bpf_trampoline_unlink_prog failed: %d\n", err);
the detach path ignores the error. The ftrace direct hook remains active and
the active trampoline image remains unchanged, keeping its hardcoded call
to the BPF program.
Since bpf_tracing_multi_link_release() proceeds to drop the link's reference
to the BPF program, if the program's refcount drops to zero and it is freed,
could ftrace continue to jump into the freed program?
> }
>
> static void bpf_tracing_multi_link_dealloc(struct bpf_link *link)
--
Sashiko AI review · https://sashiko.dev/#/patchset/20260810061329.224276-1-hui.zhu@linux.dev?part=1
^ permalink raw reply [flat|nested] 3+ messages in thread
* Re: [PATCH v4] bpf: Fix UAF in bpf_trampoline_multi_attach_free on update failure
2026-08-10 6:13 [PATCH v4] bpf: Fix UAF in bpf_trampoline_multi_attach_free on update failure Hui Zhu
2026-08-10 6:27 ` sashiko-bot
@ 2026-08-10 7:16 ` bot+bpf-ci
1 sibling, 0 replies; 3+ messages in thread
From: bot+bpf-ci @ 2026-08-10 7:16 UTC (permalink / raw)
To: hui.zhu, ast, daniel, john.fastabend, andrii, eddyz87, memxor,
martin.lau, song, yonghong.song, jolsa, emil, ihor.solodrai,
kpsingh, matt, rostedt, mhiramat, mathieu.desnoyers, bpf,
linux-kernel, linux-trace-kernel
Cc: zhuhui, ast, andrii, daniel, martin.lau, eddyz87, yonghong.song,
clm, ihor.solodrai
[-- Attachment #1: Type: text/plain, Size: 2149 bytes --]
> diff --git a/include/linux/bpf.h b/include/linux/bpf.h
> index b4a10c9878cf..f4e8d372253a 100644
> --- a/include/linux/bpf.h
> +++ b/include/linux/bpf.h
> @@ -1518,8 +1518,8 @@ int arch_prepare_bpf_dispatcher(void *image, void *buf, s64 *funcs, int num_func
>
> int bpf_trampoline_multi_attach(struct bpf_prog *prog, u32 *ids,
> struct bpf_tracing_multi_link *link);
> -int bpf_trampoline_multi_detach(struct bpf_prog *prog,
> - struct bpf_tracing_multi_link *link);
> +void bpf_trampoline_multi_detach(struct bpf_prog *prog,
> + struct bpf_tracing_multi_link *link);
[ ... ]
> diff --git a/kernel/bpf/trampoline.c b/kernel/bpf/trampoline.c
> index e07af35ed040..90b70ea0d370 100644
> --- a/kernel/bpf/trampoline.c
> +++ b/kernel/bpf/trampoline.c
[ ... ]
> @@ -1756,7 +1766,8 @@ int bpf_trampoline_multi_attach(struct bpf_prog *prog, u32 *ids,
> return err;
> }
>
> -int bpf_trampoline_multi_detach(struct bpf_prog *prog, struct bpf_tracing_multi_link *link)
> +void bpf_trampoline_multi_detach(struct bpf_prog *prog,
> + struct bpf_tracing_multi_link *link)
[ ... ]
> diff --git a/kernel/trace/bpf_trace.c b/kernel/trace/bpf_trace.c
> index 891897f8a1b3..29260951aa87 100644
> --- a/kernel/trace/bpf_trace.c
> +++ b/kernel/trace/bpf_trace.c
> @@ -3687,7 +3687,7 @@ static void bpf_tracing_multi_link_release(struct bpf_link *link)
> struct bpf_tracing_multi_link *tr_link =
> container_of(link, struct bpf_tracing_multi_link, link);
>
> - WARN_ON_ONCE(bpf_trampoline_multi_detach(link->prog, tr_link));
> + bpf_trampoline_multi_detach(link->prog, tr_link);
> }
This isn't a bug, but would it be worth splitting the return-type
change into its own patch?
The int-to-void conversion of bpf_trampoline_multi_detach() touches
both bpf.h prototypes, the definition and the bpf_trace.c call site,
but is independent of the old_image lifetime fix that the Fixes: tag
refers to.
---
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/31362089875
^ permalink raw reply [flat|nested] 3+ messages in thread
end of thread, other threads:[~2026-08-10 7:16 UTC | newest]
Thread overview: 3+ messages (download: mbox.gz follow: Atom feed
-- links below jump to the message on this page --
2026-08-10 6:13 [PATCH v4] bpf: Fix UAF in bpf_trampoline_multi_attach_free on update failure Hui Zhu
2026-08-10 6:27 ` sashiko-bot
2026-08-10 7:16 ` bot+bpf-ci
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.