From: Jiri Olsa <olsajiri@gmail.com>
To: "Florent Revest (Anthropic)" <florent.revest@linux.dev>
Cc: bpf@vger.kernel.org, Alexei Starovoitov <ast@kernel.org>,
Daniel Borkmann <daniel@iogearbox.net>,
Andrii Nakryiko <andrii@kernel.org>,
Martin KaFai Lau <martin.lau@linux.dev>,
Eduard Zingerman <eddyz87@gmail.com>,
Kumar Kartikeya Dwivedi <memxor@gmail.com>,
Song Liu <song@kernel.org>,
Yonghong Song <yonghong.song@linux.dev>,
KP Singh <kpsingh@kernel.org>,
Emil Tsalapatis <emil@etsalapatis.com>,
John Fastabend <john.fastabend@gmail.com>,
"Paul E. McKenney" <paulmck@kernel.org>,
Jose Fernandez <jose.fernandez@linux.dev>,
linux-kernel@vger.kernel.org
Subject: Re: [PATCH bpf] bpf: Keep progs alive until the trampoline image calling them is freed
Date: Mon, 31 Aug 2026 18:40:00 +0200 [thread overview]
Message-ID: <apWuYPCVCvBeaCKr@krava> (raw)
In-Reply-To: <20260819122252.1782790-1-florent.revest@linux.dev>
On Wed, Aug 19, 2026 at 12:22:50PM +0000, Florent Revest (Anthropic) wrote:
> bpf_tramp_image_put() makes sure a trampoline image is not freed while
> a task may still be running in it (call_rcu_tasks() + im->pcref), but
> nothing similar is done for the progs called by that image. Since
> commit e21aa341785c ("bpf: Fix fexit trampoline."), detach patches the
> return path so that a task still in the original function skips the
> fexit progs when it comes back, and counts on the prog's own RCU flavor
> to cover a task that is inside a prog. On that basis the last prog
> reference is dropped right away and the prog is freed after a single
> RCU / RCU tasks trace grace period.
>
> That leaves out a task in the trampoline glue itself: between two
> progs, or already past the patched jump but not yet in the first fexit
> prog's enter helper. On !PREEMPT kernels this is a few instructions
> that cannot be preempted, so it did not matter. With CONFIG_PREEMPTION
> a task can sit there, in no RCU read section of any flavor and holding
> only im->pcref, for longer than it takes to free the prog it is about
> to call:
>
> CPU 0 CPU 1
> in image I, orig_call() returned
> [preempted before lsm.s prog A]
> bpf_tracing_link_release()
> -> bpf_tramp_image_put(I)
> bpf_link_dealloc()
> bpf_prog_put(A), last ref
> tasks trace GP, A's text freed
> __bpf_prog_enter_sleepable(A)
> call A->bpf_func
hi,
do you have a selftest or another reproducer for this?
thanks,
jirak
>
> On x86 this is an int3 in poisoned bpf_prog_pack memory:
>
> Oops: int3: 0000 [#1] SMP NOPTI
> CPU: 18 UID: 0 PID: 94573 Comm: x169 Not tainted 6.18.44 #1 PREEMPT(lazy)
> RIP: 0010:0xffffffffc0601d8d
> Call Trace:
> <TASK>
> ? bpf_trampoline_6442515411+0x1a4/0x21b
> bpf_lsm_bprm_committed_creds+0x5/0x10
> security_bprm_committed_creds+0x5f/0x70
> begin_new_exec+0x2d6/0x410
> ...
>
> We hit this in production on preemptible kernels when progs attached
> through trampolines got detached while their hooks were busy. Adding
> grace periods before the prog free would not help with sleepable progs:
> neither RCU tasks nor RCU tasks trace waits for a task that slept in a
> prog and then got preempted in the gap after it.
>
> Fix it by having the image take a reference on every prog it calls, in
> bpf_tramp_image_alloc(), and drop them in bpf_tramp_image_free(). A
> detached prog now stays loaded until the old image is gone, which
> reverts a deliberate choice of commit e21aa341785c ("bpf: Fix fexit
> trampoline."). Detached fexit progs still stop being called right away
> since the return path is patched.
>
> Fixes: e21aa341785c ("bpf: Fix fexit trampoline.")
> Assisted-by: Claude:unspecified
> Signed-off-by: Florent Revest (Anthropic) <florent.revest@linux.dev>
> ---
> This should also be queued for the stable trees, the same race exists
> everywhere since struct bpf_tramp_image was introduced (v5.12+).
>
> Tested on x86_64 with PREEMPT_DYNAMIC/preempt=lazy by attaching and
> detaching a handful of sleepable and non-sleepable LSM progs in a loop
> next to an exec storm: unpatched 6.18.44 hits the int3 oops above
> within the hour, the patched kernel survived 10x that. The trampoline
> related test_progs selftests (fentry/fexit/modify_return/lsm/
> trampoline_count/tracing_multi...) pass with KASAN and lockdep on both
> this commit and its parent.
>
> include/linux/bpf.h | 2 ++
> kernel/bpf/trampoline.c | 28 ++++++++++++++++++++++------
> 2 files changed, 24 insertions(+), 6 deletions(-)
>
> diff --git a/include/linux/bpf.h b/include/linux/bpf.h
> index 7719f6528445..fc0949156a5c 100644
> --- a/include/linux/bpf.h
> +++ b/include/linux/bpf.h
> @@ -1368,6 +1368,7 @@ enum bpf_tramp_prog_type {
> struct bpf_tramp_image {
> void *image;
> int size;
> + int progs_cnt;
> struct bpf_ksym ksym;
> struct percpu_ref pcref;
> void *ip_after_call;
> @@ -1376,6 +1377,7 @@ struct bpf_tramp_image {
> struct rcu_head rcu;
> struct work_struct work;
> };
> + struct bpf_prog *progs[] __counted_by(progs_cnt);
> };
>
> struct bpf_trampoline {
> diff --git a/kernel/bpf/trampoline.c b/kernel/bpf/trampoline.c
> index 1a721fc4bef5..ca83ddd7cf37 100644
> --- a/kernel/bpf/trampoline.c
> +++ b/kernel/bpf/trampoline.c
> @@ -531,10 +531,14 @@ bpf_trampoline_get_progs(const struct bpf_trampoline *tr, int *total, bool *ip_a
>
> static void bpf_tramp_image_free(struct bpf_tramp_image *im)
> {
> + int i;
> +
> bpf_image_ksym_del(&im->ksym);
> arch_free_bpf_trampoline(im->image, im->size);
> bpf_jit_uncharge_modmem(im->size);
> percpu_ref_exit(&im->pcref);
> + for (i = 0; i < im->progs_cnt; i++)
> + bpf_prog_put(im->progs[i]);
> kfree_rcu(im, rcu);
> }
>
> @@ -588,12 +592,11 @@ static void bpf_tramp_image_put(struct bpf_tramp_image *im)
> * rcu tasks to protect trampoline asm not covered by percpu_ref
> * (which are few asm insns before __bpf_tramp_enter and
> * after __bpf_tramp_exit)
> + * im->progs refs to keep the progs alive as long as the image
> *
> * The trampoline is unreachable before bpf_tramp_image_put().
> *
> * First, patch the trampoline to avoid calling into fexit progs.
> - * The progs will be freed even if the original function is still
> - * executing or sleeping.
> * In case of CONFIG_PREEMPT=y use call_rcu_tasks() to wait on
> * first few asm instructions to execute and call into
> * __bpf_tramp_enter->percpu_ref_get.
> @@ -628,16 +631,20 @@ static void bpf_tramp_image_put(struct bpf_tramp_image *im)
> call_rcu_tasks_trace(&im->rcu, __bpf_tramp_image_put_rcu_tasks);
> }
>
> -static struct bpf_tramp_image *bpf_tramp_image_alloc(u64 key, int size)
> +static struct bpf_tramp_image *bpf_tramp_image_alloc(u64 key, int size,
> + struct bpf_tramp_nodes *tnodes,
> + int progs_cnt)
> {
> struct bpf_tramp_image *im;
> struct bpf_ksym *ksym;
> - void *image;
> + int kind, i, n = 0;
> int err = -ENOMEM;
> + void *image;
>
> - im = kzalloc_obj(*im);
> + im = kzalloc_flex(*im, progs, progs_cnt);
> if (!im)
> goto out;
> + im->progs_cnt = progs_cnt;
>
> err = bpf_jit_charge_modmem(size);
> if (err)
> @@ -658,6 +665,15 @@ static struct bpf_tramp_image *bpf_tramp_image_alloc(u64 key, int size)
> snprintf(ksym->name, KSYM_NAME_LEN, "bpf_trampoline_%llu", key);
> bpf_image_ksym_init(image, size, ksym);
> bpf_image_ksym_add(ksym);
> +
> + 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[n++] = prog;
> + }
> + }
> return im;
>
> out_free_image:
> @@ -734,7 +750,7 @@ static int bpf_trampoline_update(struct bpf_trampoline *tr, bool lock_direct_mut
> goto out;
> }
>
> - im = bpf_tramp_image_alloc(tr->key, size);
> + im = bpf_tramp_image_alloc(tr->key, size, tnodes, total);
> if (IS_ERR(im)) {
> err = PTR_ERR(im);
> goto out;
>
> base-commit: a13307e97d5c54b65720bb71fa379960ded1e51a
> --
> 2.54.0
>
>
next prev parent reply other threads:[~2026-08-31 16:40 UTC|newest]
Thread overview: 16+ messages / expand[flat|nested] mbox.gz Atom feed top
2026-08-19 12:22 Florent Revest (Anthropic)
2026-08-20 15:26 ` Junseo Lim
[not found] ` <bc0edb11e07e0f6147e9c552805d0029c7aec7fc@linux.dev>
2026-08-21 7:58 ` Junseo Lim
2026-08-31 21:07 ` Florent Revest
2026-08-20 16:19 ` Leon Hwang
2026-08-30 10:40 ` Kumar Kartikeya Dwivedi
2026-08-30 13:21 ` Alexei Starovoitov
2026-08-31 2:43 ` Kumar Kartikeya Dwivedi
2026-08-31 20:58 ` Florent Revest
2026-09-02 5:57 ` Alexei Starovoitov
2026-09-02 10:15 ` Florent Revest
2026-09-02 22:06 ` Alexei Starovoitov
2026-09-02 23:25 ` Florent Revest
2026-09-03 0:48 ` Alexei Starovoitov
2026-08-31 16:40 ` Jiri Olsa [this message]
2026-08-31 20:43 ` Florent Revest
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=apWuYPCVCvBeaCKr@krava \
--to=olsajiri@gmail.com \
--cc=andrii@kernel.org \
--cc=ast@kernel.org \
--cc=bpf@vger.kernel.org \
--cc=daniel@iogearbox.net \
--cc=eddyz87@gmail.com \
--cc=emil@etsalapatis.com \
--cc=florent.revest@linux.dev \
--cc=john.fastabend@gmail.com \
--cc=jose.fernandez@linux.dev \
--cc=kpsingh@kernel.org \
--cc=linux-kernel@vger.kernel.org \
--cc=martin.lau@linux.dev \
--cc=memxor@gmail.com \
--cc=paulmck@kernel.org \
--cc=song@kernel.org \
--cc=yonghong.song@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 a public inbox, see mirroring instructions
for how to clone and mirror all data and code used for this inbox
all inboxes | Powered by JetHome®