arch_prepare_bpf_trampoline() embeds program pointers in the generated image and passes them to __bpf_prog_enter_recur(). After bpf_trampoline_update() replaces the image, bpf_tramp_image_put() can keep the old image executable past a normal RCU grace period. A detached non-sleepable prog can therefore be freed before a preempted task reaches rcu_read_lock_dont_migrate() in __bpf_prog_enter_recur(), leading to a use-after-free. Keep image-local prog refs and drop them from bpf_tramp_image_free(). Fixes: e21aa341785c ("bpf: Fix fexit trampoline.") Reported-by: Sechang Lim Signed-off-by: Junseo Lim --- v1 -> v2: - Factor prog ref get/put logic into helpers, per Leon Hwang's suggestion. v1: https://lore.kernel.org/bpf/20260815071927.147049-1-zirajs7@gmail.com/T/ include/linux/bpf.h | 5 +++++ kernel/bpf/trampoline.c | 30 ++++++++++++++++++++++++++++++ 2 files changed, 35 insertions(+) diff --git a/include/linux/bpf.h b/include/linux/bpf.h index 7719f6528445..bacb6bc2e27b 100644 --- a/include/linux/bpf.h +++ b/include/linux/bpf.h @@ -1376,6 +1376,11 @@ struct bpf_tramp_image { struct rcu_head rcu; struct work_struct work; }; +#ifdef CONFIG_PREEMPTION + /* Programs called from this image must outlive deferred image freeing. */ + struct bpf_prog *progs[BPF_MAX_TRAMP_LINKS]; + int nr_progs; +#endif }; struct bpf_trampoline { diff --git a/kernel/bpf/trampoline.c b/kernel/bpf/trampoline.c index 1a721fc4bef5..33383d1dc1e0 100644 --- a/kernel/bpf/trampoline.c +++ b/kernel/bpf/trampoline.c @@ -529,8 +529,36 @@ bpf_trampoline_get_progs(const struct bpf_trampoline *tr, int *total, bool *ip_a return tnodes; } +static void bpf_tramp_image_get_progs(struct bpf_tramp_image *im, + struct bpf_tramp_nodes *tnodes) +{ +#ifdef CONFIG_PREEMPTION + int i, kind; + + for (kind = 0; kind < BPF_TRAMP_MAX; kind++) + for (i = 0; i < tnodes[kind].nr_nodes; i++) { + struct bpf_prog *prog = tnodes[kind].nodes[i]->link->prog; + + bpf_prog_inc(prog); + im->progs[im->nr_progs++] = prog; + } +#endif +} + +static void bpf_tramp_image_put_progs(struct bpf_tramp_image *im) +{ +#ifdef CONFIG_PREEMPTION + int i; + + for (i = 0; i < im->nr_progs; i++) + bpf_prog_put(im->progs[i]); +#endif +} + static void bpf_tramp_image_free(struct bpf_tramp_image *im) { + bpf_tramp_image_put_progs(im); + bpf_image_ksym_del(&im->ksym); arch_free_bpf_trampoline(im->image, im->size); bpf_jit_uncharge_modmem(im->size); @@ -740,6 +768,8 @@ static int bpf_trampoline_update(struct bpf_trampoline *tr, bool lock_direct_mut goto out; } + bpf_tramp_image_get_progs(im, tnodes); + err = arch_prepare_bpf_trampoline(im, im->image, im->image + size, &tr->func.model, tr->flags, tnodes, tr->func.addr); -- 2.55.0