mirror of https://lore.kernel.org/lkml/
 help / color / mirror / Atom feed
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
> 
> 

  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®